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>
91 lines
2.8 KiB
Text
91 lines
2.8 KiB
Text
# fmt_lint_json.ludic — `ludic-fmt --lint --json`: the violations --lint would print, as ONE JSON
|
|
# array on stdout for an editor, `[{"file", "line", "col", "rule", "message"}]` ordered by file, then
|
|
# line, then column; "col" only where the rule has one. Everything else --lint says goes to stderr.
|
|
var lt_json: bool = false
|
|
var lj_idx: []int = new []int # the reports --lint would have printed, by index
|
|
var lt_show_from: string = "" # a buffer on stdin is judged as this path (its baseline, its scope)
|
|
var lt_show_as: string = "" # and reported under this name (--stdin-name, or "-")
|
|
|
|
function lt_err(s: string) -> void { file_write(file_stderr(), s, len(s)) }
|
|
function lt_shown(path: string) -> string {
|
|
if len(lt_show_from) > 0 and path == lt_show_from { return lt_show_as }
|
|
return path
|
|
}
|
|
function lt_strcmp(a: string, b: string) -> int {
|
|
var i = 0
|
|
while i < len(a) and i < len(b) {
|
|
if a[i] != b[i] { return a[i] - b[i] }
|
|
i += 1
|
|
}
|
|
return len(a) - len(b)
|
|
}
|
|
# report x before report y?
|
|
function lj_before(x: int, y: int) -> bool {
|
|
let c = lt_strcmp(lr_file[x], lr_file[y])
|
|
if c != 0 { return c < 0 }
|
|
if lr_line[x] != lr_line[y] { return lr_line[x] < lr_line[y] }
|
|
if lr_col[x] != lr_col[y] { return lr_col[x] < lr_col[y] }
|
|
return lt_strcmp(lr_rule[x], lr_rule[y]) < 0
|
|
}
|
|
function lj_sort() -> void {
|
|
var i = 1
|
|
while i < len(lj_idx) {
|
|
let x = lj_idx[i]
|
|
var j = i - 1
|
|
while j >= 0 and lj_before(x, lj_idx[j]) {
|
|
lj_idx[j + 1] = lj_idx[j]
|
|
j -= 1
|
|
}
|
|
lj_idx[j + 1] = x
|
|
i += 1
|
|
}
|
|
}
|
|
function lj_hex(d: int) -> int {
|
|
if d < 10 { return '0' + d }
|
|
return 'a' + d - 10
|
|
}
|
|
# `s` as a JSON string, quotes included
|
|
function lt_jq(b: Buf, s: string) -> void {
|
|
buf_putc(b, '"')
|
|
var i = 0
|
|
while i < len(s) {
|
|
let c = s[i] & 255
|
|
if c == '"' { buf_puts(b, "\\\"") }
|
|
else if c == '\\' { buf_puts(b, "\\\\") }
|
|
else if c == '\n' { buf_puts(b, "\\n") }
|
|
else if c == '\t' { buf_puts(b, "\\t") }
|
|
else if c == '\r' { buf_puts(b, "\\r") }
|
|
else if c < 32 {
|
|
buf_puts(b, "\\u00")
|
|
buf_putc(b, lj_hex(c >> 4))
|
|
buf_putc(b, lj_hex(c & 15))
|
|
}
|
|
else { buf_putc(b, c) }
|
|
i += 1
|
|
}
|
|
buf_putc(b, '"')
|
|
}
|
|
function lt_json_out() -> void {
|
|
lj_sort()
|
|
let b = buf_new()
|
|
buf_putc(b, '[')
|
|
var k = 0
|
|
while k < len(lj_idx) {
|
|
let i = lj_idx[k]
|
|
if k > 0 { buf_putc(b, ',') }
|
|
buf_puts(b, "\n {\"file\": ")
|
|
lt_jq(b, lt_shown(lr_file[i]))
|
|
buf_puts(b, `, "line": {lr_line[i]}`)
|
|
if lr_col[i] > 0 { buf_puts(b, `, "col": {lr_col[i]}`) }
|
|
buf_puts(b, ", \"rule\": ")
|
|
lt_jq(b, lr_rule[i])
|
|
buf_puts(b, ", \"message\": ")
|
|
lt_jq(b, lr_msg[i])
|
|
buf_putc(b, '}')
|
|
k += 1
|
|
}
|
|
if k > 0 { buf_putc(b, '\n') }
|
|
buf_puts(b, "]\n")
|
|
let s = buf_str(b)
|
|
file_write(file_stdout(), s, cstr_len(s))
|
|
}
|