fix(linter/eslint/sort-vars): report every out-of-order declaration (#27767)

ESLint reports both `a` and `b` for `var c, a, b;`, while Oxlint only reported `a`. Keep the largest name seen so far to match ESLint's diagnostic count, and build a fix once per safe declaration run.

Addresses #27746, report 20 (`eslint/sort-vars`).
This commit is contained in:
camc314 committed 2026-10-17 11:56:23 +00:00
1 parent 01e1c5e37b
commit 9c35461498
2 files changed
+82 -39

No files matched your search

+33 -39
View File
@@ -79,51 +79,38 @@ impl Rule for SortVars {
return;
}
let mut previous: Option<&BindingIdentifier> = None;
for current in &var_decl.declarations {
let mut maximum: Option<&BindingIdentifier> = None;
let mut checked_until = 0;
for (current_index, current) in var_decl.declarations.iter().enumerate() {
let Some(identifier) = current.id.get_binding_identifier() else {
continue;
};
if let Some(previous) = previous {
self.check_declaration_sort_with_fix(
previous,
identifier,
current,
&var_decl.declarations,
ctx,
);
if let Some(maximum) = maximum
&& self.get_sortable_name(maximum).cmp(&self.get_sortable_name(identifier))
== Ordering::Greater
{
let diagnostic = sort_vars_diagnostic(current.span);
if current.span.start >= checked_until
&& let Some((span, replacement)) = self.build_declarations_fix(
current_index,
&var_decl.declarations,
ctx,
&mut checked_until,
)
{
ctx.diagnostic_with_fix(diagnostic, |fixer| fixer.replace(span, replacement));
} else {
ctx.diagnostic(diagnostic);
}
continue;
}
previous = Some(identifier);
maximum = Some(identifier);
}
}
}
impl SortVars {
fn check_declaration_sort_with_fix<'a>(
&self,
previous: &BindingIdentifier<'a>,
current: &BindingIdentifier<'a>,
declaration: &VariableDeclarator<'a>,
declarations: &'a [VariableDeclarator<'a>],
ctx: &LintContext<'a>,
) {
if self.get_sortable_name(previous).cmp(&self.get_sortable_name(current))
!= Ordering::Greater
{
return;
}
let diagnostic = sort_vars_diagnostic(declaration.span);
if let Some((span, replacement)) =
self.build_declarations_fix(declaration, declarations, ctx)
{
ctx.diagnostic_with_fix(diagnostic, |fixer| fixer.replace(span, replacement));
} else {
ctx.diagnostic(diagnostic);
}
}
fn get_sortable_name<'a>(&self, ident: &BindingIdentifier<'a>) -> Cow<'a, str> {
if self.ignore_case {
return ident.name.as_str().cow_to_ascii_lowercase();
@@ -134,18 +121,17 @@ impl SortVars {
fn build_declarations_fix<'a>(
&self,
current: &VariableDeclarator<'a>,
current_index: usize,
declarations: &'a [VariableDeclarator<'a>],
ctx: &LintContext<'a>,
checked_until: &mut u32,
) -> Option<(Span, String)> {
// Only reorder a contiguous safe run:
// `var {} = obj, b, a` can fix `b, a`, but must not move the destructuring.
if !is_fixable_declarator(current) {
if !is_fixable_declarator(&declarations[current_index]) {
return None;
}
let current_index = declarations.iter().position(|decl| std::ptr::eq(decl, current))?;
let mut start_index = current_index;
while start_index > 0 && is_fixable_declarator(&declarations[start_index - 1]) {
start_index -= 1;
@@ -155,6 +141,7 @@ impl SortVars {
while declarations.get(end_index + 1).is_some_and(is_fixable_declarator) {
end_index += 1;
}
*checked_until = declarations[end_index].span.end;
if start_index == end_index {
return None;
@@ -303,6 +290,9 @@ fn test() {
("var b = f(), c, d, a;", None),
("var b = `${f()}`, c, d, a;", None), // { "ecmaVersion": 6 },
("var c, a = b = 0", None),
("var c, a, b;", None),
("var C, a, b;", Some(serde_json::json!([{ "ignoreCase": true }]))),
("var c, a, e, d;", None),
];
let fix = vec![
@@ -331,6 +321,10 @@ fn test() {
("var {} = 1, b, a", "var {} = 1, a, b", Some(serde_json::json!([{ "ignoreCase": true }]))),
("var b = f(), c, d, a;", "var b = f(), a, c, d;", None),
("var b, /* comment */ a", "var b, /* comment */ a", None),
("var c, a, b;", "var a, b, c;", None),
("var C, a, b;", "var a, b, C;", Some(serde_json::json!([{ "ignoreCase": true }]))),
("var f, d, e, {} = obj, c, a, b;", "var d, e, f, {} = obj, a, b, c;", None),
("var c, /* comment */ a, b;", "var c, /* comment */ a, b;", None),
];
Tester::new(SortVars::NAME, SortVars::PLUGIN, pass, fail).expect_fix(fix).test_and_snapshot();
@@ -59,6 +59,13 @@ source: crates/oxc_linter/src/tester.rs
╰────
help: Sort variable declarations in ascending order (case-sensitive by default).
⚠ eslint(sort-vars): Variable declarations should be sorted
╭─[sort_vars.tsx:1:14]
1 │ var c, d, a, b
· ─
╰────
help: Sort variable declarations in ascending order (case-sensitive by default).
⚠ eslint(sort-vars): Variable declarations should be sorted
╭─[sort_vars.tsx:1:8]
1 │ var a, A;
@@ -177,3 +184,45 @@ source: crates/oxc_linter/src/tester.rs
· ─────────
╰────
help: Sort variable declarations in ascending order (case-sensitive by default).
⚠ eslint(sort-vars): Variable declarations should be sorted
╭─[sort_vars.tsx:1:8]
1 │ var c, a, b;
· ─
╰────
help: Sort variable declarations in ascending order (case-sensitive by default).
⚠ eslint(sort-vars): Variable declarations should be sorted
╭─[sort_vars.tsx:1:11]
1 │ var c, a, b;
· ─
╰────
help: Sort variable declarations in ascending order (case-sensitive by default).
⚠ eslint(sort-vars): Variable declarations should be sorted
╭─[sort_vars.tsx:1:8]
1 │ var C, a, b;
· ─
╰────
help: Sort variable declarations in ascending order (case-sensitive by default).
⚠ eslint(sort-vars): Variable declarations should be sorted
╭─[sort_vars.tsx:1:11]
1 │ var C, a, b;
· ─
╰────
help: Sort variable declarations in ascending order (case-sensitive by default).
⚠ eslint(sort-vars): Variable declarations should be sorted
╭─[sort_vars.tsx:1:8]
1 │ var c, a, e, d;
· ─
╰────
help: Sort variable declarations in ascending order (case-sensitive by default).
⚠ eslint(sort-vars): Variable declarations should be sorted
╭─[sort_vars.tsx:1:14]
1 │ var c, a, e, d;
· ─
╰────
help: Sort variable declarations in ascending order (case-sensitive by default).