Repository navigation
Releases: Alexl-git/Delphi-RAG-Lint
Release list
v1.31.5-alpha -- bug-fix wave: binary DFM, Config Save, rename closure, lint FPs, plugin timeouts
Bug-fix release. Run drag-lint-setup-1.31.5-alpha.exe (installs into Program Files\drag-lint). No reindex needed: extractor and resolver versions are unchanged.
Fixed
- A binary
.dfmis reported as "binary DFM (TPF0) -- save as Text DFM..." instead of "parse error (1,1)". indexno longer writes "FTS5 probe: AVAILABLE" to stderr (shown red as NativeCommandError in Windows PowerShell 5.1).used-unit-not-resolvableknows the unscoped RTL/VCL unit names (ActiveX,ComObj,CommCtrl...); its advice text is neutral.- The Config tool's Save writes only your edits into the file it opened and keeps keys it does not model.
migrate-dbskeeps them too. resolve-dbs --inprints a NOTE for a file no index covers instead of printing nothing.- Multi-project rename refuses an owner whose compile closure is not fully indexed, instead of a partial rename and a broken build.
object-leakignoresTArray<T>.Create;used-before-assignmentdoes not treatwith Rec doon a record local as a use.- Indexing into a new database no longer prints stale-database notes; the whole-database reason gives real new/changed counts; the long-run warning prints only from 1M references.
allowaccepts a whole-routine metric marker whereverlintreports it; anallowbatch on a wrapped statement keeps the continuation line's marker;lint-allrefuses--file/--in(exit 2) instead of ignoring them.document --applykeeps blocks it cannot see, and shared units converge (a certain caller is never downgraded to?by a project that cannot resolve it).
IDE plugin
- Job timeouts are enforced while the engine runs; on expiry the process tree is killed and the job shows "timed out after N s".
Details: CHANGELOG.md, v1.31.5-alpha.
v1.31.4-alpha -- installer package, sqlite3.dll shipped, type-name-prefix ancestry
The installer is now the release package: run drag-lint-setup-1.31.4-alpha.exe (installs into Program Files\drag-lint). Per-platform zips are no longer published; the source is in GitHub's source archives.
Fixed
sqlite3.dllships with the engine. Without RAD Studio on the machine,inforeported FTS5=False andindexstopped with FireDAC error -314.type-name-prefixtraces the ancestor chain into the library index, soEKeyViolation = class(EDatabaseError)asks for "E", not "T". The autofix takes its letter from the finding.
Verified on a clean VM with only Delphi 5 installed: FTS5=True; index / query / find-callers / outline / lint / lint-all over Delphi 5 Source\Vcl.
Known (fixed in 1.31.5): a binary .dfm shows "parse error (1,1)" instead of the binary-DFM hint; index prints its FTS5 probe to stderr, which Windows PowerShell shows in red.
Extractor unchanged (1.21.1): no reindex needed.
v1.31.3-alpha
Fixed
- Fix all in unit did nothing when the project had a member under a registry LIBRARY root
(e.g.ArrayHelper.pas). The multi-project rename's freshness check counted that.dproj
member as missing from the owner's index, so the owner was permanently stale and--apply
dropped every rename (75 in the report), ending with "no fixable findings". The check now
considers only members the project owns (ProjectOwnedMembers). The CLI summary now says
autofix: nothing written -- N rename(s) dropped: <reason>and appends, N rename(s) not writtenwhen some were written and some were not. - IDE plugin: Fix all shows the wait cursor while it runs and refuses a second fix. The job
queue marks fix jobs Busy; the Structure form hooks the cursor and answers a second Fix all
with a refusal (RefusedWhileFixRuns) instead of queueing a conflicting write. type-name-prefix: an exception class whose direct ancestor is exception-named is no longer
told to useT.EKeyViolation = class(EDatabaseError)is accepted, and Fix all no longer
renames it toTEKeyViolation(AncestorNamedLikeException). Name heuristic only; the
library-index hop is planned for 1.31.4-alpha.- Naming autofix no longer half-applies a rename against a stale index. Since const-casing
renames word by word (glyActive->GLY_ACTIVE), a stale use site that the position guard
skipped left the declaration renamed and the use not, so the unit no longer compiled. A rename
that changes more than letter case is now all or nothing: one skipped site drops the whole
rename, and it is counted as skipped (stale index), as the multi-project path already did.
Extractor unchanged (1.21.1): no reindex needed.
Release battery: 693 pass / 0 fail. Win64 + Win32 zips attached. Extractor unchanged (1.21.1): no reindex needed.
v1.28.3-alpha -- converter: uses reconciliation, pas_uses work list, StripDB, in-scope #convert classes; Work list editor tab; compare verb
Engine, conversion editor and charts released together. Covers engine 1.27.0 through 1.28.3.
No re-parse or re-resolve needed: the extractor (1.21.1) and resolver (1.12) are unchanged, so existing indexes keep working.
Engine -- component conversion (convert-apply)
-
usesreconciliation (C9). After a conversion, each unit'susesclauses are reconciled. Units the new components need are added; units nothing needs any more are removed. A unit is kept only when the converted.dfmstill needs it. Every change is reported as auses[]row (add/remove/kept/moved), and the project index is refreshed after the write.--no-reconcile-usesand--no-reindexopt out. -
Ties between declaring units are settled by the book. When a type is declared in more than one unit, the book's
#usedecides, then#useswap, then the#convertunit. The chosen unit goes into the interfaceuseswhen the field is declared there, so the converted unit compiles. Units named on a#convertline are now reported asaddrows too. -
#convertclasses resolve in the unit's scope (1.28.3). A bare class name such asTLabelis no longer matched to the first class of that name, which could be the FMX one. When a name is still ambiguous, the engine infers the class from:- the project framework;
- the form-file kind (
.dfmmeans VCL,.fmxmeans FMX); - the properties the
.dfmstreams.
If that settles it, the unit converts with a warning naming the evidence. Otherwise the unit is refused with a detailed reason, and
qualify_fixes[]carries paste-ready replacement lines. -
New
pas_uses[]report. It lists every member and global the converted.passtill uses, with a status:mapped,same-name,unmapped,not-rewritten,cast-refusedorglobal-dangling. Each row also carries the declaring unit, how the class was resolved, and the candidates when ambiguous. It is the work list for hand conversion. -
StripDBvalue cast.#link TableName <- TableName : StripDBremoves a trailing.dbfrom Paradox table names. It applies to the.dfmand to single-literal.pasassignments; every other form of assignment gets a warning instead of an edit. -
Fixed: a class cast (e.g.
AssignGraphic) could write unparseable.pascode. Such access sites are now refused one by one, with a warning to convert them by hand. -
New exit code 6: a cast library that cannot be loaded or executed. Code 2 still means a write failure or rollback.
Engine -- lint
function-result-not-setandout-param-not-setno longer fire on paths that end inraise,HaltorAbort.doc-driftno longer reads comments inside a split parameter list as parameters. Doc facts no longer attach a "Called from" edge to the wrong class.unused-unit-in-usesnow counts enum values, so a unit used only throughalTopor similar is no longer flagged.
Conversion editor
Conversion editor: new Work list tab lists every member the converted code still uses (unmapped / not-rewritten / cast-refused / global-dangling, with ambiguous declaring units shown as such); Convert tab reports cast sites, StripDB value changes and uses-clause changes; StripDB applied to Paradox TableName in the BDE->FireDAC book; Convert progress and Conversion > Reindex project.
Charts
New drag-lint compare verb: reruns a report from its COMMAND header and diffs the old result against the new one, with text, JSON or HTML output. It covers report text, forms CSV and chart graphs, and the old side can be the remarks block, a saved file, an automatic snapshot or an older index. The HTML page refuses to embed an unsafe graph.svg and is served under a strict CSP. The first chart emitters (text, index, source) are now native Delphi, checked against the PowerShell originals by a parity harness.
JSON consumers
convert-apply --format json has these changes:
- New capabilities:
uses_reconcile,cast_site_refusal,pas_uses,pas_uses_resolution,value_cast_strip_dbandconvert_qualify_fix. - Retired item kind:
cast-applied. uses_addedcan be one higher: a book#useTo unit placed in the interface now gets its ownaddrow.
The full details are in CHANGELOG.md.
v1.21.1-alpha -- unused-unit-in-uses fix (re-parse), tester forms CSV v6, IDE Reports menu
Extractor 1.21.1: every index must re-parse once (index --all); an older engine refuses to write a 1.21.1 index. Battery 640/640.
EXTRACTOR BUMP 1.20.0 -> 1.21.1 (1.21.0 was never released): every index re-parses once
(done 2026-10-05, 37/37 sections, 5 h 05 m).
Added
forms-csvalgorithm v6 -- a tester spreadsheet that says what to click. Edges come from
the index first (DFM control event -> handler -> resolved call path to the routine that
creates / shows the form); the old.pastext scan is only the fallback, and every row says
which method found it. Each form's.dfmis read at report time for the menu / ribbon /
action / tab path ("Ribbon: Lists > Lists Bar > Variable Names"). New columns: How to open,
Click, Control type, Handler, Opened by, Modal, Before you start, Other ways in, Confidence
(traced / handler-only / unresolved), Tester result. Micronite2027: "no path from MAIN"
7 -> 5, handler-only rows with no control 1 -> 0. Guard:run_forms_csv_v6.ps1.- IDE plugin: drag-lint > Reports. Every chart question of
charts\src\Ask-Report.ps1
(25) from the editor caret (typeatresolves the full name), run in the background; the
chart opens in the browser and the text answer is copied as a DocInsight/// <remarks>
block with no autodoc marker, so Auto Document preserves it. "Forms for testers (CSV)..."
moved here from the root menu. Tests:tests\plugin\run_reports_text.ps1.
Fixed
- Ref-gap F: signature types of header-only routines are now
type_userefs. An
implementation-only free routine (1.21.0) and a NESTED routine -- local to a free routine, a
method implementation or an interface-declared routine (1.21.1) -- emitted its symbol and
params but no ref for its parameter or return types. An imported unit whose type appeared only
there was reported byunused-unit-in-usesas a dead import. Guard:
tests\autotest\run_impl_only_signature_type_uses.ps1.
v1.20.6-alpha -- converter: unit rules, lazy book validation (BDE apply 20+ min -> 26 s), unreachable-member warnings, fast proptree
Converter/editor engine gaps + property-tree redesign. Merge notice for the editor team: docs/NOTICE-2026-10-05-engine-1.20.6-merged.md (local). Battery 630/637 (7 = known environment failures + run-order flakes that pass alone).
MINOR-in-PATCH: the converter engine gains what the rule-book editor asked for. Only
DRAGLINT_VERSION moves; the extractor version is unchanged -- no index needs a re-parse.
Added
- convert-apply applies unit rules (
#unuse,#use,#useswap).info --jsonadvertises
capabilities.apply_unit_rules; the apply/1 JSON gainscomponent_part,uses[],
uses_removedanduses_added. #depth Nbook directive. The default proptree / convert-scaffold depth is now 5
(previously effectively 3).--ruleson proptree / convert-scaffold.--progress-interval S: one JSON progress line on stderr every S seconds; off by default.info --jsoncapabilitiesbook_depth,progress_lines,lazy_validate.- apply/1
refused+reason; text mode printsREFUSED: <reason>. - apply/1
unreachable[](and the unreachable-path warning text) andclasses_built. - R26 refusal: a
#unuse/#useswapthat would remove the unit declaring the From type of
an instance left unconverted (skipped, or excluded by--only) refuses the unit:
<rule> would leave <N> unconverted instance(s) of <Type> -- unit not changed.
Changed
- convert-validate / convert-apply resolve paths lazily through a per-class member cache, and
every#convertblock is validated; proptree expands through the same cache. - Private members are never shown or resolved.
- A rule through an inaccessible member is a warning and is skipped (owner ruling 2026-09-30).
- JSON/text output is written in one buffered write, and stderr is flushed first, so a merged
stdout/stderr stream still parses. REFUSED: <reason>replacesERROR:for refusals.--onlystill filters instances only, never unit rules; a removal it would make unsafe is now
refused (R26) instead of breaking the compile.- A
.dfmcut short after indexing (an object's span runs past the end of the file) refuses the
unit as stale (<Name>: index is stale for this .dfm -- reindex) instead of skipping that one
instance and converting the rest.
Removed
--validate-all-blocks,blocks_not_validated,trees_built(replaced byclasses_built).
Fixed
- Per-file lint of a
.dprruns the AST checks. try-except-swallowedaccepts a captured exception in the routine's own local that is
checked after the try.- An indented doc block directly on its declaration keeps its indent.
- convert-apply refuses to splice a stale
.dfmspan, including a block that lost lines so its
recorded end line now holds a later sibling'send(the splice used to delete the sibling). - A From-only header (
#convert TFoo ->with its trailing space, or#convert TFoo) no longer reads
as a From typeTFoo ->: it is aline N:error#convert TFoo has no To type(R27); likewise
#useswap X ->is#useswap X has no replacement unit.
Performance
- BDE book convert-apply dry run: more than 20 min (cap hit) -> 26.5 s.
- convert-validate BDE (TQuery -> TFDQuery): 408.6 s -> 17.9 s.
- TabcToggleBtn book convert-apply: 129.8 s -> 16.7 s.
- TFDQuery proptree depth-5 JSON: 80 s -> 14 s (byte-identical output).
Known
- The
unused-unit-in-usesparser false positive ships in 1.21.0 with a re-parse.
v1.20.5-alpha -- the Config tool never overwrites a manifest it could not load; doc facts never break the build (F2069)
Patch release of drag-lint: the Config tool (drag-lint-config.exe) never writes back a drag-lint.json it could not load -- before, a malformed file was replaced by an EMPTY manifest by Save or by the silent autosave before every Build -- and the doc engine never writes a /// line the compiler can reject (F2069). Extractor 1.20.0 / resolver 1.11.0 / schema v23 are unchanged from v1.20.4-alpha.
Built from a clean worktree at f07accf (build\pack-lint-release.ps1 -Version 1.20.5-alpha; Win64 + Win32 Release, ConvRulesEditor in the Win64 archive only). Note: the release archives do not include drag-lint-config.exe (the Config tool is built from src\config\drag-lint-config.dproj); its fix below reaches you through a source build.
Upgrading from v1.15.1-alpha (the last published release): versions 1.16.0 through 1.20.4 were not published separately; their changes are all in this build and in CHANGELOG.md. In between, the extractor moved 1.17.0 -> 1.20.0 and the resolver 1.4.0 -> 1.11.0, so the first drag-lint index run after installing re-parses every database (hours on a large corpus -- the library indexes dominate). The schema is still v23, so no --rebuild is required.
Since v1.15.1-alpha, in brief:
- 1.20.4 -- a project index can no longer write into another project's database; a malformed manifest names its bad key and write verbs refuse it.
- 1.20.3 -- two doc-drift false reports fixed; a detailed INSTALL.md.
- 1.20.2 -- a source-destroying autofix defect fixed (doc-drift
--fixcould delete overload declarations); the IDE plugin's click path hardened; charts merged. - 1.20.1 -- one rule default flipped, three false positives and one false negative fixed (189 rules, 159 on by default).
- 1.20.0 -- resolver batch (1.11.0); one new rule, off by default.
- 1.19.x -- extractor batch (1.20.0, every index re-parses); backlog defects and converter asks.
- 1.18.x -- follow-up defects D20-D31;
concat-in-loopprecision. - 1.17.0 -- extractor 1.19.0: SQL built line by line (
SQL.Add) is read; quoted SQL identifiers kept; Delphi 12+ multi-line string literals no longer break the parse. - 1.16.0 -- two new lint rules, off by default.
v1.20.5-alpha -- 2026-09-30
PATCH: the Config tool no longer overwrites a drag-lint.json it could not load, and the doc
engine no longer writes a /// line the compiler can reject. Only DRAGLINT_VERSION moves;
extractor 1.20.0-alpha, resolver 1.11.0-alpha and schema v23 are unchanged -- no index needs a
re-parse or a re-resolve. Nothing under src\parser, src\preprocess or src\index was edited.
Fixed
- The Config tool could overwrite
drag-lint.jsonwith an EMPTY manifest when the file did
not load (bad JSON, a wrong-typed key). The parse failure replaced the in-memory manifest with an
empty default,Validateaccepts zero sections, and two writers then saved it over the user's
file: the Save button, and the SILENT autosave that runs before every Build (its errors
were swallowed, so nothing was said). Now every load and save goes through one session unit
(Config.ManifestSession): a file that did not load is never written -- Save is disabled (the
caption says[NOT LOADED -- saving disabled]), a click or a Build autosave is refused with
Not saved: <path> did not load (<error>). Fix the file, then Reload., and the load error is
shown in a dialog when the file is opened. The discovery path (no file resolved beside the exe)
now also disables Save on a partial load. A--configpath that does not exist yet in an
existing folder starts from the defaults and Save creates it; a path whose folder does not exist
or cannot be reached (a disconnected share, an unmounted drive) is refused as a load error
(<file>: folder not found or not reachable -- not creating a new config there), so a Save
after the share reconnects cannot put the defaults over the real manifest.
Config tool 0.45.1-alpha. The deployed
third_party\dll-win64\drag-lint-config.exewas a 2026-06-15 build, so this defect was live
independently of 1.20.4. - The doc-drift autofix and
documentwrote///lines up to 2,566 characters. The
reconciliation facts (Used by:/Called from:inbound lists) are uncapped on purpose -- a
capped window onto the list would make every cross-project merge unsound -- and they were
rendered on ONE physical line. An over-long fact line now breaks at entry boundaries (a blank after
a,or;; any blank if there is none; a single token longer than the limit stays whole), and
ONLY a line that would exceedDOC_FACT_MAX_COLS = 1000is broken: every line within 1000
characters is byte-identical to 1.20.4, so no documented project churns. A legacy fact line with
no<para>is never broken. The readers of a stored block accept the wrapped form:
doc-forget(--list-tagsand the rewrite; continuation lines were skipped, so tags on them
were missed and an emptied list left orphan lines inside the fence), the cross-project merge
(MergeInboundFacts, which rewrote the first physical line with the whole merged list and left
the continuation lines -- duplicate entries), and the merge's re-insert point (ReinsertAt,
which put a re-inserted<para>INSIDE a wrapped predecessor). A hand-broken, unterminated
<para>is read as a single line and never swallows the fence END.doc-forgetkeeps CRLF on
every line it re-wraps. - A stored managed line over 1000 characters is now drift. Drift compares whitespace-collapsed
content, so a long line written by 1.20.4 or earlier read as current and--fixnever rewrote
it.doc-driftnow reports such a block (managed facts block is out of date -- a line is over 1000 characters) and--fixrewrites it wrapped. - The compiler limit, corrected. The 1.20.4 entry below records the long lines as
F2069 Line too long; the message says "more than 1023 characters", but that is not what dcc64 37.0 was
measured to do. A 1020-character line compiled at every file offset sampled; longer lines
compiled at SOME offsets and failed at others (a 4056-character///line compiled near the
start of a file, a 1021-character one failed elsewhere). The mechanism is not settled. The
practical reading: a line over 1020 characters is a build that can break when an edit above it
moves it. The engine caps at 1000; the new tree guard checks 1020. - Two latent over-long source lines in this repo were broken up: the
Used by:line in
src\doc\DRagLint.Doc.SharedFacts.pas:111(1139 characters) was regenerated wrapped, and a
1097-character trailing comment insrc\lint\DRagLint.Lint.RuleCatalog.pas(near line 547) was
split over two lines. Both compiled only by position.
Index
- New manifest section
DragLint-Config(src\config\drag-lint-config.dproj, DB
src\config\_D-RAG\drag-lint-config.sqlite) inthird_party\dll-win64\drag-lint.jsonand
third_party\dll-win32\drag-lint.json. The Config tool had no index, so the lint-clean rule could
not be applied to it.
Tests
tests\autotest\run_config_save_guard.ps1(+fixtures\configsave\ConfigSaveHarness.dpr):
arms A/B (a file that did not load is refused and left byte-identical), C (a good file saves),
E (a missing file in an existing folder is created), F (a missing folder is refused and nothing
is created), D (+ control:src\configwrites only throughConfig.ManifestSession), and GUI arms G/H that
drive the real exe (Save disabled, Build autosave refused and SAID, Reload re-enables). G/H need
an interactive desktop.tests\autotest\run_doc_fact_wrap_readers.ps1: the merge (short, long, re-insert after a
wrapped predecessor, a wrapped FRESH render),doc-forgetover wrapped and legacy lines, CRLF.tests\autotest\run_doc_fact_line_length_guard.ps1: a rendered 200-entry list stays within
1000 and COMPILES (real dcc),documentis a fixed point, a stored over-long line is drift and
--fixwraps it (1000 current / 1001 drift), and no tracked source line exceeds 1020.drag-lint selftest fact-wrap(internal): wrap and fold cases, including the rightmost-boundary,
comma-free, exactly-at-limit, over-long-token, legacy and unterminated-<para>cases.
Known issues
- The three stale facts blocks in
src\index\DRagLint.Index.Manifest.pas(lines 151, 184, 238)
are left unapplied:src\indexis on the extractor-hash surface. A preview (--fixwithout
--apply) proves the fix now produces lines within the cap there -- widest 929 / 953 / 951
characters, none over 1000. - A hand-written source line of 1021+ characters is not caught by any lint rule; the doc engine
can only keep its own lines short.
v1.15.1-alpha -- dotted unit names visible again to unit-usage (regression in 1.15.0); every index rebuilt on schema v23
Patch release of drag-lint: dotted unit names (A.Lib, Vcl.Controls) are visible again to query unit-usage and the unused-unit-in-uses rule -- a regression shipped in v1.15.0-alpha (fdb05c9 ran the per-segment generic split before the verbatim lookup). Extractor 1.17.0 / schema v23 / resolver 1.4.0 are unchanged from v1.15.0-alpha.
Built from a clean worktree at 7821857 (build\pack-lint-release.ps1 -Version 1.15.1-alpha; Win64 + Win32 Release, ConvRulesEditor in the Win64 archive only).
REQUIRED: every index must be at schema v23. If you skipped v1.15.0-alpha, or any of your databases still carry schema v22, run drag-lint index --all --rebuild after installing -- a v23 engine REFUSES a v22 database (index schema v22 < v23) and there is no incremental path across the schema bump. An index built by v1.15.0-alpha needs no re-parse.
v1.15.1-alpha -- 2026-09-17
PATCH: fixes a regression shipped in 1.15.0 (dotted unit names invisible to
unit-usage / unused-unit-in-uses). Extractor 1.17.0 / schema v23 /
resolver 1.4.0 are UNCHANGED; an index built by 1.15.0 needs no re-parse.
Fixed
FindSymbolsByExactNametries the VERBATIM dotted name first: unit names are stored with their dots (A.Lib,Vcl.Controls), and the per-segment split from fdb05c9 (TList<T>.Add->Add+ qualified-name suffix) ran first and found nothing, soquery unit-usage --unit A.Libdegraded toexports_known:falseandunused-unit-in-usesreported zero for every dotted unit. The split now runs only when the verbatim lookup misses; pinned byrun_generic_symbol_names.ps1(D1, throughunit-usage, which has no qualified-name fallback of its own).- glyph-vacuum (converter):
FindCountPropmatches dotted count props by
their last segment (2df9601); length-prefixed bare bitmaps
(TBitBtn.Glyph.Data) and raw SVG (TdxSmartGlyph) decode, and EMF
requires theEMFsignature -- 852+3 of 1016 ORM3 rows were undecoded
(2343c0b);runtime_refsderived from the merged write set, the
class-tree count fallback matches dotted names, the collection scan fires
only at an end-of-line= {, gallery cards sorted by sha (6a2803f).
Changed
docs\GLYPH-CLASSES.mdseeded from the first glyph-vacuum run on ORM3,
re-measured after the SVG / length-prefix decoders, and the M2022 corpus
added (d298ae1, 64a5d5d, 7df84bc).- glyph-vacuum fixtures for bare-BMP-at-offset-0 geometry and the
Int32-equals-length collision fall-through (fd810e9). - autodoc: the CLI project's doc-comments regenerated after extractor 1.17.0
(inherited fields, nested locals in facts) -- comment-only (1307e77).
v1.15.0-alpha -- generic names resolve by arity, nested locals, inherited fields (schema v23, re-parse required)
Archives below are self-contained: the exe, the rules\ folder, and (win64 only) the component converter.
Provenance: engine (
drag-lint.exe, Win64 and Win32) andConvRulesEditor.exewere built from a clean git worktree at2793f206; nothing uncommitted went into either archive.
REQUIRED after installing: rebuild every existing index. This release moves the index format (schema v22 -> v23, extractor 1.16.0 -> 1.17.0, resolver 1.3.0 -> 1.4.0). A v23 engine refuses a v22 database with index schema v22 < v23 and does not migrate it in place -- run drag-lint index --all --rebuild (or index --project <X.dproj> --db <db> --rebuild per project) before querying, linting or starting the LSP against an existing index.
v1.15.0-alpha -- 2026-09-17
Added
glyph-vacuum --root <dir> --out <dir> [--db <db>] [--append](converter):
walks every.dfm/.fmxunder the root, decodes every streamed graphic
(a binary.dfmis converted in memory), and writesinstances.tsv, the
decoded images, a per-classclasses.tsv(count property, inferred strip N,
agreement, kind, format and distinct-payload distributions,runtime_refs
counted through resolved property refs when--dbis given) and a
gallery.htmlwith one card per distinct payload per class, slot separators
overlaid at width div N. Unparseable files are listed inskipped.tsv, never
dropped;--appendmerges a rescan by (dfm_path, object_path, property), so
a second run is idempotent. The input todocs\GLYPH-CLASSES.mdand the
G[I/N]grammar.- ConvRulesEditor: the Unit Rules tab lists each used unit with the section it
is declared in (interface / implementation) and fills the moment a unit is
picked -- Browse, combo select or Fill, form or not. The declared-class list
on the same tab was empty for every input (ScanClassesDeclaredstarted its
backtrack past the keyword and never met the=).
Changed -- extractor 1.17.0-alpha / schema v23 (re-parses every index)
- Generic types and methods are indexed under their BARE name; the parameter
list is insymbols.generic_params. Ancestor edges carry
ancestor_type_argsand resolve by arity before the scope rules:
TObjectList<T: class>now climbs toSystem.Generics.Collections.TList<T>
(wasSystem.Classes.TList); ORM3's 134TMicObjectBase<I>descendants
resolve their base.query --nameacceptsTList<T>, and a dotted name is
stripped per segment, soUnit.TList<T>.Addfinds the METHOD (was the class).
Arity counts PARAMETERS, not commas:<T: class, constructor>is 1 and
<S: IUnknown; I: IUnknown>is 2, so edges onto constrained generics resolve. - Nested-routine locals and inline
var/for vardeclarations are
local_varsymbols parented to the innermost routine. Two sibling
for var Item: T inloops of DIFFERENT element types leaveItem.Mcalls
unresolved (decline) rather than typing both from the first loop. symbol_facts.reads_fields/writes_fieldsinclude INHERITED fields
(own first, nearest ancestor next) via the newfacts-inheritedindex stage.query ancestors --jsonrows carrytype_args('' on a plain edge). The
LSP's ephemeral store for a file no index owns now runs thefacts-inherited
stage too, so hover on such a file shows a same-unit inherited field in
Writes:.
Fixed
context --task "modify Class.Member"resolves when exactly one symbol's
qualified name ends with that dotted suffix (segment-aligned; an ambiguous
Class.Memberstill declines). A RECORD's class surface lists every field:
the lean DFM-component filter now runs only when the owner is a class, so
modify TTypeAncestorno longer prints one field of eight.- A property whose accessor is OVERLOADED (
read GetItembeside twoGetItem
declarations) binds the overload whose parameter count is the property's index
count (+1 for a setter); when the count leaves several, the access records no
accessor and no call edge instead of the first-declared one (resolver 1.4.0, re-pinned). FindSymbolsByExactName(query --name) splits a dotted input per segment:
TList<T>.Addfinds the METHODgnB.TList.Add, not the class, and a dotted
path keeps only rows whose qualified name ends with it.dataset-open-without-closeignores a member namedOpenthat is indexed, passed as
an argument or assigned -- only a bareX.Open;/X.Open();statement is a
dataset open. A record fieldOpen: TArray<...>read asAState.Open[i]fired
eight times on drag-lint's own source (INBOX 2026-09-17, section 2).GetSymbolFacts/GetSymbolDocinitialise a managedResultwith
Default(), notFillChar(leak when a caller reused the variable).uses-report --name <pattern>matching NO source unit now exits 2 with
ERROR: uses-report: no index passed contains a source unit named <pattern>
on stderr and writes no output file (an existing--outputis left
untouched). It used to print0 source units, 0 rows writtenand exit 0 --
a complete-looking report against a corpus that did not contain the subject,
whileoutlineon the same file and DB refused. Converter gap 2026-09-16
(stats\draglint-gaps.log, classwrong). Guard:
tests\autotest\run_uses_report_name_no_match.ps1.
v1.14.0-alpha -- Catches: (exceptions a routine handles), property/field refs resolve
Archives below are self-contained: the exe, the rules\ folder, and (win64 only) the component converter.
Note: the win64 archive's
ConvRulesEditor.exewas built from the converter stream's working tree, which held four uncommittedsrc\tools\convrules-editoredits at cut time (ConvRules.Engine/MainForm/Usage.pas,ConvRulesModelTests.dpr). The engine (drag-lint.exe) is exactly377be3e5.
v1.14.0-alpha -- 2026-09-17
Catches: -- the exceptions a routine HANDLES (INBOX-report-exceptions-raised-and-handled, gap 3)
The managed facts block gains one line per routine with a try..except:
Catches: EConvertError (dialog: ShowMessage); Exception (re-raise) -- one
entry per (class, disposition), sorted by class name, deduped, capped at
docs.max_handles (default 8) with a visible (+N more). An else arm
and a bare except .. end both count as Exception. Dispositions, first
match wins: re-raise (a bare raise; or raise of the handler's own
variable), raises X (the handler raises a DIFFERENT class -- the plan's four
did not cover the translation shape, and swallowed would misdescribe it),
dialog: <name> (a call on docs.dialog_routines, the witness spelled as
the source spells it), empty, else swallowed. Mined from SOURCE at doc
time on the raise miners' own comment/literal scan state (TDocFactsBuilder. MineHandlers / RenderCatches), NOT an index-time symbol_facts column
-- no extractor bump, no reindex, and a handler in a comment or string literal
is never reported. The label is Catches:, not a second Handles::
Doc.SharedFacts bounds a fact's slice in the flattened stored block by
label, so two lines under one label would collide there. New manifest keys
docs.dialog_routines (default the VCL four; [] is the off switch) and
docs.max_handles, read by document, doc-drift, hover and the
LSP through one TDocHandlesOptions threaded like the caps, so the writer
and the checker cannot disagree. document --project was run to a fixed
point on this repo in the same change (76 Catches: lines). Guard:
tests\autodoc\run_doc_exception_handles.ps1 (27 assertions, RED-first).
Property and field references RESOLVE (resolver 1.3.0-alpha; re-resolve every index)
A member-access ref naming a PROPERTY or FIELD (FConnection.Connected)
reached the resolve pass since v20b and was discarded there, because only a
routine could own a call_edges row -- so find-callers --resolved answered 0
for a property that a method on the same receiver answered 4 for, and
lint-tree reported "0 place(s)" when a public property with 207 dependents
was removed. Per the owner's ruling: the ref now binds to the member
(refs.symbol_id); a READ is also a resolved call to the read accessor and a
WRITE to the write accessor (a call_edges row when the accessor is a method
-- call_edges stays routine-only); a FIELD-backed accessor lists the access
as a use of that field. New member_accesses table (additive, no schema bump;
readers probe for it). find-callers --resolved on a property or field prints
[certain, read] / [certain, write] (JSON mode); lint-tree reports a
removed property's stale references and property/field leave
not_reportable. Every index resolved before 1.3.0-alpha answers 0 for
properties until index --all --resolve-only (or its next index). A bare
property access inside its own class (if Flag then) is a read ref, not
member-access, and is not bound by this change.
convert-apply: unlinked source properties are warned by default, as "N of M instances", keyed by (source type, property)
A source property some converted instance carries that no #link carries and
no #ignore acknowledges is now warned ONCE per (source type, property):
TabcToggleBtn.Style: no #link carries it -- dropped on 20 of 20 converted instance(s); add a #link, or #ignore Style to accept the drop
Read the fraction, not the count: 2 of 20 is the two controls somebody
deliberately styled (ParentFont = False) and is the STRONGER signal; 20 of 20 is a structural non-mapping. Keyed by source type so two #convert blocks
both dropping Style are two rows, not one. --format json carries
unlinked_source_properties (rows), unlinked_source_property_sites (sum of
sites -- both keep their step-1 meaning) and an additive unlinked[]
(from_type, path, sites, instances); the warnings are items[] of kind
unlinked-source-property. --no-warn-unlinked drops the warnings and keeps
the count. Default-on because the number earned it: 2 distinct / 22 sites on a
real 36-link book.
convert: #link between class-typed properties carries sub-leaves on TYPE IDENTITY
#link Font <- Font with TFont on both sides now carries every sub-leaf the
.dfm streams (Font.Charset, Font.Name, ...) automatically -- the five
hand-written Font.* lines become one. When the types DIFFER
(OptionsImage.Glyph <- Picture, TdxSmartGlyph <- TPicture) nothing is
carried implicitly and every dotted leaf must still be named, because an
invented target path is how a form stops loading. An explicit per-leaf #link
/ #ignore / #remove always wins over the carry; a carried leaf is reported
(sub-leaf-carried with path and rule_line in apply/1; report.carried[]
in convert-reemit) so the leaves nobody typed are visible. Not implemented:
the "target type is an ancestor of the source type" case. Two step-4b defects
fixed alongside: VCL-shaped dotted leaves (Font.Size = 9 as one child) were
reported "absent from the F DFM", and class-typed containers were listed under
"defaults may diverge".
duplicate-global-decl: LIBRARY tier
A project global whose NAME is also an interface-level global of a library
unit that the declaring file USES (interface or implementation uses;
SysUtils matches System.SysUtils) is reported -- masking an RTL name is
almost always a naming error. 2+ project units AND the library is the stronger
three-declaration message. Severity warning, NO autofix (only you know which
declaration is meant). Measured before the severity was final: 44 findings on
ORM3 CLIENT (every one a genuine RTL/WinAPI re-declaration), rule cost 1.6 s
against the 3.7 GB Win32 library.
lint: two false positives fixed
hardcoded-ip-addressno longer reads a dotted-quad VERSION const
(YADF_MIN_VERSION = '1.0.6.6') as an IPv4 address. New rule predicate
(#in? @cap "nodeType" ["nameRegex"])/#not-in?-- "some ancestor of the
captured node has that type and itsname:matches" -- documented in
rules\README.md.review-marker-unusedno longer fires on adl:okthat IS suppressing a
store-backed (project-scope) finding: the marker join keyed the finding's
ABSOLUTE path against the scanned file's path as typed, so a relativelint src\X.pasnever matched. Both sides are now normalised.