Skip to content

tuncb/dfixxer

Repository files navigation

dfixxer

A command-line tool that reformats Delphi/Pascal files.

What dfixxer can fix

  • Sorts and normalizes uses sections, including namespace-priority ordering and optional unit alias expansion from config
  • Normalizes unit and program headers
  • Normalizes single-keyword sections such as interface, implementation, initialization, and finalization
  • Reformats routine declarations, including procedure, function, constructor, destructor, and operator
  • Wraps eligible single-statement control bodies in begin / end blocks for for, for .. in, while, and if branches
  • Expands bare inherited calls 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 var blocks into inline var / const definitions 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.

Install / build

Requires Rust (stable).

  • Build debug: cargo build
  • Build release: cargo build --release
  • Run tests: cargo test

The binary is dfixxer (on Windows: dfixxer.exe).

Usage

Command Syntax

dfixxer [GLOBAL_OPTIONS] <COMMAND> [COMMAND_OPTIONS]

Global Options

  • -l, --log-level <LEVEL>: Set the logging level
    • Possible values: off, error, warn, info, debug, trace
    • Default: No logging output
    • info includes a performance summary on stderr with stage timings, parse subtimings, rule timings, and text-transformation counters
    • debug additionally logs individual stage durations as they complete

Commands

update - Reformat file in-place

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 --multi is used, this can be a glob pattern.

Options:

  • --config <path>: Path to configuration file
    • If not provided, searches for dfixxer.toml starting from the file's directory and walking up parent directories
    • If no config file is found, uses built-in defaults
  • --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

check - Preview changes without modifying

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 --multi is used, this can be a glob pattern.

Options:

  • --config <path>: Path to configuration file (same behavior as update)
  • --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

Exit Code:

  • Returns the number of replacements that would be made as the exit code
  • 0 if no changes are needed
  • N (where N > 0) if N total replacements would be made across all files
  • 1 if an error occurred (with error message printed to stderr)

init-config - Create default configuration

dfixxer init-config <filename>

Creates a default configuration file at the specified path.

Arguments:

  • <filename>: Path where the configuration file should be created (required)

parse - Debug: Show AST

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 --multi is 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

parse-debug - Debug: Show detailed parsing information

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 --multi is 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

Exit Codes

  • 0: Success (no changes needed for check command, or successful completion for other commands)
  • N (where N > 0): For check command only - indicates N replacements would be made
  • 1: Error occurred (message printed to stderr)

Processing Notes

  • 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

Inline Suppression Directives

You can disable dfixxer for a region with standalone comment directives:

// dfixxer:off
...
// dfixxer:on

Equivalent 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 off and compiler-directive forms such as {$DFIXXER OFF} are not supported.

Examples

Update a file using auto-discovered or default config

./target/debug/dfixxer update .\examples\simple.pas

Update a file with explicit config and debug logging

./target/debug/dfixxer --log-level debug update .\examples\simple.pas --config .\dfixxer.toml

Check what changes would be made without modifying the file

./target/debug/dfixxer check .\examples\simple.pas

Check for changes and use exit code in scripts

# 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"
}

Create a default config file

./target/debug/dfixxer init-config .\dfixxer.toml

Process multiple files using glob patterns

# 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 multiple files and get total replacement count

# 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"
}

Debug parsing for multiple files

# 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

Get help for any command

./target/debug/dfixxer --help
./target/debug/dfixxer update --help

Configuration (dfixxer.toml)

The 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, and module_names_to_update) have been moved under a [uses_section] section for better organization. Update your configuration files accordingly.

Configuration Options

indentation (string)

  • Purpose: Indentation used for generated section formatting and local routine indentation
  • Default: " " (two spaces)
  • Examples:
    • " " (four spaces)
    • "\t" (tab character)

line_ending (enum)

  • 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"

exclude_files (array of strings)

  • 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"]

custom_config_patterns (array of pattern-config pairs)

  • 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"]]

uses_section (object)

  • Purpose: Configuration options specific to uses section formatting

  • Properties:

    uses_section_style (enum)
    • 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"
    override_sorting_order (array of strings)
    • Purpose: Namespace prefixes to prioritize during sorting
    • Behavior: Prefixes are matched case-insensitively using plain starts_with semantics (no required dot boundary). Prefixes are applied in listed order; fallback ordering uses locale-aware base collation.
    • Default: [] (empty array)
    • Example: ["System", "Vcl", "FireDAC"]
    module_names_to_update (array of strings)
    • 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 canonical Prefix.ShortName from 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"]

transformations (object)

  • 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 leading var blocks into inline var / const definitions. The current implementation skips routines with nested local routines, labels/goto, comments or preprocessors inside the var block, inline declarations that shadow the locals, and other unsupported mutation patterns (default: true)
    • enable_for_body_wrapping (boolean) - Wrap eligible single-statement for and for .. in bodies in begin / end (default: true)
    • enable_while_body_wrapping (boolean) - Wrap eligible single-statement while bodies in begin / end (default: true)
    • enable_if_body_wrapping (boolean) - Wrap eligible standalone if bodies and if / else if / else chain branches in begin / end (default: true)
    • skip_terminating_for_body_wrapping (boolean) - When enable_for_body_wrapping is enabled, skip wrapping terminating for and for .. in bodies such as Exit, Continue, Break, raise, Abort, and Halt (case-insensitive, including call forms such as Exit(1)) (default: true)
    • skip_terminating_while_body_wrapping (boolean) - When enable_while_body_wrapping is enabled, skip wrapping terminating while bodies such as Exit, Continue, Break, raise, Abort, and Halt (default: true)
    • skip_terminating_if_body_wrapping (boolean) - When enable_if_body_wrapping is enabled, skip wrapping terminating standalone if bodies and if / else if / else chain branches such as Exit, Continue, Break, raise, Abort, and Halt (default: true)
    • enable_inherited_call_expansion (boolean) - Expand bare inherited; to an explicit inherited call using the current routine name/arguments (default: true)
    • enable_text_transformations (boolean) - Enable text formatting transformations (default: true)

text_changes (object)

  • 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 like 1: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: [])
  • Space Operations:
    • "NoChange" - Leave spacing as-is
    • "Before" - Add space before operator
    • "After" - Add space after operator
    • "BeforeAndAfter" - Add spaces before and after operator

Complete Example Configuration

# 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"]

Configuration File Discovery

When --config is not specified:

  1. Starts from the target file's directory
  2. Looks for dfixxer.toml in current directory
  3. If not found, walks up parent directories
  4. Uses the first dfixxer.toml file found
  5. If no config file is found, uses built-in defaults

Formatting Examples

Input Code

uses UnitC, UnitA, Classes, Forms;

With CommaAtTheEnd style (default)

uses
    System.Classes,
    UnitA,
    UnitC,
    Vcl.Forms;

With CommaAtTheBeginning style

uses
    System.Classes
    , UnitA
    , UnitC
    , Vcl.Forms
    ;

Configuration Used for Above Examples

indentation = "    "

[uses_section]
override_sorting_order = ["System", "Vcl"]
module_names_to_update = [
    "System:Classes",
    "Vcl:Forms"
]

Limitations

  • 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:on must be standalone single-line comments; inline trailing comments are ignored with a warning

License

Licensed under the Apache License, Version 2.0. See the LICENSE file for the full license text.

Contributing

This project is open source. Contributions are welcome through pull requests and issue reports.

About

Delphi code formatter

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors