====== Convenzioni del codice ====== Standard di codifica vincolanti per la suite WvdS FPC RAD. ===== Convenzioni di naming ===== ==== Tipi ==== ^ Categoria ^ Prefisso ^ Esempio ^ | Classe | TWvdS* | TWvdSBuildConfig, TWvdSProjectManager | | Interface | IWvdS* | IWvdSLogger, IWvdSParser | | Record | TWvdS* | TWvdSBuildResult, TWvdSToolPath | | Enum | TWvdS* | TWvdSProjectType, TWvdSBuildStatus | | Exception | EWvdS* | EWvdSFileNotFound, EWvdSParseError | | Callback | TWvdS* | TWvdSBuildCallback, TWvdSProgressHandler | I tipi API esterni (VSCode, Node.js) mantengono i loro nomi originali. ==== Nomi delle unit ==== Namespace stile Microsoft con prefisso WvdS: WvdS.{Domain}.{Layer}.pas Esempi: WvdS.Build.Models.pas WvdS.Build.Service.pas WvdS.Projects.SettingsDialog.pas WvdS.VSCode.Commands.pas ==== Suffissi layer ==== ^ Suffisso ^ Contenuto ^ Esempio ^ | .Models | Record, enum, tipi | WvdS.Build.Models | | .Service | Logica di business | WvdS.Build.Service | | .Dialog | Dialoghi WebView | WvdS.Projects.SettingsDialog | | .Provider | Wrapper API VSCode | WvdS.Designer.EditorProvider | ==== Variabili e parametri ==== ^ Categoria ^ Prefisso ^ Esempio ^ | Campi privati | F | FProjectPath, FConfig | | Parametri | A | APath, AOptions, ACallback | | Variabili locali | Nessuno | Result, I, Config | ==== Resourcestrings ==== Prefisso per feature: ^ Prefisso ^ Feature ^ | rsCore* | Estensione Core | | rsBuild* | Estensione Build | | rsProject* | Estensione Projects | | rsDesigner* | UI Designer | | rsPreview* | UI Preview | | rsMeta* | UI Meta | | rsPackaging* | Packaging | | rsTool* | Toolchain | ===== Struttura del codice ===== ==== Struttura unit ==== unit WvdS.{Feature}.{Layer}; {$mode objfpc}{$H+} interface uses // 1. Unit di sistema SysUtils, Classes, // 2. WvdS Common WvdS.System, WvdS.Collections, // 3. Specifiche della feature WvdS.{Feature}.Models; type // Definizioni di tipo function PublicFunction(const AParam: string): Boolean; procedure PublicProcedure(AValue: Integer); implementation uses // Uses privati (unit necessarie solo qui) WvdS.VSCode.Strings; // Tipi e variabili private type TInternalHelper = class end; var InternalState: TObject; // Implementazioni function PublicFunction(const AParam: string): Boolean; begin // ... end; procedure PublicProcedure(AValue: Integer); begin // ... end; initialization // Inizializzazione finalization // Pulizia end. ==== Struttura classe ==== 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; ===== Documentazione ===== ==== Formato PasDoc ==== (* @abstract(Breve descrizione in una frase.) Descrizione dettagliata dello scopo e dell'utilizzo. Puo comprendere piu frasi. @param(APath Percorso completo del file) @param(AOptions Configurazione opzionale, puo essere nil) @returns(True se successo, False in caso di errore) @raises(EWvdSFileNotFound se il file non esiste) @raises(EWvdSAccessDenied se non ci sono permessi di lettura) Security: - CWE-22: Il percorso viene validato contro Path Traversal @seealso(RelatedFunction) @seealso(TWvdSRelatedClass) *) function ProcessFile(const APath: string; AOptions: TOptions): Boolean; ==== Commenti inline ==== // Commento breve per singola riga Result := CalculateValue; // Commento multiriga per logica piu complessa // Spiega il perche, non il cosa if (Value > Threshold) and (Mode = mAdvanced) then begin // Dobbiamo usare l'algoritmo avanzato qui, // perche quello semplice diventa impreciso con valori grandi Result := AdvancedCalculation(Value); end; ===== Costanti invece di magic number ===== // VIETATO if Length(Name) > 64 then if Timeout > 30000 then // CORRETTO const MAX_PROJECT_NAME_LENGTH = 64; DEFAULT_TIMEOUT_MS = 30000; if Length(Name) > MAX_PROJECT_NAME_LENGTH then if Timeout > DEFAULT_TIMEOUT_MS then ===== Terminologia ===== Usare termini consistenti: ^ Usa ^ Evita ^ | Path | Url, Location, Dir (inconsistente) | | Config | Settings, Options, Prefs (inconsistente) | | Create | Make, Build (per oggetti) | | Generate | Create (per output) | | Validate | Check, Verify (per input) | | Initialize | Setup, Init (inconsistente) | | Execute | Run, Process (inconsistente) | ===== Formattazione ===== ==== Indentazione ==== * **2 spazi** per indentazione (non tab) * **begin** su riga propria per i blocchi * **end** stesso livello di indentazione del begin corrispondente // CORRETTO procedure Example; begin if Condition then begin DoSomething; DoMore; end else begin DoAlternative; end; end; // VIETATO procedure Example; begin if Condition then begin DoSomething; DoMore; end else DoAlternative; end; ==== Lunghezza riga ==== * Massimo **120 caratteri** per riga * Andare a capo per espressioni lunghe // Per liste parametri lunghe function VeryLongFunctionName( const AFirstParameter: string; const ASecondParameter: Integer; AThirdParameter: TOptions ): Boolean; // Per condizioni lunghe if (FirstCondition) and (SecondCondition) and (ThirdCondition) then begin // ... end; ===== Pattern vietati ===== ==== Handler eccezioni vuoti ==== // VIETATO try DoSomething; except // Non fare nulla - ingoiare l'errore! end; // CORRETTO try DoSomething; except on E: Exception do LogError(rsUnexpectedError, [E.ClassName, E.Message]); end; ==== TODO/FIXME nel codice di produzione ==== // VIETATO nel branch main // TODO: Implement this later // FIXME: This is broken // CONSENTITO solo in branch feature, deve essere rimosso prima del merge ==== Stringhe hardcoded ==== // VIETATO ShowMessage('File not found'); ShowMessage('File non trovato'); // CORRETTO ShowMessage(rsFileNotFound); // resourcestring ==== Variabili globali nei service ==== // VIETATO var GlobalConfig: TConfig; // Difficile da testare! // CORRETTO - passare parametri function ProcessWithConfig(const AConfig: TConfig): Boolean; ===== Testabilita ===== I service devono essere testabili senza VSCode/UI: // VIETATO - UI nel service procedure ValidateAndShowError(const AName: string); begin if not IsValid(AName) then ShowMessage('Invalid name'); // Dipendenza UI! end; // CORRETTO - valore di ritorno function ValidateName(const AName: string; out AError: string): Boolean; begin Result := IsValid(AName); if not Result then AError := rsInvalidName; end; ===== Compatibilita pas2js (IMPORTANTE) ===== **pas2js NON e 100% compatibile con FPC!** Queste limitazioni DEVONO essere rispettate. ==== Feature non supportate ==== ^ Feature ^ FPC ^ pas2js ^ Workaround ^ | ''class var'' | OK | No | Usare variabile a livello unit | | ''var'' inline | OK | No | Dichiarare nel blocco ''var'' | | ''Int64'' | OK | No | Usare ''Integer'' | | ''//'' in ''asm'' | OK | No | Usare ''/* */'' per commenti JS | | Var locale in ''asm'' | OK | No | Usare direttamente oggetti JS | | Costanti in ''asm'' | OK | No | Usare valori letterali | | ''-O2'' + chiamate asm | OK | No | ''-O-'' o rendere i metodi public | | Funzione unit in ''asm'' | OK | No | Usare ''pas["Unit.Name"].FuncName()'' | | Param ''out'' in ''asm'' | OK | No | Usare ''Param.set(value)'' | ==== Esempi: Tipi e sintassi ==== (* VIETATO - class var *) TMyClass = class class var FInstance: TMyClass; (* Errore pas2js! *) end; (* CORRETTO - variabile a livello unit *) var GMyInstance: TMyClass = nil; (* VIETATO - var inline *) begin var X := 42; (* Errore pas2js! *) end; (* CORRETTO - blocco var *) var X: Integer; begin X := 42; end; (* VIETATO - Int64 *) var Size: Int64; (* pas2js: "Symbol Int64 is not implemented" *) (* CORRETTO - Integer *) var Size: Integer; (* VIETATO - // in asm *) asm // JavaScript code (* Errore pas2js! *) end; (* CORRETTO - /* */ in asm *) asm /* JavaScript code */ end; ==== Esempi: Insidie blocco asm ==== Questi errori sono difficili da trovare, perche si verificano solo a runtime! **Le variabili locali NON sono visibili in asm:** (* VIETATO - variabile locale in asm *) procedure Test; var MyVar: TJSObject; begin asm MyVar = {}; (* Errore pas2js: MyVar is not defined! *) end; end; (* CORRETTO - usare direttamente oggetti JS *) procedure Test; begin asm module.exports.myFunc = function() {}; /* Accesso diretto */ end; end; **Le costanti NON sono visibili in asm:** (* VIETATO - costante in asm *) const TIMEOUT_MS = 5000; begin asm setTimeout(func, TIMEOUT_MS); (* Errore pas2js: TIMEOUT_MS is not defined! *) end; end; (* CORRETTO - usare valore letterale *) begin asm setTimeout(func, 5000); /* Letterale invece di costante */ end; end; **Funzioni unit - NON chiamare con underscore:** (* VIETATO - sintassi self.* o underscore *) procedure TMyClass.HandleBrowse; begin asm self.ToolchainConfig().DoSomething(); (* Errore pas2js! *) pas.Toolchain_ConfigService.ToolchainConfig(); (* SBAGLIATO: underscore! *) end; end; (* CORRETTO - sintassi pas["Unit.Name"].FuncName() *) procedure TMyClass.HandleBrowse; begin asm pas["Toolchain.ConfigService"].ToolchainConfig().DoSomething(); /* Corretto! */ end; end; **Parametri out - NON assegnare direttamente:** (* VIETATO - assegnare direttamente parametro out *) function ValidateMessage(AMsg: TJSObject; out AType: string): Boolean; begin asm AType = AMsg.type; (* SBAGLIATO: sovrascrive oggetto getter/setter! *) end; end; (* CORRETTO - assegnare parametro out con .set() *) function ValidateMessage(AMsg: TJSObject; out AType: string): Boolean; begin asm AType.set(AMsg.type); /* CORRETTO: chiama il setter */ end; end; **Ottimizzazione -O2 - puo rompere chiamate asm:** # PROBLEMA: Con -O2 i metodi privati vengono ottimizzati via pas2js -O2 extension_main.pas (* Le chiamate asm possono fallire *) # SOLUZIONE 1: Disabilitare ottimizzazione pas2js -O- extension_main.pas # SOLUZIONE 2: Rendere i metodi public (non raccomandato) ==== Pattern API Node.js ==== **Le classi TNJ* NON sono classi** - sono wrapper per funzioni: (* VIETATO - metodi di classe *) Path := TNJPath.resolve(APath); (* Errore! *) Exists := TNJFS.existsSync(APath); (* Errore! *) Bytes := TNJCrypto.randomBytes(16); (* Errore! *) (* CORRETTO - funzioni standalone *) uses NodeJS.Path, NodeJS.FS, NodeJS.Crypto; Path := PathResolve([APath]); (* Funzione da NodeJS.Path *) Exists := ExistsSync(APath); (* Funzione da NodeJS.FS *) Bytes := RandomBytes(16); (* Funzione da NodeJS.Crypto *) ==== Metodo catch di TJSPromise ==== (* VIETATO - underscore catch *) MyPromise._catch(ErrorHandler); (* NON funziona! *) (* CORRETTO - punto catch *) MyPromise.catch(ErrorHandler); (* Cosi e corretto *) ==== Accesso bracket concatenato ==== pas2js NON permette accessi bracket concatenati su JSValue: (* VIETATO - bracket concatenati *) function GetStdout(RawResult: TJSObject): string; begin asm Result = RawResult['stdout']['toString'](); (* Errore pas2js! *) end; end; (* CORRETTO - usare variabile intermedia *) function GetStdout(RawResult: TJSObject): string; var StdoutObj: TJSObject; begin StdoutObj := TJSObject(RawResult['stdout']); asm Result = StdoutObj['toString'](); /* Funziona */ end; end; **Errore compilatore:** ''illegal qualifier "[" after "JSValue"'' ==== Attenzione alle firme dei costruttori ==== I costruttori hanno firme specifiche - non indovinare: (* VIETATO - tipo parametro sbagliato *) var Channel: TOutputChannel; Logger: TWvdSLogger; begin Channel := CreateOutputChannel('WvdS Projects'); Logger := TWvdSLogger.Create(Channel); (* Errore: Got TOutputChannel, expected String *) end; (* CORRETTO - verificare la firma *) var Logger: TWvdSLogger; begin Logger := TWvdSLogger.Create('wvds.projects'); (* Source-Name come String *) end; Per errori sui tipi dei parametri, verificare sempre la dichiarazione nella unit. ==== Riferimenti unit in blocchi asm ==== pas2js converte i punti in underscore: (* Pascal *) uses WvdS.Build.Service; (* In blocco asm *) asm var service = pas.WvdS_Build_Service; /* Underscore! */ end; ==== Interop JavaScript ==== (* Blocco asm per JavaScript diretto *) asm console.log('Direct JS call'); end; (* Variabili tipizzate per oggetti JS *) var JsObj: TJSObject; JsArr: TJSArray; ==== Configurazione build ==== Due file di configurazione per estensione: **1. Config condivisa** (''~/sources/common/wvds_common.pas2js.cfg''): * Modalita compilatore, target, define * Percorsi RTL, percorsi unit comuni **2. Config specifica estensione** (''build.cfg''): * Percorsi unit locali (''-Fupas'') * Percorso output (''-FEdist/'', ''-odist/extension_main.js'') Invocazione: pas2js @../../common/wvds_common.pas2js.cfg @build.cfg pas/extension_main.pas ===== SSOT - Usare le librerie common ===== **Le chiamate require() dirette sono VIETATE!** Usare sempre i wrapper Pascal da ''~/sources/common/''. ==== Wrapper Node.js ==== Disponibili in ''~/sources/common/web/nodejs/'': ^ Unit ^ Scopo ^ Funzioni principali ^ | ''NodeJS.Base'' | Tipi core Node.js | TJSObject, RequireModule | | ''NodeJS.FS'' | Filesystem | ExistsSync, StatSync, ReadFileSync, WriteFileSync | | ''NodeJS.Path'' | Manipolazione percorsi | PathJoin, PathDirname, PathResolve, PathBasename | | ''NodeJS.ChildProcess'' | Avviare processi | Spawn, Exec, SpawnSync, TSpawnOptions | | ''NodeJS.Crypto'' | Crittografia | RandomBytes | | ''NodeJS.Buffer'' | Dati binari | TBuffer | ==== Wrapper VSCode ==== Disponibili in ''~/sources/common/web/vscode/'': ^ Unit ^ Scopo ^ Funzioni principali ^ | ''VSCode.Base'' | Tipi core VSCode | TUri, TPosition, TRange, TTextDocument | | ''VSCode.Window'' | Operazioni finestra | ShowInformationMessage, CreateWebviewPanel | | ''VSCode.Commands'' | Registrazione comandi | RegisterCommand, ExecuteCommand | | ''VSCode.Workspace'' | Operazioni workspace | FindFiles, GetConfiguration | | ''VSCode.Tasks'' | Esecuzione task | CreateShellTask, ExecuteTask | ==== Sistema WvdS Core ==== Disponibile in ''~/sources/common/core/system/'': ^ Unit ^ Scopo ^ Funzioni principali ^ | ''WvdS.System.Logging'' | Logging unificato | TWvdSLogger, LogDebug, LogInfo, LogError | | ''WvdS.VSCode.Security'' | Funzioni sicurezza | EscapeHtml, ValidateWebViewMessage, GetSecureNonce | | ''WvdS.VSCode.Strings'' | Resourcestrings | 130+ stringhe localizzate | | ''WvdS.WebView.Bridge'' | Comunicazione Host-WebView | THostBridge, TBaseBridge | ==== Pattern refactoring SSOT ==== **Prima (violazione SSOT):** function IsFpcAvailable(const AFpcPath: string): Boolean; begin asm try { var fs = require('fs'); /* VIETATO: require diretto! */ Result = fs.existsSync(AFpcPath); } catch (e) { Result = false; } end; end; **Dopo (corretto con libreria common):** uses NodeJS.FS; (* Wrapper da common/ *) function IsFpcAvailable(const AFpcPath: string): Boolean; begin Result := ExistsSync(AFpcPath); (* Funzione Pascal *) end; ==== Operazioni file async ==== **Prima (violazione SSOT):** function LoadMetadata(const APath: string): TJSPromise; begin asm Result = new Promise(function(resolve, reject) { var fs = require('fs'); /* VIETATO */ fs.stat(APath, function(err, stats) { ... }); }); end; end; **Dopo (corretto con libreria common):** uses NodeJS.FS; function LoadMetadata(const APath: string): TJSPromise; begin Result := Stat(APath)._then( function(stats: JSValue): JSValue begin (* Elaborare stats *) end ); end; ===== Checklist prima del commit ===== [ ] Convenzioni naming rispettate [ ] Documentazione PasDoc presente [ ] Nessun magic number [ ] Nessun commento TODO/FIXME [ ] Nessuna stringa hardcoded [ ] Nessun handler eccezione vuoto [ ] Nessuna variabile globale nei service [ ] Nessuna dipendenza UI nei service [ ] Formattazione corretta (indentazione, lunghezza riga) [ ] Terminologia consistente [ ] Librerie common invece di require() diretto ===== Vedi anche ===== * [[.:sicherheit|Linee guida sulla sicurezza]] * [[.:i18n|Internazionalizzazione]] * [[.:extension-entwicklung|Sviluppo estensioni]]