feat(linter): configure rules from json

This commit is contained in:
Boshen 2023-02-26 15:46:30 +08:00
parent a342a01c4a
commit 5c64b4874c
7 changed files with 66 additions and 48 deletions

1
Cargo.lock generated
View file

@ -884,6 +884,7 @@ dependencies = [
"oxc_diagnostics",
"oxc_parser",
"oxc_semantic",
"serde_json",
]
[[package]]

View file

@ -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" }

View file

@ -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>);
}

View file

@ -27,6 +27,17 @@ impl RuleEnum {
}
}
pub fn from_json(&self, maybe_value: Option<serde_json::Value>) -> 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),

View file

@ -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();
}

View file

@ -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();

View file

@ -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<String>,
expect_fail: Vec<String>,
expect_pass: Vec<(String, Option<Value>)>,
expect_fail: Vec<(String, Option<Value>)>,
snapshot: String,
}
impl Tester {
#[allow(clippy::needless_pass_by_value)]
pub fn new<S: Into<String>>(
rule_name: &'static str,
expect_pass: Vec<S>,
expect_fail: Vec<S>,
expect_pass: Vec<(S, Option<Value>)>,
expect_fail: Vec<(S, Option<Value>)>,
) -> Self {
let expect_pass = expect_pass.into_iter().map(Into::into).collect::<Vec<_>>();
let expect_fail = expect_fail.into_iter().map(Into::into).collect::<Vec<_>>();
let expect_pass = expect_pass.into_iter().map(|(s, r)| (s.into(), r)).collect::<Vec<_>>();
let expect_fail = expect_fail.into_iter().map(|(s, r)| (s.into(), r)).collect::<Vec<_>>();
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<Value>) -> 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;
}