diff --git a/Cargo.lock b/Cargo.lock index 83caff470..6967b4016 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -896,8 +896,10 @@ dependencies = [ "oxc_allocator", "oxc_ast", "oxc_diagnostics", + "oxc_linter", "oxc_parser", "oxc_printer", + "oxc_semantic", "pico-args", "rayon", "regex", diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index 1f3dce2e3..854c0d0d6 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -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, + + 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) -> 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) -> 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>, + source_text: &'a str, + fix: bool, + ) -> Vec> { + 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> { fs::read_to_string(".eslintrc.json") .ok() diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 26d5e0728..48d248b3a 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -1,3 +1,7 @@ +pub mod early_error { + pub mod javascript; +} + oxc_macros::declare_all_lint_rules! { constructor_super, eq_eq_eq, diff --git a/crates/oxc_linter/src/rules/early_error/javascript.rs b/crates/oxc_linter/src/rules/early_error/javascript.rs new file mode 100644 index 000000000..3868ec01c --- /dev/null +++ b/crates/oxc_linter/src/rules/early_error/javascript.rs @@ -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)); + } +} diff --git a/tasks/coverage/Cargo.toml b/tasks/coverage/Cargo.toml index 8b2b414bf..61883508d 100644 --- a/tasks/coverage/Cargo.toml +++ b/tasks/coverage/Cargo.toml @@ -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 } diff --git a/tasks/coverage/src/suite.rs b/tasks/coverage/src/suite.rs index 25312b41b..49455872a 100644 --- a/tasks/coverage/src/suite.rs +++ b/tasks/coverage/src/suite.rs @@ -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::>(); + 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(), diff --git a/tasks/coverage/test262.snap b/tasks/coverage/test262.snap index 86d1ca3d8..ffa48380b 100644 --- a/tasks/coverage/test262.snap +++ b/tasks/coverage/test262.snap @@ -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