hako_check: add --fix-dry-run (MVP text scope) for HC003 using→quoted; emit minimal unified diff
This commit is contained in:
@ -28,6 +28,9 @@ static box HakoAnalyzerBox {
|
||||
local debug = 0
|
||||
// Default: AST path OFF (enable with --force-ast)
|
||||
local no_ast = 1
|
||||
// QuickFix options (Phase 21.8)
|
||||
local fix_dry_run = 0
|
||||
local fix_scope = "text" // reserved for future
|
||||
// single-pass parse: handle options in-place and collect sources
|
||||
local i = 0
|
||||
local fail = 0
|
||||
@ -47,6 +50,11 @@ static box HakoAnalyzerBox {
|
||||
if i + 1 >= args.size() { print("[lint/error] --format requires value"); return 2 }
|
||||
fmt = args.get(i+1); i = i + 2; continue
|
||||
}
|
||||
if p == "--fix-dry-run" { fix_dry_run = 1; i = i + 1; continue }
|
||||
if p == "--fix-scope" {
|
||||
if i + 1 >= args.size() { print("[lint/error] --fix-scope requires value"); return 2 }
|
||||
fix_scope = args.get(i+1); i = i + 2; continue
|
||||
}
|
||||
if p == "--rules" {
|
||||
if i + 1 >= args.size() { print("[lint/error] --rules requires CSV"); return 2 }
|
||||
rules_only = me._parse_csv(args.get(i+1)); i = i + 2; continue
|
||||
@ -68,6 +76,17 @@ static box HakoAnalyzerBox {
|
||||
local text_raw = text
|
||||
// pre-sanitize (ASCII quotes, normalize newlines) - minimal & reversible
|
||||
text = me._sanitize(text)
|
||||
|
||||
// Phase 21.8: QuickFix (--fix-dry-run)
|
||||
if fix_dry_run == 1 {
|
||||
// Apply text-scope quick fixes in a safe subset and print unified diff
|
||||
local patched = me._fix_text_quick(text)
|
||||
if patched != null && patched != text {
|
||||
me._print_unified_diff(p, text, patched)
|
||||
}
|
||||
// In dry-run mode, we do not run analyzers further
|
||||
continue
|
||||
}
|
||||
// analysis - only build IR if needed by active rules
|
||||
local ir = null
|
||||
if me._needs_ir(rules_only, rules_skip) == 1 {
|
||||
@ -230,6 +249,74 @@ static box HakoAnalyzerBox {
|
||||
print("]}")
|
||||
return 0
|
||||
}
|
||||
// ---- QuickFix (text scope, MVP: HC003 using quoted) ----
|
||||
_fix_text_quick(text) {
|
||||
if text == null { return null }
|
||||
// Only HC003: using 非引用→引用化
|
||||
local lines = me._split_lines(text)
|
||||
local changed = 0
|
||||
local out = new ArrayBox()
|
||||
local i = 0
|
||||
while i < lines.size() {
|
||||
local ln = lines.get(i)
|
||||
local ltrim = me._ltrim(ln)
|
||||
if ltrim.indexOf("using ") == 0 && ltrim.indexOf('using "') != 0 {
|
||||
// rewrite: using X ... -> using "X" ...(X は最初の空白/セミコロン/"as" まで)
|
||||
local prefix_len = ln.indexOf("using ")
|
||||
if prefix_len < 0 { prefix_len = 0 }
|
||||
local head = ln.substring(0, prefix_len)
|
||||
local rest = ln.substring(prefix_len)
|
||||
// rest starts with 'using '
|
||||
local after = rest.substring(6)
|
||||
// Find boundary
|
||||
local b = 0; local n = after.length()
|
||||
while b < n {
|
||||
local ch = after.substring(b,b+1)
|
||||
// stop at space, semicolon or before ' as '
|
||||
if ch == " " || ch == ";" { break }
|
||||
b = b + 1
|
||||
}
|
||||
local name = after.substring(0,b)
|
||||
local tail = after.substring(b)
|
||||
// If tail starts with ' as ' but name had trailing spaces, we already stopped at space
|
||||
// Wrap name with quotes if not already
|
||||
if name.length() > 0 {
|
||||
local newline = head + "using \"" + name + "\"" + tail
|
||||
if newline != ln { changed = 1; out.push(newline) } else { out.push(ln) }
|
||||
} else { out.push(ln) }
|
||||
} else {
|
||||
out.push(ln)
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
if changed == 0 { return text }
|
||||
// Join with \n
|
||||
local j = 0; local buf = ""; while j < out.size() { buf = buf + out.get(j); if j < out.size()-1 { buf = buf + "\n" } j = j + 1 }
|
||||
return buf
|
||||
}
|
||||
_print_unified_diff(path, before, after) {
|
||||
if before == null || after == null || before == after { return 0 }
|
||||
// Minimal unified diff for whole file replacement(MVP)
|
||||
print("--- " + path)
|
||||
print("+++ " + path)
|
||||
print("@@")
|
||||
// Print lines with +/-; this MVP prints only changed lines as + and original as - when they differ line-wise
|
||||
local bl = me._split_lines(before); local al = me._split_lines(after)
|
||||
local max = bl.size(); if al.size() > max { max = al.size() }
|
||||
local i = 0
|
||||
while i < max {
|
||||
local b = i < bl.size() ? bl.get(i) : null
|
||||
local a = i < al.size() ? al.get(i) : null
|
||||
if b == a {
|
||||
print(" " + (a==null?"":a))
|
||||
} else {
|
||||
if b != null { print("-" + b) }
|
||||
if a != null { print("+" + a) }
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
_needs_ast(only, skip) {
|
||||
// Parse AST when duplicate_method is explicitly requested (needs method spans/definitions precision).
|
||||
if only != null {
|
||||
|
||||
Reference in New Issue
Block a user