Skip to content

convert apply

Alexander Liberov edited this page Oct 12, 2026 · 2 revisions

convert-apply

Locates .dfm component instances that match a #convert rule from a conversion-rules file and rewrites all five surfaces: declaration retype, uses-add, .dfm re-emit, property/event access-site rewrite, and runtime-creator retype/TODO markers. Reach for it to actually perform a component-type conversion, after validating and scaffolding the rules file.

Running it from the CLI

drag-lint convert-apply --unit <F.pas> --rules <file> --db PATH [--db ...] [--only Name1,Name2,...] [--apply] [--no-backup]

--unit <F.pas> is the unit to convert. --rules <file> is the conversion-rules file. --only Name1,Name2,... restricts the conversion to named component instances. Without --apply this is dry-run only: a preview that writes nothing. --apply writes for real, with backups and a recovery.txt, unless --no-backup is also given. --db PATH is required and may be repeated.

Uses rules: #unuse, #use, #useswap

A book's unit rules act on the unit being converted, in the same single write: #unuse Old takes Old out of whichever uses clause holds it, #use New adds New to the implementation uses when neither clause has it, and #useswap Old -> New1, New2 swaps them in Old's section (a unit without Old is not touched). A unit the book both adds and removes is kept. An entry to remove inside a {$IF...} region refuses the unit (exit 1, nothing written). The JSON uses[] lists every change with its action, section, line and rule.

Uses reconciliation (C9, 1.27.0; on by default)

convert-apply re-indexes the converted .pas and .dfm IN MEMORY (nothing is written to a database, so the dry run shows exactly what --apply will write) and decides each #unuse / #useswap Old from what the converted unit still needs:

  • kept while a component the converted .dfm still streams has a class from that unit (an unconverted instance, a second component type, an inherited instance) -- a kept row and a warning naming the components;
  • removed when only .pas code still needs it -- the remove row names the references left dangling (needed_by, and pas_uses[] rows with status global-dangling); the compile errors are the intended signal;
  • kept when no --db indexes the unit (unit-rule-unverified): pass the platform library --db (drag-lint resolve-dbs --platform Win32|Win64) so the engine can see it.

Presence is scope-alias aware (#unuse DBTables matches Bde.DBTables, #use DB is satisfied by Data.DB). When a To type is declared in two units, none is picked: it is reported in uses_unresolved (with the line of the retyped field) and as a uses-unit-ambiguous warning -- add #use <unit> to the book to choose. That #use then puts the unit where the retyped field needs it: the INTERFACE uses when the field is declared there, moved there when the unit used it only in its implementation (a moved row). Every unit the conversion itself writes into the uses clause (a To type's unit, or a unit named after the To type on a #convert line) is an add row with rule #convert, counted in uses_added. After --apply the unit's own project index is refreshed (--no-reindex skips it). --no-reconcile-uses restores the 1.26 behaviour exactly.

Cast links in code (1.27.0)

A #link To <- From : Cast converts the .dfm as before, but never rewrites a .pas access site: a cast's pas template is a statement, and there is no correct place to put a statement inside an expression such as pic1.Picture.Data := 'seed'. Each such site is left as it is and reported as a cast-not-applied warning naming the file, line, instance and cast -- "convert this access site by hand". The unit will not compile until you do; that compile error is the signal.

StripDB: dropping a Paradox .db (1.27.0)

#link TableName <- TableName : StripDB (with --castlib pointing at the shipped casts.castlib) turns 'MET1.db' into 'MET1' -- in the .dfm, and in code where the table name is assigned one plain string that ends the statement on its line (t1.TableName := 'MET1.db';). Anything else in code (a concatenation, a variable, a read, a string split over lines, a with t1 do TableName := ...) is left alone and listed as a warning to check by hand, naming its line. Values without .db are never touched.

What is left to convert (1.27.0)

Every run also lists what the unit's code still uses: each member reached through a converted instance, and each global from a unit the run removes. JSON pas_uses[] gives one row per member with its kind, where it is declared (the From class found through the unit's own uses, so a VCL TLabel is never read as the FMX one; when nothing settles it the row says resolution: ambiguous and lists the candidates, 1.28.2), its lines and a status -- mapped, cast-refused, not-rewritten (a rule covers it, but convert-apply does not edit the code for that rule's shape -- a dotted path such as #link Properties.X <- X -- so the code still names the old member), same-name (the new type has the same name, so it carries over), unmapped (no rule and no such member: your work list) or global-dangling. Text mode prints a one-line count and the unmapped and not-rewritten members with their lines.

Exit codes

0 converted (or nothing to do); 1 the unit was REFUSED (a deliberate refusal: nothing written, the reason printed); 2 a write failed or was rolled back, or the run could not start (a missing unit or rules file); 3 an unknown argument; 6 the --castlib cannot be used (it fails to load, or names an unknown value keyword) -- a configuration error, so it no longer reads as a write failure. A batch (--unit repeated) exits with the worst unit's code.

Reaching it in the IDE

No IDE surface -- this is a CLI-only feature.

What it needs

An index IS required -- convert-apply reads the project's index database. The project's DB lives at <project folder>\_D-RAG\<project file>.sqlite; run drag-lint resolve-dbs --project <X.dproj> if you are unsure which one.

Example

Illustrative only:

drag-lint convert-apply --unit C:\Projects\MyApp\frmOrders.pas --rules C:\Projects\MyApp\convert-rules\OvcTable-to-cxGrid.rules --db C:\Projects\MyApp\_D-RAG\MyApp.sqlite --only tblOrders

This would preview (no --apply) the conversion of the tblOrders component in frmOrders.pas across all five surfaces, without writing any changes.

Clone this wiki locally