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