====== Code-Konventionen ====== Verbindliche Coding-Standards für die WvdS FPC RAD Suite. ===== Namenskonventionen ===== ==== Typen ==== ^ Kategorie ^ Präfix ^ Beispiel ^ | Klasse | TWvdS* | TWvdSBuildConfig, TWvdSProjectManager | | Interface | IWvdS* | IWvdSLogger, IWvdSParser | | Record | TWvdS* | TWvdSBuildResult, TWvdSToolPath | | Enum | TWvdS* | TWvdSProjectType, TWvdSBuildStatus | | Exception | EWvdS* | EWvdSFileNotFound, EWvdSParseError | | Callback | TWvdS* | TWvdSBuildCallback, TWvdSProgressHandler | Externe API-Typen (VSCode, Node.js) behalten ihre originalen Namen. ==== Unit-Namen ==== Microsoft-Stil Namespaces mit WvdS-Präfix: WvdS.{Domain}.{Layer}.pas Beispiele: WvdS.Build.Models.pas WvdS.Build.Service.pas WvdS.Projects.SettingsDialog.pas WvdS.VSCode.Commands.pas ==== Layer-Suffixe ==== ^ Suffix ^ Inhalt ^ Beispiel ^ | .Models | Records, Enums, Typen | WvdS.Build.Models | | .Service | Geschäftslogik | WvdS.Build.Service | | .Dialog | WebView-Dialoge | WvdS.Projects.SettingsDialog | | .Provider | VSCode API Wrapper | WvdS.Designer.EditorProvider | ==== Variablen und Parameter ==== ^ Kategorie ^ Präfix ^ Beispiel ^ | Private Felder | F | FProjectPath, FConfig | | Parameter | A | APath, AOptions, ACallback | | Lokale Variablen | Kein | Result, I, Config | ==== Resourcestrings ==== Präfix nach Feature: ^ Präfix ^ Feature ^ | rsCore* | Core Extension | | rsBuild* | Build Extension | | rsProject* | Projects Extension | | rsDesigner* | UI Designer | | rsPreview* | UI Preview | | rsMeta* | UI Meta | | rsPackaging* | Packaging | | rsTool* | Toolchain | ===== Code-Struktur ===== ==== Unit-Aufbau ==== unit WvdS.{Feature}.{Layer}; {$mode objfpc}{$H+} interface uses // 1. System Units SysUtils, Classes, // 2. WvdS Common WvdS.System, WvdS.Collections, // 3. Feature-spezifisch WvdS.{Feature}.Models; type // Typ-Definitionen function PublicFunction(const AParam: string): Boolean; procedure PublicProcedure(AValue: Integer); implementation uses // Private Uses (nur hier benötigte Units) WvdS.VSCode.Strings; // Private Typen und Variablen type TInternalHelper = class end; var InternalState: TObject; // Implementierungen function PublicFunction(const AParam: string): Boolean; begin // ... end; procedure PublicProcedure(AValue: Integer); begin // ... end; initialization // Initialisierung finalization // Aufräumen end. ==== Klassen-Aufbau ==== 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; ===== Dokumentation ===== ==== PasDoc-Format ==== (* @abstract(Kurze Beschreibung in einem Satz.) Ausführliche Beschreibung des Zwecks und der Verwendung. Kann mehrere Sätze umfassen. @param(APath Vollständiger Pfad zur Datei) @param(AOptions Optionale Konfiguration, kann nil sein) @returns(True wenn erfolgreich, False bei Fehler) @raises(EWvdSFileNotFound wenn Datei nicht existiert) @raises(EWvdSAccessDenied wenn keine Leserechte) Security: - CWE-22: Pfad wird gegen Path Traversal validiert @seealso(RelatedFunction) @seealso(TWvdSRelatedClass) *) function ProcessFile(const APath: string; AOptions: TOptions): Boolean; ==== Inline-Kommentare ==== // Kurzer Kommentar für einzelne Zeile Result := CalculateValue; // Mehrzeiliger Kommentar für komplexere Logik // Erklärt den Grund, nicht das Was if (Value > Threshold) and (Mode = mAdvanced) then begin // Wir müssen hier den erweiterten Algorithmus verwenden, // da der einfache bei großen Werten ungenau wird Result := AdvancedCalculation(Value); end; ===== Konstanten statt Magic Numbers ===== // VERBOTEN if Length(Name) > 64 then if Timeout > 30000 then // KORREKT const MAX_PROJECT_NAME_LENGTH = 64; DEFAULT_TIMEOUT_MS = 30000; if Length(Name) > MAX_PROJECT_NAME_LENGTH then if Timeout > DEFAULT_TIMEOUT_MS then ===== Terminologie ===== Konsistente Begriffe verwenden: ^ Verwende ^ Vermeide ^ | Path | Url, Location, Dir (inkonsistent) | | Config | Settings, Options, Prefs (inkonsistent) | | Create | Make, Build (für Objekte) | | Generate | Create (für Ausgabe) | | Validate | Check, Verify (für Eingaben) | | Initialize | Setup, Init (inkonsistent) | | Execute | Run, Process (inkonsistent) | ===== Formatierung ===== ==== Einrückung ==== * **2 Spaces** für Einrückung (nicht Tabs) * **begin** auf eigener Zeile bei Blöcken * **end** auf gleicher Einrückungsebene wie zugehöriges begin // KORREKT procedure Example; begin if Condition then begin DoSomething; DoMore; end else begin DoAlternative; end; end; // VERBOTEN procedure Example; begin if Condition then begin DoSomething; DoMore; end else DoAlternative; end; ==== Zeilenlänge ==== * Maximum **120 Zeichen** pro Zeile * Bei langen Ausdrücken umbrechen // Bei langen Parameterlisten function VeryLongFunctionName( const AFirstParameter: string; const ASecondParameter: Integer; AThirdParameter: TOptions ): Boolean; // Bei langen Bedingungen if (FirstCondition) and (SecondCondition) and (ThirdCondition) then begin // ... end; ===== Verbotene Patterns ===== ==== Leere Exception-Handler ==== // VERBOTEN try DoSomething; except // Nichts tun - Fehler verschlucken! end; // KORREKT try DoSomething; except on E: Exception do LogError(rsUnexpectedError, [E.ClassName, E.Message]); end; ==== TODO/FIXME im Produktionscode ==== // VERBOTEN in main-Branch // TODO: Implement this later // FIXME: This is broken // ERLAUBT nur in Feature-Branches, muß vor Merge entfernt werden ==== Hardcodierte Strings ==== // VERBOTEN ShowMessage('File not found'); ShowMessage('Datei nicht gefunden'); // KORREKT ShowMessage(rsFileNotFound); // resourcestring ==== Globale Variablen in Services ==== // VERBOTEN var GlobalConfig: TConfig; // Schwer testbar! // KORREKT - Parameter übergeben function ProcessWithConfig(const AConfig: TConfig): Boolean; ===== Testbarkeit ===== Services müssen ohne VSCode/UI testbar sein: // VERBOTEN - UI in Service procedure ValidateAndShowError(const AName: string); begin if not IsValid(AName) then ShowMessage('Invalid name'); // UI-Abhängigkeit! end; // KORREKT - Rückgabewert function ValidateName(const AName: string; out AError: string): Boolean; begin Result := IsValid(AName); if not Result then AError := rsInvalidName; end; ===== pas2js Kompatibilität (WICHTIG) ===== **pas2js ist NICHT 100% kompatibel mit FPC!** Diese Einschränkungen MÜSSEN beachtet werden. ==== Nicht unterstützte Features ==== ^ Feature ^ FPC ^ pas2js ^ Workaround ^ | ''class var'' | ✓ | ✗ | Unit-Level Variable verwenden | | Inline ''var'' | ✓ | ✗ | Im ''var''-Block deklarieren | | ''Int64'' | ✓ | ✗ | ''Integer'' verwenden | | ''//'' in ''asm'' | ✓ | ✗ | ''/* */'' für JS-Kommentare | | Lokale Var in ''asm'' | ✓ | ✗ | Direkt JS-Objekte verwenden | | Konstanten in ''asm'' | ✓ | ✗ | Literal-Werte verwenden | | ''-O2'' + asm-Aufrufe | ✓ | ✗ | ''-O-'' oder Methoden public machen | | Unit-Funktion in ''asm'' | ✓ | ✗ | ''pas["Unit.Name"].FuncName()'' verwenden | | ''out'' Param in ''asm'' | ✓ | ✗ | ''Param.set(value)'' verwenden | ==== Beispiele: Typen und Syntax ==== (* VERBOTEN - class var *) TMyClass = class class var FInstance: TMyClass; (* pas2js Error! *) end; (* KORREKT - Unit-Level Variable *) var GMyInstance: TMyClass = nil; (* VERBOTEN - Inline var *) begin var X := 42; (* pas2js Error! *) end; (* KORREKT - var-Block *) var X: Integer; begin X := 42; end; (* VERBOTEN - Int64 *) var Size: Int64; (* pas2js: "Symbol Int64 is not implemented" *) (* KORREKT - Integer *) var Size: Integer; (* VERBOTEN - // in asm *) asm // JavaScript code (* pas2js Error! *) end; (* KORREKT - /* */ in asm *) asm /* JavaScript code */ end; ==== Beispiele: asm-Block Pitfalls ==== Diese Fehler sind schwer zu finden, da sie erst zur Laufzeit auftreten! **Lokale Variablen sind NICHT in asm sichtbar:** (* VERBOTEN - Lokale Variable in asm *) procedure Test; var MyVar: TJSObject; begin asm MyVar = {}; (* pas2js Error: MyVar is not defined! *) end; end; (* KORREKT - Direkt JS-Objekte verwenden *) procedure Test; begin asm module.exports.myFunc = function() {}; /* Direkt zugreifen */ end; end; **Konstanten sind NICHT in asm sichtbar:** (* VERBOTEN - Konstante in asm *) const TIMEOUT_MS = 5000; begin asm setTimeout(func, TIMEOUT_MS); (* pas2js Error: TIMEOUT_MS is not defined! *) end; end; (* KORREKT - Literal-Wert verwenden *) begin asm setTimeout(func, 5000); /* Literal statt Konstante */ end; end; **Unit-Funktionen - NICHT mit Unterstrichen aufrufen:** (* VERBOTEN - self.* oder Unterstrich-Syntax *) procedure TMyClass.HandleBrowse; begin asm self.ToolchainConfig().DoSomething(); (* pas2js Error! *) pas.Toolchain_ConfigService.ToolchainConfig(); (* FALSCH: Unterstriche! *) end; end; (* KORREKT - pas["Unit.Name"].FuncName() Syntax *) procedure TMyClass.HandleBrowse; begin asm pas["Toolchain.ConfigService"].ToolchainConfig().DoSomething(); /* Richtig! */ end; end; **out-Parameter - NICHT direkt zuweisen:** (* VERBOTEN - out-Parameter direkt zuweisen *) function ValidateMessage(AMsg: TJSObject; out AType: string): Boolean; begin asm AType = AMsg.type; (* FALSCH: überschreibt getter/setter Objekt! *) end; end; (* KORREKT - out-Parameter mit .set() zuweisen *) function ValidateMessage(AMsg: TJSObject; out AType: string): Boolean; begin asm AType.set(AMsg.type); /* RICHTIG: ruft den Setter auf */ end; end; **-O2 Optimierung - kann asm-Aufrufe brechen:** # PROBLEM: Bei -O2 werden private Methoden wegoptimiert pas2js -O2 extension_main.pas (* asm-Aufrufe können fehlschlagen *) # LÖSUNG 1: Optimierung deaktivieren pas2js -O- extension_main.pas # LÖSUNG 2: Methoden public machen (nicht empfohlen) ==== Node.js API Pattern ==== **TNJ* Klassen sind KEINE Klassen** - sie sind Wrapper für Funktionen: (* VERBOTEN - Klassen-Methoden *) Path := TNJPath.resolve(APath); (* Error! *) Exists := TNJFS.existsSync(APath); (* Error! *) Bytes := TNJCrypto.randomBytes(16); (* Error! *) (* KORREKT - Standalone Funktionen *) uses NodeJS.Path, NodeJS.FS, NodeJS.Crypto; Path := PathResolve([APath]); (* Funktion aus NodeJS.Path *) Exists := ExistsSync(APath); (* Funktion aus NodeJS.FS *) Bytes := RandomBytes(16); (* Funktion aus NodeJS.Crypto *) ==== TJSPromise Catch-Methode ==== (* VERBOTEN - underscore catch *) MyPromise._catch(ErrorHandler); (* Funktioniert NICHT! *) (* KORREKT - Punkt catch *) MyPromise.catch(ErrorHandler); (* So ist es richtig *) ==== Chained Bracket Access ==== pas2js erlaubt KEINE verketteten Bracket-Zugriffe auf JSValue: (* VERBOTEN - Verkettete Brackets *) function GetStdout(RawResult: TJSObject): string; begin asm Result = RawResult['stdout']['toString'](); (* pas2js Error! *) end; end; (* KORREKT - Zwischenvariable verwenden *) function GetStdout(RawResult: TJSObject): string; var StdoutObj: TJSObject; begin StdoutObj := TJSObject(RawResult['stdout']); asm Result = StdoutObj['toString'](); /* Funktioniert */ end; end; **Compiler-Fehler:** ''illegal qualifier "[" after "JSValue"'' ==== Konstruktor-Signaturen beachten ==== Konstruktoren haben spezifische Signaturen - nicht raten: (* VERBOTEN - Falscher Parameter-Typ *) var Channel: TOutputChannel; Logger: TWvdSLogger; begin Channel := CreateOutputChannel('WvdS Projects'); Logger := TWvdSLogger.Create(Channel); (* Error: Got TOutputChannel, expected String *) end; (* KORREKT - Signatur prüfen *) var Logger: TWvdSLogger; begin Logger := TWvdSLogger.Create('wvds.projects'); (* Source-Name als String *) end; Bei Compiler-Fehlern zu Parameter-Typen immer die Unit-Deklaration prüfen. ==== Unit-Referenzen in asm-Blöcken ==== pas2js wandelt Punkte in Unterstriche: (* Pascal *) uses WvdS.Build.Service; (* In asm-Block *) asm var service = pas.WvdS_Build_Service; /* Unterstriche! */ end; ==== JavaScript-Interop ==== (* asm-Block für direktes JavaScript *) asm console.log('Direct JS call'); end; (* Typisierte Variablen für JS-Objekte *) var JsObj: TJSObject; JsArr: TJSArray; ==== Build-Konfiguration ==== Zwei Config-Dateien pro Extension: **1. Shared Config** (''~/sources/common/wvds_common.pas2js.cfg''): * Compiler Mode, Target, Defines * RTL-Pfade, Common Unit-Pfade **2. Extension-spezifische Config** (''build.cfg''): * Lokale Unit-Pfade (''-Fupas'') * Output-Pfad (''-FEdist/'', ''-odist/extension_main.js'') Aufruf: pas2js @../../common/wvds_common.pas2js.cfg @build.cfg pas/extension_main.pas ===== SSOT - Common Libraries verwenden ===== **Direkte require()-Aufrufe sind VERBOTEN!** Immer die Pascal-Wrapper aus ''~/sources/common/'' verwenden. ==== Node.js Wrappers ==== Verfügbar in ''~/sources/common/web/nodejs/'': ^ Unit ^ Zweck ^ Wichtige Funktionen ^ | ''NodeJS.Base'' | Core Node.js-Typen | TJSObject, RequireModule | | ''NodeJS.FS'' | Dateisystem | ExistsSync, StatSync, ReadFileSync, WriteFileSync | | ''NodeJS.Path'' | Pfadmanipulation | PathJoin, PathDirname, PathResolve, PathBasename | | ''NodeJS.ChildProcess'' | Prozesse starten | Spawn, Exec, SpawnSync, TSpawnOptions | | ''NodeJS.Crypto'' | Kryptographie | RandomBytes | | ''NodeJS.Buffer'' | Binärdaten | TBuffer | ==== VSCode Wrappers ==== Verfügbar in ''~/sources/common/web/vscode/'': ^ Unit ^ Zweck ^ Wichtige Funktionen ^ | ''VSCode.Base'' | Core VSCode-Typen | TUri, TPosition, TRange, TTextDocument | | ''VSCode.Window'' | Fenster-Operationen | ShowInformationMessage, CreateWebviewPanel | | ''VSCode.Commands'' | Befehlsregistrierung | RegisterCommand, ExecuteCommand | | ''VSCode.Workspace'' | Workspace-Operationen | FindFiles, GetConfiguration | | ''VSCode.Tasks'' | Task-Ausführung | CreateShellTask, ExecuteTask | ==== WvdS Core System ==== Verfügbar in ''~/sources/common/core/system/'': ^ Unit ^ Zweck ^ Wichtige Funktionen ^ | ''WvdS.System.Logging'' | Unified Logging | TWvdSLogger, LogDebug, LogInfo, LogError | | ''WvdS.VSCode.Security'' | Sicherheitsfunktionen | EscapeHtml, ValidateWebViewMessage, GetSecureNonce | | ''WvdS.VSCode.Strings'' | Resourcestrings | 130+ lokalisierte Strings | | ''WvdS.WebView.Bridge'' | Host-WebView Kommunikation | THostBridge, TBaseBridge | ==== SSOT-Refactoring Pattern ==== **Vorher (SSOT-Verletzung):** function IsFpcAvailable(const AFpcPath: string): Boolean; begin asm try { var fs = require('fs'); /* VERBOTEN: direkter require! */ Result = fs.existsSync(AFpcPath); } catch (e) { Result = false; } end; end; **Nachher (korrekt mit Common Library):** uses NodeJS.FS; (* Wrapper aus common/ *) function IsFpcAvailable(const AFpcPath: string): Boolean; begin Result := ExistsSync(AFpcPath); (* Pascal-Funktion *) end; ==== Async File Operations ==== **Vorher (SSOT-Verletzung):** function LoadMetadata(const APath: string): TJSPromise; begin asm Result = new Promise(function(resolve, reject) { var fs = require('fs'); /* VERBOTEN */ fs.stat(APath, function(err, stats) { ... }); }); end; end; **Nachher (korrekt mit Common Library):** uses NodeJS.FS; function LoadMetadata(const APath: string): TJSPromise; begin Result := Stat(APath)._then( function(stats: JSValue): JSValue begin (* Verarbeite Stats *) end ); end; ===== Checkliste vor Commit ===== [ ] Namenskonventionen eingehalten [ ] PasDoc-Dokumentation vorhanden [ ] Keine Magic Numbers [ ] Keine TODO/FIXME-Kommentare [ ] Keine hardcodierten Strings [ ] Keine leeren Exception-Handler [ ] Keine globalen Variablen in Services [ ] Keine UI-Abhängigkeiten in Services [ ] Formatierung korrekt (Einrückung, Zeilenlänge) [ ] Konsistente Terminologie [ ] Common Libraries statt direkter require() ===== Siehe auch ===== * [[.:sicherheit|Sicherheitsrichtlinien]] * [[.:i18n|Internationalisierung]] * [[.:extension-entwicklung|Extension-Entwicklung]]