use super::convert::source_is_canonical;
use super::lint::{count_redundant_payload, lex_kinds, payload_true_runs, wait_runs_around};
use qql_core::parser::Parser;
fn excise_start(source: &str, s: usize) -> usize {
let bytes = source.as_bytes();
let mut i = s.min(bytes.len());
while i > 0 && (bytes[i - 1] == b' ' || bytes[i - 1] == b'\t') {
i -= 1;
}
i
}
pub(crate) fn apply_fixes(source: &str) -> (String, usize) {
let mut current = source.to_string();
let mut fixes = 0;
for _ in 0..4 {
let (next, n) = apply_fixes_once(¤t);
fixes += n;
if next == current {
break;
}
current = next;
}
(current, fixes)
}
fn apply_fixes_once(source: &str) -> (String, usize) {
let recovered = Parser::parse_all_recovering(source);
let Some(toks) = lex_kinds(source) else {
return (source.to_string(), 0);
};
let mut ranges: Vec<(usize, usize)> = Vec::new();
for (stmt, span) in &recovered.statements {
let expected = count_redundant_payload(stmt);
if expected == 0 {
continue;
}
for (rs, re) in payload_true_runs(source, &toks, span.start, span.end)
.into_iter()
.take(expected)
{
ranges.push((excise_start(source, rs), re));
}
}
for err in &recovered.errors {
if err.code != "QQL-PARSE-DUPLICATE-CLAUSE" {
continue;
}
let err_pos = err.span.map(|s| s.start).unwrap_or(0);
let runs = wait_runs_around(source, &toks, err_pos);
if runs.len() > 1 {
for (rs, re) in runs.iter().take(runs.len() - 1) {
ranges.push((excise_start(source, *rs), *re));
}
}
}
ranges.sort_unstable();
ranges.dedup();
let mut fixes = ranges.len();
let mut out = source.to_string();
for (rs, re) in ranges.into_iter().rev() {
if rs < re && re <= out.len() && out.is_char_boundary(rs) && out.is_char_boundary(re) {
out.replace_range(rs..re, "");
} else {
fixes = fixes.saturating_sub(1);
}
}
if let Ok(formatted) = qql_core::fmt::format(&out) {
if !source_is_canonical(&out, &formatted) {
fixes += 1;
}
(formatted, fixes)
} else {
(out, fixes)
}
}