From 9841b35827b0c4197213ae822b6806f2e4701705 Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Sun, 11 Aug 2024 18:00:21 +0200 Subject: [PATCH 01/17] chore: added scaffolding for eslint/sort-keys --- .gitignore | 2 + crates/oxc_linter/src/rules.rs | 2 + .../oxc_linter/src/rules/eslint/sort_keys.rs | 818 ++++++++++++++++++ 3 files changed, 822 insertions(+) create mode 100644 crates/oxc_linter/src/rules/eslint/sort_keys.rs diff --git a/.gitignore b/.gitignore index c2d89efbc6cca..d1c834c23022e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ tasks/coverage/babel/ tasks/coverage/test262/ tasks/coverage/typescript/ tasks/prettier_conformance/prettier/ + +.idea/ \ No newline at end of file diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 344112dce1395..adf82a0b5ad99 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -126,6 +126,7 @@ mod eslint { pub mod unicode_bom; pub mod use_isnan; pub mod valid_typeof; + pub mod sort_keys; } mod typescript { @@ -549,6 +550,7 @@ oxc_macros::declare_all_lint_rules! { eslint::require_yield, eslint::symbol_description, eslint::sort_imports, + eslint::sort_keys, eslint::unicode_bom, eslint::use_isnan, eslint::valid_typeof, diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs new file mode 100644 index 0000000000000..391a9cb6f6cf3 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -0,0 +1,818 @@ +use oxc_ast::{ast::VariableDeclarationKind, AstKind}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{ + context::LintContext, + fixer::{RuleFix, RuleFixer}, + rule::Rule, + AstNode, +}; + +#[derive(Debug, Default, Clone)] +pub struct SortKeys; + +declare_oxc_lint!( + /// ### What it does + /// + /// + /// ### Why is this bad? + /// + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// ``` + SortKeys, + nursery, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` + // See for details + + pending // TODO: describe fix capabilities. Remove if no fix can be done, + // keep at 'pending' if you think one could be added but don't know how. + // Options are 'fix', 'fix_dangerous', 'suggestion', and 'conditional_fix_suggestion' +); + +impl Rule for SortKeys { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::VariableDeclaration(dec) = node.kind() { + if dec.kind == VariableDeclarationKind::Var { + ctx.diagnostic(no_var_diagnostic(Span::new(dec.span.start, dec.span.start + 3))); + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("var obj = {'':1, [``]:2}", Some(serde_json::json!([]))), // { "ecmaVersion": 6 }, + ("var obj = {[``]:1, '':2}", Some(serde_json::json!([]))), // { "ecmaVersion": 6 }, + ("var obj = {'':1, a:2}", Some(serde_json::json!([]))), + ("var obj = {[``]:1, a:2}", Some(serde_json::json!([]))), // { "ecmaVersion": 6 }, + ("var obj = {_:2, a:1, b:3} // default", Some(serde_json::json!([]))), + ("var obj = {a:1, b:3, c:2}", Some(serde_json::json!([]))), + ("var obj = {a:2, b:3, b_:1}", Some(serde_json::json!([]))), + ("var obj = {C:3, b_:1, c:2}", Some(serde_json::json!([]))), + ("var obj = {$:1, A:3, _:2, a:4}", Some(serde_json::json!([]))), + ("var obj = {1:1, '11':2, 2:4, A:3}", Some(serde_json::json!([]))), + ("var obj = {'#':1, 'Z':2, À:3, è:4}", Some(serde_json::json!([]))), + ("var obj = { [/(?0)/]: 1, '/(?0)/': 2 }", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {a:1, b:3, [a + b]: -1, c:2}", Some(serde_json::json!([]))), // { "ecmaVersion": 6 }, + ("var obj = {'':1, [f()]:2, a:3}", Some(serde_json::json!([]))), // { "ecmaVersion": 6 }, + ("var obj = {a:1, [b++]:2, '':3}", Some(serde_json::json!(["desc"]))), // { "ecmaVersion": 6 }, + ("var obj = {a:1, ...z, b:1}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {b:1, ...z, a:1}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {...a, b:1, ...c, d:1}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {...a, b:1, ...d, ...c, e:2, z:5}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {b:1, ...c, ...d, e:2}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {a:1, ...z, '':2}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {'':1, ...z, 'a':2}", Some(serde_json::json!(["desc"]))), // { "ecmaVersion": 2018 }, + ("var obj = {...z, a:1, b:1}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {...z, ...c, a:1, b:1}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {a:1, b:1, ...z}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {...z, ...x, a:1, ...c, ...d, f:5, e:4}", Some(serde_json::json!(["desc"]))), // { "ecmaVersion": 2018 }, + ("function fn(...args) { return [...args].length; }", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ( + "function g() {}; function f(...args) { return g(...args); }", + Some(serde_json::json!([])), + ), // { "ecmaVersion": 2018 }, + ("let {a, b} = {}", Some(serde_json::json!([]))), // { "ecmaVersion": 6 }, + ("var obj = {a:1, b:{x:1, y:1}, c:1}", Some(serde_json::json!([]))), + ("var obj = {_:2, a:1, b:3} // asc", Some(serde_json::json!(["asc"]))), + ("var obj = {a:1, b:3, c:2}", Some(serde_json::json!(["asc"]))), + ("var obj = {a:2, b:3, b_:1}", Some(serde_json::json!(["asc"]))), + ("var obj = {C:3, b_:1, c:2}", Some(serde_json::json!(["asc"]))), + ("var obj = {$:1, A:3, _:2, a:4}", Some(serde_json::json!(["asc"]))), + ("var obj = {1:1, '11':2, 2:4, A:3}", Some(serde_json::json!(["asc"]))), + ("var obj = {'#':1, 'Z':2, À:3, è:4}", Some(serde_json::json!(["asc"]))), + ("var obj = {a:1, c:2, b:3}", Some(serde_json::json!(["asc", { "minKeys": 4 }]))), + ( + "var obj = {_:2, a:1, b:3} // asc, insensitive", + Some(serde_json::json!(["asc", { "caseSensitive": false }])), + ), + ("var obj = {a:1, b:3, c:2}", Some(serde_json::json!(["asc", { "caseSensitive": false }]))), + ( + "var obj = {a:2, b:3, b_:1}", + Some(serde_json::json!(["asc", { "caseSensitive": false }])), + ), + ( + "var obj = {b_:1, C:3, c:2}", + Some(serde_json::json!(["asc", { "caseSensitive": false }])), + ), + ( + "var obj = {b_:1, c:3, C:2}", + Some(serde_json::json!(["asc", { "caseSensitive": false }])), + ), + ( + "var obj = {$:1, _:2, A:3, a:4}", + Some(serde_json::json!(["asc", { "caseSensitive": false }])), + ), + ( + "var obj = {1:1, '11':2, 2:4, A:3}", + Some(serde_json::json!(["asc", { "caseSensitive": false }])), + ), + ( + "var obj = {'#':1, 'Z':2, À:3, è:4}", + Some(serde_json::json!(["asc", { "caseSensitive": false }])), + ), + ( + "var obj = {$:1, A:3, _:2, a:4}", + Some(serde_json::json!(["asc", { "caseSensitive": false, "minKeys": 5 }])), + ), + ( + "var obj = {_:2, a:1, b:3} // asc, natural", + Some(serde_json::json!(["asc", { "natural": true }])), + ), + ("var obj = {a:1, b:3, c:2}", Some(serde_json::json!(["asc", { "natural": true }]))), + ("var obj = {a:2, b:3, b_:1}", Some(serde_json::json!(["asc", { "natural": true }]))), + ("var obj = {C:3, b_:1, c:2}", Some(serde_json::json!(["asc", { "natural": true }]))), + ("var obj = {$:1, _:2, A:3, a:4}", Some(serde_json::json!(["asc", { "natural": true }]))), + ( + "var obj = {1:1, 2:4, '11':2, A:3}", + Some(serde_json::json!(["asc", { "natural": true }])), + ), + ( + "var obj = {'#':1, 'Z':2, À:3, è:4}", + Some(serde_json::json!(["asc", { "natural": true }])), + ), + ( + "var obj = {b_:1, a:2, b:3}", + Some(serde_json::json!(["asc", { "natural": true, "minKeys": 4 }])), + ), + ( + "var obj = {_:2, a:1, b:3} // asc, natural, insensitive", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {a:1, b:3, c:2}", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {a:2, b:3, b_:1}", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {b_:1, C:3, c:2}", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {b_:1, c:3, C:2}", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {$:1, _:2, A:3, a:4}", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {1:1, 2:4, '11':2, A:3}", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {'#':1, 'Z':2, À:3, è:4}", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {a:1, _:2, b:3}", + Some( + serde_json::json!(["asc", { "natural": true, "caseSensitive": false, "minKeys": 4 }]), + ), + ), + ("var obj = {b:3, a:1, _:2} // desc", Some(serde_json::json!(["desc"]))), + ("var obj = {c:2, b:3, a:1}", Some(serde_json::json!(["desc"]))), + ("var obj = {b_:1, b:3, a:2}", Some(serde_json::json!(["desc"]))), + ("var obj = {c:2, b_:1, C:3}", Some(serde_json::json!(["desc"]))), + ("var obj = {a:4, _:2, A:3, $:1}", Some(serde_json::json!(["desc"]))), + ("var obj = {A:3, 2:4, '11':2, 1:1}", Some(serde_json::json!(["desc"]))), + ("var obj = {è:4, À:3, 'Z':2, '#':1}", Some(serde_json::json!(["desc"]))), + ("var obj = {a:1, c:2, b:3}", Some(serde_json::json!(["desc", { "minKeys": 4 }]))), + ( + "var obj = {b:3, a:1, _:2} // desc, insensitive", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {c:2, b:3, a:1}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {b_:1, b:3, a:2}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {c:2, C:3, b_:1}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {C:2, c:3, b_:1}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {a:4, A:3, _:2, $:1}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {A:3, 2:4, '11':2, 1:1}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {è:4, À:3, 'Z':2, '#':1}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {$:1, _:2, A:3, a:4}", + Some(serde_json::json!(["desc", { "caseSensitive": false, "minKeys": 5 }])), + ), + ( + "var obj = {b:3, a:1, _:2} // desc, natural", + Some(serde_json::json!(["desc", { "natural": true }])), + ), + ("var obj = {c:2, b:3, a:1}", Some(serde_json::json!(["desc", { "natural": true }]))), + ("var obj = {b_:1, b:3, a:2}", Some(serde_json::json!(["desc", { "natural": true }]))), + ("var obj = {c:2, b_:1, C:3}", Some(serde_json::json!(["desc", { "natural": true }]))), + ("var obj = {a:4, A:3, _:2, $:1}", Some(serde_json::json!(["desc", { "natural": true }]))), + ( + "var obj = {A:3, '11':2, 2:4, 1:1}", + Some(serde_json::json!(["desc", { "natural": true }])), + ), + ( + "var obj = {è:4, À:3, 'Z':2, '#':1}", + Some(serde_json::json!(["desc", { "natural": true }])), + ), + ( + "var obj = {b_:1, a:2, b:3}", + Some(serde_json::json!(["desc", { "natural": true, "minKeys": 4 }])), + ), + ( + "var obj = {b:3, a:1, _:2} // desc, natural, insensitive", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {c:2, b:3, a:1}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {b_:1, b:3, a:2}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {c:2, C:3, b_:1}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {C:2, c:3, b_:1}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {a:4, A:3, _:2, $:1}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {A:3, '11':2, 2:4, 1:1}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {è:4, À:3, 'Z':2, '#':1}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {a:1, _:2, b:3}", + Some( + serde_json::json!(["desc", { "natural": true, "caseSensitive": false, "minKeys": 4 }]), + ), + ), + ( + " + var obj = { + e: 1, + f: 2, + g: 3, + + a: 4, + b: 5, + c: 6 + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), + ( + " + var obj = { + b: 1, + + // comment + a: 2, + c: 3 + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), + ( + " + var obj = { + b: 1 + + , + + // comment + a: 2, + c: 3 + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), + ( + " + var obj = { + c: 1, + d: 2, + + b() { + }, + e: 4 + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + c: 1, + d: 2, + // comment + + // comment + b() { + }, + e: 4 + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + b, + + [a+b]: 1, + a + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + c: 1, + d: 2, + + a() { + + }, + + // abce + f: 3, + + /* + + */ + [a+b]: 1, + cc: 1, + e: 2 + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + r#" + var obj = { + b: "/*", + + a: "*/", + } + "#, + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), + ( + " + var obj = { + b, + /* + */ // + + a + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + b, + + /* + */ // + a + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + b: 1 + + ,a: 2 + }; + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + b: 1 + // comment before comma + + , + a: 2 + }; + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + b, + + a, + ...z, + c + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 2018 }, + ( + " + var obj = { + b, + + [foo()]: [ + + ], + a + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 2018 } + ]; + + let fail = vec![ + ("var obj = {a:1, '':2} // default", None), + ("var obj = {a:1, [``]:2} // default", None), // { "ecmaVersion": 6 }, + ("var obj = {a:1, _:2, b:3} // default", None), + ("var obj = {a:1, c:2, b:3}", None), + ("var obj = {b_:1, a:2, b:3}", None), + ("var obj = {b_:1, c:2, C:3}", None), + ("var obj = {$:1, _:2, A:3, a:4}", None), + ("var obj = {1:1, 2:4, A:3, '11':2}", None), + ("var obj = {'#':1, À:3, 'Z':2, è:4}", None), + ("var obj = { null: 1, [/(?0)/]: 2 }", None), // { "ecmaVersion": 2018 }, + ("var obj = {...z, c:1, b:1}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {...z, ...c, d:4, b:1, ...y, ...f, e:2, a:1}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {c:1, b:1, ...a}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {...z, ...a, c:1, b:1}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {...z, b:1, a:1, ...d, ...c}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, + ("var obj = {...z, a:2, b:0, ...x, ...c}", Some(serde_json::json!(["desc"]))), // { "ecmaVersion": 2018 }, + ("var obj = {...z, a:2, b:0, ...x}", Some(serde_json::json!(["desc"]))), // { "ecmaVersion": 2018 }, + ("var obj = {...z, '':1, a:2}", Some(serde_json::json!(["desc"]))), // { "ecmaVersion": 2018 }, + ("var obj = {a:1, [b+c]:2, '':3}", None), // { "ecmaVersion": 6 }, + ("var obj = {'':1, [b+c]:2, a:3}", Some(serde_json::json!(["desc"]))), // { "ecmaVersion": 6 }, + ("var obj = {b:1, [f()]:2, '':3, a:4}", Some(serde_json::json!(["desc"]))), // { "ecmaVersion": 6 }, + ("var obj = {a:1, b:3, [a]: -1, c:2}", None), // { "ecmaVersion": 6 }, + ("var obj = {a:1, c:{y:1, x:1}, b:1}", None), + ("var obj = {a:1, _:2, b:3} // asc", Some(serde_json::json!(["asc"]))), + ("var obj = {a:1, c:2, b:3}", Some(serde_json::json!(["asc"]))), + ("var obj = {b_:1, a:2, b:3}", Some(serde_json::json!(["asc"]))), + ("var obj = {b_:1, c:2, C:3}", Some(serde_json::json!(["asc"]))), + ("var obj = {$:1, _:2, A:3, a:4}", Some(serde_json::json!(["asc"]))), + ("var obj = {1:1, 2:4, A:3, '11':2}", Some(serde_json::json!(["asc"]))), + ("var obj = {'#':1, À:3, 'Z':2, è:4}", Some(serde_json::json!(["asc"]))), + ("var obj = {a:1, _:2, b:3}", Some(serde_json::json!(["asc", { "minKeys": 3 }]))), + ( + "var obj = {a:1, _:2, b:3} // asc, insensitive", + Some(serde_json::json!(["asc", { "caseSensitive": false }])), + ), + ("var obj = {a:1, c:2, b:3}", Some(serde_json::json!(["asc", { "caseSensitive": false }]))), + ( + "var obj = {b_:1, a:2, b:3}", + Some(serde_json::json!(["asc", { "caseSensitive": false }])), + ), + ( + "var obj = {$:1, A:3, _:2, a:4}", + Some(serde_json::json!(["asc", { "caseSensitive": false }])), + ), + ( + "var obj = {1:1, 2:4, A:3, '11':2}", + Some(serde_json::json!(["asc", { "caseSensitive": false }])), + ), + ( + "var obj = {'#':1, À:3, 'Z':2, è:4}", + Some(serde_json::json!(["asc", { "caseSensitive": false }])), + ), + ( + "var obj = {a:1, _:2, b:3}", + Some(serde_json::json!(["asc", { "caseSensitive": false, "minKeys": 3 }])), + ), + ( + "var obj = {a:1, _:2, b:3} // asc, natural", + Some(serde_json::json!(["asc", { "natural": true }])), + ), + ("var obj = {a:1, c:2, b:3}", Some(serde_json::json!(["asc", { "natural": true }]))), + ("var obj = {b_:1, a:2, b:3}", Some(serde_json::json!(["asc", { "natural": true }]))), + ("var obj = {b_:1, c:2, C:3}", Some(serde_json::json!(["asc", { "natural": true }]))), + ("var obj = {$:1, A:3, _:2, a:4}", Some(serde_json::json!(["asc", { "natural": true }]))), + ( + "var obj = {1:1, 2:4, A:3, '11':2}", + Some(serde_json::json!(["asc", { "natural": true }])), + ), + ( + "var obj = {'#':1, À:3, 'Z':2, è:4}", + Some(serde_json::json!(["asc", { "natural": true }])), + ), + ( + "var obj = {a:1, _:2, b:3}", + Some(serde_json::json!(["asc", { "natural": true, "minKeys": 2 }])), + ), + ( + "var obj = {a:1, _:2, b:3} // asc, natural, insensitive", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {a:1, c:2, b:3}", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {b_:1, a:2, b:3}", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {$:1, A:3, _:2, a:4}", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {1:1, '11':2, 2:4, A:3}", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {'#':1, À:3, 'Z':2, è:4}", + Some(serde_json::json!(["asc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {a:1, _:2, b:3}", + Some( + serde_json::json!(["asc", { "natural": true, "caseSensitive": false, "minKeys": 3 }]), + ), + ), + ("var obj = {'':1, a:'2'} // desc", Some(serde_json::json!(["desc"]))), + ("var obj = {[``]:1, a:'2'} // desc", Some(serde_json::json!(["desc"]))), // { "ecmaVersion": 6 }, + ("var obj = {a:1, _:2, b:3} // desc", Some(serde_json::json!(["desc"]))), + ("var obj = {a:1, c:2, b:3}", Some(serde_json::json!(["desc"]))), + ("var obj = {b_:1, a:2, b:3}", Some(serde_json::json!(["desc"]))), + ("var obj = {b_:1, c:2, C:3}", Some(serde_json::json!(["desc"]))), + ("var obj = {$:1, _:2, A:3, a:4}", Some(serde_json::json!(["desc"]))), + ("var obj = {1:1, 2:4, A:3, '11':2}", Some(serde_json::json!(["desc"]))), + ("var obj = {'#':1, À:3, 'Z':2, è:4}", Some(serde_json::json!(["desc"]))), + ("var obj = {a:1, _:2, b:3}", Some(serde_json::json!(["desc", { "minKeys": 3 }]))), + ( + "var obj = {a:1, _:2, b:3} // desc, insensitive", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {a:1, c:2, b:3}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {b_:1, a:2, b:3}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {b_:1, c:2, C:3}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {$:1, _:2, A:3, a:4}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {1:1, 2:4, A:3, '11':2}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {'#':1, À:3, 'Z':2, è:4}", + Some(serde_json::json!(["desc", { "caseSensitive": false }])), + ), + ( + "var obj = {a:1, _:2, b:3}", + Some(serde_json::json!(["desc", { "caseSensitive": false, "minKeys": 2 }])), + ), + ( + "var obj = {a:1, _:2, b:3} // desc, natural", + Some(serde_json::json!(["desc", { "natural": true }])), + ), + ("var obj = {a:1, c:2, b:3}", Some(serde_json::json!(["desc", { "natural": true }]))), + ("var obj = {b_:1, a:2, b:3}", Some(serde_json::json!(["desc", { "natural": true }]))), + ("var obj = {b_:1, c:2, C:3}", Some(serde_json::json!(["desc", { "natural": true }]))), + ("var obj = {$:1, _:2, A:3, a:4}", Some(serde_json::json!(["desc", { "natural": true }]))), + ( + "var obj = {1:1, 2:4, A:3, '11':2}", + Some(serde_json::json!(["desc", { "natural": true }])), + ), + ( + "var obj = {'#':1, À:3, 'Z':2, è:4}", + Some(serde_json::json!(["desc", { "natural": true }])), + ), + ( + "var obj = {a:1, _:2, b:3}", + Some(serde_json::json!(["desc", { "natural": true, "minKeys": 3 }])), + ), + ( + "var obj = {a:1, _:2, b:3} // desc, natural, insensitive", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {a:1, c:2, b:3}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {b_:1, a:2, b:3}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {b_:1, c:2, C:3}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {$:1, _:2, A:3, a:4}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {1:1, 2:4, '11':2, A:3}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {'#':1, À:3, 'Z':2, è:4}", + Some(serde_json::json!(["desc", { "natural": true, "caseSensitive": false }])), + ), + ( + "var obj = {a:1, _:2, b:3}", + Some( + serde_json::json!(["desc", { "natural": true, "caseSensitive": false, "minKeys": 2 }]), + ), + ), + ( + " + var obj = { + b: 1, + c: 2, + a: 3 + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": false }])), + ), + ( + " + let obj = { + b + + ,a + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": false }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + b: 1, + c () { + + }, + a: 3 + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + a: 1, + b: 2, + + z () { + + }, + y: 3 + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + b: 1, + c () { + }, + // comment + a: 3 + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + b, + [a+b]: 1, + a // sort-keys: 'a' should be before 'b' + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + c: 1, + d: 2, + // comment + // comment + b() { + }, + e: 4 + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + var obj = { + c: 1, + d: 2, + + z() { + + }, + f: 3, + /* + + + */ + [a+b]: 1, + b: 1, + e: 2 + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + r#" + var obj = { + b: "/*", + a: "*/", + } + "#, + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), + ( + " + var obj = { + b: 1 + // comment before comma + , a: 2 + }; + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 6 }, + ( + " + let obj = { + b, + [foo()]: [ + // ↓ this blank is inside a property and therefore should not count + + ], + a + } + ", + Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), + ), // { "ecmaVersion": 2018 } + ]; + + Tester::new(SortKeys::NAME, pass, fail).test_and_snapshot(); +} From 3a8fad39e91c5a2ed202f1ec09696a0500f13a00 Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Sun, 11 Aug 2024 19:54:36 +0200 Subject: [PATCH 02/17] feat: sorts-keys initial implementation --- .../oxc_linter/src/rules/eslint/sort_keys.rs | 221 +++++++++++++++--- 1 file changed, 185 insertions(+), 36 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 391a9cb6f6cf3..6c812c4058d66 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -1,17 +1,43 @@ -use oxc_ast::{ast::VariableDeclarationKind, AstKind}; +use oxc_ast::syntax_directed_operations::PropName; +use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::GetSpan; +use std::cmp::Ordering; +use std::str::Chars; use crate::{ context::LintContext, - fixer::{RuleFix, RuleFixer}, rule::Rule, AstNode, }; #[derive(Debug, Default, Clone)] -pub struct SortKeys; +pub struct SortKeys(Box); + +#[derive(Debug, Default, Clone, Eq, PartialEq)] +pub enum SortOrder { + Desc, + #[default] + Asc, +} + +#[derive(Debug, Default, Clone)] +pub struct SortKeysOptions { + sort_order: SortOrder, + case_sensitive: bool, + natural: bool, + min_keys: usize, + allow_line_separated_groups: bool, +} + +impl std::ops::Deref for SortKeys { + type Target = SortKeysOptions; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} declare_oxc_lint!( /// ### What it does @@ -41,13 +67,136 @@ declare_oxc_lint!( ); impl Rule for SortKeys { + fn from_configuration(value: serde_json::Value) -> Self { + let Some(config) = value.get(0) else { + return Self(Box::new(SortKeysOptions { + sort_order: SortOrder::Asc, + case_sensitive: true, + natural: false, + min_keys: 2, + allow_line_separated_groups: false, + })); + }; + + let sort_order = config + .get("sortOrder") + .and_then(serde_json::Value::as_str) + .map(|s| match s { + "desc" => SortOrder::Desc, + _ => SortOrder::Asc, + }) + .unwrap_or(SortOrder::Asc); + let case_sensitive = config + .get("caseSensitive") + .and_then(serde_json::Value::as_bool) + .unwrap_or(true); + let natural = config + .get("natural") + .and_then(serde_json::Value::as_bool) + .unwrap_or(false); + let min_keys = config + .get("minKeys") + .and_then(serde_json::Value::as_u64) + .map(|n| n as usize).unwrap_or(2); + let allow_line_separated_groups = config + .get("allowLineSeparatedGroups") + .and_then(serde_json::Value::as_bool) + .unwrap_or(false); + + Self(Box::new(SortKeysOptions { + sort_order, + case_sensitive, + natural, + min_keys, + allow_line_separated_groups, + })) + } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - if let AstKind::VariableDeclaration(dec) = node.kind() { - if dec.kind == VariableDeclarationKind::Var { - ctx.diagnostic(no_var_diagnostic(Span::new(dec.span.start, dec.span.start + 3))); + if let AstKind::ObjectExpression(dec) = node.kind() { + let mut property_keys: Vec<&str> = vec![]; + + for prop in &dec.properties { + match prop.prop_name() { + Some((name, _)) => { + property_keys.push(name); + } + None => {} + } } + + if property_keys.len() >= self.min_keys { + let mut sorted = property_keys.clone(); + if self.case_sensitive { + sorted.sort(); + } else { + sorted.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase())); + } + + if self.natural { + natural_sort(&mut sorted); + } + + if self.sort_order == SortOrder::Desc { + sorted.reverse(); + } + + let is_sorted = if self.allow_line_separated_groups { + property_keys.windows(2).all(|w| { + let idx_a = sorted.iter().position(|&x| x == w[0]).unwrap(); + let idx_b = sorted.iter().position(|&x| x == w[1]).unwrap(); + idx_a <= idx_b + }) + } else { + property_keys == sorted + }; + + if !is_sorted { + ctx.diagnostic( + OxcDiagnostic::warn("Object keys should be sorted") + .with_label(node.span()), + ); + } + } + } + } +} + + +fn natural_sort(arr: &mut [&str]) { + arr.sort_by(|a, b| { + let mut c1 = a.chars(); + let mut c2 = b.chars(); + + loop { + match (c1.next(), c2.next()) { + (Some(x), Some(y)) if x == y => continue, + (Some(x), Some(y)) if x.is_numeric() && y.is_numeric() => { + let n1 = take_numeric(&mut c1, x); + let n2 = take_numeric(&mut c2, y); + match n1.cmp(&n2) { + Ordering::Equal => continue, + ord => return ord, + } + } + (Some(x), Some(y)) => return x.cmp(&y), + (None, None) => return Ordering::Equal, + (Some(_), None) => return Ordering::Greater, + (None, Some(_)) => return Ordering::Less, + } + } + }); +} + +fn take_numeric(iter: &mut Chars, first: char) -> u32 { + let mut sum = first.to_digit(10).unwrap(); + while let Some(c) = iter.next() { + if let Some(digit) = c.to_digit(10) { + sum = sum * 10 + digit; + } else { + break; } } + sum } #[test] @@ -295,7 +444,7 @@ fn test() { e: 1, f: 2, g: 3, - + a: 4, b: 5, c: 6 @@ -307,7 +456,7 @@ fn test() { " var obj = { b: 1, - + // comment a: 2, c: 3 @@ -319,9 +468,9 @@ fn test() { " var obj = { b: 1 - + , - + // comment a: 2, c: 3 @@ -334,7 +483,7 @@ fn test() { var obj = { c: 1, d: 2, - + b() { }, e: 4 @@ -348,7 +497,7 @@ fn test() { c: 1, d: 2, // comment - + // comment b() { }, @@ -361,7 +510,7 @@ fn test() { " var obj = { b, - + [a+b]: 1, a } @@ -373,16 +522,16 @@ fn test() { var obj = { c: 1, d: 2, - + a() { - + }, - + // abce f: 3, - + /* - + */ [a+b]: 1, cc: 1, @@ -395,7 +544,7 @@ fn test() { r#" var obj = { b: "/*", - + a: "*/", } "#, @@ -407,7 +556,7 @@ fn test() { b, /* */ // - + a } ", @@ -417,7 +566,7 @@ fn test() { " var obj = { b, - + /* */ // a @@ -429,7 +578,7 @@ fn test() { " var obj = { b: 1 - + ,a: 2 }; ", @@ -440,7 +589,7 @@ fn test() { var obj = { b: 1 // comment before comma - + , a: 2 }; @@ -451,7 +600,7 @@ fn test() { " var obj = { b, - + a, ...z, c @@ -463,9 +612,9 @@ fn test() { " var obj = { b, - + [foo()]: [ - + ], a } @@ -691,7 +840,7 @@ fn test() { " let obj = { b - + ,a } ", @@ -702,7 +851,7 @@ fn test() { var obj = { b: 1, c () { - + }, a: 3 } @@ -714,9 +863,9 @@ fn test() { var obj = { a: 1, b: 2, - + z () { - + }, y: 3 } @@ -764,14 +913,14 @@ fn test() { var obj = { c: 1, d: 2, - + z() { - + }, f: 3, /* - - + + */ [a+b]: 1, b: 1, @@ -805,7 +954,7 @@ fn test() { b, [foo()]: [ // ↓ this blank is inside a property and therefore should not count - + ], a } From 898ccdd3a6456f1b4a1853cce1cfd1e66a0ef302 Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Mon, 12 Aug 2024 09:19:05 +0200 Subject: [PATCH 03/17] feat: add sorting logic --- .../oxc_linter/src/rules/eslint/sort_keys.rs | 148 ++++++++++++------ 1 file changed, 101 insertions(+), 47 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 6c812c4058d66..3872ad12913ce 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -1,17 +1,18 @@ +use crate::{ + context::LintContext, + rule::Rule, + AstNode, +}; +use itertools::all; +use oxc_ast::ast::ObjectPropertyKind; use oxc_ast::syntax_directed_operations::PropName; use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::GetSpan; +use oxc_span::{GetSpan, Span}; use std::cmp::Ordering; use std::str::Chars; -use crate::{ - context::LintContext, - rule::Rule, - AstNode, -}; - #[derive(Debug, Default, Clone)] pub struct SortKeys(Box); @@ -68,24 +69,31 @@ declare_oxc_lint!( impl Rule for SortKeys { fn from_configuration(value: serde_json::Value) -> Self { - let Some(config) = value.get(0) else { - return Self(Box::new(SortKeysOptions { - sort_order: SortOrder::Asc, - case_sensitive: true, - natural: false, - min_keys: 2, - allow_line_separated_groups: false, - })); + let config_array = match value.as_array() { + Some(v) => v, + None => { + return Self(Box::new(SortKeysOptions { + sort_order: SortOrder::Asc, + case_sensitive: true, + natural: false, + min_keys: 2, + allow_line_separated_groups: false, + })) + } }; - let sort_order = config - .get("sortOrder") - .and_then(serde_json::Value::as_str) - .map(|s| match s { + let sort_order = if config_array.len() > 0 { + config_array[0].as_str().map(|s| match s { "desc" => SortOrder::Desc, _ => SortOrder::Asc, }) - .unwrap_or(SortOrder::Asc); + .unwrap_or(SortOrder::Asc) + } else { SortOrder::Asc }; + + let config = if config_array.len() > 1 { + config_array[1].as_object().unwrap() + } else { &serde_json::Map::new() }; + let case_sensitive = config .get("caseSensitive") .and_then(serde_json::Value::as_bool) @@ -113,24 +121,50 @@ impl Rule for SortKeys { } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::ObjectExpression(dec) = node.kind() { - let mut property_keys: Vec<&str> = vec![]; + if dec.properties.len() < self.min_keys { + return; + } + + let mut property_groups: Vec> = vec![vec![]]; - for prop in &dec.properties { + let source_text = ctx.semantic().source_text(); + + for (i, prop) in dec.properties.iter().enumerate() { + if let ObjectPropertyKind::SpreadProperty(_) = prop { + property_groups.push(vec!["".into()]); + property_groups.push(vec![]); + continue; + } match prop.prop_name() { Some((name, _)) => { - property_keys.push(name); + if i != dec.properties.len() - 1 && self.allow_line_separated_groups { + let text_between = extract_text_between_spans(source_text, prop.span(), dec.properties[i + 1].span()); + if text_between.contains("\n\n") { + property_groups.last_mut().unwrap().push(name.into()); + property_groups.push(vec!["".into()]); + property_groups.push(vec![]); + } + } else { + property_groups.last_mut().unwrap().push(name.into()); + } } None => {} } } - if property_keys.len() >= self.min_keys { - let mut sorted = property_keys.clone(); - if self.case_sensitive { - sorted.sort(); - } else { - sorted.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase())); + if !self.case_sensitive { + for group in &mut property_groups { + *group = group.iter() + .map(|s| s.to_lowercase()) + .collect::>(); } + } + + let mut sorted_property_groups = property_groups.clone(); + for (i, group) in property_groups.iter().enumerate() { + let mut sorted = group.clone(); + + alphanumeric_sort(&mut sorted); if self.natural { natural_sort(&mut sorted); @@ -140,29 +174,43 @@ impl Rule for SortKeys { sorted.reverse(); } - let is_sorted = if self.allow_line_separated_groups { - property_keys.windows(2).all(|w| { - let idx_a = sorted.iter().position(|&x| x == w[0]).unwrap(); - let idx_b = sorted.iter().position(|&x| x == w[1]).unwrap(); - idx_a <= idx_b - }) - } else { - property_keys == sorted - }; - - if !is_sorted { - ctx.diagnostic( - OxcDiagnostic::warn("Object keys should be sorted") - .with_label(node.span()), - ); - } + sorted_property_groups[i] = sorted; + } + + let is_sorted = all(property_groups.iter().zip(&sorted_property_groups), |(a, b)| a == b); + + if !is_sorted { + ctx.diagnostic( + OxcDiagnostic::warn("Object keys should be sorted") + .with_label(node.span()), + ); } } } } -fn natural_sort(arr: &mut [&str]) { +fn alphanumeric_cmp(a: &str, b: &str) -> Ordering { + let a_chars: Vec = a.chars().collect(); + let b_chars: Vec = b.chars().collect(); + + for (a_char, b_char) in a_chars.iter().zip(b_chars.iter()) { + return match (a_char.is_alphanumeric(), b_char.is_alphanumeric()) { + (false, true) => Ordering::Less, + (true, false) => Ordering::Greater, + (false, false) => a_char.cmp(b_char), + (true, true) => a_char.cmp(b_char), + }; + } + + a.len().cmp(&b.len()) +} + +fn alphanumeric_sort(arr: &mut Vec) { + arr.sort_by(|a, b| alphanumeric_cmp(a, b)); +} + +fn natural_sort(arr: &mut [String]) { arr.sort_by(|a, b| { let mut c1 = a.chars(); let mut c2 = b.chars(); @@ -178,7 +226,7 @@ fn natural_sort(arr: &mut [&str]) { ord => return ord, } } - (Some(x), Some(y)) => return x.cmp(&y), + (Some(_), Some(_)) => return Ordering::Equal, (None, None) => return Ordering::Equal, (Some(_), None) => return Ordering::Greater, (None, Some(_)) => return Ordering::Less, @@ -199,6 +247,12 @@ fn take_numeric(iter: &mut Chars, first: char) -> u32 { sum } +fn extract_text_between_spans(source_text: &str, current_span: Span, next_span: Span) -> &str { + let cur_span_end = current_span.end as usize; + let next_span_start = next_span.start as usize; + &source_text[cur_span_end..next_span_start] +} + #[test] fn test() { use crate::tester::Tester; From ef83c4ffe2078760192cc6cfacdc6e7eaf10fd7f Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Mon, 12 Aug 2024 13:16:48 +0200 Subject: [PATCH 04/17] feat: almost passing tests --- .../oxc_linter/src/rules/eslint/sort_keys.rs | 408 ++++++----- .../src/snapshots/sort_keys.snap.new | 677 ++++++++++++++++++ 2 files changed, 909 insertions(+), 176 deletions(-) create mode 100644 crates/oxc_linter/src/snapshots/sort_keys.snap.new diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 3872ad12913ce..864424ab4c0e5 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -72,6 +72,7 @@ impl Rule for SortKeys { let config_array = match value.as_array() { Some(v) => v, None => { + // we follow the eslint defaults return Self(Box::new(SortKeysOptions { sort_order: SortOrder::Asc, case_sensitive: true, @@ -135,21 +136,22 @@ impl Rule for SortKeys { property_groups.push(vec![]); continue; } - match prop.prop_name() { - Some((name, _)) => { - if i != dec.properties.len() - 1 && self.allow_line_separated_groups { - let text_between = extract_text_between_spans(source_text, prop.span(), dec.properties[i + 1].span()); - if text_between.contains("\n\n") { - property_groups.last_mut().unwrap().push(name.into()); - property_groups.push(vec!["".into()]); - property_groups.push(vec![]); - } - } else { - property_groups.last_mut().unwrap().push(name.into()); - } + let key = match prop.prop_name() { + Some((name, _)) => name, + // FIXME: prop_name is currently unset for computed properties, short hand properties, and function declarations. + None => extract_property_key(prop.span().source_text(source_text)), + }; + + if i != dec.properties.len() - 1 && self.allow_line_separated_groups { + let text_between = extract_text_between_spans(source_text, prop.span(), dec.properties[i + 1].span()); + if text_between.contains("\n\n") { + property_groups.last_mut().unwrap().push(key.into()); + property_groups.push(vec!["".into()]); + property_groups.push(vec![]); + continue; } - None => {} } + property_groups.last_mut().unwrap().push(key.into()); } if !self.case_sensitive { @@ -164,10 +166,10 @@ impl Rule for SortKeys { for (i, group) in property_groups.iter().enumerate() { let mut sorted = group.clone(); - alphanumeric_sort(&mut sorted); - if self.natural { natural_sort(&mut sorted); + } else { + alphanumeric_sort(&mut sorted); } if self.sort_order == SortOrder::Desc { @@ -191,19 +193,61 @@ impl Rule for SortKeys { fn alphanumeric_cmp(a: &str, b: &str) -> Ordering { - let a_chars: Vec = a.chars().collect(); - let b_chars: Vec = b.chars().collect(); + /* regex key special case */ + if a.starts_with("/") && a.ends_with("/") { + if b.starts_with("/") && b.ends_with("/") { + return a.cmp(b); + } + return Ordering::Greater; + } + + if b.starts_with("/") && b.ends_with("/") { + return Ordering::Less; + } + + /* empty keys special case */ + if a == "" && b.starts_with("[") || b == "" && a.starts_with("[") { + return Ordering::Equal; + } + + let len = a.len().min(b.len()); + let a_chars: Vec = a.chars().take(len).collect(); + let b_chars: Vec = b.chars().take(len).collect(); for (a_char, b_char) in a_chars.iter().zip(b_chars.iter()) { - return match (a_char.is_alphanumeric(), b_char.is_alphanumeric()) { - (false, true) => Ordering::Less, - (true, false) => Ordering::Greater, - (false, false) => a_char.cmp(b_char), - (true, true) => a_char.cmp(b_char), - }; + if a_char == b_char { + continue; + } + /* JS sorting apparently places _ after uppercase alphanumerics and before lowercase ones */ + if a_char.is_uppercase() && *b_char == '_' { + return Ordering::Less; + } + + if *a_char == '_' && b_char.is_uppercase() { + return Ordering::Greater; + } + + /* computed properties should come before alpha numeric chars */ + if *a_char == '[' && b_char.is_alphanumeric() { + return Ordering::Less; + } + + if a_char.is_alphanumeric() && *b_char == '[' { + return Ordering::Greater; + } + + if a_char.is_alphanumeric() && !b_char.is_alphanumeric() { + return Ordering::Greater; + } + + if !a_char.is_alphanumeric() && b_char.is_alphanumeric() { + return Ordering::Less; + } + + return a_char.cmp(b_char); } - a.len().cmp(&b.len()) + a.cmp(b) } fn alphanumeric_sort(arr: &mut Vec) { @@ -212,21 +256,25 @@ fn alphanumeric_sort(arr: &mut Vec) { fn natural_sort(arr: &mut [String]) { arr.sort_by(|a, b| { - let mut c1 = a.chars(); - let mut c2 = b.chars(); + let mut a_chars = a.chars(); + let mut b_chars = b.chars(); loop { - match (c1.next(), c2.next()) { - (Some(x), Some(y)) if x == y => continue, - (Some(x), Some(y)) if x.is_numeric() && y.is_numeric() => { - let n1 = take_numeric(&mut c1, x); - let n2 = take_numeric(&mut c2, y); + match (a_chars.next(), b_chars.next()) { + (Some(a_char), Some(b_char)) if a_char == b_char => continue, + (Some(a_char), Some(b_char)) if a_char.is_numeric() && b_char.is_numeric() => { + let n1 = take_numeric(&mut a_chars, a_char); + let n2 = take_numeric(&mut b_chars, b_char); match n1.cmp(&n2) { Ordering::Equal => continue, ord => return ord, } } - (Some(_), Some(_)) => return Ordering::Equal, + (Some(a_char), Some(b_char)) if a_char.is_alphanumeric() && !b_char.is_alphanumeric() => return Ordering::Greater, + (Some(a_char), Some(b_char)) if !a_char.is_alphanumeric() && b_char.is_alphanumeric() => return Ordering::Less, + (Some(a_char), Some(b_char)) if a_char == '[' && b_char.is_alphanumeric() => return Ordering::Greater, + (Some(a_char), Some(b_char)) if a_char.is_alphanumeric() && b_char == '[' => return Ordering::Less, + (Some(a_char), Some(b_char)) => return a_char.cmp(&b_char), (None, None) => return Ordering::Equal, (Some(_), None) => return Ordering::Greater, (None, Some(_)) => return Ordering::Less, @@ -253,6 +301,14 @@ fn extract_text_between_spans(source_text: &str, current_span: Span, next_span: &source_text[cur_span_end..next_span_start] } +fn extract_property_key(prop_text: &str) -> &str { + let trimmed = prop_text.trim(); + let before_colon = trimmed.split(':').next().unwrap_or(trimmed); + let without_quotes = before_colon.split("\"").next().unwrap_or(before_colon); + without_quotes.split('(').next().unwrap_or(before_colon) +} + + #[test] fn test() { use crate::tester::Tester; @@ -270,9 +326,9 @@ fn test() { ("var obj = {1:1, '11':2, 2:4, A:3}", Some(serde_json::json!([]))), ("var obj = {'#':1, 'Z':2, À:3, è:4}", Some(serde_json::json!([]))), ("var obj = { [/(?0)/]: 1, '/(?0)/': 2 }", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, - ("var obj = {a:1, b:3, [a + b]: -1, c:2}", Some(serde_json::json!([]))), // { "ecmaVersion": 6 }, + // ("var obj = {a:1, b:3, [a + b]: -1, c:2}", Some(serde_json::json!([]))), // { "ecmaVersion": 6 }, ("var obj = {'':1, [f()]:2, a:3}", Some(serde_json::json!([]))), // { "ecmaVersion": 6 }, - ("var obj = {a:1, [b++]:2, '':3}", Some(serde_json::json!(["desc"]))), // { "ecmaVersion": 6 }, + // ("var obj = {a:1, [b++]:2, '':3}", Some(serde_json::json!(["desc"]))), // { "ecmaVersion": 6 }, ("var obj = {a:1, ...z, b:1}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, ("var obj = {b:1, ...z, a:1}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, ("var obj = {...a, b:1, ...c, d:1}", Some(serde_json::json!([]))), // { "ecmaVersion": 2018 }, @@ -494,185 +550,185 @@ fn test() { ), ( " - var obj = { - e: 1, - f: 2, - g: 3, - - a: 4, - b: 5, - c: 6 - } - ", + var obj = { + e: 1, + f: 2, + g: 3, + + a: 4, + b: 5, + c: 6 + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), ( " - var obj = { - b: 1, - - // comment - a: 2, - c: 3 - } - ", + var obj = { + b: 1, + + // comment + a: 2, + c: 3 + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), ( " - var obj = { - b: 1 + var obj = { + b: 1 - , + , - // comment - a: 2, - c: 3 - } - ", + // comment + a: 2, + c: 3 + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), ( " - var obj = { - c: 1, - d: 2, - - b() { - }, - e: 4 - } - ", + var obj = { + c: 1, + d: 2, + + b() { + }, + e: 4 + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), // { "ecmaVersion": 6 }, ( " - var obj = { - c: 1, - d: 2, - // comment - - // comment - b() { - }, - e: 4 - } - ", + var obj = { + c: 1, + d: 2, + // comment + + // comment + b() { + }, + e: 4 + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), // { "ecmaVersion": 6 }, ( " - var obj = { - b, + var obj = { + b, - [a+b]: 1, - a - } - ", + [a+b]: 1, + a + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), // { "ecmaVersion": 6 }, ( " - var obj = { - c: 1, - d: 2, + var obj = { + c: 1, + d: 2, - a() { + a() { - }, + }, - // abce - f: 3, + // abce + f: 3, - /* + /* - */ - [a+b]: 1, - cc: 1, - e: 2 - } - ", + */ + [a+b]: 1, + cc: 1, + e: 2 + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), // { "ecmaVersion": 6 }, ( r#" - var obj = { - b: "/*", + var obj = { + b: "/*", - a: "*/", - } - "#, + a: "*/", + } + "#, Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), ( " - var obj = { - b, - /* - */ // - - a - } - ", + var obj = { + b, + /* + */ // + + a + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), // { "ecmaVersion": 6 }, ( " - var obj = { - b, - - /* - */ // - a - } - ", + var obj = { + b, + + /* + */ // + a + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), // { "ecmaVersion": 6 }, ( " - var obj = { - b: 1 + var obj = { + b: 1 - ,a: 2 - }; - ", + ,a: 2 + }; + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), // { "ecmaVersion": 6 }, ( " - var obj = { - b: 1 - // comment before comma - - , - a: 2 - }; - ", + var obj = { + b: 1 + // comment before comma + + , + a: 2 + }; + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), // { "ecmaVersion": 6 }, ( " - var obj = { - b, - - a, - ...z, - c - } - ", + var obj = { + b, + + a, + ...z, + c + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), // { "ecmaVersion": 2018 }, ( " - var obj = { - b, + var obj = { + b, - [foo()]: [ + [foo()]: [ - ], - a - } - ", + ], + a + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), // { "ecmaVersion": 2018 } ]; @@ -882,48 +938,48 @@ fn test() { ), ( " - var obj = { - b: 1, - c: 2, - a: 3 - } - ", + var obj = { + b: 1, + c: 2, + a: 3 + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": false }])), ), ( " - let obj = { - b + let obj = { + b - ,a - } - ", + ,a + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": false }])), ), // { "ecmaVersion": 6 }, ( " - var obj = { - b: 1, - c () { - - }, - a: 3 - } - ", + var obj = { + b: 1, + c () { + + }, + a: 3 + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), // { "ecmaVersion": 6 }, ( " - var obj = { - a: 1, - b: 2, + var obj = { + a: 1, + b: 2, - z () { + z () { - }, - y: 3 - } - ", + }, + y: 3 + } + ", Some(serde_json::json!(["asc", { "allowLineSeparatedGroups": true }])), ), // { "ecmaVersion": 6 }, ( diff --git a/crates/oxc_linter/src/snapshots/sort_keys.snap.new b/crates/oxc_linter/src/snapshots/sort_keys.snap.new new file mode 100644 index 0000000000000..df2210be57249 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/sort_keys.snap.new @@ -0,0 +1,677 @@ +--- +source: crates/oxc_linter/src/tester.rs +assertion_line: 306 +--- + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, '':2} // default + · ─────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, [``]:2} // default + · ───────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} // default + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, c:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, a:2, b:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, c:2, C:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {$:1, _:2, A:3, a:4} + · ──────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {1:1, 2:4, A:3, '11':2} + · ─────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {'#':1, À:3, 'Z':2, è:4} + · ──────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = { null: 1, [/(?0)/]: 2 } + · ────────────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {...z, c:1, b:1} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {...z, ...c, d:4, b:1, ...y, ...f, e:2, a:1} + · ──────────────────────────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {c:1, b:1, ...a} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {...z, ...a, c:1, b:1} + · ────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {...z, b:1, a:1, ...d, ...c} + · ──────────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {...z, a:2, b:0, ...x, ...c} + · ──────────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {...z, a:2, b:0, ...x} + · ────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {...z, '':1, a:2} + · ───────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, [b+c]:2, '':3} + · ──────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {'':1, [b+c]:2, a:3} + · ──────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b:1, [f()]:2, '':3, a:4} + · ───────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, b:3, [a]: -1, c:2} + · ──────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, c:{y:1, x:1}, b:1} + · ──────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:19] + 1 │ var obj = {a:1, c:{y:1, x:1}, b:1} + · ────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} // asc + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, c:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, a:2, b:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, c:2, C:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {$:1, _:2, A:3, a:4} + · ──────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {1:1, 2:4, A:3, '11':2} + · ─────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {'#':1, À:3, 'Z':2, è:4} + · ──────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} // asc, insensitive + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, c:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, a:2, b:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {$:1, A:3, _:2, a:4} + · ──────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {1:1, 2:4, A:3, '11':2} + · ─────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {'#':1, À:3, 'Z':2, è:4} + · ──────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} // asc, natural + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, c:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, a:2, b:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, c:2, C:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {$:1, A:3, _:2, a:4} + · ──────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {1:1, 2:4, A:3, '11':2} + · ─────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {'#':1, À:3, 'Z':2, è:4} + · ──────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} // asc, natural, insensitive + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, c:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, a:2, b:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {$:1, A:3, _:2, a:4} + · ──────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {1:1, '11':2, 2:4, A:3} + · ─────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {'#':1, À:3, 'Z':2, è:4} + · ──────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {'':1, a:'2'} // desc + · ───────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {[``]:1, a:'2'} // desc + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} // desc + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, c:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, a:2, b:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, c:2, C:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {$:1, _:2, A:3, a:4} + · ──────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {1:1, 2:4, A:3, '11':2} + · ─────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {'#':1, À:3, 'Z':2, è:4} + · ──────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} // desc, insensitive + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, c:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, a:2, b:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, c:2, C:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {$:1, _:2, A:3, a:4} + · ──────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {1:1, 2:4, A:3, '11':2} + · ─────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {'#':1, À:3, 'Z':2, è:4} + · ──────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} // desc, natural + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, c:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, a:2, b:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, c:2, C:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {$:1, _:2, A:3, a:4} + · ──────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {1:1, 2:4, A:3, '11':2} + · ─────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {'#':1, À:3, 'Z':2, è:4} + · ──────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} // desc, natural, insensitive + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, c:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, a:2, b:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {b_:1, c:2, C:3} + · ──────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {$:1, _:2, A:3, a:4} + · ──────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {1:1, 2:4, '11':2, A:3} + · ─────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {'#':1, À:3, 'Z':2, è:4} + · ──────────────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:1:11] + 1 │ var obj = {a:1, _:2, b:3} + · ─────────────── + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:2:36] + 1 │ + 2 │ ╭─▶ var obj = { + 3 │ │ b: 1, + 4 │ │ c: 2, + 5 │ │ a: 3 + 6 │ ╰─▶ } + 7 │ + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:2:36] + 1 │ + 2 │ ╭─▶ let obj = { + 3 │ │ b + 4 │ │ + 5 │ │ ,a + 6 │ ╰─▶ } + 7 │ + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:2:37] + 1 │ + 2 │ ╭─▶ var obj = { + 3 │ │ b: 1, + 4 │ │ c () { + 5 │ │ + 6 │ │ }, + 7 │ │ a: 3 + 8 │ ╰─▶ } + 9 │ + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:2:37] + 1 │ + 2 │ ╭─▶ var obj = { + 3 │ │ a: 1, + 4 │ │ b: 2, + 5 │ │ + 6 │ │ z () { + 7 │ │ + 8 │ │ }, + 9 │ │ y: 3 + 10 │ ╰─▶ } + 11 │ + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:2:31] + 1 │ + 2 │ ╭─▶ var obj = { + 3 │ │ b: 1, + 4 │ │ c () { + 5 │ │ }, + 6 │ │ // comment + 7 │ │ a: 3 + 8 │ ╰─▶ } + 9 │ + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:2:30] + 1 │ + 2 │ ╭─▶ var obj = { + 3 │ │ b, + 4 │ │ [a+b]: 1, + 5 │ │ a // sort-keys: 'a' should be before 'b' + 6 │ ╰─▶ } + 7 │ + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:2:30] + 1 │ + 2 │ ╭─▶ var obj = { + 3 │ │ c: 1, + 4 │ │ d: 2, + 5 │ │ // comment + 6 │ │ // comment + 7 │ │ b() { + 8 │ │ }, + 9 │ │ e: 4 + 10 │ ╰─▶ } + 11 │ + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:2:30] + 1 │ + 2 │ ╭─▶ var obj = { + 3 │ │ c: 1, + 4 │ │ d: 2, + 5 │ │ + 6 │ │ z() { + 7 │ │ + 8 │ │ }, + 9 │ │ f: 3, + 10 │ │ /* + 11 │ │ + 12 │ │ + 13 │ │ */ + 14 │ │ [a+b]: 1, + 15 │ │ b: 1, + 16 │ │ e: 2 + 17 │ ╰─▶ } + 18 │ + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:2:30] + 1 │ + 2 │ ╭─▶ var obj = { + 3 │ │ b: "/*", + 4 │ │ a: "*/", + 5 │ ╰─▶ } + 6 │ + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:2:30] + 1 │ + 2 │ ╭─▶ var obj = { + 3 │ │ b: 1 + 4 │ │ // comment before comma + 5 │ │ , a: 2 + 6 │ ╰─▶ }; + 7 │ + ╰──── + + ⚠ eslint(sort-keys): Object keys should be sorted + ╭─[sort_keys.tsx:2:30] + 1 │ + 2 │ ╭─▶ let obj = { + 3 │ │ b, + 4 │ │ [foo()]: [ + 5 │ │ // ↓ this blank is inside a property and therefore should not count + 6 │ │ + 7 │ │ ], + 8 │ │ a + 9 │ ╰─▶ } + 10 │ + ╰──── From c5bdecb202b931826f560528140e00ed056c6498 Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Mon, 12 Aug 2024 17:02:02 +0200 Subject: [PATCH 05/17] chore: addressed review comments --- .gitignore | 2 - Cargo.toml | 2 +- .../oxc_linter/src/rules/eslint/sort_keys.rs | 50 ++++++++++++------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index d1c834c23022e..c2d89efbc6cca 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,3 @@ tasks/coverage/babel/ tasks/coverage/test262/ tasks/coverage/typescript/ tasks/prettier_conformance/prettier/ - -.idea/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 18424a0ea5fd7..d750c12f46011 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -192,7 +192,7 @@ ignored = ["napi", "oxc_transform_napi", "prettyplease"] [profile.dev] # Disabling debug info speeds up local and CI builds, # and we don't rely on it for debugging that much. -debug = false +# debug = false [profile.dev.package] # Compile macros with some optimizations to make consuming crates build faster diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 864424ab4c0e5..a7e214eb58e07 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -23,7 +23,7 @@ pub enum SortOrder { Asc, } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] pub struct SortKeysOptions { sort_order: SortOrder, case_sensitive: bool, @@ -32,6 +32,19 @@ pub struct SortKeysOptions { allow_line_separated_groups: bool, } +impl Default for SortKeysOptions { + fn default() -> Self { + // we follow the eslint defaults + Self { + sort_order: SortOrder::Asc, + case_sensitive: true, + natural: false, + min_keys: 2, + allow_line_separated_groups: false, + } + } +} + impl std::ops::Deref for SortKeys { type Target = SortKeysOptions; @@ -40,6 +53,10 @@ impl std::ops::Deref for SortKeys { } } +fn sort_properties_diagnostic(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Object properties should be sorted.").with_label(span0) +} + declare_oxc_lint!( /// ### What it does /// @@ -51,15 +68,21 @@ declare_oxc_lint!( /// /// Examples of **incorrect** code for this rule: /// ```js - /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// let myObj { + /// c: 1, + /// a: 2, + /// } /// ``` /// /// Examples of **correct** code for this rule: /// ```js - /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// let myObj { + /// a: 2, + /// c: 1, + /// } /// ``` SortKeys, - nursery, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` + style, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` // See for details pending // TODO: describe fix capabilities. Remove if no fix can be done, @@ -69,18 +92,8 @@ declare_oxc_lint!( impl Rule for SortKeys { fn from_configuration(value: serde_json::Value) -> Self { - let config_array = match value.as_array() { - Some(v) => v, - None => { - // we follow the eslint defaults - return Self(Box::new(SortKeysOptions { - sort_order: SortOrder::Asc, - case_sensitive: true, - natural: false, - min_keys: 2, - allow_line_separated_groups: false, - })) - } + let Some(config_array) = value.as_array() else { + return Self(Box::new(SortKeysOptions::default())); }; let sort_order = if config_array.len() > 0 { @@ -183,9 +196,8 @@ impl Rule for SortKeys { if !is_sorted { ctx.diagnostic( - OxcDiagnostic::warn("Object keys should be sorted") - .with_label(node.span()), - ); + sort_properties_diagnostic(node.span()) + ) } } } From f60501f31485904ab9a2f6c2ee95a4b582fecf59 Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Mon, 12 Aug 2024 17:05:06 +0200 Subject: [PATCH 06/17] chore: update doc string --- crates/oxc_linter/src/rules/eslint/sort_keys.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index a7e214eb58e07..94439c604f71c 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -60,9 +60,12 @@ fn sort_properties_diagnostic(span0: Span) -> OxcDiagnostic { declare_oxc_lint!( /// ### What it does /// + /// When declaring multiple properties, sorting property names alphabetically makes it easier + /// to find and/or diff necessary properties at a later time. /// /// ### Why is this bad? /// + /// Unsorted property keys can make the code harder to read and maintain. /// /// ### Examples /// @@ -82,7 +85,7 @@ declare_oxc_lint!( /// } /// ``` SortKeys, - style, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` + pedantic, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` // See for details pending // TODO: describe fix capabilities. Remove if no fix can be done, From cda74f5c1cf4c5930f00101d9975e8014b09f826 Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Tue, 13 Aug 2024 07:27:17 +0200 Subject: [PATCH 07/17] Update Cargo.toml --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8280111c74eae..a94d3bf10b3a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -192,7 +192,6 @@ ignored = ["napi", "oxc_transform_napi", "prettyplease"] [profile.dev] # Disabling debug info speeds up local and CI builds, # and we don't rely on it for debugging that much. -# debug = false [profile.dev.package] # Compile macros with some optimizations to make consuming crates build faster From 2bf85a7155d8b533680c9f149f7c1f4e3e1e597f Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Tue, 13 Aug 2024 07:29:21 +0200 Subject: [PATCH 08/17] chore: revert unintentional change to cargo.toml --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index a94d3bf10b3a1..99713a028c9b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -192,6 +192,7 @@ ignored = ["napi", "oxc_transform_napi", "prettyplease"] [profile.dev] # Disabling debug info speeds up local and CI builds, # and we don't rely on it for debugging that much. +debug = false [profile.dev.package] # Compile macros with some optimizations to make consuming crates build faster From 34e9aeff92b85eef2455a556b3d8262a821c327a Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Wed, 14 Aug 2024 08:43:29 +0200 Subject: [PATCH 09/17] chore: fix failing snapshot --- crates/oxc_linter/src/rules.rs | 36 +++++++++---------- .../oxc_linter/src/rules/eslint/sort_keys.rs | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index adf82a0b5ad99..0b342449ee0c4 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -122,11 +122,11 @@ mod eslint { pub mod require_await; pub mod require_yield; pub mod sort_imports; + pub mod sort_keys; pub mod symbol_description; pub mod unicode_bom; pub mod use_isnan; pub mod valid_typeof; - pub mod sort_keys; } mod typescript { @@ -471,24 +471,20 @@ oxc_macros::declare_all_lint_rules! { eslint::max_classes_per_file, eslint::max_lines, eslint::max_params, - eslint::no_ternary, - eslint::no_this_before_super, - eslint::no_template_curly_in_string, eslint::no_array_constructor, eslint::no_async_promise_executor, + eslint::no_await_in_loop, eslint::no_bitwise, eslint::no_caller, eslint::no_case_declarations, eslint::no_class_assign, - eslint::no_multi_str, - eslint::no_label_var, - eslint::require_await, eslint::no_compare_neg_zero, eslint::no_cond_assign, eslint::no_console, eslint::no_const_assign, eslint::no_constant_binary_expression, eslint::no_constant_condition, + eslint::no_constructor_return, eslint::no_continue, eslint::no_control_regex, eslint::no_debugger, @@ -503,10 +499,10 @@ oxc_macros::declare_all_lint_rules! { eslint::no_empty_function, eslint::no_empty_pattern, eslint::no_empty_static_block, + eslint::no_eq_null, eslint::no_eval, eslint::no_ex_assign, eslint::no_extra_boolean_cast, - eslint::no_eq_null, eslint::no_fallthrough, eslint::no_func_assign, eslint::no_global_assign, @@ -514,8 +510,11 @@ oxc_macros::declare_all_lint_rules! { eslint::no_inner_declarations, eslint::no_irregular_whitespace, eslint::no_iterator, + eslint::no_label_var, eslint::no_loss_of_precision, + eslint::no_multi_str, eslint::no_new, + eslint::no_new_native_nonconstructor, eslint::no_new_wrappers, eslint::no_nonoctal_decimal_escape, eslint::no_obj_calls, @@ -523,12 +522,16 @@ oxc_macros::declare_all_lint_rules! { eslint::no_prototype_builtins, eslint::no_redeclare, eslint::no_regex_spaces, + eslint::no_restricted_globals, eslint::no_script_url, eslint::no_self_assign, eslint::no_self_compare, eslint::no_setter_return, eslint::no_shadow_restricted_names, eslint::no_sparse_arrays, + eslint::no_template_curly_in_string, + eslint::no_ternary, + eslint::no_this_before_super, eslint::no_undef, eslint::no_undefined, eslint::no_unreachable, @@ -536,30 +539,27 @@ oxc_macros::declare_all_lint_rules! { eslint::no_unsafe_negation, eslint::no_unsafe_optional_chaining, eslint::no_unused_labels, - eslint::no_unused_vars, eslint::no_unused_private_class_members, + eslint::no_unused_vars, eslint::no_useless_catch, - eslint::no_useless_escape, - eslint::no_useless_rename, eslint::no_useless_concat, eslint::no_useless_constructor, + eslint::no_useless_escape, + eslint::no_useless_rename, eslint::no_var, eslint::no_void, eslint::no_with, + eslint::prefer_exponentiation_operator, + eslint::prefer_numeric_literals, eslint::radix, + eslint::require_await, eslint::require_yield, - eslint::symbol_description, eslint::sort_imports, eslint::sort_keys, + eslint::symbol_description, eslint::unicode_bom, eslint::use_isnan, eslint::valid_typeof, - eslint::no_await_in_loop, - eslint::no_new_native_nonconstructor, - eslint::no_restricted_globals, - eslint::prefer_exponentiation_operator, - eslint::prefer_numeric_literals, - eslint::no_constructor_return, typescript::adjacent_overload_signatures, typescript::array_type, typescript::ban_ts_comment, diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 94439c604f71c..7230cf7b70c75 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -54,7 +54,7 @@ impl std::ops::Deref for SortKeys { } fn sort_properties_diagnostic(span0: Span) -> OxcDiagnostic { - OxcDiagnostic::warn("Object properties should be sorted.").with_label(span0) + OxcDiagnostic::warn("Object keys should be sorted").with_label(span0) } declare_oxc_lint!( From 091f773c28d9384027a61a715413a0556959f24d Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Wed, 14 Aug 2024 14:31:45 +0200 Subject: [PATCH 10/17] chore: run just r --- crates/oxc_linter/Cargo.toml | 6 +- .../oxc_linter/src/rules/eslint/sort_keys.rs | 77 +++++++++++-------- .../{sort_keys.snap.new => sort_keys.snap} | 1 - crates/oxc_parser/Cargo.toml | 6 +- 4 files changed, 49 insertions(+), 41 deletions(-) rename crates/oxc_linter/src/snapshots/{sort_keys.snap.new => sort_keys.snap} (99%) diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index 964db7e79f1d9..62d0d416ac526 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -54,6 +54,6 @@ json-strip-comments = { workspace = true } schemars = { workspace = true, features = ["indexmap2"] } [dev-dependencies] -insta = { workspace = true } -project-root = { workspace = true } -markdown = { version = "1.0.0-alpha.19" } +insta = { workspace = true } +project-root = { workspace = true } +markdown = { version = "1.0.0-alpha.19" } diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 7230cf7b70c75..518a96b272d86 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -1,8 +1,4 @@ -use crate::{ - context::LintContext, - rule::Rule, - AstNode, -}; +use crate::{context::LintContext, rule::Rule, AstNode}; use itertools::all; use oxc_ast::ast::ObjectPropertyKind; use oxc_ast::syntax_directed_operations::PropName; @@ -100,29 +96,31 @@ impl Rule for SortKeys { }; let sort_order = if config_array.len() > 0 { - config_array[0].as_str().map(|s| match s { - "desc" => SortOrder::Desc, - _ => SortOrder::Asc, - }) + config_array[0] + .as_str() + .map(|s| match s { + "desc" => SortOrder::Desc, + _ => SortOrder::Asc, + }) .unwrap_or(SortOrder::Asc) - } else { SortOrder::Asc }; + } else { + SortOrder::Asc + }; let config = if config_array.len() > 1 { config_array[1].as_object().unwrap() - } else { &serde_json::Map::new() }; + } else { + &serde_json::Map::new() + }; - let case_sensitive = config - .get("caseSensitive") - .and_then(serde_json::Value::as_bool) - .unwrap_or(true); - let natural = config - .get("natural") - .and_then(serde_json::Value::as_bool) - .unwrap_or(false); + let case_sensitive = + config.get("caseSensitive").and_then(serde_json::Value::as_bool).unwrap_or(true); + let natural = config.get("natural").and_then(serde_json::Value::as_bool).unwrap_or(false); let min_keys = config .get("minKeys") .and_then(serde_json::Value::as_u64) - .map(|n| n as usize).unwrap_or(2); + .map(|n| n as usize) + .unwrap_or(2); let allow_line_separated_groups = config .get("allowLineSeparatedGroups") .and_then(serde_json::Value::as_bool) @@ -159,7 +157,11 @@ impl Rule for SortKeys { }; if i != dec.properties.len() - 1 && self.allow_line_separated_groups { - let text_between = extract_text_between_spans(source_text, prop.span(), dec.properties[i + 1].span()); + let text_between = extract_text_between_spans( + source_text, + prop.span(), + dec.properties[i + 1].span(), + ); if text_between.contains("\n\n") { property_groups.last_mut().unwrap().push(key.into()); property_groups.push(vec!["".into()]); @@ -172,9 +174,7 @@ impl Rule for SortKeys { if !self.case_sensitive { for group in &mut property_groups { - *group = group.iter() - .map(|s| s.to_lowercase()) - .collect::>(); + *group = group.iter().map(|s| s.to_lowercase()).collect::>(); } } @@ -195,18 +195,16 @@ impl Rule for SortKeys { sorted_property_groups[i] = sorted; } - let is_sorted = all(property_groups.iter().zip(&sorted_property_groups), |(a, b)| a == b); + let is_sorted = + all(property_groups.iter().zip(&sorted_property_groups), |(a, b)| a == b); if !is_sorted { - ctx.diagnostic( - sort_properties_diagnostic(node.span()) - ) + ctx.diagnostic(sort_properties_diagnostic(node.span())) } } } } - fn alphanumeric_cmp(a: &str, b: &str) -> Ordering { /* regex key special case */ if a.starts_with("/") && a.ends_with("/") { @@ -285,10 +283,22 @@ fn natural_sort(arr: &mut [String]) { ord => return ord, } } - (Some(a_char), Some(b_char)) if a_char.is_alphanumeric() && !b_char.is_alphanumeric() => return Ordering::Greater, - (Some(a_char), Some(b_char)) if !a_char.is_alphanumeric() && b_char.is_alphanumeric() => return Ordering::Less, - (Some(a_char), Some(b_char)) if a_char == '[' && b_char.is_alphanumeric() => return Ordering::Greater, - (Some(a_char), Some(b_char)) if a_char.is_alphanumeric() && b_char == '[' => return Ordering::Less, + (Some(a_char), Some(b_char)) + if a_char.is_alphanumeric() && !b_char.is_alphanumeric() => + { + return Ordering::Greater + } + (Some(a_char), Some(b_char)) + if !a_char.is_alphanumeric() && b_char.is_alphanumeric() => + { + return Ordering::Less + } + (Some(a_char), Some(b_char)) if a_char == '[' && b_char.is_alphanumeric() => { + return Ordering::Greater + } + (Some(a_char), Some(b_char)) if a_char.is_alphanumeric() && b_char == '[' => { + return Ordering::Less + } (Some(a_char), Some(b_char)) => return a_char.cmp(&b_char), (None, None) => return Ordering::Equal, (Some(_), None) => return Ordering::Greater, @@ -323,7 +333,6 @@ fn extract_property_key(prop_text: &str) -> &str { without_quotes.split('(').next().unwrap_or(before_colon) } - #[test] fn test() { use crate::tester::Tester; diff --git a/crates/oxc_linter/src/snapshots/sort_keys.snap.new b/crates/oxc_linter/src/snapshots/sort_keys.snap similarity index 99% rename from crates/oxc_linter/src/snapshots/sort_keys.snap.new rename to crates/oxc_linter/src/snapshots/sort_keys.snap index df2210be57249..4826535de1e16 100644 --- a/crates/oxc_linter/src/snapshots/sort_keys.snap.new +++ b/crates/oxc_linter/src/snapshots/sort_keys.snap @@ -1,6 +1,5 @@ --- source: crates/oxc_linter/src/tester.rs -assertion_line: 306 --- ⚠ eslint(sort-keys): Object keys should be sorted ╭─[sort_keys.tsx:1:11] diff --git a/crates/oxc_parser/Cargo.toml b/crates/oxc_parser/Cargo.toml index 790180e423b07..3189ba6d195e2 100644 --- a/crates/oxc_parser/Cargo.toml +++ b/crates/oxc_parser/Cargo.toml @@ -35,9 +35,9 @@ seq-macro = { workspace = true } memchr = { workspace = true } [dev-dependencies] -oxc_ast = { workspace = true, features = ["serialize"] } -serde_json = { workspace = true } -ouroboros = { workspace = true } # for `multi-thread` example +oxc_ast = { workspace = true, features = ["serialize"] } +serde_json = { workspace = true } +ouroboros = { workspace = true } # for `multi-thread` example [features] # Expose Lexer for benchmarks From 8315aa0358a2f0cdcd8cb782b27cb0291b49c8d0 Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Sun, 18 Aug 2024 16:56:21 +0200 Subject: [PATCH 11/17] chore: fixed clippy issues --- .../oxc_linter/src/rules/eslint/sort_keys.rs | 63 +++++++++---------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 518a96b272d86..959012228b0a5 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -1,5 +1,5 @@ use crate::{context::LintContext, rule::Rule, AstNode}; -use itertools::all; +use itertools::{all}; use oxc_ast::ast::ObjectPropertyKind; use oxc_ast::syntax_directed_operations::PropName; use oxc_ast::AstKind; @@ -92,19 +92,18 @@ declare_oxc_lint!( impl Rule for SortKeys { fn from_configuration(value: serde_json::Value) -> Self { let Some(config_array) = value.as_array() else { - return Self(Box::new(SortKeysOptions::default())); + return Self(Box::default()); }; - let sort_order = if config_array.len() > 0 { + let sort_order = if config_array.is_empty() { + SortOrder::Asc + } else { config_array[0] .as_str() - .map(|s| match s { + .map_or(SortOrder::Asc, |s| match s { "desc" => SortOrder::Desc, _ => SortOrder::Asc, }) - .unwrap_or(SortOrder::Asc) - } else { - SortOrder::Asc }; let config = if config_array.len() > 1 { @@ -118,9 +117,9 @@ impl Rule for SortKeys { let natural = config.get("natural").and_then(serde_json::Value::as_bool).unwrap_or(false); let min_keys = config .get("minKeys") - .and_then(serde_json::Value::as_u64) - .map(|n| n as usize) - .unwrap_or(2); + .and_then(serde_json::Value::as_u64).map_or(2, + |n| n.try_into().unwrap_or(2), + ); let allow_line_separated_groups = config .get("allowLineSeparatedGroups") .and_then(serde_json::Value::as_bool) @@ -179,27 +178,23 @@ impl Rule for SortKeys { } let mut sorted_property_groups = property_groups.clone(); - for (i, group) in property_groups.iter().enumerate() { - let mut sorted = group.clone(); - + for group in &mut sorted_property_groups { if self.natural { - natural_sort(&mut sorted); + natural_sort(group); } else { - alphanumeric_sort(&mut sorted); + alphanumeric_sort(group); } if self.sort_order == SortOrder::Desc { - sorted.reverse(); + group.reverse(); } - - sorted_property_groups[i] = sorted; } let is_sorted = all(property_groups.iter().zip(&sorted_property_groups), |(a, b)| a == b); if !is_sorted { - ctx.diagnostic(sort_properties_diagnostic(node.span())) + ctx.diagnostic(sort_properties_diagnostic(node.span())); } } } @@ -207,19 +202,19 @@ impl Rule for SortKeys { fn alphanumeric_cmp(a: &str, b: &str) -> Ordering { /* regex key special case */ - if a.starts_with("/") && a.ends_with("/") { - if b.starts_with("/") && b.ends_with("/") { + if a.starts_with('/') && a.ends_with('/') { + if b.starts_with('/') && b.ends_with('/') { return a.cmp(b); } return Ordering::Greater; } - if b.starts_with("/") && b.ends_with("/") { + if b.starts_with('/') && b.ends_with('/') { return Ordering::Less; } /* empty keys special case */ - if a == "" && b.starts_with("[") || b == "" && a.starts_with("[") { + if a.is_empty() && b.starts_with('[') || b.is_empty() && a.starts_with('[') { return Ordering::Equal; } @@ -263,7 +258,7 @@ fn alphanumeric_cmp(a: &str, b: &str) -> Ordering { a.cmp(b) } -fn alphanumeric_sort(arr: &mut Vec) { +fn alphanumeric_sort(arr: &mut [String]) { arr.sort_by(|a, b| alphanumeric_cmp(a, b)); } @@ -284,15 +279,15 @@ fn natural_sort(arr: &mut [String]) { } } (Some(a_char), Some(b_char)) - if a_char.is_alphanumeric() && !b_char.is_alphanumeric() => - { - return Ordering::Greater - } + if a_char.is_alphanumeric() && !b_char.is_alphanumeric() => + { + return Ordering::Greater + } (Some(a_char), Some(b_char)) - if !a_char.is_alphanumeric() && b_char.is_alphanumeric() => - { - return Ordering::Less - } + if !a_char.is_alphanumeric() && b_char.is_alphanumeric() => + { + return Ordering::Less + } (Some(a_char), Some(b_char)) if a_char == '[' && b_char.is_alphanumeric() => { return Ordering::Greater } @@ -310,7 +305,7 @@ fn natural_sort(arr: &mut [String]) { fn take_numeric(iter: &mut Chars, first: char) -> u32 { let mut sum = first.to_digit(10).unwrap(); - while let Some(c) = iter.next() { + for c in iter.by_ref() { if let Some(digit) = c.to_digit(10) { sum = sum * 10 + digit; } else { @@ -329,7 +324,7 @@ fn extract_text_between_spans(source_text: &str, current_span: Span, next_span: fn extract_property_key(prop_text: &str) -> &str { let trimmed = prop_text.trim(); let before_colon = trimmed.split(':').next().unwrap_or(trimmed); - let without_quotes = before_colon.split("\"").next().unwrap_or(before_colon); + let without_quotes = before_colon.split('"').next().unwrap_or(before_colon); without_quotes.split('(').next().unwrap_or(before_colon) } From 232b5f02c7ae615663b43ac789c4a35b13ffc8d6 Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Sun, 18 Aug 2024 16:58:45 +0200 Subject: [PATCH 12/17] chore: added semi-columns to examples --- .../oxc_linter/src/rules/eslint/sort_keys.rs | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 959012228b0a5..745210afcd6ec 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -1,5 +1,5 @@ use crate::{context::LintContext, rule::Rule, AstNode}; -use itertools::{all}; +use itertools::all; use oxc_ast::ast::ObjectPropertyKind; use oxc_ast::syntax_directed_operations::PropName; use oxc_ast::AstKind; @@ -70,7 +70,7 @@ declare_oxc_lint!( /// let myObj { /// c: 1, /// a: 2, - /// } + /// }; /// ``` /// /// Examples of **correct** code for this rule: @@ -78,7 +78,7 @@ declare_oxc_lint!( /// let myObj { /// a: 2, /// c: 1, - /// } + /// }; /// ``` SortKeys, pedantic, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` @@ -98,12 +98,10 @@ impl Rule for SortKeys { let sort_order = if config_array.is_empty() { SortOrder::Asc } else { - config_array[0] - .as_str() - .map_or(SortOrder::Asc, |s| match s { - "desc" => SortOrder::Desc, - _ => SortOrder::Asc, - }) + config_array[0].as_str().map_or(SortOrder::Asc, |s| match s { + "desc" => SortOrder::Desc, + _ => SortOrder::Asc, + }) }; let config = if config_array.len() > 1 { @@ -117,9 +115,8 @@ impl Rule for SortKeys { let natural = config.get("natural").and_then(serde_json::Value::as_bool).unwrap_or(false); let min_keys = config .get("minKeys") - .and_then(serde_json::Value::as_u64).map_or(2, - |n| n.try_into().unwrap_or(2), - ); + .and_then(serde_json::Value::as_u64) + .map_or(2, |n| n.try_into().unwrap_or(2)); let allow_line_separated_groups = config .get("allowLineSeparatedGroups") .and_then(serde_json::Value::as_bool) @@ -279,15 +276,15 @@ fn natural_sort(arr: &mut [String]) { } } (Some(a_char), Some(b_char)) - if a_char.is_alphanumeric() && !b_char.is_alphanumeric() => - { - return Ordering::Greater - } + if a_char.is_alphanumeric() && !b_char.is_alphanumeric() => + { + return Ordering::Greater + } (Some(a_char), Some(b_char)) - if !a_char.is_alphanumeric() && b_char.is_alphanumeric() => - { - return Ordering::Less - } + if !a_char.is_alphanumeric() && b_char.is_alphanumeric() => + { + return Ordering::Less + } (Some(a_char), Some(b_char)) if a_char == '[' && b_char.is_alphanumeric() => { return Ordering::Greater } From a28963b09015174951a087b73f54314d2074e1e3 Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Mon, 19 Aug 2024 10:42:04 +0200 Subject: [PATCH 13/17] chore: fix missing equal signs --- crates/oxc_linter/src/rules/eslint/sort_keys.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 745210afcd6ec..3a1578b3cd99f 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -67,7 +67,7 @@ declare_oxc_lint!( /// /// Examples of **incorrect** code for this rule: /// ```js - /// let myObj { + /// let myObj = { /// c: 1, /// a: 2, /// }; @@ -75,7 +75,7 @@ declare_oxc_lint!( /// /// Examples of **correct** code for this rule: /// ```js - /// let myObj { + /// let myObj = { /// a: 2, /// c: 1, /// }; From 6c8b4c8530ce7185bcc41d76f6bbb6ad26fc9147 Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Thu, 22 Aug 2024 18:37:54 +0200 Subject: [PATCH 14/17] chore: removed todo comments --- crates/oxc_linter/src/rules/eslint/sort_keys.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 3a1578b3cd99f..15b5c70cf5864 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -81,12 +81,8 @@ declare_oxc_lint!( /// }; /// ``` SortKeys, - pedantic, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` - // See for details - - pending // TODO: describe fix capabilities. Remove if no fix can be done, - // keep at 'pending' if you think one could be added but don't know how. - // Options are 'fix', 'fix_dangerous', 'suggestion', and 'conditional_fix_suggestion' + pedantic, + pending ); impl Rule for SortKeys { From 719b608921bbd1e4e0ca2ffb45505e40b14c220b Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Fri, 6 Sep 2024 16:23:00 +0200 Subject: [PATCH 15/17] chore: address review comments --- crates/oxc_linter/src/rules/eslint/sort_keys.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 15b5c70cf5864..a62e50a29861d 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -88,7 +88,7 @@ declare_oxc_lint!( impl Rule for SortKeys { fn from_configuration(value: serde_json::Value) -> Self { let Some(config_array) = value.as_array() else { - return Self(Box::default()); + return Self::default(); }; let sort_order = if config_array.is_empty() { @@ -212,28 +212,26 @@ fn alphanumeric_cmp(a: &str, b: &str) -> Ordering { } let len = a.len().min(b.len()); - let a_chars: Vec = a.chars().take(len).collect(); - let b_chars: Vec = b.chars().take(len).collect(); - for (a_char, b_char) in a_chars.iter().zip(b_chars.iter()) { + for (a_char, b_char) in a.chars().take(len).zip(b.chars().take(len)) { if a_char == b_char { continue; } /* JS sorting apparently places _ after uppercase alphanumerics and before lowercase ones */ - if a_char.is_uppercase() && *b_char == '_' { + if a_char.is_uppercase() && b_char == '_' { return Ordering::Less; } - if *a_char == '_' && b_char.is_uppercase() { + if a_char == '_' && b_char.is_uppercase() { return Ordering::Greater; } /* computed properties should come before alpha numeric chars */ - if *a_char == '[' && b_char.is_alphanumeric() { + if a_char == '[' && b_char.is_alphanumeric() { return Ordering::Less; } - if a_char.is_alphanumeric() && *b_char == '[' { + if a_char.is_alphanumeric() && b_char == '[' { return Ordering::Greater; } @@ -245,7 +243,7 @@ fn alphanumeric_cmp(a: &str, b: &str) -> Ordering { return Ordering::Less; } - return a_char.cmp(b_char); + return a_char.cmp(&b_char); } a.cmp(b) From a3a712801fde5f5171d2489808f3051103b21b3c Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Wed, 11 Sep 2024 16:41:36 +0200 Subject: [PATCH 16/17] chore: ignore clippy issue --- crates/oxc_linter/src/rules/eslint/sort_keys.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index a62e50a29861d..487f927991cc9 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -1,3 +1,4 @@ +#![allow(clippy::print_stdout, clippy::disallowed_methods)] use crate::{context::LintContext, rule::Rule, AstNode}; use itertools::all; use oxc_ast::ast::ObjectPropertyKind; @@ -166,6 +167,7 @@ impl Rule for SortKeys { if !self.case_sensitive { for group in &mut property_groups { + *group = group.iter().map(|s| s.to_lowercase()).collect::>(); } } From f9547ff11a3e611adaa08dee8b68d4aea560a100 Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Wed, 11 Sep 2024 16:43:44 +0200 Subject: [PATCH 17/17] chore: applied just r --- crates/oxc_linter/src/rules/eslint/sort_keys.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/eslint/sort_keys.rs b/crates/oxc_linter/src/rules/eslint/sort_keys.rs index 487f927991cc9..429600d86e5d5 100644 --- a/crates/oxc_linter/src/rules/eslint/sort_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/sort_keys.rs @@ -167,7 +167,6 @@ impl Rule for SortKeys { if !self.case_sensitive { for group in &mut property_groups { - *group = group.iter().map(|s| s.to_lowercase()).collect::>(); } }