Skip to content

target compiler

Alexander Liberov edited this page Nov 8, 2026 · 3 revisions

Target compiler

--target-compiler <ver|auto> tells lint, lint-all and rules the oldest Delphi compiler the code must still compile on. Modernize advice that needs a newer compiler is then not reported, so a project that has to build on Delphi 12 (or 10.4, or XE7) no longer hears "write X is not T" or "use an if expression". The default is 37 (Delphi 13): without the switch nothing changes.

Running it from the CLI

drag-lint lint <file.pas> --target-compiler 12           (Delphi 12 = CompilerVersion 36)
drag-lint lint-all --db <db> --target-compiler 10.4
drag-lint lint-all --db <db> --project <x.dproj> --target-compiler auto   (from the .dproj ProjectVersion)
drag-lint rules --json --target-compiler 36              (target_active per rule at that target)

Accepted tokens: product versions 13, 12, 12.2, 11, 10.4, 10.3, 10.2, 10.1, 10, XE8 .. XE2, XE, 2010, 2009, 2007, 2005, 7, 6, 5 .. 1 (an optional Delphi prefix, D5 .. D1 and the code names Florence, Athens, Alexandria, Sydney, Rio, Tokyo, Berlin, Seattle work too), a conditional symbol VER80 .. VER370 (VER130 = Delphi 5), or a CompilerVersion 14 .. 37. A bare 13 is Delphi 13, never CompilerVersion 13. An unknown token exits 2 and lists the accepted set.

Delphi 5 and older (1.43)

Delphi 5 has no CompilerVersion constant (Delphi 6 = 14.0 introduced it); it defines VER130. drag-lint numbers VER80 .. VER130 as 8 .. 13, so D5 is target 13: --target-compiler 5. The preprocessor then describes that compiler's own symbols: VER130, WIN32, CPU386, and not CONDITIONALEXPRESSIONS, UNICODE, MSWINDOWS or WIN64, whatever --platform says. That set comes from the Delphi 5 documentation and has not been measured against a D5 compiler. Nothing here turns code into Delphi 5 code: D5 is upconvert only.

Nothing is detected from the machine: on a D5 VM with no RAD Studio a run with no token and no config still describes 37. Pass the token or set target_compiler.

auto reads the <ProjectVersion> of the .dproj named by --project (20.4 -> 37, 20.1-20.3 -> 36, 19.3-19.5 -> 35, 19.0-19.2 -> 34, 18.5-18.8 -> 33, 18.2-18.4 -> 32, 18.1 -> 31). A value outside that table exits 2. On a .dpr / .dpk it reads the .dproj beside it; with none, nothing names the compiler, so it assumes Delphi 5 (13) and says so on stderr. auto with no project at all exits 2. ProjectVersion says which IDE last saved the project, not which compilers must accept it -- name the version when they differ.

The config key

{ "target_compiler": "13", "profiles": { "legacy": { "target_compiler": "12" } } }

"target_compiler" in drag-lint-lint.json takes the same tokens. A profile overrides it (--profile legacy), and --target-compiler on the command line wins over both. Fix it / Fix all in the IDE run the engine with the project's config, so a project that sets the key is linted and fixed for its target from the IDE too.

Order of precedence: command line, then the config (after --profile), then the project context file's targetCompiler, then 37.

When indexing (1.39)

drag-lint index --project <x.dproj> --db <db>                       (VER<N>0 from the .dproj ProjectVersion)
drag-lint index --project <x.dproj> --db <db> --target-compiler 10.3 (VER330, whatever the .dproj says)
drag-lint index --all [--jobs N]                                    (each section from ITS OWN .dproj)

The indexer's preprocessor defines VER<N>0, CompilerVersion and RTLVersion for the Delphi the code is parsed for, so {$IFDEF VER330} / {$IF CompilerVersion >= 35} branches parse as that compiler would take them. Precedence: --target-compiler, then the <ProjectVersion> of the --project .dproj, then the installed RAD Studio. index --all applies this per section (a .dpr root uses its sibling .dproj; a section without one gets the installed Delphi, never the previous section's), in --jobs N children too. A non-37 target adds a cv=<N> limb to the indexer fingerprint, so changing it re-parses that index. From outside PRs #8 and #11 (Jonathas Paiva).

What is gated

rule what its advice needs gated below
is-not-operator X is not T 37 (Delphi 13)
ifthen-to-if-expression X := if C then A else B 37
nonminimal-bool, the not (A in S) form only A not in S 37
unreadable-literal digit separators 1_000 35 (Delphi 11)
pos-for-contains, the Contains forms TStringHelper.Contains 24 (XE3)
manual-inrange System.Math.InRange 14 (Delphi 6)

The inline-var fixes of swap-without-temp, field-by-name-in-loop and index-loop-to-for-in need 33 (10.3): below that the finding is still reported, the edit is skipped, and stderr says N fix(es) skipped (target-compiler).

A non-default target prints one line on stderr: target-compiler: 36 (Delphi 12) -- N modernize rule(s) gated, M compat rule(s) on.

In rules --json

Every row carries needs_compiler (0 = any compiler), compat_since (0 = not a compat rule) and target_active (does the rule run by default at this target; equal to default_enabled at 37). The root carries target_compiler.

Writing a gated rule

A sidecar .json gates its rule with "needs_compiler": N; the same key on one fix entry gates only the matches that entry would fix. A rule of category compat carries "compat_since": N, ships "enabled": false, and is switched on for a run whose target is older than N. See rules/README.md. The downgrade rules themselves are a separate entry: Downgrade fixes.

Clone this wiki locally