Skip to content

Releases: Alexl-git/Delphi-RAG-Lint

v1.31.5-alpha -- bug-fix wave: binary DFM, Config Save, rename closure, lint FPs, plugin timeouts

Choose a tag to compare

@Alexl-git Alexl-git released this 19 Oct 07:22

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 .dfm is reported as "binary DFM (TPF0) -- save as Text DFM..." instead of "parse error (1,1)".
  • index no longer writes "FTS5 probe: AVAILABLE" to stderr (shown red as NativeCommandError in Windows PowerShell 5.1).
  • used-unit-not-resolvable knows 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-dbs keeps them too.
  • resolve-dbs --in prints 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-leak ignores TArray<T>.Create; used-before-assignment does not treat with Rec do on 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.
  • allow accepts a whole-routine metric marker wherever lint reports it; an allow batch on a wrapped statement keeps the continuation line's marker; lint-all refuses --file / --in (exit 2) instead of ignoring them.
  • document --apply keeps 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

Choose a tag to compare

@Alexl-git Alexl-git released this 18 Oct 20:05

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.dll ships with the engine. Without RAD Studio on the machine, info reported FTS5=False and index stopped with FireDAC error -314.
  • type-name-prefix traces the ancestor chain into the library index, so EKeyViolation = 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

Choose a tag to compare

@Alexl-git Alexl-git released this 16 Oct 21:32

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 written when 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 use T.
    EKeyViolation = class(EDatabaseError) is accepted, and Fix all no longer
    renames it to TEKeyViolation (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

Choose a tag to compare

@Alexl-git Alexl-git released this 12 Oct 01:20

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)

  • uses reconciliation (C9). After a conversion, each unit's uses clauses are reconciled. Units the new components need are added; units nothing needs any more are removed. A unit is kept only when the converted .dfm still needs it. Every change is reported as a uses[] row (add / remove / kept / moved), and the project index is refreshed after the write. --no-reconcile-uses and --no-reindex opt out.

  • Ties between declaring units are settled by the book. When a type is declared in more than one unit, the book's #use decides, then #useswap, then the #convert unit. The chosen unit goes into the interface uses when the field is declared there, so the converted unit compiles. Units named on a #convert line are now reported as add rows too.

  • #convert classes resolve in the unit's scope (1.28.3). A bare class name such as TLabel is 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:

    1. the project framework;
    2. the form-file kind (.dfm means VCL, .fmx means FMX);
    3. the properties the .dfm streams.

    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 .pas still uses, with a status: mapped, same-name, unmapped, not-rewritten, cast-refused or global-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.

  • StripDB value cast. #link TableName <- TableName : StripDB removes a trailing .db from Paradox table names. It applies to the .dfm and to single-literal .pas assignments; every other form of assignment gets a warning instead of an edit.

  • Fixed: a class cast (e.g. AssignGraphic) could write unparseable .pas code. 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-set and out-param-not-set no longer fire on paths that end in raise, Halt or Abort.
  • doc-drift no 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-uses now counts enum values, so a unit used only through alTop or 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_db and convert_qualify_fix.
  • Retired item kind: cast-applied.
  • uses_added can be one higher: a book #use To unit placed in the interface now gets its own add row.

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

Choose a tag to compare

@Alexl-git Alexl-git released this 05 Oct 22:30

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-csv algorithm 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 .pas text scan is only the fallback, and every row says
    which method found it. Each form's .dfm is 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 (typeat resolves 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_use refs. 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 by unused-unit-in-uses as 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

Choose a tag to compare

@Alexl-git Alexl-git released this 05 Oct 10:54

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 --json advertises
    capabilities.apply_unit_rules; the apply/1 JSON gains component_part, uses[],
    uses_removed and uses_added.
  • #depth N book directive. The default proptree / convert-scaffold depth is now 5
    (previously effectively 3).
  • --rules on proptree / convert-scaffold.
  • --progress-interval S: one JSON progress line on stderr every S seconds; off by default.
  • info --json capabilities book_depth, progress_lines, lazy_validate.
  • apply/1 refused + reason; text mode prints REFUSED: <reason>.
  • apply/1 unreachable[] (and the unreachable-path warning text) and classes_built.
  • R26 refusal: a #unuse / #useswap that 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 #convert block 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> replaces ERROR: for refusals.
  • --only still filters instances only, never unit rules; a removal it would make unsafe is now
    refused (R26) instead of breaking the compile.
  • A .dfm cut 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 by classes_built).

Fixed

  • Per-file lint of a .dpr runs the AST checks.
  • try-except-swallowed accepts 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 .dfm span, including a block that lost lines so its
    recorded end line now holds a later sibling's end (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 type TFoo ->: it is a line 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-uses parser 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)

Choose a tag to compare

@Alexl-git Alexl-git released this 05 Oct 10:51

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 --fix could 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-loop precision.
  • 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.json with 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, Validate accepts 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 --config path 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.exe was a 2026-06-15 build, so this defect was live
    independently of 1.20.4.
  • The doc-drift autofix and document wrote /// 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 exceed DOC_FACT_MAX_COLS = 1000 is 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-tags and 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-forget keeps 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 --fix never rewrote
    it. doc-drift now reports such a block (managed facts block is out of date -- a line is over 1000 characters) and --fix rewrites 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 in src\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) in third_party\dll-win64\drag-lint.json and
    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\config writes only through Config.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-forget over 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), document is a fixed point, a stored over-long line is drift and
    --fix wraps 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\index is on the extractor-hash surface. A preview (--fix without
    --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

Choose a tag to compare

@Alexl-git Alexl-git released this 18 Sep 01:04

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

  • FindSymbolsByExactName tries 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, so query unit-usage --unit A.Lib degraded to exports_known:false and unused-unit-in-uses reported zero for every dotted unit. The split now runs only when the verbatim lookup misses; pinned by run_generic_symbol_names.ps1 (D1, through unit-usage, which has no qualified-name fallback of its own).
  • glyph-vacuum (converter): FindCountProp matches dotted count props by
    their last segment (2df9601); length-prefixed bare bitmaps
    (TBitBtn.Glyph.Data) and raw SVG (TdxSmartGlyph) decode, and EMF
    requires the EMF signature -- 852+3 of 1016 ORM3 rows were undecoded
    (2343c0b); runtime_refs derived 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.md seeded 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)

Choose a tag to compare

@Alexl-git Alexl-git released this 17 Sep 18:44

Archives below are self-contained: the exe, the rules\ folder, and (win64 only) the component converter.

Provenance: engine (drag-lint.exe, Win64 and Win32) and ConvRulesEditor.exe were built from a clean git worktree at 2793f206; 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 / .fmx under the root, decodes every streamed graphic
    (a binary .dfm is converted in memory), and writes instances.tsv, the
    decoded images, a per-class classes.tsv (count property, inferred strip N,
    agreement, kind, format and distinct-payload distributions, runtime_refs
    counted through resolved property refs when --db is given) and a
    gallery.html with one card per distinct payload per class, slot separators
    overlaid at width div N. Unparseable files are listed in skipped.tsv, never
    dropped; --append merges a rescan by (dfm_path, object_path, property), so
    a second run is idempotent. The input to docs\GLYPH-CLASSES.md and 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 (ScanClassesDeclared started 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 in symbols.generic_params. Ancestor edges carry
    ancestor_type_args and resolve by arity before the scope rules:
    TObjectList<T: class> now climbs to System.Generics.Collections.TList<T>
    (was System.Classes.TList); ORM3's 134 TMicObjectBase<I> descendants
    resolve their base. query --name accepts TList<T>, and a dotted name is
    stripped per segment, so Unit.TList<T>.Add finds 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 var declarations are
    local_var symbols parented to the innermost routine. Two sibling
    for var Item: T in loops of DIFFERENT element types leave Item.M calls
    unresolved (decline) rather than typing both from the first loop.
  • symbol_facts.reads_fields / writes_fields include INHERITED fields
    (own first, nearest ancestor next) via the new facts-inherited index stage.
  • query ancestors --json rows carry type_args ('' on a plain edge). The
    LSP's ephemeral store for a file no index owns now runs the facts-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.Member still 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 TTypeAncestor no longer prints one field of eight.
  • A property whose accessor is OVERLOADED (read GetItem beside two GetItem
    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>.Add finds the METHOD gnB.TList.Add, not the class, and a dotted
    path keeps only rows whose qualified name ends with it.
  • dataset-open-without-close ignores a member named Open that is indexed, passed as
    an argument or assigned -- only a bare X.Open; / X.Open(); statement is a
    dataset open. A record field Open: TArray<...> read as AState.Open[i] fired
    eight times on drag-lint's own source (INBOX 2026-09-17, section 2).
  • GetSymbolFacts / GetSymbolDoc initialise a managed Result with
    Default(), not FillChar (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 --output is left
    untouched). It used to print 0 source units, 0 rows written and exit 0 --
    a complete-looking report against a corpus that did not contain the subject,
    while outline on the same file and DB refused. Converter gap 2026-09-16
    (stats\draglint-gaps.log, class wrong). Guard:
    tests\autotest\run_uses_report_name_no_match.ps1.

v1.14.0-alpha -- Catches: (exceptions a routine handles), property/field refs resolve

Choose a tag to compare

@Alexl-git Alexl-git released this 17 Sep 09:08

Archives below are self-contained: the exe, the rules\ folder, and (win64 only) the component converter.

Note: the win64 archive's ConvRulesEditor.exe was built from the converter stream's working tree, which held four uncommitted src\tools\convrules-editor edits at cut time (ConvRules.Engine/MainForm/Usage.pas, ConvRulesModelTests.dpr). The engine (drag-lint.exe) is exactly 377be3e5.

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-address no 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 its name: matches" -- documented in
    rules\README.md.
  • review-marker-unused no longer fires on a dl:ok that 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 relative lint src\X.pas never matched. Both sides are now normalised.