Implement --fix mode and add Guix-tuned config
--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).
This commit is contained in:
@@ -92,6 +92,10 @@
|
||||
(let* ((config-path (option-ref options 'config #f))
|
||||
(user-config (load-config config-path))
|
||||
(config (merge-configs default-config user-config))
|
||||
;; Wire up --fix
|
||||
(config (if (option-ref options 'fix #f)
|
||||
(cons '(%fix . #t) config)
|
||||
config))
|
||||
;; Wire up --pass
|
||||
(pass-str (option-ref options 'pass #f))
|
||||
(config (if pass-str
|
||||
|
||||
@@ -127,24 +127,73 @@ and '%min-severity with value 'error, 'warning, or 'info (default)."
|
||||
;; Sort by location
|
||||
(sort diagnostics diagnostic<?)))
|
||||
|
||||
(define (apply-fixes-to-file file diagnostics)
|
||||
"Apply all fixable diagnostics to FILE. Returns count of fixes applied."
|
||||
(let* ((fixable (filter diagnostic-fix diagnostics))
|
||||
(fixes (map (lambda (d) (cons (diagnostic-line d) (diagnostic-fix d)))
|
||||
fixable)))
|
||||
(if (null? fixes)
|
||||
0
|
||||
(let* ((text (read-file-to-string file))
|
||||
(lines (list->vector (string-split text #\newline)))
|
||||
(count 0))
|
||||
;; Apply fixes: each fix is a replace-line, apply by line number
|
||||
;; Process in reverse line order so indices stay valid
|
||||
(for-each
|
||||
(lambda (fix-pair)
|
||||
(let* ((line-num (car fix-pair))
|
||||
(fix (cdr fix-pair))
|
||||
(idx (1- line-num)))
|
||||
(when (and (>= idx 0) (< idx (vector-length lines))
|
||||
(eq? (fix-type fix) 'replace-line))
|
||||
(vector-set! lines idx (fix-replacement fix))
|
||||
(set! count (1+ count)))))
|
||||
(sort fixes (lambda (a b) (> (car a) (car b)))))
|
||||
;; Write back
|
||||
(when (> count 0)
|
||||
(call-with-output-file file
|
||||
(lambda (port)
|
||||
(let ((len (vector-length lines)))
|
||||
(let lp ((i 0))
|
||||
(when (< i len)
|
||||
(display (vector-ref lines i) port)
|
||||
(when (< i (1- len))
|
||||
(newline port))
|
||||
(lp (1+ i))))))))
|
||||
count))))
|
||||
(define (lint-files files config)
|
||||
"Lint multiple FILES in parallel. Returns total diagnostic count.
|
||||
Uses n-par-map to distribute work across threads, then outputs
|
||||
diagnostics sequentially to maintain deterministic file order."
|
||||
diagnostics sequentially to maintain deterministic file order.
|
||||
When '%fix is #t in config, apply auto-fixes and report unfixed."
|
||||
(let* ((ncpus (max 1 (total-processor-count)))
|
||||
(fix-mode? (assq-ref config '%fix))
|
||||
(results (n-par-map ncpus
|
||||
(lambda (file)
|
||||
(cons file (lint-file file config)))
|
||||
files))
|
||||
(total 0))
|
||||
;; Output in original file order (n-par-map preserves it)
|
||||
(total-diags 0)
|
||||
(total-fixed 0))
|
||||
(for-each
|
||||
(lambda (result)
|
||||
(let ((diags (cdr result)))
|
||||
(set! total (+ total (length diags)))
|
||||
(format-diagnostics diags (current-output-port))))
|
||||
(let* ((file (car result))
|
||||
(diags (cdr result)))
|
||||
(if fix-mode?
|
||||
;; In fix mode: apply fixes, then report only unfixed diagnostics
|
||||
(let* ((fixed-count (apply-fixes-to-file file diags))
|
||||
(unfixed (filter (lambda (d) (not (diagnostic-fix d))) diags)))
|
||||
(set! total-fixed (+ total-fixed fixed-count))
|
||||
(set! total-diags (+ total-diags (length unfixed)))
|
||||
(format-diagnostics unfixed (current-output-port)))
|
||||
;; Normal mode: report everything
|
||||
(begin
|
||||
(set! total-diags (+ total-diags (length diags)))
|
||||
(format-diagnostics diags (current-output-port))))))
|
||||
results)
|
||||
total))
|
||||
(when (and fix-mode? (> total-fixed 0))
|
||||
(format (current-error-port) "Fixed ~a issue~a.~%"
|
||||
total-fixed (if (= total-fixed 1) "" "s")))
|
||||
total-diags))
|
||||
|
||||
(define (scheme-file? path)
|
||||
"Is PATH a Scheme source file?"
|
||||
|
||||
@@ -54,18 +54,30 @@
|
||||
;; Inline comment (after code) should use single ;
|
||||
;; But we don't enforce this strictly — just flag ;;; or more inline
|
||||
((and (not own-line?) (>= semis 3))
|
||||
(list (make-diagnostic
|
||||
file line-num pos
|
||||
'info 'comment-semicolons
|
||||
"inline comments should use ; or ;; not ;;;"
|
||||
#f)))
|
||||
(let ((fixed (string-append
|
||||
(substring line-text 0 pos)
|
||||
";;"
|
||||
(substring line-text (+ pos semis)))))
|
||||
(list (make-diagnostic
|
||||
file line-num pos
|
||||
'info 'comment-semicolons
|
||||
"inline comments should use ; or ;; not ;;;"
|
||||
(make-fix 'replace-line line-num 0
|
||||
line-num (string-length line-text)
|
||||
fixed)))))
|
||||
;; Own-line comment with single ; (should be ;;)
|
||||
((and own-line? (= semis 1) (> (string-length line-text) (1+ pos))
|
||||
(not (char=? (string-ref line-text (1+ pos)) #\!)))
|
||||
(list (make-diagnostic
|
||||
file line-num pos
|
||||
'info 'comment-semicolons
|
||||
"line comments should use ;; not ;"
|
||||
#f)))
|
||||
(let ((fixed (string-append
|
||||
(substring line-text 0 pos)
|
||||
";;"
|
||||
(substring line-text (1+ pos)))))
|
||||
(list (make-diagnostic
|
||||
file line-num pos
|
||||
'info 'comment-semicolons
|
||||
"line comments should use ;; not ;"
|
||||
(make-fix 'replace-line line-num 0
|
||||
line-num (string-length line-text)
|
||||
fixed)))))
|
||||
(else '()))))))))
|
||||
#f))
|
||||
|
||||
Reference in New Issue
Block a user