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