From 5c64b4874c28bbe575045fb009f2c8c16abac166 Mon Sep 17 00:00:00 2001 From: Boshen Date: Sun, 26 Feb 2023 15:46:30 +0800 Subject: [PATCH] feat(linter): configure rules from json --- Cargo.lock | 1 + crates/oxc_linter/Cargo.toml | 1 + crates/oxc_linter/src/rule.rs | 4 ++ crates/oxc_linter/src/rules.rs | 11 ++++ crates/oxc_linter/src/rules/no_debugger.rs | 4 +- crates/oxc_linter/src/rules/no_empty.rs | 66 +++++++++++----------- crates/oxc_linter/src/tester.rs | 27 ++++----- 7 files changed, 66 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08c6e0173..64ae031b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -884,6 +884,7 @@ dependencies = [ "oxc_diagnostics", "oxc_parser", "oxc_semantic", + "serde_json", ] [[package]] diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index 8c593a4ff..494034c00 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -15,6 +15,7 @@ oxc_diagnostics = { path = "../oxc_diagnostics" } oxc_semantic = { path = "../oxc_semantic" } lazy_static = { workspace = true } +serde_json = { workspace = true } [dev_dependencies] oxc_allocator = { path = "../oxc_allocator" } diff --git a/crates/oxc_linter/src/rule.rs b/crates/oxc_linter/src/rule.rs index f8655affb..8ffbbfa87 100644 --- a/crates/oxc_linter/src/rule.rs +++ b/crates/oxc_linter/src/rule.rs @@ -5,5 +5,9 @@ use crate::{context::LintContext, AstNode}; pub trait Rule: Sized + Default + Debug { const NAME: &'static str; + fn from_json(_value: serde_json::Value) -> Self { + Self::default() + } + fn run<'a>(&self, node: &AstNode<'a>, _ctx: &LintContext<'a>); } diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index ef3c4580b..94bfc0a6e 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -27,6 +27,17 @@ impl RuleEnum { } } + pub fn from_json(&self, maybe_value: Option) -> Self { + match self { + Self::NoDebugger(_) => { + RuleEnum::NoDebugger(maybe_value.map(NoDebugger::from_json).unwrap_or_default()) + } + Self::NoEmpty(_) => { + RuleEnum::NoEmpty(maybe_value.map(NoEmpty::from_json).unwrap_or_default()) + } + } + } + pub fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { match self { Self::NoDebugger(rule) => rule.run(node, ctx), diff --git a/crates/oxc_linter/src/rules/no_debugger.rs b/crates/oxc_linter/src/rules/no_debugger.rs index a8d3594a0..aab4614f3 100644 --- a/crates/oxc_linter/src/rules/no_debugger.rs +++ b/crates/oxc_linter/src/rules/no_debugger.rs @@ -30,9 +30,9 @@ impl Rule for NoDebugger { fn test() { use crate::tester::Tester; - let pass = vec!["var test = { debugger: 1 }; test.debugger;"]; + let pass = vec![("var test = { debugger: 1 }; test.debugger;", None)]; - let fail = vec!["if (foo) debugger"]; + let fail = vec![("if (foo) debugger", None)]; Tester::new(RULE_NAME, pass, fail).test_and_snapshot(); } diff --git a/crates/oxc_linter/src/rules/no_empty.rs b/crates/oxc_linter/src/rules/no_empty.rs index b6df5cb3e..01033f45c 100644 --- a/crates/oxc_linter/src/rules/no_empty.rs +++ b/crates/oxc_linter/src/rules/no_empty.rs @@ -48,43 +48,43 @@ fn test() { use crate::tester::Tester; let pass = vec![ - "if (foo) { bar() }", - "while (foo) { bar() }", - "for (;foo;) { bar() }", - "try { foo() } catch (ex) { foo() }", - "switch(foo) {case 'foo': break;}", - "(function() { }())", - "var foo = () => {};", - "function foo() { }", - "if (foo) {/* empty */}", - "while (foo) {/* empty */}", - "for (;foo;) {/* empty */}", - "try { foo() } catch (ex) {/* empty */}", - "try { foo() } catch (ex) {// empty\n}", - "try { foo() } finally {// empty\n}", - "try { foo() } finally {// test\n}", - "try { foo() } finally {\n \n // hi i am off no use\n}", - "try { foo() } catch (ex) {/* test111 */}", - "if (foo) { bar() } else { // nothing in me \n}", - "if (foo) { bar() } else { /**/ \n}", - "if (foo) { bar() } else { // \n}", - "try { foo(); } catch (ex) {}", - "try { foo(); } catch (ex) {} finally { bar(); }", + ("if (foo) { bar() }", None), + ("while (foo) { bar() }", None), + ("for (;foo;) { bar() }", None), + ("try { foo() } catch (ex) { foo() }", None), + ("switch(foo) {case 'foo': break;}", None), + ("(function() { }())", None), + ("var foo = () => {};", None), + ("function foo() { }", None), + ("if (foo) {/* empty */}", None), + ("while (foo) {/* empty */}", None), + ("for (;foo;) {/* empty */}", None), + ("try { foo() } catch (ex) {/* empty */}", None), + ("try { foo() } catch (ex) {// empty\n}", None), + ("try { foo() } finally {// empty\n}", None), + ("try { foo() } finally {// test\n}", None), + ("try { foo() } finally {\n \n // hi i am off no use\n}", None), + ("try { foo() } catch (ex) {/* test111 */}", None), + ("if (foo) { bar() } else { // nothing in me \n}", None), + ("if (foo) { bar() } else { /**/ \n}", None), + ("if (foo) { bar() } else { // \n}", None), + ("try { foo(); } catch (ex) {}", None), + ("try { foo(); } catch (ex) {} finally { bar(); }", None), ]; let fail = vec![ - "try {} catch (ex) {throw ex}", + ("try {} catch (ex) {throw ex}", None), + ("try { foo() } catch (ex) {throw ex} finally {}", None), // "try { foo() } catch (ex) {}", // TODO: options - "try { foo() } catch (ex) {throw ex} finally {}", - "if (foo) {}", - "while (foo) {}", - "for (;foo;) {}", - "switch(foo) {}", - "switch (foo) { /* empty */ }", - "try {} catch (ex) {}", - "try { foo(); } catch (ex) {} finally {}", - "try {} catch (ex) {} finally {}", - "try { foo(); } catch (ex) {} finally {}", + ("if (foo) {}", None), + ("while (foo) {}", None), + ("for (;foo;) {}", None), + ("switch(foo) {}", None), + ("switch (foo) { /* empty */ }", None), + ("try {} catch (ex) {}", None), + ("try { foo(); } catch (ex) {} finally {}", None), + ("try {} catch (ex) {} finally {}", None), + ("try { foo(); } catch (ex) {} finally {}", None), ]; Tester::new(RULE_NAME, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/tester.rs b/crates/oxc_linter/src/tester.rs index 21a5e3e83..e64a0c5b4 100644 --- a/crates/oxc_linter/src/tester.rs +++ b/crates/oxc_linter/src/tester.rs @@ -5,25 +5,25 @@ use oxc_ast::SourceType; use oxc_diagnostics::miette::{GraphicalReportHandler, GraphicalTheme, NamedSource}; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; +use serde_json::Value; use crate::{rules::RULES, Linter}; pub struct Tester { rule_name: &'static str, - expect_pass: Vec, - expect_fail: Vec, + expect_pass: Vec<(String, Option)>, + expect_fail: Vec<(String, Option)>, snapshot: String, } impl Tester { - #[allow(clippy::needless_pass_by_value)] pub fn new>( rule_name: &'static str, - expect_pass: Vec, - expect_fail: Vec, + expect_pass: Vec<(S, Option)>, + expect_fail: Vec<(S, Option)>, ) -> Self { - let expect_pass = expect_pass.into_iter().map(Into::into).collect::>(); - let expect_fail = expect_fail.into_iter().map(Into::into).collect::>(); + let expect_pass = expect_pass.into_iter().map(|(s, r)| (s.into(), r)).collect::>(); + let expect_fail = expect_fail.into_iter().map(|(s, r)| (s.into(), r)).collect::>(); Self { rule_name, expect_pass, expect_fail, snapshot: String::new() } } @@ -34,15 +34,15 @@ impl Tester { } fn test_pass(&mut self) { - for test in self.expect_pass.clone() { - let passed = self.run(&test); + for (test, config) in self.expect_pass.clone() { + let passed = self.run(&test, config); assert!(passed, "expect test to pass: {test} {}", self.snapshot); } } fn test_fail(&mut self) { - for test in self.expect_fail.clone() { - let passed = self.run(&test); + for (test, config) in self.expect_fail.clone() { + let passed = self.run(&test, config); assert!(!passed, "expect test to fail: {test}"); } } @@ -54,7 +54,7 @@ impl Tester { }); } - fn run(&mut self, source_text: &str) -> bool { + fn run(&mut self, source_text: &str, config: Option) -> bool { let name = self.rule_name.replace('-', "_"); let allocator = Allocator::default(); let path = PathBuf::from(name).with_extension("tsx"); @@ -65,7 +65,8 @@ impl Tester { let semantic = SemanticBuilder::new().build(program, ret.trivias); let semantic = std::rc::Rc::new(semantic); let rule = RULES.iter().find(|rule| rule.name() == self.rule_name).unwrap(); - let diagnostics = Linter::from_rules(vec![rule.clone()]).run(&semantic); + let rule = rule.from_json(config); + let diagnostics = Linter::from_rules(vec![rule]).run(&semantic); if diagnostics.is_empty() { return true; }