mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter): add infrastructure for handling early errors
This commit is contained in:
parent
8f26b9932a
commit
4e0d785b25
7 changed files with 89 additions and 12 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -896,8 +896,10 @@ dependencies = [
|
|||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
"oxc_diagnostics",
|
||||
"oxc_linter",
|
||||
"oxc_parser",
|
||||
"oxc_printer",
|
||||
"oxc_semantic",
|
||||
"pico-args",
|
||||
"rayon",
|
||||
"regex",
|
||||
|
|
|
|||
|
|
@ -13,15 +13,18 @@ use std::{fs, rc::Rc};
|
|||
pub use fixer::{Fixer, Message};
|
||||
pub(crate) use oxc_semantic::AstNode;
|
||||
use oxc_semantic::Semantic;
|
||||
use rule::Rule;
|
||||
|
||||
use crate::{
|
||||
context::LintContext,
|
||||
rules::{RuleEnum, RULES},
|
||||
rules::{early_error::javascript::EarlyErrorJavaScript, RuleEnum, RULES},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Linter {
|
||||
rules: Vec<RuleEnum>,
|
||||
|
||||
early_error_javascript: EarlyErrorJavaScript,
|
||||
}
|
||||
|
||||
impl Linter {
|
||||
|
|
@ -41,7 +44,12 @@ impl Linter {
|
|||
.collect()
|
||||
},
|
||||
);
|
||||
Self { rules }
|
||||
Self::from_rules(rules)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_rules(rules: Vec<RuleEnum>) -> Self {
|
||||
Self { rules, early_error_javascript: EarlyErrorJavaScript }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -63,12 +71,7 @@ impl Linter {
|
|||
},
|
||||
);
|
||||
|
||||
Self { rules }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_rules(rules: Vec<RuleEnum>) -> Self {
|
||||
Self { rules }
|
||||
Self::from_rules(rules)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -81,6 +84,7 @@ impl Linter {
|
|||
let ctx = LintContext::new(source_text, semantic.clone(), fix);
|
||||
|
||||
for node in semantic.nodes().iter() {
|
||||
self.early_error_javascript.run(node, &ctx);
|
||||
for rule in &self.rules {
|
||||
rule.run(node, &ctx);
|
||||
}
|
||||
|
|
@ -89,6 +93,20 @@ impl Linter {
|
|||
ctx.into_message()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn run_early_error<'a>(
|
||||
&self,
|
||||
semantic: &Rc<Semantic<'a>>,
|
||||
source_text: &'a str,
|
||||
fix: bool,
|
||||
) -> Vec<Message<'a>> {
|
||||
let ctx = LintContext::new(source_text, semantic.clone(), fix);
|
||||
for node in semantic.nodes().iter() {
|
||||
self.early_error_javascript.run(node, &ctx);
|
||||
}
|
||||
ctx.into_message()
|
||||
}
|
||||
|
||||
fn read_rules_configuration() -> Option<serde_json::Map<String, serde_json::Value>> {
|
||||
fs::read_to_string(".eslintrc.json")
|
||||
.ok()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
pub mod early_error {
|
||||
pub mod javascript;
|
||||
}
|
||||
|
||||
oxc_macros::declare_all_lint_rules! {
|
||||
constructor_super,
|
||||
eq_eq_eq,
|
||||
|
|
|
|||
33
crates/oxc_linter/src/rules/early_error/javascript.rs
Normal file
33
crates/oxc_linter/src/rules/early_error/javascript.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#[allow(clippy::wildcard_imports)]
|
||||
use oxc_ast::{ast::*, AstKind, Span};
|
||||
use oxc_diagnostics::{
|
||||
miette::{self, Diagnostic},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct EarlyErrorJavaScript;
|
||||
|
||||
impl Rule for EarlyErrorJavaScript {
|
||||
#[allow(clippy::single_match)]
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
match node.get().kind() {
|
||||
AstKind::RegExpLiteral(lit) => check_regexp_literal(lit, ctx),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_regexp_literal(lit: &RegExpLiteral, ctx: &LintContext) {
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("The 'u' and 'v' regular expression flags cannot be enabled at the same time")]
|
||||
#[diagnostic()]
|
||||
struct RegExpFlagUAndV(#[label] Span);
|
||||
|
||||
let flags = lit.regex.flags;
|
||||
if flags.contains(RegExpFlags::U | RegExpFlags::V) {
|
||||
ctx.diagnostic(RegExpFlagUAndV(lit.span));
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,8 @@ oxc_parser = { path = "../../crates/oxc_parser" }
|
|||
oxc_ast = { path = "../../crates/oxc_ast" }
|
||||
oxc_printer = { path = "../../crates/oxc_printer" }
|
||||
oxc_diagnostics = { path = "../../crates/oxc_diagnostics" }
|
||||
oxc_semantic = { path = "../../crates/oxc_semantic" }
|
||||
oxc_linter = { path = "../../crates/oxc_linter" }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use std::{
|
|||
io::{stdout, Read, Write},
|
||||
panic::{catch_unwind, UnwindSafe},
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
result::Result,
|
||||
};
|
||||
|
||||
|
|
@ -12,7 +13,9 @@ use encoding_rs_io::DecodeReaderBytesBuilder;
|
|||
use oxc_allocator::Allocator;
|
||||
use oxc_ast::SourceType;
|
||||
use oxc_diagnostics::miette::{GraphicalReportHandler, GraphicalTheme, NamedSource};
|
||||
use oxc_linter::Linter;
|
||||
use oxc_parser::Parser;
|
||||
use oxc_semantic::SemanticBuilder;
|
||||
use rayon::prelude::*;
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use walkdir::WalkDir;
|
||||
|
|
@ -262,12 +265,21 @@ pub trait Case: Sized + Sync + Send + UnwindSafe {
|
|||
let allocator = Allocator::default();
|
||||
let source_text = self.code();
|
||||
let ret = Parser::new(&allocator, source_text, source_type).parse();
|
||||
let result = if ret.errors.is_empty() {
|
||||
let program = allocator.alloc(ret.program);
|
||||
let trivias = Rc::new(ret.trivias);
|
||||
let semantic = SemanticBuilder::new(source_type).build(program, trivias);
|
||||
let result = Linter::new().run_early_error(&Rc::new(semantic), source_text, false);
|
||||
let errors = result
|
||||
.into_iter()
|
||||
.map(|msg| msg.error)
|
||||
.chain(ret.errors.into_iter())
|
||||
.collect::<Vec<_>>();
|
||||
let result = if errors.is_empty() {
|
||||
Ok(String::new())
|
||||
} else {
|
||||
let handler = GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor());
|
||||
let mut output = String::new();
|
||||
for error in ret.errors {
|
||||
for error in errors {
|
||||
let error = error.with_source_code(NamedSource::new(
|
||||
self.path().to_string_lossy(),
|
||||
source_text.to_string(),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
Test262 Summary:
|
||||
AST Parsed : 44000/44009 (99.98%)
|
||||
Positive Passed: 44000/44009 (99.98%)
|
||||
Negative Passed: 1934/3917 (49.37%)
|
||||
Negative Passed: 1935/3917 (49.40%)
|
||||
Expect Syntax Error: "annexB/language/expressions/template-literal/legacy-octal-escape-sequence-strict.js"
|
||||
Expect Syntax Error: "annexB/language/statements/for-in/const-initializer.js"
|
||||
Expect Syntax Error: "annexB/language/statements/for-in/let-initializer.js"
|
||||
Expect Syntax Error: "annexB/language/statements/for-in/strict-initializer.js"
|
||||
Expect Syntax Error: "annexB/language/statements/for-in/var-arraybindingpattern-initializer.js"
|
||||
Expect Syntax Error: "annexB/language/statements/for-in/var-objectbindingpattern-initializer.js"
|
||||
Expect Syntax Error: "built-ins/RegExp/prototype/unicodeSets/uv-flags.js"
|
||||
Expect Syntax Error: "language/arguments-object/10.5-1gs.js"
|
||||
Expect Syntax Error: "language/block-scope/syntax/for-in/disallow-initialization-assignment.js"
|
||||
Expect Syntax Error: "language/block-scope/syntax/for-in/disallow-multiple-lexical-bindings-with-and-without-initializer.js"
|
||||
|
|
@ -2071,6 +2070,13 @@ Expect to Parse: "language/statements/class/decorator/syntax/class-valid/decorat
|
|||
· ─────
|
||||
╰────
|
||||
|
||||
× The 'u' and 'v' regular expression flags cannot be enabled at the same time
|
||||
╭─[built-ins/RegExp/prototype/unicodeSets/uv-flags.js:16:1]
|
||||
16 │
|
||||
17 │ /./uv;
|
||||
· ─────
|
||||
╰────
|
||||
|
||||
× Automatic Semicolon Insertion
|
||||
╭─[language/asi/S7.9.2_A1_T1.js:15:1]
|
||||
15 │ //CHECK#1
|
||||
|
|
|
|||
Loading…
Reference in a new issue