====== Kodne konvencije ======
Obvezujoči standardi kodiranja za WvdS FPC RAD Suite.
===== Konvencije poimenovanja =====
==== Tipi ====
^ Kategorija ^ Predpona ^ Primer ^
| Razred | TWvdS* | TWvdSBuildConfig, TWvdSProjectManager |
| Vmesnik | IWvdS* | IWvdSLogger, IWvdSParser |
| Zapis | TWvdS* | TWvdSBuildResult, TWvdSToolPath |
| Naštevanje | TWvdS* | TWvdSProjectType, TWvdSBuildStatus |
| Izjema | EWvdS* | EWvdSFileNotFound, EWvdSParseError |
| Povratni klic | TWvdS* | TWvdSBuildCallback, TWvdSProgressHandler |
Zunanji API tipi (VSCode, Node.js) ohranijo svoja izvirna imena.
==== Imena enot ====
Microsoftov stil imenskih prostorov s predpono WvdS:
WvdS.{Domena}.{Plast}.pas
Primeri:
WvdS.Build.Models.pas
WvdS.Build.Service.pas
WvdS.Projects.SettingsDialog.pas
WvdS.VSCode.Commands.pas
==== Pripone plasti ====
^ Pripona ^ Vsebina ^ Primer ^
| .Models | Zapisi, naštevanja, tipi | WvdS.Build.Models |
| .Service | Poslovna logika | WvdS.Build.Service |
| .Dialog | WebView dialogi | WvdS.Projects.SettingsDialog |
| .Provider | VSCode API ovojniki | WvdS.Designer.EditorProvider |
==== Spremenljivke in parametri ====
^ Kategorija ^ Predpona ^ Primer ^
| Zasebna polja | F | FProjectPath, FConfig |
| Parametri | A | APath, AOptions, ACallback |
| Lokalne spremenljivke | Brez | Result, I, Config |
==== Resourcestrings ====
Predpona po funkcionalnosti:
^ Predpona ^ Funkcionalnost ^
| rsCore* | Core Extension |
| rsBuild* | Build Extension |
| rsProject* | Projects Extension |
| rsDesigner* | UI Designer |
| rsPreview* | UI Preview |
| rsMeta* | UI Meta |
| rsPackaging* | Packaging |
| rsTool* | Toolchain |
===== Struktura kode =====
==== Zgradba enote ====
unit WvdS.{Feature}.{Layer};
{$mode objfpc}{$H+}
interface
uses
// 1. Sistemske enote
SysUtils, Classes,
// 2. WvdS Common
WvdS.System, WvdS.Collections,
// 3. Funkcionalnostno specifične
WvdS.{Feature}.Models;
type
// Definicije tipov
function PublicFunction(const AParam: string): Boolean;
procedure PublicProcedure(AValue: Integer);
implementation
uses
// Zasebni Uses (enote potrebne samo tukaj)
WvdS.VSCode.Strings;
// Zasebni tipi in spremenljivke
type
TInternalHelper = class
end;
var
InternalState: TObject;
// Implementacije
function PublicFunction(const AParam: string): Boolean;
begin
// ...
end;
procedure PublicProcedure(AValue: Integer);
begin
// ...
end;
initialization
// Inicializacija
finalization
// Čiščenje
end.
==== Zgradba razreda ====
type
TWvdSExampleClass = class(TObject)
private
FName: string;
FValue: Integer;
procedure SetName(const AValue: string);
protected
procedure DoInternalWork; virtual;
public
constructor Create(const AName: string);
destructor Destroy; override;
procedure Execute;
property Name: string read FName write SetName;
property Value: Integer read FValue write FValue;
end;
===== Dokumentacija =====
==== Format PasDoc ====
(*
@abstract(Kratek opis v enem stavku.)
Podroben opis namena in uporabe.
Lahko obsega več stavkov.
@param(APath Polna pot do datoteke)
@param(AOptions Opcijska konfiguracija, lahko nil)
@returns(True če uspešno, False pri napaki)
@raises(EWvdSFileNotFound če datoteka ne obstaja)
@raises(EWvdSAccessDenied če ni pravic za branje)
Varnost:
- CWE-22: Pot se validira proti Path Traversal
@seealso(RelatedFunction)
@seealso(TWvdSRelatedClass)
*)
function ProcessFile(const APath: string; AOptions: TOptions): Boolean;
==== Vrstični komentarji ====
// Kratek komentar za posamezno vrstico
Result := CalculateValue;
// Večvrstični komentar za kompleksnejšo logiko
// Razlaga razlog, ne kaj
if (Value > Threshold) and (Mode = mAdvanced) then
begin
// Tukaj moramo uporabiti napredni algoritem,
// ker je preprost pri velikih vrednostih nenatančen
Result := AdvancedCalculation(Value);
end;
===== Konstante namesto čarobnih števil =====
// PREPOVEDANO
if Length(Name) > 64 then
if Timeout > 30000 then
// PRAVILNO
const
MAX_PROJECT_NAME_LENGTH = 64;
DEFAULT_TIMEOUT_MS = 30000;
if Length(Name) > MAX_PROJECT_NAME_LENGTH then
if Timeout > DEFAULT_TIMEOUT_MS then
===== Terminologija =====
Dosledno uporabljajte izraze:
^ Uporabljajte ^ Izogibajte se ^
| Path | Url, Location, Dir (nedosledno) |
| Config | Settings, Options, Prefs (nedosledno) |
| Create | Make, Build (za objekte) |
| Generate | Create (za izhod) |
| Validate | Check, Verify (za vnose) |
| Initialize | Setup, Init (nedosledno) |
| Execute | Run, Process (nedosledno) |
===== Oblikovanje =====
==== Zamik ====
* **2 presledka** za zamik (ne tabulatorji)
* **begin** na svoji vrstici pri blokih
* **end** na isti ravni zamika kot pripadajoči begin
// PRAVILNO
procedure Example;
begin
if Condition then
begin
DoSomething;
DoMore;
end
else
begin
DoAlternative;
end;
end;
// PREPOVEDANO
procedure Example; begin
if Condition then begin DoSomething; DoMore; end
else DoAlternative;
end;
==== Dolžina vrstice ====
* Največ **120 znakov** na vrstico
* Pri dolgih izrazih prelomite
// Pri dolgih seznamih parametrov
function VeryLongFunctionName(
const AFirstParameter: string;
const ASecondParameter: Integer;
AThirdParameter: TOptions
): Boolean;
// Pri dolgih pogojih
if (FirstCondition) and
(SecondCondition) and
(ThirdCondition) then
begin
// ...
end;
===== Prepovedani vzorci =====
==== Prazni obravnavalci izjem ====
// PREPOVEDANO
try
DoSomething;
except
// Nič ne stori - napaka se potlači!
end;
// PRAVILNO
try
DoSomething;
except
on E: Exception do
LogError(rsUnexpectedError, [E.ClassName, E.Message]);
end;
==== TODO/FIXME v produkcijski kodi ====
// PREPOVEDANO v main veji
// TODO: Implement this later
// FIXME: This is broken
// DOVOLJENO samo v vejah funkcionalnosti, mora biti odstranjeno pred združitvijo
==== Zakodirani nizi ====
// PREPOVEDANO
ShowMessage('File not found');
ShowMessage('Datei nicht gefunden');
// PRAVILNO
ShowMessage(rsFileNotFound); // resourcestring
==== Globalne spremenljivke v storitvah ====
// PREPOVEDANO
var
GlobalConfig: TConfig; // Težko testabilno!
// PRAVILNO - parameter predati
function ProcessWithConfig(const AConfig: TConfig): Boolean;
===== Testabilnost =====
Storitve morajo biti testabilne brez VSCode/UI:
// PREPOVEDANO - UI v storitvi
procedure ValidateAndShowError(const AName: string);
begin
if not IsValid(AName) then
ShowMessage('Invalid name'); // UI odvisnost!
end;
// PRAVILNO - povratna vrednost
function ValidateName(const AName: string; out AError: string): Boolean;
begin
Result := IsValid(AName);
if not Result then
AError := rsInvalidName;
end;
===== Združljivost s pas2js (POMEMBNO) =====
**pas2js NI 100% združljiv s FPC!** Te omejitve JE TREBA upoštevati.
==== Nepodprte funkcionalnosti ====
^ Funkcionalnost ^ FPC ^ pas2js ^ Rešitev ^
| ''class var'' | Da | Ne | Uporabite spremenljivko na ravni enote |
| Inline ''var'' | Da | Ne | Deklarirajte v bloku ''var'' |
| ''Int64'' | Da | Ne | Uporabite ''Integer'' |
| ''//'' v ''asm'' | Da | Ne | ''/* */'' za JS komentarje |
| Lokalna spr. v ''asm'' | Da | Ne | Neposredno uporabite JS objekte |
| Konstante v ''asm'' | Da | Ne | Uporabite literalne vrednosti |
| ''-O2'' + asm klici | Da | Ne | ''-O-'' ali metode naredite javne |
| Funkcija enote v ''asm'' | Da | Ne | Uporabite ''pas["Unit.Name"].FuncName()'' |
| ''out'' param v ''asm'' | Da | Ne | Uporabite ''Param.set(value)'' |
==== Primeri: Tipi in sintaksa ====
(* PREPOVEDANO - class var *)
TMyClass = class
class var FInstance: TMyClass; (* pas2js napaka! *)
end;
(* PRAVILNO - spremenljivka na ravni enote *)
var
GMyInstance: TMyClass = nil;
(* PREPOVEDANO - Inline var *)
begin
var X := 42; (* pas2js napaka! *)
end;
(* PRAVILNO - blok var *)
var
X: Integer;
begin
X := 42;
end;
(* PREPOVEDANO - Int64 *)
var
Size: Int64; (* pas2js: "Symbol Int64 is not implemented" *)
(* PRAVILNO - Integer *)
var
Size: Integer;
(* PREPOVEDANO - // v asm *)
asm
// JavaScript code (* pas2js napaka! *)
end;
(* PRAVILNO - /* */ v asm *)
asm
/* JavaScript code */
end;
==== Primeri: Pasti bloka asm ====
Te napake je težko najti, saj se pojavijo šele med izvajanjem!
**Lokalne spremenljivke NISO vidne v asm:**
(* PREPOVEDANO - lokalna spremenljivka v asm *)
procedure Test;
var
MyVar: TJSObject;
begin
asm
MyVar = {}; (* pas2js napaka: MyVar is not defined! *)
end;
end;
(* PRAVILNO - neposredno uporabite JS objekte *)
procedure Test;
begin
asm
module.exports.myFunc = function() {}; /* Neposreden dostop */
end;
end;
**Konstante NISO vidne v asm:**
(* PREPOVEDANO - konstanta v asm *)
const
TIMEOUT_MS = 5000;
begin
asm
setTimeout(func, TIMEOUT_MS); (* pas2js napaka: TIMEOUT_MS is not defined! *)
end;
end;
(* PRAVILNO - uporabite literalno vrednost *)
begin
asm
setTimeout(func, 5000); /* Literal namesto konstante */
end;
end;
**Funkcije enote - NE kličite s podčrtaji:**
(* PREPOVEDANO - self.* ali sintaksa s podčrtaji *)
procedure TMyClass.HandleBrowse;
begin
asm
self.ToolchainConfig().DoSomething(); (* pas2js napaka! *)
pas.Toolchain_ConfigService.ToolchainConfig(); (* NAROBE: Podčrtaji! *)
end;
end;
(* PRAVILNO - sintaksa pas["Unit.Name"].FuncName() *)
procedure TMyClass.HandleBrowse;
begin
asm
pas["Toolchain.ConfigService"].ToolchainConfig().DoSomething(); /* Pravilno! */
end;
end;
**out parametri - NE dodelite neposredno:**
(* PREPOVEDANO - neposredna dodelitev out parametra *)
function ValidateMessage(AMsg: TJSObject; out AType: string): Boolean;
begin
asm
AType = AMsg.type; (* NAROBE: prepiše getter/setter objekt! *)
end;
end;
(* PRAVILNO - dodelitev out parametra z .set() *)
function ValidateMessage(AMsg: TJSObject; out AType: string): Boolean;
begin
asm
AType.set(AMsg.type); /* PRAVILNO: pokliče setter */
end;
end;
**Optimizacija -O2 - lahko pokvari asm klice:**
# PROBLEM: Pri -O2 se zasebne metode optimizirajo stran
pas2js -O2 extension_main.pas (* asm klici lahko odpovejo *)
# REŠITEV 1: Deaktivirajte optimizacijo
pas2js -O- extension_main.pas
# REŠITEV 2: Naredite metode javne (ni priporočeno)
==== Vzorec Node.js API ====
**Razredi TNJ* NISO razredi** - so ovojniki za funkcije:
(* PREPOVEDANO - metode razreda *)
Path := TNJPath.resolve(APath); (* Napaka! *)
Exists := TNJFS.existsSync(APath); (* Napaka! *)
Bytes := TNJCrypto.randomBytes(16); (* Napaka! *)
(* PRAVILNO - samostojne funkcije *)
uses NodeJS.Path, NodeJS.FS, NodeJS.Crypto;
Path := PathResolve([APath]); (* Funkcija iz NodeJS.Path *)
Exists := ExistsSync(APath); (* Funkcija iz NodeJS.FS *)
Bytes := RandomBytes(16); (* Funkcija iz NodeJS.Crypto *)
==== Metoda Catch za TJSPromise ====
(* PREPOVEDANO - podčrtaj catch *)
MyPromise._catch(ErrorHandler); (* NE deluje! *)
(* PRAVILNO - pika catch *)
MyPromise.catch(ErrorHandler); (* Tako je pravilno *)
==== Verižni dostop z oglatimi oklepaji ====
pas2js NE dovoljuje verižnih dostopov z oglatimi oklepaji na JSValue:
(* PREPOVEDANO - verižni oklepaji *)
function GetStdout(RawResult: TJSObject): string;
begin
asm
Result = RawResult['stdout']['toString'](); (* pas2js napaka! *)
end;
end;
(* PRAVILNO - uporabite vmesno spremenljivko *)
function GetStdout(RawResult: TJSObject): string;
var
StdoutObj: TJSObject;
begin
StdoutObj := TJSObject(RawResult['stdout']);
asm
Result = StdoutObj['toString'](); /* Deluje */
end;
end;
**Napaka prevajalnika:** ''illegal qualifier "[" after "JSValue"''
==== Upoštevajte podpise konstruktorjev ====
Konstruktorji imajo specifične podpise - ne ugibajte:
(* PREPOVEDANO - napačen tip parametra *)
var
Channel: TOutputChannel;
Logger: TWvdSLogger;
begin
Channel := CreateOutputChannel('WvdS Projects');
Logger := TWvdSLogger.Create(Channel); (* Napaka: Got TOutputChannel, expected String *)
end;
(* PRAVILNO - preverite podpis *)
var
Logger: TWvdSLogger;
begin
Logger := TWvdSLogger.Create('wvds.projects'); (* Ime vira kot niz *)
end;
Pri napakah prevajalnika glede tipov parametrov vedno preverite deklaracijo enote.
==== Reference enot v blokih asm ====
pas2js pretvori pike v podčrtaje:
(* Pascal *)
uses WvdS.Build.Service;
(* V bloku asm *)
asm
var service = pas.WvdS_Build_Service; /* Podčrtaji! */
end;
==== JavaScript interop ====
(* Blok asm za neposreden JavaScript *)
asm
console.log('Direct JS call');
end;
(* Tipizirane spremenljivke za JS objekte *)
var
JsObj: TJSObject;
JsArr: TJSArray;
==== Konfiguracija gradnje ====
Dve konfiguracijski datoteki na razširitev:
**1. Deljena konfiguracija** (''~/sources/common/wvds_common.pas2js.cfg''):
* Način prevajalnika, cilj, definicije
* RTL poti, poti skupnih enot
**2. Konfiguracija specifična za razširitev** (''build.cfg''):
* Lokalne poti enot (''-Fupas'')
* Izhodna pot (''-FEdist/'', ''-odist/extension_main.js'')
Klic:
pas2js @../../common/wvds_common.pas2js.cfg @build.cfg pas/extension_main.pas
===== SSOT - Uporaba skupnih knjižnic =====
**Neposredni klici require() so PREPOVEDANI!** Vedno uporabite Pascal ovojnike iz ''~/sources/common/''.
==== Node.js ovojniki ====
Na voljo v ''~/sources/common/web/nodejs/'':
^ Enota ^ Namen ^ Pomembne funkcije ^
| ''NodeJS.Base'' | Jedrni Node.js tipi | TJSObject, RequireModule |
| ''NodeJS.FS'' | Datotečni sistem | ExistsSync, StatSync, ReadFileSync, WriteFileSync |
| ''NodeJS.Path'' | Manipulacija poti | PathJoin, PathDirname, PathResolve, PathBasename |
| ''NodeJS.ChildProcess'' | Zagon procesov | Spawn, Exec, SpawnSync, TSpawnOptions |
| ''NodeJS.Crypto'' | Kriptografija | RandomBytes |
| ''NodeJS.Buffer'' | Binarni podatki | TBuffer |
==== VSCode ovojniki ====
Na voljo v ''~/sources/common/web/vscode/'':
^ Enota ^ Namen ^ Pomembne funkcije ^
| ''VSCode.Base'' | Jedrni VSCode tipi | TUri, TPosition, TRange, TTextDocument |
| ''VSCode.Window'' | Operacije okna | ShowInformationMessage, CreateWebviewPanel |
| ''VSCode.Commands'' | Registracija ukazov | RegisterCommand, ExecuteCommand |
| ''VSCode.Workspace'' | Operacije delovnega prostora | FindFiles, GetConfiguration |
| ''VSCode.Tasks'' | Izvajanje opravil | CreateShellTask, ExecuteTask |
==== WvdS jedrni sistem ====
Na voljo v ''~/sources/common/core/system/'':
^ Enota ^ Namen ^ Pomembne funkcije ^
| ''WvdS.System.Logging'' | Poenoteno beleženje | TWvdSLogger, LogDebug, LogInfo, LogError |
| ''WvdS.VSCode.Security'' | Varnostne funkcije | EscapeHtml, ValidateWebViewMessage, GetSecureNonce |
| ''WvdS.VSCode.Strings'' | Resourcestrings | 130+ lokaliziranih nizov |
| ''WvdS.WebView.Bridge'' | Komunikacija gostitelj-WebView | THostBridge, TBaseBridge |
==== Vzorec SSOT refaktoriranja ====
**Prej (kršitev SSOT):**
function IsFpcAvailable(const AFpcPath: string): Boolean;
begin
asm
try {
var fs = require('fs'); /* PREPOVEDANO: neposreden require! */
Result = fs.existsSync(AFpcPath);
} catch (e) {
Result = false;
}
end;
end;
**Potem (pravilno s skupno knjižnico):**
uses
NodeJS.FS; (* Ovojnik iz common/ *)
function IsFpcAvailable(const AFpcPath: string): Boolean;
begin
Result := ExistsSync(AFpcPath); (* Pascal funkcija *)
end;
==== Asinhrone datotečne operacije ====
**Prej (kršitev SSOT):**
function LoadMetadata(const APath: string): TJSPromise;
begin
asm
Result = new Promise(function(resolve, reject) {
var fs = require('fs'); /* PREPOVEDANO */
fs.stat(APath, function(err, stats) { ... });
});
end;
end;
**Potem (pravilno s skupno knjižnico):**
uses
NodeJS.FS;
function LoadMetadata(const APath: string): TJSPromise;
begin
Result := Stat(APath)._then(
function(stats: JSValue): JSValue
begin
(* Obdelaj statistiko *)
end
);
end;
===== Kontrolni seznam pred potrditvijo =====
[ ] Konvencije poimenovanja upoštevane
[ ] PasDoc dokumentacija prisotna
[ ] Brez čarobnih števil
[ ] Brez komentarjev TODO/FIXME
[ ] Brez zakodiranih nizov
[ ] Brez praznih obravnavalcev izjem
[ ] Brez globalnih spremenljivk v storitvah
[ ] Brez UI odvisnosti v storitvah
[ ] Oblikovanje pravilno (zamik, dolžina vrstice)
[ ] Dosledna terminologija
[ ] Skupne knjižnice namesto neposrednih require()
===== Glejte tudi =====
* [[.:sicherheit|Varnostne smernice]]
* [[.:i18n|Internacionalizacija]]
* [[.:extension-entwicklung|Razvoj razširitev]]