ludic/tools/ludic-tools/fmt_lint_run.ludic
Orkuncakilkaya 047bdf4189 ludic fmt for editors: --lint --json, and a buffer on stdin (R9)
ludic-fmt --lint --json prints the violations --lint reports as one JSON array on stdout,
[{file, line, col, rule, message}] ordered by file, line and column (col where the rule knows it),
the summary on stderr, --lint's exit status, and never rewrites the baseline. ludic-fmt - refuses a
buffer that does not read as Ludic (a string/template/key literal left open, a bracket never closed
or closed by the wrong one) with exit 2 and name:line:col on stderr; - --lint judges a buffer as the
file --stdin-name names (--stdin-rel: that path relative to the project), against its baseline and
lint paths. ludic fmt --lint and ludic fmt - run it from the nearest package.ludic upwards (from
--stdin-name's directory when given). Hooks read nothing and write to stderr under --json or -.
Regression cases added to test-tools and ludic-dev test (fmt_editor_cases), not run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 00:24:43 +03:00

126 lines
3.6 KiB
Text

# fmt_lint_run.ludic — L10: walking a project, and the baseline that lets a rule arrive in a
# codebase that breaks it today without letting it get worse
var bl_file: []string = new []string
var bl_rule: []string = new []string
var bl_count: []int = new []int
var lt_partial: bool = false # --check of some files: the baseline is read, never written
function lint_walk(dir: string, out: []string) -> void {
let names = Fs.list(dir)
if names == null { return }
var i = 0
while i < len(names) {
let p = `{dir}/{names[i]}`
if Fs.is_dir(p) { lint_walk(p, out) }
else { if ends_with(p, ".ludic") { push(out, p) } }
i += 1
}
}
function lt_baseline_read() -> void {
if len(lt_baseline) == 0 { return }
let t = read_file(lt_baseline)
if t == null { return }
let ls = lt_lines(string(t))
var i = 0
while i < len(ls) {
let w = lt_words(ls[i])
if len(w) == 3 {
push(bl_count, lt_atoi(w[0]))
push(bl_rule, w[1])
push(bl_file, w[2])
}
i += 1
}
}
function lt_allowed(path: string, rule: string) -> int {
var i = 0
while i < len(bl_file) {
if bl_file[i] == path and bl_rule[i] == rule { return bl_count[i] }
i += 1
}
return 0
}
# how many violations of `rule` the file has now
function lt_count(path: string, rule: string) -> int {
var n = 0
var i = 0
while i < len(lr_file) {
if lr_file[i] == path and lr_rule[i] == rule { n += 1 }
i += 1
}
return n
}
function lt_seen(fs: []string, rs: []string, path: string, rule: string) -> bool {
var i = 0
while i < len(fs) {
if fs[i] == path and rs[i] == rule { return true }
i += 1
}
return false
}
# true when every file is within its allowance; prints what is not
function lint_judge(init: bool) -> bool {
lt_baseline_read()
let pf = new []string
let pr = new []string
var i = 0
while i < len(lr_file) {
if not lt_seen(pf, pr, lr_file[i], lr_rule[i]) {
push(pf, lr_file[i])
push(pr, lr_rule[i])
}
i += 1
}
var ok = true
var lowered = false
var total = 0
let out = buf_new()
buf_puts(out, "# lint baseline: the violations each file had when a rule came in (ludic-fmt --lint); only ever lowered\n")
var p = 0
while p < len(pf) {
let now = lt_count(pf[p], pr[p])
let allow = lt_allowed(pf[p], pr[p])
total += now
if not init and now > allow {
ok = false
lt_print_pair(pf[p], pr[p], now, allow)
}
var keep = now
if not init and now > allow { keep = allow }
if not init and now < allow { lowered = true }
if keep > 0 { buf_puts(out, `{keep} {pr[p]} {pf[p]}\n`) }
p += 1
}
var b = 0
while b < len(bl_file) {
if lt_count(bl_file[b], bl_rule[b]) == 0 and bl_count[b] > 0 and not init { lowered = true }
b += 1
}
if len(lt_baseline) > 0 and (init or lowered) and not lt_partial {
let f = file_open(lt_baseline, "wb")
if f != null {
let s = buf_str(out)
file_write(f, s, cstr_len(s))
file_close(f)
}
}
let summary = ` lint: {total} violation(s) in the baseline, {len(pf)} file/rule pair(s)`
if lt_json {
lt_err(`{summary}\n`)
lt_json_out()
} else { print(summary) }
return ok
}
function lt_print_pair(path: string, rule: string, now: int, allow: int) -> void {
var i = 0
while i < len(lr_file) {
if lr_file[i] == path and lr_rule[i] == rule {
if lt_json { push(lj_idx, i) } else { print(`{lt_shown(path)}:{lr_line[i]}: {rule}: {lr_msg[i]}`) }
}
i += 1
}
if allow > 0 {
let note = ` ({lt_shown(path)} may have {allow} of {rule}, and has {now})`
if lt_json { lt_err(`{note}\n`) } else { print(note) }
}
}