diff --git a/crates/oxc_language_server/src/linter.rs b/crates/oxc_language_server/src/linter.rs index 21e8cb9e419b4..b92f25e98f929 100644 --- a/crates/oxc_language_server/src/linter.rs +++ b/crates/oxc_language_server/src/linter.rs @@ -290,9 +290,10 @@ impl IsolatedLintHandler { return Some(Self::wrap_diagnostics(path, &source_text, reports, start)); }; + let module_record = Arc::new(ret.module_record); let mut semantic = semantic_ret.semantic; semantic.set_irregular_whitespaces(ret.irregular_whitespaces); - let result = self.linter.run(path, Rc::new(semantic)); + let result = self.linter.run(path, Rc::new(semantic), module_record); let reports = result .into_iter() diff --git a/crates/oxc_linter/src/context/host.rs b/crates/oxc_linter/src/context/host.rs index 3fd3be0f0f16a..15cdf30ace115 100644 --- a/crates/oxc_linter/src/context/host.rs +++ b/crates/oxc_linter/src/context/host.rs @@ -1,6 +1,8 @@ +use std::{cell::RefCell, path::Path, rc::Rc, sync::Arc}; + use oxc_semantic::Semantic; use oxc_span::SourceType; -use std::{cell::RefCell, path::Path, rc::Rc, sync::Arc}; +use oxc_syntax::module_record::ModuleRecord; use crate::{ config::{LintConfig, LintPlugins}, @@ -37,6 +39,8 @@ pub(crate) struct ContextHost<'a> { /// Shared semantic information about the file being linted, which includes scopes, symbols /// and AST nodes. See [`Semantic`]. pub(super) semantic: Rc>, + /// Cross module information. + pub(super) module_record: Arc, /// Information about specific rules that should be disabled or enabled, via comment directives like /// `eslint-disable` or `eslint-disable-next-line`. pub(super) disable_directives: DisableDirectives<'a>, @@ -67,6 +71,7 @@ impl<'a> ContextHost<'a> { pub fn new>( file_path: P, semantic: Rc>, + module_record: Arc, options: LintOptions, config: Arc, ) -> Self { @@ -87,6 +92,7 @@ impl<'a> ContextHost<'a> { Self { semantic, + module_record, disable_directives, diagnostics: RefCell::new(Vec::with_capacity(DIAGNOSTICS_INITIAL_CAPACITY)), fix: options.fix, @@ -119,6 +125,12 @@ impl<'a> ContextHost<'a> { &self.semantic } + /// Shared reference to the [`ModuleRecord`] of the file. + #[inline] + pub fn module_record(&self) -> &ModuleRecord { + &self.module_record + } + /// Path to the file being linted. /// /// When created from a [`LintService`](`crate::service::LintService`), this @@ -214,9 +226,9 @@ impl<'a> ContextHost<'a> { if self.plugins.has_test() { // let mut test_flags = FrameworkFlags::empty(); - let vitest_like = frameworks::has_vitest_imports(self.semantic.module_record()); + let vitest_like = frameworks::has_vitest_imports(self.module_record()); let jest_like = frameworks::is_jestlike_file(&self.file_path) - || frameworks::has_jest_imports(self.semantic.module_record()); + || frameworks::has_jest_imports(self.module_record()); self.frameworks.set(FrameworkFlags::Vitest, vitest_like); self.frameworks.set(FrameworkFlags::Jest, jest_like); diff --git a/crates/oxc_linter/src/context/mod.rs b/crates/oxc_linter/src/context/mod.rs index 6076b8767dea7..d02c0d568221c 100644 --- a/crates/oxc_linter/src/context/mod.rs +++ b/crates/oxc_linter/src/context/mod.rs @@ -7,6 +7,7 @@ use oxc_cfg::ControlFlowGraph; use oxc_diagnostics::{OxcDiagnostic, Severity}; use oxc_semantic::Semantic; use oxc_span::{GetSpan, Span}; +use oxc_syntax::module_record::ModuleRecord; #[cfg(debug_assertions)] use crate::rule::RuleFixMeta; @@ -105,6 +106,11 @@ impl<'a> LintContext<'a> { &self.parent.semantic } + #[inline] + pub fn module_record(&self) -> &ModuleRecord { + self.parent.module_record() + } + /// Get the control flow graph for the current program. #[inline] pub fn cfg(&self) -> &ControlFlowGraph { diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index fcffc292c5f4b..06fa743a0bfdb 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -21,14 +21,10 @@ mod utils; pub mod loader; pub mod table; -use crate::config::ResolvedLinterState; use std::{io::Write, path::Path, rc::Rc, sync::Arc}; -use config::{ConfigStore, LintConfig}; -use context::ContextHost; -use options::LintOptions; use oxc_semantic::{AstNode, Semantic}; -use utils::iter_possible_jest_call_node; +use oxc_syntax::module_record::ModuleRecord; pub use crate::{ builder::{LinterBuilder, LinterBuilderError}, @@ -41,10 +37,15 @@ pub use crate::{ service::{LintService, LintServiceOptions}, }; use crate::{ - config::{OxlintEnv, OxlintGlobals, OxlintSettings}, + config::{ + ConfigStore, LintConfig, OxlintEnv, OxlintGlobals, OxlintSettings, ResolvedLinterState, + }, + context::ContextHost, fixer::{Fixer, Message}, + options::LintOptions, rules::RuleEnum, table::RuleTable, + utils::iter_possible_jest_call_node, }; #[cfg(target_pointer_width = "64")] @@ -110,10 +111,16 @@ impl Linter { self.config.rules() } - pub fn run<'a>(&self, path: &Path, semantic: Rc>) -> Vec> { + pub fn run<'a>( + &self, + path: &Path, + semantic: Rc>, + module_record: Arc, + ) -> Vec> { // Get config + rules for this file. Takes base rules and applies glob-based overrides. let ResolvedLinterState { rules, config } = self.config.resolve(path); - let ctx_host = Rc::new(ContextHost::new(path, semantic, self.options, config)); + let ctx_host = + Rc::new(ContextHost::new(path, semantic, module_record, self.options, config)); let rules = rules .iter() diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs index cc04771d3ba5a..094f05c94642f 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs @@ -2,6 +2,7 @@ //! consider variables ignored by name pattern, but by where they are declared. use oxc_ast::{ast::*, AstKind}; use oxc_semantic::{NodeId, Semantic}; +use oxc_syntax::module_record::ModuleRecord; use super::{options::ArgsOption, NoUnusedVars, Symbol}; use crate::rules::eslint::no_unused_vars::binding_pattern::{BindingContext, HasAnyUsedBinding}; @@ -120,6 +121,7 @@ impl NoUnusedVars { pub(super) fn is_allowed_argument<'a>( &self, semantic: &Semantic<'a>, + module_record: &ModuleRecord, symbol: &Symbol<'_, 'a>, param: &FormalParameter<'a>, ) -> bool { @@ -178,7 +180,7 @@ impl NoUnusedVars { return false; } - let ctx = BindingContext { options: self, semantic }; + let ctx = BindingContext { options: self, semantic, module_record }; params .items .iter() diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/binding_pattern.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/binding_pattern.rs index eb11b4f179708..51243df3eb79c 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/binding_pattern.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/binding_pattern.rs @@ -1,5 +1,6 @@ use oxc_ast::ast::*; use oxc_semantic::{Semantic, SymbolId}; +use oxc_syntax::module_record::ModuleRecord; use super::{symbol::Symbol, NoUnusedVars}; @@ -7,16 +8,18 @@ use super::{symbol::Symbol, NoUnusedVars}; pub(super) struct BindingContext<'s, 'a> { pub options: &'s NoUnusedVars, pub semantic: &'s Semantic<'a>, + pub module_record: &'s ModuleRecord, } + impl<'s, 'a> BindingContext<'s, 'a> { #[inline] - pub fn symbol(&self, symbol_id: SymbolId) -> Symbol<'s, 'a> { - Symbol::new(self.semantic, symbol_id) + pub fn symbol(&self, module_record: &'s ModuleRecord, symbol_id: SymbolId) -> Symbol<'s, 'a> { + Symbol::new(self.semantic, module_record, symbol_id) } #[inline] - pub fn has_usages(&self, symbol_id: SymbolId) -> bool { - self.symbol(symbol_id).has_usages(self.options) + pub fn has_usages(&self, symbol_id: SymbolId, module_record: &'s ModuleRecord) -> bool { + self.symbol(module_record, symbol_id).has_usages(self.options) } } @@ -44,7 +47,7 @@ impl<'a> HasAnyUsedBinding<'a> for BindingPatternKind<'a> { impl<'a> HasAnyUsedBinding<'a> for BindingIdentifier<'a> { fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool { - ctx.has_usages(self.symbol_id()) + ctx.has_usages(self.symbol_id(), ctx.module_record) } } impl<'a> HasAnyUsedBinding<'a> for ObjectPattern<'a> { diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs index 9f4dcdc1a4393..6ae3bb022f932 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs @@ -205,7 +205,7 @@ impl Rule for NoUnusedVars { } fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) { - let symbol = Symbol::new(ctx.semantic().as_ref(), symbol_id); + let symbol = Symbol::new(ctx.semantic().as_ref(), ctx.module_record(), symbol_id); if Self::should_skip_symbol(&symbol) { return; } @@ -294,7 +294,12 @@ impl NoUnusedVars { }); } AstKind::FormalParameter(param) => { - if self.is_allowed_argument(ctx.semantic().as_ref(), symbol, param) { + if self.is_allowed_argument( + ctx.semantic().as_ref(), + ctx.module_record(), + symbol, + param, + ) { return; } ctx.diagnostic(diagnostic::param(symbol, &self.args_ignore_pattern)); diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/symbol.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/symbol.rs index 2edc3bc71ed84..055c7c6afbc53 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/symbol.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/symbol.rs @@ -12,10 +12,12 @@ use oxc_semantic::{ SymbolTable, }; use oxc_span::{GetSpan, Span}; +use oxc_syntax::module_record::ModuleRecord; #[derive(Clone)] pub(super) struct Symbol<'s, 'a> { semantic: &'s Semantic<'a>, + module_record: &'s ModuleRecord, id: SymbolId, flags: SymbolFlags, span: OnceCell, @@ -29,9 +31,13 @@ impl PartialEq for Symbol<'_, '_> { // constructor and simple getters impl<'s, 'a> Symbol<'s, 'a> { - pub fn new(semantic: &'s Semantic<'a>, symbol_id: SymbolId) -> Self { + pub fn new( + semantic: &'s Semantic<'a>, + module_record: &'s ModuleRecord, + symbol_id: SymbolId, + ) -> Self { let flags = semantic.symbols().get_flags(symbol_id); - Self { semantic, id: symbol_id, flags, span: OnceCell::new() } + Self { semantic, module_record, id: symbol_id, flags, span: OnceCell::new() } } #[inline] @@ -172,7 +178,7 @@ impl<'a> Symbol<'_, 'a> { pub fn is_exported(&self) -> bool { let is_in_exportable_scope = self.is_root() || self.is_in_ts_namespace(); is_in_exportable_scope - && (self.semantic.module_record().exported_bindings.contains_key(self.name()) + && (self.module_record.exported_bindings.contains_key(self.name()) || self.in_export_node()) } diff --git a/crates/oxc_linter/src/rules/import/named.rs b/crates/oxc_linter/src/rules/import/named.rs index 80b1e9cda2d80..f262f8db38851 100644 --- a/crates/oxc_linter/src/rules/import/named.rs +++ b/crates/oxc_linter/src/rules/import/named.rs @@ -86,7 +86,7 @@ impl Rule for Named { return; } - let module_record = semantic.module_record(); + let module_record = ctx.module_record(); for import_entry in &module_record.import_entries { // Get named import diff --git a/crates/oxc_linter/src/rules/import/unambiguous.rs b/crates/oxc_linter/src/rules/import/unambiguous.rs index dfdcd638e98bb..b775eacf25384 100644 --- a/crates/oxc_linter/src/rules/import/unambiguous.rs +++ b/crates/oxc_linter/src/rules/import/unambiguous.rs @@ -48,7 +48,7 @@ declare_oxc_lint!( /// impl Rule for Unambiguous { fn run_once(&self, ctx: &LintContext<'_>) { - if ctx.semantic().module_record().not_esm { + if ctx.module_record().not_esm { ctx.diagnostic(unambiguous_diagnostic(Span::default())); } } diff --git a/crates/oxc_linter/src/rules/oxc/no_barrel_file.rs b/crates/oxc_linter/src/rules/oxc/no_barrel_file.rs index 3146b690985f6..695114055d74c 100644 --- a/crates/oxc_linter/src/rules/oxc/no_barrel_file.rs +++ b/crates/oxc_linter/src/rules/oxc/no_barrel_file.rs @@ -69,8 +69,7 @@ impl Rule for NoBarrelFile { } fn run_once(&self, ctx: &LintContext<'_>) { - let semantic = ctx.semantic(); - let module_record = semantic.module_record(); + let module_record = ctx.module_record(); if module_record.not_esm { return; diff --git a/crates/oxc_linter/src/rules/react/jsx_key.rs b/crates/oxc_linter/src/rules/react/jsx_key.rs index 68ba91a629638..ecdcc23b720aa 100644 --- a/crates/oxc_linter/src/rules/react/jsx_key.rs +++ b/crates/oxc_linter/src/rules/react/jsx_key.rs @@ -97,7 +97,7 @@ pub fn import_matcher<'a>( expected_module_name: &'a str, ) -> bool { let expected_module_name = expected_module_name.cow_to_lowercase(); - ctx.semantic().module_record().import_entries.iter().any(|import| { + ctx.module_record().import_entries.iter().any(|import| { import.module_request.name().as_str() == expected_module_name && import.local_name.name().as_str() == actual_local_name }) @@ -109,7 +109,7 @@ pub fn is_import<'a>( expected_local_name: &'a str, expected_module_name: &'a str, ) -> bool { - if ctx.semantic().module_record().requested_modules.is_empty() + if ctx.module_record().requested_modules.is_empty() && ctx.scopes().get_bindings(ctx.scopes().root_scope_id()).is_empty() { return actual_local_name == expected_local_name; diff --git a/crates/oxc_linter/src/service/runtime.rs b/crates/oxc_linter/src/service/runtime.rs index 8f5d95ac79982..440c15a397d7f 100644 --- a/crates/oxc_linter/src/service/runtime.rs +++ b/crates/oxc_linter/src/service/runtime.rs @@ -297,9 +297,8 @@ impl Runtime { }; let mut semantic = semantic_ret.semantic; - semantic.set_module_record(&module_record); semantic.set_irregular_whitespaces(ret.irregular_whitespaces); - self.linter.run(path, Rc::new(semantic)) + self.linter.run(path, Rc::new(semantic), Arc::clone(&module_record)) } pub(super) fn init_cache_state(&self, path: &Path) -> bool { diff --git a/crates/oxc_linter/src/utils/jest.rs b/crates/oxc_linter/src/utils/jest.rs index 8a16b773f9a41..2586ee588d5dd 100644 --- a/crates/oxc_linter/src/utils/jest.rs +++ b/crates/oxc_linter/src/utils/jest.rs @@ -321,10 +321,12 @@ mod test { SemanticBuilder::new().with_cfg(true).build(&parser_ret.program).semantic; let semantic_ret = Rc::new(semantic_ret); + let module_record = Arc::new(parser_ret.module_record); let build_ctx = |path: &'static str| { Rc::new(ContextHost::new( path, Rc::clone(&semantic_ret), + Arc::clone(&module_record), LintOptions::default(), Arc::default(), )) diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index a2641ad91d399..ea4c6b7e4d628 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -1,9 +1,6 @@ //! Semantic Builder -use std::{ - cell::{Cell, RefCell}, - sync::Arc, -}; +use std::cell::{Cell, RefCell}; use rustc_hash::FxHashMap; @@ -289,7 +286,6 @@ impl<'a> SemanticBuilder<'a> { source_type: self.source_type, comments, irregular_whitespaces: [].into(), - module_record: Arc::new(oxc_syntax::module_record::ModuleRecord::default()), nodes: self.nodes, scopes: self.scope, symbols: self.symbols, diff --git a/crates/oxc_semantic/src/lib.rs b/crates/oxc_semantic/src/lib.rs index 966ef9e0a4f5f..88205849214c7 100644 --- a/crates/oxc_semantic/src/lib.rs +++ b/crates/oxc_semantic/src/lib.rs @@ -6,14 +6,12 @@ //! ``` use std::ops::RangeBounds; -use std::sync::Arc; use oxc_ast::{ ast::IdentifierReference, comments_range, has_comments_between, AstKind, Comment, CommentsRange, }; use oxc_cfg::ControlFlowGraph; use oxc_span::{GetSpan, SourceType, Span}; -use oxc_syntax::module_record::ModuleRecord; pub use oxc_syntax::{ scope::{ScopeFlags, ScopeId}, symbol::{SymbolFlags, SymbolId}, @@ -81,8 +79,6 @@ pub struct Semantic<'a> { comments: &'a oxc_allocator::Vec<'a, Comment>, irregular_whitespaces: Box<[Span]>, - module_record: Arc, - /// Parsed JSDoc comments. jsdoc: JSDocFinder<'a>, @@ -134,10 +130,6 @@ impl<'a> Semantic<'a> { self.irregular_whitespaces = irregular_whitespaces; } - pub fn set_module_record(&mut self, module_record: &Arc) { - self.module_record = Arc::clone(module_record); - } - /// Trivias (comments) found while parsing pub fn comments(&self) -> &[Comment] { self.comments @@ -165,11 +157,6 @@ impl<'a> Semantic<'a> { &self.jsdoc } - /// ESM module record containing imports and exports. - pub fn module_record(&self) -> &ModuleRecord { - self.module_record.as_ref() - } - /// [`SymbolTable`] containing all symbols in the program and their /// [`Reference`]s. pub fn symbols(&self) -> &SymbolTable { diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index 734170cb20f00..84d8764d78244 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -7,6 +7,7 @@ use std::{ cell::{Cell, RefCell}, path::{Path, PathBuf}, rc::Rc, + sync::Arc, }; use oxc::{ @@ -20,6 +21,7 @@ use oxc::{ ScopeFlags, ScopeId, ScopeTree, SemanticBuilder, SymbolTable, }, span::SourceType, + syntax::module_record::ModuleRecord, transformer::{TransformOptions, Transformer}, }; use oxc_index::Idx; @@ -198,7 +200,7 @@ impl Oxc { .preserve_parens .unwrap_or(default_parser_options.preserve_parens), }; - let ParserReturn { mut program, errors, .. } = + let ParserReturn { mut program, errors, module_record, .. } = Parser::new(&allocator, source_text, source_type) .with_options(oxc_parser_options) .parse(); @@ -227,7 +229,8 @@ impl Oxc { ); } - self.run_linter(&run_options, &path, &program); + let module_record = Arc::new(module_record); + self.run_linter(&run_options, &path, &program, &module_record); self.run_prettier(&run_options, source_text, source_type); @@ -296,12 +299,19 @@ impl Oxc { Ok(()) } - fn run_linter(&mut self, run_options: &OxcRunOptions, path: &Path, program: &Program) { + fn run_linter( + &mut self, + run_options: &OxcRunOptions, + path: &Path, + program: &Program, + module_record: &Arc, + ) { // Only lint if there are no syntax errors if run_options.lint.unwrap_or_default() && self.diagnostics.borrow().is_empty() { let semantic_ret = SemanticBuilder::new().with_cfg(true).build(program); let semantic = Rc::new(semantic_ret.semantic); - let linter_ret = Linter::default().run(path, Rc::clone(&semantic)); + let linter_ret = + Linter::default().run(path, Rc::clone(&semantic), Arc::clone(module_record)); let diagnostics = linter_ret.into_iter().map(|e| e.error).collect(); self.save_diagnostics(diagnostics); } diff --git a/tasks/benchmark/benches/linter.rs b/tasks/benchmark/benches/linter.rs index 6e0fc16cbfb5d..e4d5305650788 100644 --- a/tasks/benchmark/benches/linter.rs +++ b/tasks/benchmark/benches/linter.rs @@ -1,4 +1,4 @@ -use std::{env, path::Path, rc::Rc}; +use std::{env, path::Path, rc::Rc, sync::Arc}; use oxc_allocator::Allocator; use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion}; @@ -39,7 +39,8 @@ fn bench_linter(criterion: &mut Criterion) { .build(&ret.program); let linter = LinterBuilder::all().with_fix(FixKind::All).build(); let semantic = Rc::new(semantic_ret.semantic); - b.iter(|| linter.run(path, Rc::clone(&semantic))); + let module_record = Arc::new(ret.module_record); + b.iter(|| linter.run(path, Rc::clone(&semantic), Arc::clone(&module_record))); }, ); }