====== 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]]