ludic/tools/ludic-tools/fmt_lint.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

114 lines
3.8 KiB
Text

# fmt_lint.ludic — L10: the style rules, checked by the formatter. A project states them in its
# package.ludic, and `ludic-fmt --check` (the files given) or `ludic-fmt --lint` (the project's
# paths) fails a file that breaks one:
# lint one_statement two statements on one line (`a(); b()`)
# lint max_file_lines 100 a file's length
# lint max_function_lines 50 a function's, from `function` to its closing brace
# lint max_comment_lines 2 a block of comment lines - a comment says why, in a line or two
# lint max_header_lines 6 the block that opens a file, which says what the file is
# lint paths "src" "lab" what --lint walks
# lint baseline "tests/lint-baseline.txt"
# The baseline is a ratchet: `<count> <rule> <file>` lines, the violations a file had when a rule
# came in. A file may have that many and no more; when it has fewer the baseline is lowered, and
# `--init-baseline` writes it from what is there now.
var lt_one: bool = false
var lt_max_file: int = 0
var lt_max_fn: int = 0
var lt_max_comment: int = 0
var lt_max_header: int = 0
var lt_baseline: string = ""
var lt_paths: []string = new []string
var lr_file: []string = new []string
var lr_rule: []string = new []string
var lr_line: []int = new []int
var lr_msg: []string = new []string
var lr_col: []int = new []int # 1-based byte column, 0 when the rule has none (a file's length)
function lt_atoi(s: string) -> int {
var n = 0
var i = 0
while i < len(s) {
let c = s[i]
if c >= '0' and c <= '9' { n = n * 10 + (c - '0') }
i += 1
}
return n
}
function lt_unquote(s: string) -> string {
let n = len(s)
if n >= 2 and s[0] == '"' and s[n - 1] == '"' { return s[1 .. n - 1] }
return s
}
# the words of a line, a "quoted" one kept whole, a # ending it
function lt_words(line: string) -> []string {
let out = new []string
var i = 0
let n = len(line)
while i < n {
while i < n and (line[i] == ' ' or line[i] == 9) { i += 1 }
if i >= n or line[i] == '#' { break }
var j = i
if line[i] == '"' {
j = i + 1
while j < n and line[j] != '"' { j += 1 }
if j < n { j += 1 }
} else {
while j < n and line[j] != ' ' and line[j] != 9 { j += 1 }
}
push(out, lt_unquote(line[i .. j]))
i = j
}
return out
}
function lt_lines(text: string) -> []string {
let out = new []string
var a = 0
var i = 0
let n = len(text)
while i < n {
if text[i] == '\n' {
push(out, text[a .. i])
a = i + 1
}
i += 1
}
if a < n { push(out, text[a .. n]) }
return out
}
# the project's rules, from package.ludic in the directory ludic-fmt runs in
function lint_config() -> bool {
let man = read_file("package.ludic")
if man == null { return false }
var any = false
let ls = lt_lines(string(man))
var i = 0
while i < len(ls) {
let w = lt_words(ls[i])
if len(w) >= 2 and w[0] == "lint" {
any = true
if w[1] == "one_statement" { lt_one = true }
if w[1] == "max_file_lines" and len(w) > 2 { lt_max_file = lt_atoi(w[2]) }
if w[1] == "max_function_lines" and len(w) > 2 { lt_max_fn = lt_atoi(w[2]) }
if w[1] == "max_comment_lines" and len(w) > 2 { lt_max_comment = lt_atoi(w[2]) }
if w[1] == "max_header_lines" and len(w) > 2 { lt_max_header = lt_atoi(w[2]) }
if w[1] == "baseline" and len(w) > 2 { lt_baseline = w[2] }
if w[1] == "paths" {
var k = 2
while k < len(w) {
push(lt_paths, w[k])
k += 1
}
}
}
i += 1
}
return any
}
function lt_report(path: string, rule: string, line: int, msg: string) -> void { lt_report_at(path, rule, line, 0, msg) }
function lt_report_at(path: string, rule: string, line: int, col: int, msg: string) -> void {
push(lr_file, path)
push(lr_rule, rule)
push(lr_line, line)
push(lr_col, col)
push(lr_msg, msg)
}