A command-line tool that reformats Delphi/Pascal files.
- VS Code extension: https://github.com/tuncb/dfixxer-vscode
- Pre-commit hook: https://github.com/tuncb/dfixxer-pre-commit
- Sorts and normalizes
usessections, including namespace-priority ordering and optional unit alias expansion from config - Normalizes
unitandprogramheaders - Normalizes single-keyword sections such as
interface,implementation,initialization, andfinalization - Reformats routine declarations, including
procedure,function,constructor,destructor, andoperator - Wraps eligible single-statement control bodies in
begin/endblocks forfor,for .. in,while, andifbranches - Expands bare
inheritedcalls to explicit inherited routine calls when the target can be resolved - Normalizes spacing and casing in general source text, including commas, operators, generics, comments, and keywords
- Fixes spacing between local routine declarations
- Fixes indentation of local routine blocks
- Rewrites conservative routine-local
varblocks into inlinevar/constdefinitions when every declared variable can be safely handled - Trims trailing whitespace
- Ensures files end with a single trailing newline
Exact behavior is configurable through dfixxer.toml, and individual transformation groups can be turned on or off.
Requires Rust (stable).
- Build debug:
cargo build - Build release:
cargo build --release - Run tests:
cargo test
The binary is dfixxer (on Windows: dfixxer.exe).
dfixxer [GLOBAL_OPTIONS] <COMMAND> [COMMAND_OPTIONS]
-l, --log-level <LEVEL>: Set the logging level- Possible values:
off,error,warn,info,debug,trace - Default: No logging output
infoincludes a performance summary on stderr with stage timings, parse subtimings, rule timings, and text-transformation countersdebugadditionally logs individual stage durations as they complete
- Possible values:
dfixxer update <filename> [--config <path>] [--multi]
Reformats and sorts the uses section(s) in the given Pascal file, modifying it in-place.
Arguments:
<filename>: Path to the Pascal file to update (required). When--multiis used, this can be a glob pattern.
Options:
--config <path>: Path to configuration file- If not provided, searches for
dfixxer.tomlstarting from the file's directory and walking up parent directories - If no config file is found, uses built-in defaults
- If not provided, searches for
--multi: Process multiple files using glob patterns- When enabled,
<filename>is treated as a glob pattern (e.g.,"src/**/*.pas") - Processes all matching files individually
- Logs processing progress at info level
- When enabled,
dfixxer check <filename> [--config <path>] [--multi]
Shows a unified diff of what would change without modifying the file.
Arguments:
<filename>: Path to the Pascal file to check (required). When--multiis used, this can be a glob pattern.
Options:
--config <path>: Path to configuration file (same behavior asupdate)--multi: Process multiple files using glob patterns- When enabled,
<filename>is treated as a glob pattern (e.g.,"src/**/*.pas") - Shows the absolute path of each file being processed
- Prints a per-file unified diff for files that would change
- Returns the total number of replacements across all files
- When enabled,
Exit Code:
- Returns the number of replacements that would be made as the exit code
0if no changes are neededN(where N > 0) if N total replacements would be made across all files1if an error occurred (with error message printed to stderr)
dfixxer init-config <filename>
Creates a default configuration file at the specified path.
Arguments:
<filename>: Path where the configuration file should be created (required)
dfixxer parse <filename> [--multi]
Parses a Pascal file and prints its Abstract Syntax Tree (AST) for debugging purposes.
Arguments:
<filename>: Path to the Pascal file to parse (required). When--multiis used, this can be a glob pattern.
Options:
--multi: Process multiple files using glob patterns- When enabled,
<filename>is treated as a glob pattern (e.g.,"src/**/*.pas") - Shows the absolute path of each file being processed
- When enabled,
dfixxer parse-debug <filename> [--multi]
Parses a Pascal file and prints detailed debug information including parser output for troubleshooting.
Arguments:
<filename>: Path to the Pascal file to parse with debug output (required). When--multiis used, this can be a glob pattern.
Options:
--multi: Process multiple files using glob patterns- When enabled,
<filename>is treated as a glob pattern (e.g.,"src/**/*.pas") - Shows the absolute path of each file being processed
- When enabled,
0: Success (no changes needed forcheckcommand, or successful completion for other commands)N(where N > 0): Forcheckcommand only - indicates N replacements would be made1: Error occurred (message printed to stderr)
- If a uses section or its parent has a parse error, it is skipped and a warning is printed
- If a uses section contains preprocessor directives (
{$...}) or comment nodes at the same level as unit names, it's treated as unsupported and skipped with a warning
You can disable dfixxer for a region with standalone comment directives:
// dfixxer:off
...
// dfixxer:onEquivalent standalone aliases are also supported:
{ dfixxer:off }
...
{ dfixxer:on }(* dfixxer:off *)
...
(* dfixxer:on *)Notes:
- Directives must appear in a standalone single-line comment.
- The directive comment lines themselves are preserved verbatim.
- Raw
# dfixxer offand compiler-directive forms such as{$DFIXXER OFF}are not supported.
./target/debug/dfixxer update .\examples\simple.pas./target/debug/dfixxer --log-level debug update .\examples\simple.pas --config .\dfixxer.toml./target/debug/dfixxer check .\examples\simple.pas# Check file and capture the number of replacements
./target/debug/dfixxer check .\examples\simple.pas
$replacements = $LASTEXITCODE
if ($replacements -eq 0) {
Write-Host "File is already properly formatted"
} elseif ($replacements -gt 0) {
Write-Host "File needs $replacements changes"
} else {
Write-Host "Error checking file"
}./target/debug/dfixxer init-config .\dfixxer.toml# Update all Pascal files in the src directory
./target/debug/dfixxer update "src/**/*.pas" --multi
# Check all Pascal files in current directory and subdirectories
./target/debug/dfixxer check "**/*.pas" --multi
# Update all Pascal files matching a specific pattern with custom config
./target/debug/dfixxer --log-level info update "project/**/*.pas" --multi --config custom.toml# Check all Pascal files and get total replacements needed
./target/debug/dfixxer check "src/**/*.pas" --multi
$totalReplacements = $LASTEXITCODE
if ($totalReplacements -eq 0) {
Write-Host "All files are properly formatted"
} else {
Write-Host "Total replacements needed across all files: $totalReplacements"
}# Parse all Pascal files in a directory for debugging
./target/debug/dfixxer parse "src/*.pas" --multi
# Get detailed debug output for multiple files
./target/debug/dfixxer parse-debug "problematic_files/*.pas" --multi./target/debug/dfixxer --help
./target/debug/dfixxer update --helpThe configuration file uses TOML format. All keys are optional; unspecified keys use built-in defaults.
⚠️ Breaking Change in v0.6.0: Uses section configuration options (uses_section_style,override_sorting_order, andmodule_names_to_update) have been moved under a[uses_section]section for better organization. Update your configuration files accordingly.
- Purpose: Indentation used for generated section formatting and local routine indentation
- Default:
" "(two spaces) - Examples:
" "(four spaces)"\t"(tab character)
- Purpose: Controls line ending style in output
- Values:
"Auto"- Use platform default (CRLF on Windows, LF elsewhere) (default)"Crlf"- Force Windows-style line endings (\r\n)"Lf"- Force Unix-style line endings (\n)
- Default:
"Auto"
- Purpose: File patterns to exclude from processing
- Format: Glob patterns (e.g.,
"*.tmp","backup/*") - Behavior: Files matching these patterns will be skipped
- Default:
[](empty array) - Example:
["*.tmp", "backup/*", "test_*.pas"]
- Purpose: Use different configuration files for specific file patterns
- Format: Array of
[pattern, config_path]pairs - Behavior: Files matching the pattern use the specified config file
- Default:
[](empty array) - Example:
[["test/*.pas", "test_config.toml"], ["legacy/*.pas", "/absolute/legacy_config.toml"]]
-
Purpose: Configuration options specific to uses section formatting
-
Properties:
- Purpose: Controls comma placement in formatted uses sections
- Values:
"CommaAtTheEnd"- Comma appears at the end of each line (default)"CommaAtTheBeginning"- Comma appears at the start of each line
- Default:
"CommaAtTheEnd"
- Purpose: Namespace prefixes to prioritize during sorting
- Behavior: Prefixes are matched case-insensitively using plain
starts_withsemantics (no required dot boundary). Prefixes are applied in listed order; fallback ordering uses locale-aware base collation. - Default:
[](empty array) - Example:
["System", "Vcl", "FireDAC"]
- Purpose: Map short unit names to fully-qualified names
- Format: Each entry is
"Prefix:ShortName" - Behavior: Matching is case-insensitive. When the tool encounters
ShortName, it rewrites it to the canonicalPrefix.ShortNamefrom the mapping before sorting/formatting - Default: Extensive list of 258 built-in mappings for System, Winapi, and other common namespaces
- Example:
["System:Classes", "Vcl:Dialogs", "FireDAC:Comp.Client"]
- Purpose: Controls which transformation features are enabled
- Default: All transformations enabled
- Properties:
enable_uses_section(boolean) - Enable uses section formatting (default:true)enable_unit_program_section(boolean) - Enable unit/program section processing (default:true)enable_single_keyword_sections(boolean) - Enable single keyword section processing (default:true)enable_procedure_section(boolean) - Enable procedure section processing (default:true)enable_local_routine_spacing(boolean) - Ensure implemented local routines have one empty line before and after them, while keeping attached comments / clean{$IF...}wrappers with the routine block (default:true)enable_local_routine_indentation(boolean) - Indent implemented local routine blocks by one configured indentation level relative to their owning routine, including attached comments / clean{$IF...}wrappers (default:true)enable_inline_local_var_definitions(boolean) - Rewrite conservative routine-local leadingvarblocks into inlinevar/constdefinitions. The current implementation skips routines with nested local routines, labels/goto, comments or preprocessors inside thevarblock, inline declarations that shadow the locals, and other unsupported mutation patterns (default:true)enable_for_body_wrapping(boolean) - Wrap eligible single-statementforandfor .. inbodies inbegin/end(default:true)enable_while_body_wrapping(boolean) - Wrap eligible single-statementwhilebodies inbegin/end(default:true)enable_if_body_wrapping(boolean) - Wrap eligible standaloneifbodies andif/else if/elsechain branches inbegin/end(default:true)skip_terminating_for_body_wrapping(boolean) - Whenenable_for_body_wrappingis enabled, skip wrapping terminatingforandfor .. inbodies such asExit,Continue,Break,raise,Abort, andHalt(case-insensitive, including call forms such asExit(1)) (default:true)skip_terminating_while_body_wrapping(boolean) - Whenenable_while_body_wrappingis enabled, skip wrapping terminatingwhilebodies such asExit,Continue,Break,raise,Abort, andHalt(default:true)skip_terminating_if_body_wrapping(boolean) - Whenenable_if_body_wrappingis enabled, skip wrapping terminating standaloneifbodies andif/else if/elsechain branches such asExit,Continue,Break,raise,Abort, andHalt(default:true)enable_inherited_call_expansion(boolean) - Expand bareinherited;to an explicit inherited call using the current routine name/arguments (default:true)enable_text_transformations(boolean) - Enable text formatting transformations (default:true)
- Purpose: Controls spacing around various operators/punctuation and optional identifier casing enforcement
- Default: Optimized defaults for Pascal code formatting
- Properties:
- Punctuation:
comma- Comma spacing (default:"After")semi_colon- Semicolon spacing (default:"After")colon- Colon spacing (default:"After")colon_numeric_exception- Skip colon spacing for numeric ranges like1:10(default:true)space_inside_brace_comments- For non-directive{...}comments, enforce one space after{and before}(default:true)space_inside_paren_star_comments- For non-directive(*...*)comments, enforce one space after(*and before*)(default:true)space_after_line_comment_slashes- For//...comments, ensure at least one space after the leading slash run while preserving existing spacing (default:true)
- Comparison operators:
lt- Less than<(default:"BeforeAndAfter")eq- Equals=(default:"BeforeAndAfter")neq- Not equals<>(default:"BeforeAndAfter")gt- Greater than>(default:"BeforeAndAfter")lte- Less than or equal<=(default:"BeforeAndAfter")gte- Greater than or equal>=(default:"BeforeAndAfter")
- Arithmetic operators:
add- Addition+(default:"BeforeAndAfter")sub- Subtraction-(default:"BeforeAndAfter")mul- Multiplication*(default:"BeforeAndAfter")fdiv- Division/(default:"BeforeAndAfter")
- Assignment operators:
assign- Assignment:=(default:"BeforeAndAfter")assign_add- Add assignment+=(default:"BeforeAndAfter")assign_sub- Subtract assignment-=(default:"BeforeAndAfter")assign_mul- Multiply assignment*=(default:"BeforeAndAfter")assign_div- Divide assignment/=(default:"BeforeAndAfter")
- Other:
trim_trailing_whitespace- Remove trailing whitespace (default:true)ensure_single_trailing_newline- Ensure the file ends with exactly one line ending (default:true)enforce_word_casing- List of canonical identifier spellings to enforce in code (case-insensitive match; strings/comments are not changed) (default:[])
- Punctuation:
- Space Operations:
"NoChange"- Leave spacing as-is"Before"- Add space before operator"After"- Add space after operator"BeforeAndAfter"- Add spaces before and after operator
# Use 4-space indentation
indentation = " "
# Force Unix-style line endings
line_ending = "Lf"
# Exclude temporary and backup files
exclude_files = ["*.tmp", "backup/*", "test_*.pas"]
# Use different configs for different file patterns
custom_config_patterns = [
["legacy/*.pas", "legacy_config.toml"],
["test/**/*.pas", "test_config.toml"]
]
# Uses section configuration
[uses_section]
# Put commas at the beginning of lines
uses_section_style = "CommaAtTheBeginning"
# Prioritize System and Vcl namespaces
override_sorting_order = ["System", "Vcl", "FireDAC"]
# Automatically qualify common unit names
module_names_to_update = [
"System:Classes",
"System:SysUtils",
"System:Variants",
"Vcl:Forms",
"Vcl:Controls",
"Vcl:Dialogs",
"FireDAC:Comp.Client",
"FireDAC:Stan.Def"
]
# Control which transformations are enabled
[transformations]
enable_uses_section = true
enable_unit_program_section = true
enable_single_keyword_sections = true
enable_procedure_section = true
enable_local_routine_spacing = true
enable_local_routine_indentation = true
enable_inline_local_var_definitions = true
enable_for_body_wrapping = true
enable_while_body_wrapping = true
enable_if_body_wrapping = true
skip_terminating_for_body_wrapping = true
skip_terminating_while_body_wrapping = true
skip_terminating_if_body_wrapping = true
enable_inherited_call_expansion = true
enable_text_transformations = true
# Control text formatting and spacing
[text_changes]
comma = "After"
semi_colon = "After"
colon = "After"
colon_numeric_exception = true
lt = "BeforeAndAfter"
eq = "BeforeAndAfter"
neq = "BeforeAndAfter"
gt = "BeforeAndAfter"
lte = "BeforeAndAfter"
gte = "BeforeAndAfter"
add = "BeforeAndAfter"
sub = "BeforeAndAfter"
mul = "BeforeAndAfter"
fdiv = "BeforeAndAfter"
assign = "BeforeAndAfter"
assign_add = "BeforeAndAfter"
assign_sub = "BeforeAndAfter"
assign_mul = "BeforeAndAfter"
assign_div = "BeforeAndAfter"
space_inside_brace_comments = true
space_inside_paren_star_comments = true
space_after_line_comment_slashes = true
trim_trailing_whitespace = true
ensure_single_trailing_newline = true
enforce_word_casing = ["HTTPClient", "iOS"]When --config is not specified:
- Starts from the target file's directory
- Looks for
dfixxer.tomlin current directory - If not found, walks up parent directories
- Uses the first
dfixxer.tomlfile found - If no config file is found, uses built-in defaults
uses UnitC, UnitA, Classes, Forms;uses
System.Classes,
UnitA,
UnitC,
Vcl.Forms;uses
System.Classes
, UnitA
, UnitC
, Vcl.Forms
;indentation = " "
[uses_section]
override_sorting_order = ["System", "Vcl"]
module_names_to_update = [
"System:Classes",
"Vcl:Forms"
]- Parse errors: Sections with syntax errors are skipped (warning printed)
- Unsupported constructs: Sections containing preprocessor directives (
{$...}) or comments at the same level as unit names are skipped (warning printed) - Inline suppression directives:
dfixxer:off/dfixxer:onmust be standalone single-line comments; inline trailing comments are ignored with a warning
Licensed under the Apache License, Version 2.0. See the LICENSE file for the full license text.
This project is open source. Contributions are welcome through pull requests and issue reports.