- Source: https://github.com/radprogrammer/delphi-project-standards
- Last updated: 2026-02-22
Imported via @submodules/delphi-project-standards/code-formatting-guide.md in the project's CLAUDE.md.
AI Agents should apply these conventions to all generated and modified Delphi code
unless a project-level CLAUDE.md explicitly overrides a rule. For whole-file
reformatting of existing code, prefer dedicated formatting tools over manual edits.
Numbered rules are checkable constraints an agent can evaluate from visible code alone. They carry a severity:
- Fail -- objective and unambiguous. Must not be violated.
- Warn -- strongly preferred. Flag violations but do not block.
Guidance blocks cover contextual preferences and rationale that require judgment rather than mechanical checking. No rule number is assigned.
Indentation must use exactly 2 spaces per level. Tab characters are not permitted anywhere in Delphi source files. The Delphi IDE converts tabs to spaces by default -- leave this setting on.
// correct
begin
if A > B then
begin
DoSomething;
end;
end;
// incorrect -- tab characters used
begin
if A > B then
begin
DoSomething;
end;
end;begin and end are not indented relative to their controlling statement -- they
align with it.
// correct
if i > 10 then
begin
j := 3;
end;
// incorrect
if i > 10 then
begin
j := 3;
end;Statements inside a begin/end block are indented 2 spaces relative to the
begin/end keywords.
Comments are indented at the same level as the statement they accompany. They are not moved to the left margin or given special indentation.
// correct
if Condition then
begin
// explain what we are doing here
DoSomething;
end;
// incorrect
if Condition then
begin
// explain what we are doing here
DoSomething;
end;Contents of asm sections are indented 2 spaces.
Visibility modifiers (public, private, protected, strict private) align with
the class name -- no extra indentation.
type
TMyClass = class
FField: Integer;
public
procedure DoSomething;
end;Compiler switch directives ({$IFDEF}, {$ENDIF}, etc.) are indented at the same
level as the surrounding statements, not at the left margin.
// correct
if i > 10 then
begin
{$IFDEF DEBUG}
Log('value exceeded');
{$ENDIF}
end;
// incorrect
if i > 10 then
begin
{$IFDEF DEBUG}
Log('value exceeded');
{$ENDIF}
end;Contents of interface and implementation sections are not indented.
Do not add extra indentation for nested brackets or parentheses beyond the standard 2-space continuation indent. Each nesting level does not add another indent.
// correct -- no extra indent per nesting level
Result := SomeFunc(
AnotherFunc(
Value));
// incorrect -- extra indent added for each nesting level
Result := SomeFunc(
AnotherFunc(
Value));Inner (nested) functions and procedures are indented 2 spaces relative to their containing function.
procedure TMyClass.DoWork;
procedure LocalHelper;
begin
// nested function body
end;
begin
LocalHelper;
end;The var block and begin of a function or procedure body are at column 0 relative
to the procedure declaration. Statements inside the begin/end block are indented
2 spaces relative to begin. This is consistent with FMT-002 -- begin aligns with
the procedure/function declaration, which is itself at column 0.
procedure TMyClass.DoSomething;
var
I: Integer;
begin
I := 0;
end;Fields inside record type declarations are indented 2 spaces relative to the record
keyword, following the same rules as var block declarations.
// correct
type
TMyRecord = record
Name: string;
Value: Integer;
end;
// incorrect
type
TMyRecord = record
Name: string;
Value: Integer;
end;Guidance -- case statement indentation:
- Case labels indent relative to the
casekeyword - Case contents indent relative to case labels
elsein a case statement aligns one level out from case labels:
case i of
value1:
statement1;
value2:
statement2;
else
statement3;
end;Guidance -- labels: place labels one indent level less than the current level (not at the left margin, not at the current level).
A single space follows the colon in declarations. No space before the colon.
i: Integer; // correct
function Foo: Boolean // correct
i : Integer; // incorrectColons used as field-width and precision separators in write/writeln format
specifiers have no spaces before or after them.
writeln(Value:10:2); // correct -- field width 10, 2 decimal places
writeln(Value : 10); // incorrectA single space follows each comma and semicolon. No space before.
procedure Foo(A, B: Integer); // correct
procedure Foo(A,B: Integer); // incorrectNo space between a function/procedure name and its opening parenthesis, in both declarations and calls.
procedure Foo(i: Integer); // correct
MyFunc(42); // correct
procedure Foo (i: Integer); // incorrect
MyFunc (42); // incorrectThe := operator must have a single space before and after it.
A := B; // correct
A:= B; // incorrect
A :=B; // incorrectBinary operators (including but not limited to: +, -, *, /, =, <>, <,
>, <=, >=, or, and, xor, div, mod) must have a single space before
and after.
A := B + C * D; // correct
A := B+C*D; // incorrectUnary prefix operators (+, -, not) have a space before but not after.
A := B + -C; // correct
A := not Flag; // correctNo spaces inside () parentheses, [] square brackets, or <> angle brackets
in generics.
MyFunc(A, B); // correct
MyArray[5]; // correct
MyFunc( A, B ); // incorrect
MyArray[ 5 ]; // incorrectA single space follows the // token in line comments.
// correct comment
//incorrect commentBlock comments ({ } and (* *)) use a space after the opening delimiter and before
the closing delimiter.
{ correct }
(* correct *)
{incorrect}
(*incorrect*)Guidance -- conflict resolution: when spacing rules conflict, insert one space.
begin must appear on its own line before a controlled block. Never place begin
on the same line as then, do, or else.
// correct
if A < B then
begin
DoSomething;
end;
// incorrect
if A < B then begin
DoSomething;
end;else must appear on its own line. Never collapse end else begin.
// correct
if True then
begin
A := 1;
end
else
begin
A := 2;
end;
// incorrect
if True then
begin
A := 1;
end else begin
A := 2;
end;No line break between else and if.
// correct
if Condition1 then
DoThis
else if Condition2 then
DoThat;
// incorrect
if Condition1 then
DoThis
else
if Condition2 then
DoThat;A single instruction following if, case, for, while, or repeat goes on its
own line, indented 2 spaces. It does not share the line with then or do.
// correct
if i > 10 then
j := 3;
for I := 0 to Count - 1 do
Process(I);
// incorrect
if i > 10 then j := 3;
for I := 0 to Count - 1 do Process(I);A single instruction inside try, except, or finally blocks goes on its own
line, indented 2 spaces. Inside except, the on E: clause is indented 2 spaces
relative to except, and the handler body is indented a further 2 spaces relative
to the on line.
// correct
try
DoSomething;
except
on E: EMyException do
HandleSpecific(E);
on E: Exception do
HandleGeneral(E);
end;
try
DoSomething;
finally
Cleanup;
end;
// incorrect
try DoSomething; except on E: Exception do HandleGeneral(E); end;The then keyword stays on the same line as the boolean condition.
// correct
if A < B then
DoSomething;
// incorrect
if A < B
then DoSomething;Do not add parentheses around the boolean condition of an if statement.
if A < B then // correct
if (A < B) then // incorrectEach line contains at most one statement. Inline variable declarations in for loop
headers count as one statement and are not subject to STY-023 (one declaration per line).
// correct
for var I := 0 to Count - 1 do
Process(I);
// incorrect -- two statements on one line
A := 1; B := 2;Generated code should aim to stay within 200 columns. Lines beyond 200 columns should be wrapped. The formatter's hard wrap limit is 240 columns.
All lines must have trailing whitespace removed.
Each unit in a uses clause appears on its own line.
uses
System.SysUtils,
System.Classes;Each variable or constant declaration begins on its own line. Array and record constant initialisers may span multiple lines -- the declaration itself still begins on its own line.
// correct
const
DAYS: array[0..6] of string = (
'Mon', 'Tue', 'Wed',
'Thu', 'Fri', 'Sat', 'Sun');
// incorrect -- two declarations on one line
var A, B: Integer;The return type stays on the same line as the function name. For anonymous functions,
the return type stays on the same line as the function keyword.
function GetValue: Integer; // correct -- named function
FFunc :=
function(AValue: Integer): Integer // correct -- anonymous function
begin
Result := AValue + 1;
end;Anonymous method assignments break to the next line after :=.
FProc :=
procedure
begin
DoWork;
end;
FFunc :=
function(AValue: Integer): Integer
begin
Result := AValue + 1;
end;Declare properties on a single line where possible. When a property declaration exceeds
200 columns, break after the property name and type, and indent each clause (read,
write, default, stored) by 2 spaces.
// correct -- fits on one line
property MyValue: Integer read GetMyValue write SetMyValue;
// correct -- too long, broken at clauses
property MyLongPropertyName: TMyVeryLongTypeName
read GetMyLongPropertyName
write SetMyLongPropertyName;
// incorrect -- broken unnecessarily when it fits on one line
property MyValue: Integer
read GetMyValue
write SetMyValue;Guidance -- parameters: keep function call and definition parameters on one line when possible. When wrapping is required, indent continuation lines 2 spaces from the opening parenthesis.
Guidance -- anonymous methods as parameters:
TThread.CreateAnonymousThread(
procedure
begin
DoWork;
end).Start;
TTask.Run(
procedure
begin
DoWork;
end);Guidance -- chained anonymous method calls: each chained call goes on its own line, indented 2 spaces as a continuation:
TTask.Run(
procedure
begin
DoWork;
end)
.ContinueWith(
procedure(ATask: ITask)
begin
DoMore;
end);Guidance -- generic constraints: when a generic type constraint list exceeds 200 columns,
break after the opening < and indent continuation lines 2 spaces. There is no single
Delphi convention for this; prefer clarity over strict column alignment:
// fits on one line -- preferred
type TMyClass<T: class, constructor, IMyInterface> = class
// too long -- break at constraints
type TMyClass<T: class, constructor,
IMyInterface, IDisposable> = classEmpty line counts are minimums between declarations and ceilings within code. Do not add blank lines beyond the ceiling counts. Do not remove user-added blank lines that fall within the allowed maximum.
No more than 2 consecutive empty lines anywhere in the file. This is a ceiling -- never add more than 2, but preserve up to 2 if the author placed them.
No blank lines immediately before or after compiler switch directives ({$IFDEF},
{$ENDIF}, etc.). When a compiler switch directive immediately precedes or follows
a method declaration, the 2-line method separation rule (FMT-041) takes priority
over this rule.
One empty line before and after interface and implementation keywords.
Two empty lines between method/function declarations in both the interface and
implementation sections. The formatter enforces this minimum regardless of what
the author wrote.
No blank lines immediately before var, const, or label subsection keywords.
One empty line before the type keyword.
No blank lines immediately before visibility modifiers (public, private,
protected, strict private).
All keywords that appear directly in code -- reserved words (if, then, begin,
end, array) and language directives (abstract, virtual, override,
deprecated, platform, inline) -- must be lowercase.
Compiler switch directives wrapped in {$...} or (*$...*) must be UPPERCASE:
{$IFDEF}, {$WARNINGS}, {$R+}, etc. These are distinct from language directives
(FMT-045) and follow the opposite casing convention.
Hexadecimal and floating point literals must be UPPERCASE.
$FF // correct
1.23E-8 // correct
$ff // incorrect
1.23e-8 // incorrectWhen modifying existing code, do not change the casing of user-defined identifiers. Preserve the casing as first declared in the file.
All alignment options are disabled. Do not align:
=in constants, initializations, or type declarations:=assignment operators- End-of-line comments
- Type names in
varsections or records - Fields in property declarations
- Parameter types in function definitions
:before type names
Each identifier stands at its natural indent with no extra spaces for column alignment.
// correct
var
MyData: Integer;
MyLongerName: string;
// incorrect -- vertically aligned
var
MyData : Integer;
MyLongerName: string;Guidance: Multi-line string literals are used sparingly -- see STY-022. When they do appear:
- The opening
'''aligns with the surrounding statement's indent level - Do not alter whitespace or indentation inside the string content -- it is data, not code
- The closing
'''determines the base indentation of the string content:
Msg := '''
Hello
World
''';