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
|
local debug = 0
|
||||||
// Default: AST path OFF (enable with --force-ast)
|
// Default: AST path OFF (enable with --force-ast)
|
||||||
local no_ast = 1
|
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
|
// single-pass parse: handle options in-place and collect sources
|
||||||
local i = 0
|
local i = 0
|
||||||
local fail = 0
|
local fail = 0
|
||||||
@ -47,6 +50,11 @@ static box HakoAnalyzerBox {
|
|||||||
if i + 1 >= args.size() { print("[lint/error] --format requires value"); return 2 }
|
if i + 1 >= args.size() { print("[lint/error] --format requires value"); return 2 }
|
||||||
fmt = args.get(i+1); i = i + 2; continue
|
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 p == "--rules" {
|
||||||
if i + 1 >= args.size() { print("[lint/error] --rules requires CSV"); return 2 }
|
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
|
rules_only = me._parse_csv(args.get(i+1)); i = i + 2; continue
|
||||||
@ -68,6 +76,17 @@ static box HakoAnalyzerBox {
|
|||||||
local text_raw = text
|
local text_raw = text
|
||||||
// pre-sanitize (ASCII quotes, normalize newlines) - minimal & reversible
|
// pre-sanitize (ASCII quotes, normalize newlines) - minimal & reversible
|
||||||
text = me._sanitize(text)
|
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
|
// analysis - only build IR if needed by active rules
|
||||||
local ir = null
|
local ir = null
|
||||||
if me._needs_ir(rules_only, rules_skip) == 1 {
|
if me._needs_ir(rules_only, rules_skip) == 1 {
|
||||||
@ -230,6 +249,74 @@ static box HakoAnalyzerBox {
|
|||||||
print("]}")
|
print("]}")
|
||||||
return 0
|
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) {
|
_needs_ast(only, skip) {
|
||||||
// Parse AST when duplicate_method is explicitly requested (needs method spans/definitions precision).
|
// Parse AST when duplicate_method is explicitly requested (needs method spans/definitions precision).
|
||||||
if only != null {
|
if only != null {
|
||||||
|
|||||||
Reference in New Issue
Block a user