New auto-fixes:
- no-tabs: expand tabs to 8-space tab stops, also trim trailing
whitespace to avoid conflicts with trailing-whitespace fix
- blank-lines: delete excess blank lines via delete-line fix type.
First excess line emits a warning; subsequent excess lines emit
info-level 'blank-lines-fixup' diagnostics (hidden at --severity
warning) with delete-line fixes.
Fix engine improvements:
- apply-fixes-to-file now supports delete-line (sentinel-based skip)
- When multiple replace-line fixes target the same line (e.g. both
trailing-whitespace and no-tabs), the replacement with the greatest
edit distance from the original is chosen, avoiding conflicts where
one fix would overwrite another.
Result on Guix tree (1336 files):
gulie --pass surface --config .gulie.sexp --fix refs/guix/
→ Fixed 2007 issues.
→ 0 remaining warnings.
→ Single pass, idempotent, files parse correctly.
--fix mode:
- apply-fixes-to-file collects fixable diagnostics, applies
replace-line fixes bottom-up via a vector, writes file back
- lint-files in fix mode applies fixes then reports only unfixed
diagnostics; prints fix count to stderr
- Idempotent: second run finds nothing to fix
Fix records added:
- trailing-whitespace already had fix records
- comment-semicolons now produces fix records for both
single-; on own line (→ ;;) and ;;;+ inline (→ ;;)
Guix config (refs/guix/.gulie.sexp):
- Surface pass only (semantic pass useless without Guix on load path)
- line-length disabled (URLs, hashes, descriptions)
- Remaining rules: trailing-whitespace, no-tabs, comment-semicolons,
blank-lines
On Guix tree: 2005 findings → 594 after --fix (1411 auto-fixed).
Remaining: 523 no-tabs (real tabs) + 71 blank-lines (real).
Use Guile's n-par-map from (ice-9 threads) to lint files in parallel,
one thread per CPU core. Results are collected per-file then output
sequentially to maintain deterministic ordering.
Performance on Guix tree (1336 files, 12 cores):
Before (sequential) After (parallel)
full: 21s full: 14s (1.5x)
surface: 15s surface: 8s (1.9x)
line: 14s line: 7s (2.0x)
Check filter-rules-by-config before invoking the tokenizer in
lint-file. When all CST rules are disabled (e.g. --disable
comment-semicolons), the expensive tokenize+parse-cst pass is
skipped entirely.
On the Guix tree (1336 files), this saves ~2s on surface-only
runs (15.6s → 13.7s). The savings are more pronounced on
projects with fewer monster files.
Bug 1: Config auto-discovery — load-config now walks CWD and parent
directories for .gulie.sexp when no --config is given, matching the
documented behavior. Added find-config helper.
Bug 2: Compile-error messages garbled — the catch handler now properly
destructures Guile exception args (subr fmt fmt-args . _) and uses
apply+format instead of raw display, producing readable messages like
"no code for module (guix licenses)" instead of
"#f no code for module ~S ((guix licenses)) #f".
Bug 3: --init template unreadable — generate-template now uses
pretty-print instead of write, producing properly indented output.
Bug 4: CLI options silently ignored — --pass, --disable, --rule, and
--severity are now wired up in main and threaded into config as %pass,
augmented disable list, enable list, and %min-severity respectively.
Bug 5: enable/disable config keys never consulted — added
filter-rules-by-config, used in run-line-rules and run-cst-rules.
Bug 6: blank-lines rule spammed one diagnostic per excess line —
changed trigger to fire once per group at the boundary.
Bug 7: read-file-to-string slow — replaced line-by-line accumulation
with get-string-all.
Bug 8: No way to skip semantic pass — lint-file now respects %pass
config key (surface/semantic/all) and %min-severity for filtering.