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