====== Konvencije koda ====== Obavezni standardi kodiranja za WvdS FPC RAD Suite. ===== Konvencije imenovanja ===== ==== Tipovi ==== ^ Kategorija ^ Prefiks ^ Primjer ^ | Klasa | TWvdS* | TWvdSBuildConfig, TWvdSProjectManager | | Interface | IWvdS* | IWvdSLogger, IWvdSParser | | Record | TWvdS* | TWvdSBuildResult, TWvdSToolPath | | Enum | TWvdS* | TWvdSProjectType, TWvdSBuildStatus | | Exception | EWvdS* | EWvdSFileNotFound, EWvdSParseError | | Callback | TWvdS* | TWvdSBuildCallback, TWvdSProgressHandler | Vanjski API-tipovi (VSCode, Node.js) zadržavaju svoje originalne nazive. ==== Nazivi Unita ==== Microsoft-stil Namespaces s WvdS-prefiksom: WvdS.{Domain}.{Layer}.pas Primjeri: WvdS.Build.Models.pas WvdS.Build.Service.pas WvdS.Projects.SettingsDialog.pas WvdS.VSCode.Commands.pas ==== Layer-Sufiksi ==== ^ Sufiks ^ Sadržaj ^ Primjer ^ | .Models | Records, Enumi, Tipovi | WvdS.Build.Models | | .Service | Poslovna logika | WvdS.Build.Service | | .Dialog | WebView-Dijalozi | WvdS.Projects.SettingsDialog | | .Provider | VSCode API Wrapper | WvdS.Designer.EditorProvider | ==== Varijable i Parametri ==== ^ Kategorija ^ Prefiks ^ Primjer ^ | Privatna polja | F | FProjectPath, FConfig | | Parametri | A | APath, AOptions, ACallback | | Lokalne varijable | Bez | Result, I, Config | ==== Resourcestrings ==== Prefiks prema značajci: ^ Prefiks ^ Značajka ^ | rsCore* | Core Extension | | rsBuild* | Build Extension | | rsProject* | Projects Extension | | rsDesigner* | UI Designer | | rsPreview* | UI Preview | | rsMeta* | UI Meta | | rsPackaging* | Packaging | | rsTool* | Toolchain | ===== Struktura koda ===== ==== Struktura Unita ==== unit WvdS.{Feature}.{Layer}; {$mode objfpc}{$H+} interface uses // 1. System Units SysUtils, Classes, // 2. WvdS Common WvdS.System, WvdS.Collections, // 3. Feature-specifično WvdS.{Feature}.Models; type // Definicije tipova function PublicFunction(const AParam: string): Boolean; procedure PublicProcedure(AValue: Integer); implementation uses // Private Uses (samo ovdje potrebne Units) WvdS.VSCode.Strings; // Privatni tipovi i varijable type TInternalHelper = class end; var InternalState: TObject; // Implementacije function PublicFunction(const AParam: string): Boolean; begin // ... end; procedure PublicProcedure(AValue: Integer); begin // ... end; initialization // Inicijalizacija finalization // Čišćenje end. ==== Struktura klase ==== 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 ===== ==== PasDoc-Format ==== (* @abstract(Kratki opis u jednoj rečenici.) Detaljni opis svrhe i korištenja. Može sadržavati više rečenica. @param(APath Potpuna putanja do datoteke) @param(AOptions Opcionalna konfiguracija, može biti nil) @returns(True ako uspješno, False kod greške) @raises(EWvdSFileNotFound ako datoteka ne postoji) @raises(EWvdSAccessDenied ako nema prava čitanja) Security: - CWE-22: Putanja se validira protiv Path Traversal @seealso(RelatedFunction) @seealso(TWvdSRelatedClass) *) function ProcessFile(const APath: string; AOptions: TOptions): Boolean; ==== Inline-Komentari ==== // Kratki komentar za jednu liniju Result := CalculateValue; // Višelinijski komentar za složeniju logiku // Objašnjava razlog, ne što if (Value > Threshold) and (Mode = mAdvanced) then begin // Moramo ovdje koristiti prošireni algoritam, // jer jednostavni nije točan kod velikih vrijednosti Result := AdvancedCalculation(Value); end; ===== Konstante umjesto Magic Numbers ===== // ZABRANJENO if Length(Name) > 64 then if Timeout > 30000 then // ISPRAVNO 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 ===== Koristite konzistentne pojmove: ^ Koristite ^ Izbjegavajte ^ | Path | Url, Location, Dir (nekonzistentno) | | Config | Settings, Options, Prefs (nekonzistentno) | | Create | Make, Build (za objekte) | | Generate | Create (za izlaz) | | Validate | Check, Verify (za unose) | | Initialize | Setup, Init (nekonzistentno) | | Execute | Run, Process (nekonzistentno) | ===== Formatiranje ===== ==== Uvlačenje ==== * **2 razmaka** za uvlačenje (ne tabovi) * **begin** na vlastitoj liniji kod blokova * **end** na istoj razini uvlačenja kao pripadajući begin // ISPRAVNO procedure Example; begin if Condition then begin DoSomething; DoMore; end else begin DoAlternative; end; end; // ZABRANJENO procedure Example; begin if Condition then begin DoSomething; DoMore; end else DoAlternative; end; ==== Duljina linije ==== * Maksimalno **120 znakova** po liniji * Prelom kod dugih izraza // Kod dugih lista parametara function VeryLongFunctionName( const AFirstParameter: string; const ASecondParameter: Integer; AThirdParameter: TOptions ): Boolean; // Kod dugih uvjeta if (FirstCondition) and (SecondCondition) and (ThirdCondition) then begin // ... end; ===== Zabranjeni Patterni ===== ==== Prazni Exception-Handleri ==== // ZABRANJENO try DoSomething; except // Ništa ne raditi - gutanje greške! end; // ISPRAVNO try DoSomething; except on E: Exception do LogError(rsUnexpectedError, [E.ClassName, E.Message]); end; ==== TODO/FIXME u produkcijskom kodu ==== // ZABRANJENO u main-Branchu // TODO: Implement this later // FIXME: This is broken // DOZVOLJENO samo u Feature-Branchevima, mora se ukloniti prije Mergea ==== Hardkodirani Stringovi ==== // ZABRANJENO ShowMessage('File not found'); ShowMessage('Datei nicht gefunden'); // ISPRAVNO ShowMessage(rsFileNotFound); // resourcestring ==== Globalne varijable u Servisima ==== // ZABRANJENO var GlobalConfig: TConfig; // Teško testabilno! // ISPRAVNO - predavanje parametra function ProcessWithConfig(const AConfig: TConfig): Boolean; ===== Testabilnost ===== Servisi moraju biti testabilni bez VSCode/UI: // ZABRANJENO - UI u Servisu procedure ValidateAndShowError(const AName: string); begin if not IsValid(AName) then ShowMessage('Invalid name'); // UI-ovisnost! end; // ISPRAVNO - povratna vrijednost function ValidateName(const AName: string; out AError: string): Boolean; begin Result := IsValid(AName); if not Result then AError := rsInvalidName; end; ===== pas2js Kompatibilnost (VAŽNO) ===== **pas2js NIJE 100% kompatibilan s FPC-om!** Ova ograničenja MORAJU se poštivati. ==== Nepodržane značajke ==== ^ Značajka ^ FPC ^ pas2js ^ Zaobilazno rješenje ^ | ''class var'' | ✓ | ✗ | Koristiti Unit-Level Variable | | Inline ''var'' | ✓ | ✗ | Deklarirati u ''var''-bloku | | ''Int64'' | ✓ | ✗ | Koristiti ''Integer'' | | ''//'' u ''asm'' | ✓ | ✗ | ''/* */'' za JS-komentare | | Lokalna Var u ''asm'' | ✓ | ✗ | Direktno koristiti JS-objekte | | Konstante u ''asm'' | ✓ | ✗ | Koristiti literal-vrijednosti | | ''-O2'' + asm-pozivi | ✓ | ✗ | ''-O-'' ili metode učiniti public | | Unit-funkcija u ''asm'' | ✓ | ✗ | Koristiti ''pas["Unit.Name"].FuncName()'' | | ''out'' Param u ''asm'' | ✓ | ✗ | Koristiti ''Param.set(value)'' | ==== Primjeri: Tipovi i Sintaksa ==== (* ZABRANJENO - class var *) TMyClass = class class var FInstance: TMyClass; (* pas2js Error! *) end; (* ISPRAVNO - Unit-Level Variable *) var GMyInstance: TMyClass = nil; (* ZABRANJENO - Inline var *) begin var X := 42; (* pas2js Error! *) end; (* ISPRAVNO - var-blok *) var X: Integer; begin X := 42; end; (* ZABRANJENO - Int64 *) var Size: Int64; (* pas2js: "Symbol Int64 is not implemented" *) (* ISPRAVNO - Integer *) var Size: Integer; (* ZABRANJENO - // u asm *) asm // JavaScript code (* pas2js Error! *) end; (* ISPRAVNO - /* */ u asm *) asm /* JavaScript code */ end; ==== Primjeri: asm-Block Zamke ==== Ove greške je teško pronaći jer se javljaju tek za vrijeme izvršavanja! **Lokalne varijable NISU vidljive u asm:** (* ZABRANJENO - Lokalna Varijabla u asm *) procedure Test; var MyVar: TJSObject; begin asm MyVar = {}; (* pas2js Error: MyVar is not defined! *) end; end; (* ISPRAVNO - Direktno koristiti JS-objekte *) procedure Test; begin asm module.exports.myFunc = function() {}; /* Direktan pristup */ end; end; **Konstante NISU vidljive u asm:** (* ZABRANJENO - Konstanta u asm *) const TIMEOUT_MS = 5000; begin asm setTimeout(func, TIMEOUT_MS); (* pas2js Error: TIMEOUT_MS is not defined! *) end; end; (* ISPRAVNO - Koristiti literal-vrijednost *) begin asm setTimeout(func, 5000); /* Literal umjesto konstante */ end; end; **Unit-funkcije - NE pozivati s donjim crtama:** (* ZABRANJENO - self.* ili sintaksa s donjim crtama *) procedure TMyClass.HandleBrowse; begin asm self.ToolchainConfig().DoSomething(); (* pas2js Error! *) pas.Toolchain_ConfigService.ToolchainConfig(); (* POGREŠNO: Donje crte! *) end; end; (* ISPRAVNO - pas["Unit.Name"].FuncName() sintaksa *) procedure TMyClass.HandleBrowse; begin asm pas["Toolchain.ConfigService"].ToolchainConfig().DoSomething(); /* Ispravno! */ end; end; **out-Parametri - NE dodjeljivati direktno:** (* ZABRANJENO - out-parametar direktno dodijeliti *) function ValidateMessage(AMsg: TJSObject; out AType: string): Boolean; begin asm AType = AMsg.type; (* POGREŠNO: prepisuje getter/setter objekt! *) end; end; (* ISPRAVNO - out-parametar s .set() dodijeliti *) function ValidateMessage(AMsg: TJSObject; out AType: string): Boolean; begin asm AType.set(AMsg.type); /* ISPRAVNO: poziva setter */ end; end; **-O2 Optimizacija - može pokvariti asm-pozive:** # PROBLEM: Kod -O2 privatne metode se optimiziraju pas2js -O2 extension_main.pas (* asm-pozivi mogu propasti *) # RJEŠENJE 1: Deaktivirati optimizaciju pas2js -O- extension_main.pas # RJEŠENJE 2: Metode učiniti public (nije preporučeno) ==== Node.js API Pattern ==== **TNJ* Klase NISU klase** - to su wrapperi za funkcije: (* ZABRANJENO - Metode klasa *) Path := TNJPath.resolve(APath); (* Error! *) Exists := TNJFS.existsSync(APath); (* Error! *) Bytes := TNJCrypto.randomBytes(16); (* Error! *) (* ISPRAVNO - Standalone 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 *) ==== TJSPromise Catch-Metoda ==== (* ZABRANJENO - underscore catch *) MyPromise._catch(ErrorHandler); (* NE funkcionira! *) (* ISPRAVNO - točka catch *) MyPromise.catch(ErrorHandler); (* Tako je ispravno *) ==== Ulančani Bracket Access ==== pas2js NE dopušta ulančane Bracket-pristupe na JSValue: (* ZABRANJENO - Ulančani Bracketi *) function GetStdout(RawResult: TJSObject): string; begin asm Result = RawResult['stdout']['toString'](); (* pas2js Error! *) end; end; (* ISPRAVNO - Koristiti međuvarijablu *) function GetStdout(RawResult: TJSObject): string; var StdoutObj: TJSObject; begin StdoutObj := TJSObject(RawResult['stdout']); asm Result = StdoutObj['toString'](); /* Funkcionira */ end; end; **Compiler-greška:** ''illegal qualifier "[" after "JSValue"'' ==== Paziti na potpise konstruktora ==== Konstruktori imaju specifične potpise - ne pogađajte: (* ZABRANJENO - Pogrešan tip parametra *) var Channel: TOutputChannel; Logger: TWvdSLogger; begin Channel := CreateOutputChannel('WvdS Projects'); Logger := TWvdSLogger.Create(Channel); (* Error: Got TOutputChannel, expected String *) end; (* ISPRAVNO - Provjeriti potpis *) var Logger: TWvdSLogger; begin Logger := TWvdSLogger.Create('wvds.projects'); (* Source-Name kao String *) end; Kod Compiler-grešaka za tipove parametara uvijek provjerite Unit-deklaraciju. ==== Unit-Reference u asm-blokovima ==== pas2js pretvara točke u donje crte: (* Pascal *) uses WvdS.Build.Service; (* U asm-bloku *) asm var service = pas.WvdS_Build_Service; /* Donje crte! */ end; ==== JavaScript-Interop ==== (* asm-blok za direktni JavaScript *) asm console.log('Direct JS call'); end; (* Tipizirane varijable za JS-objekte *) var JsObj: TJSObject; JsArr: TJSArray; ==== Build-Konfiguracija ==== Dvije Config-datoteke po Extensionu: **1. Shared Config** (''~/sources/common/wvds_common.pas2js.cfg''): * Compiler Mode, Target, Defines * RTL-putanje, Common Unit-putanje **2. Extension-specifična Config** (''build.cfg''): * Lokalne Unit-putanje (''-Fupas'') * Output-putanja (''-FEdist/'', ''-odist/extension_main.js'') Poziv: pas2js @../../common/wvds_common.pas2js.cfg @build.cfg pas/extension_main.pas ===== SSOT - Koristiti Common Libraries ===== **Direktni require()-pozivi su ZABRANJENI!** Uvijek koristite Pascal-wrappere iz ''~/sources/common/''. ==== Node.js Wrappers ==== Dostupni u ''~/sources/common/web/nodejs/'': ^ Unit ^ Svrha ^ Važne funkcije ^ | ''NodeJS.Base'' | Core Node.js-tipovi | TJSObject, RequireModule | | ''NodeJS.FS'' | Datotečni sustav | ExistsSync, StatSync, ReadFileSync, WriteFileSync | | ''NodeJS.Path'' | Manipulacija putanjama | PathJoin, PathDirname, PathResolve, PathBasename | | ''NodeJS.ChildProcess'' | Pokretanje procesa | Spawn, Exec, SpawnSync, TSpawnOptions | | ''NodeJS.Crypto'' | Kriptografija | RandomBytes | | ''NodeJS.Buffer'' | Binarni podaci | TBuffer | ==== VSCode Wrappers ==== Dostupni u ''~/sources/common/web/vscode/'': ^ Unit ^ Svrha ^ Važne funkcije ^ | ''VSCode.Base'' | Core VSCode-tipovi | TUri, TPosition, TRange, TTextDocument | | ''VSCode.Window'' | Window-operacije | ShowInformationMessage, CreateWebviewPanel | | ''VSCode.Commands'' | Registracija naredbi | RegisterCommand, ExecuteCommand | | ''VSCode.Workspace'' | Workspace-operacije | FindFiles, GetConfiguration | | ''VSCode.Tasks'' | Izvršavanje taskova | CreateShellTask, ExecuteTask | ==== WvdS Core System ==== Dostupno u ''~/sources/common/core/system/'': ^ Unit ^ Svrha ^ Važne funkcije ^ | ''WvdS.System.Logging'' | Unified Logging | TWvdSLogger, LogDebug, LogInfo, LogError | | ''WvdS.VSCode.Security'' | Sigurnosne funkcije | EscapeHtml, ValidateWebViewMessage, GetSecureNonce | | ''WvdS.VSCode.Strings'' | Resourcestrings | 130+ lokaliziranih stringova | | ''WvdS.WebView.Bridge'' | Host-WebView Komunikacija | THostBridge, TBaseBridge | ==== SSOT-Refactoring Pattern ==== **Prije (SSOT-kršenje):** function IsFpcAvailable(const AFpcPath: string): Boolean; begin asm try { var fs = require('fs'); /* ZABRANJENO: direktni require! */ Result = fs.existsSync(AFpcPath); } catch (e) { Result = false; } end; end; **Poslije (ispravno s Common Library):** uses NodeJS.FS; (* Wrapper iz common/ *) function IsFpcAvailable(const AFpcPath: string): Boolean; begin Result := ExistsSync(AFpcPath); (* Pascal-funkcija *) end; ==== Async File Operations ==== **Prije (SSOT-kršenje):** function LoadMetadata(const APath: string): TJSPromise; begin asm Result = new Promise(function(resolve, reject) { var fs = require('fs'); /* ZABRANJENO */ fs.stat(APath, function(err, stats) { ... }); }); end; end; **Poslije (ispravno s Common Library):** uses NodeJS.FS; function LoadMetadata(const APath: string): TJSPromise; begin Result := Stat(APath)._then( function(stats: JSValue): JSValue begin (* Obrada Stats *) end ); end; ===== Checklista prije commita ===== [ ] Konvencije imenovanja poštovane [ ] PasDoc-dokumentacija prisutna [ ] Nema Magic Numbers [ ] Nema TODO/FIXME-komentara [ ] Nema hardkodiranih stringova [ ] Nema praznih Exception-handlera [ ] Nema globalnih varijabli u Servisima [ ] Nema UI-ovisnosti u Servisima [ ] Formatiranje ispravno (uvlačenje, duljina linije) [ ] Konzistentna terminologija [ ] Common Libraries umjesto direktnog require() ===== Vidi također ===== * [[.:sicherheit|Sigurnosne smjernice]] * [[.:i18n|Internacionalizacija]] * [[.:extension-entwicklung|Razvoj Extensiona]]