Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/oxc_linter/src/generated/rule_runner_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ impl RuleRunner for crate::rules::eslint::no_prototype_builtins::NoPrototypeBuil

impl RuleRunner for crate::rules::eslint::no_redeclare::NoRedeclare {
const NODE_TYPES: Option<&AstTypesBitset> = None;
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnSymbol;
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce;
}

impl RuleRunner for crate::rules::eslint::no_regex_spaces::NoRegexSpaces {
Expand Down Expand Up @@ -631,7 +631,7 @@ impl RuleRunner for crate::rules::eslint::no_setter_return::NoSetterReturn {

impl RuleRunner for crate::rules::eslint::no_shadow_restricted_names::NoShadowRestrictedNames {
const NODE_TYPES: Option<&AstTypesBitset> = None;
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnSymbol;
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce;
}

impl RuleRunner for crate::rules::eslint::no_sparse_arrays::NoSparseArrays {
Expand Down Expand Up @@ -757,7 +757,7 @@ impl RuleRunner

impl RuleRunner for crate::rules::eslint::no_unused_vars::NoUnusedVars {
const NODE_TYPES: Option<&AstTypesBitset> = None;
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnSymbol;
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce;
}

impl RuleRunner for crate::rules::eslint::no_useless_backreference::NoUselessBackreference {
Expand Down
16 changes: 0 additions & 16 deletions crates/oxc_linter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,16 +250,6 @@ impl Linter {
}
}

for symbol in semantic.scoping().symbol_ids() {
for (rule, ctx) in &rules {
if !with_runtime_optimization
|| rule.run_info().is_run_on_symbol_implemented()
{
rule.run_on_symbol(symbol, ctx);
}
}
}

// Run rules on nodes
for node in semantic.nodes() {
for (rule, ctx) in &rules_by_ast_type[node.kind().ty() as usize] {
Expand Down Expand Up @@ -288,12 +278,6 @@ impl Linter {
rule.run_once(ctx);
}

if !with_runtime_optimization || run_info.is_run_on_symbol_implemented() {
for symbol in semantic.scoping().symbol_ids() {
rule.run_on_symbol(symbol, ctx);
}
}

if !with_runtime_optimization || run_info.is_run_implemented() {
// For smaller files, benchmarking showed it was faster to iterate over all rules and just check the
// node types as we go, rather than pre-bucketing rules by AST node type and doing extra allocations.
Expand Down
17 changes: 3 additions & 14 deletions crates/oxc_linter/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{fmt, hash::Hash};
use schemars::{JsonSchema, SchemaGenerator, schema::Schema};
use serde::{Deserialize, Serialize};

use oxc_semantic::{AstTypesBitset, SymbolId};
use oxc_semantic::AstTypesBitset;

use crate::{
AstNode, FixKind,
Expand All @@ -30,11 +30,6 @@ pub trait Rule: Sized + Default + fmt::Debug {
#[inline]
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {}

/// Visit each symbol
#[expect(unused_variables)]
#[inline]
fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) {}

/// Run only once. Useful for inspecting scopes and trivias etc.
#[expect(unused_variables)]
#[inline]
Expand Down Expand Up @@ -72,8 +67,8 @@ pub trait RuleRunner: Rule {
const NODE_TYPES: Option<&AstTypesBitset>;

/// What `Rule` functions are implemented by this `Rule`. For example, if a rule only
/// implements `run_on_symbol`, then the linter can skip calling `run` and `run_once`, so
/// this value would be tagged as [`RuleRunFunctionsImplemented::RunOnSymbol`].
/// implements `run_once`, then the linter can skip calling `run`, so
/// this value would be tagged as [`RuleRunFunctionsImplemented::RunOnce`].
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Unknown;

fn types_info(&self) -> Option<&'static AstTypesBitset> {
Expand All @@ -94,8 +89,6 @@ pub enum RuleRunFunctionsImplemented {
Run,
/// Only `run_once` is implemented
RunOnce,
/// Only `run_on_symbol` is implemented
RunOnSymbol,
/// Only `run_on_jest_node` is implemented
RunOnJestNode,
}
Expand All @@ -109,10 +102,6 @@ impl RuleRunFunctionsImplemented {
matches!(self, Self::RunOnce | Self::Unknown)
}

pub fn is_run_on_symbol_implemented(self) -> bool {
matches!(self, Self::RunOnSymbol | Self::Unknown)
}

pub fn is_run_on_jest_node_implemented(self) -> bool {
matches!(self, Self::RunOnJestNode | Self::Unknown)
}
Expand Down
71 changes: 36 additions & 35 deletions crates/oxc_linter/src/rules/eslint/no_redeclare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use javascript_globals::GLOBALS;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{ModuleKind, Span};
use oxc_syntax::symbol::SymbolId;

use crate::{
context::{ContextHost, LintContext},
Expand Down Expand Up @@ -81,48 +80,50 @@ impl Rule for NoRedeclare {
Self { built_in_globals }
}

fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext) {
let name = ctx.scoping().symbol_name(symbol_id);
let decl_span = ctx.scoping().symbol_span(symbol_id);
let is_builtin = self.built_in_globals
&& (GLOBALS["builtin"].contains_key(name) || ctx.globals().is_enabled(name));
fn run_once(&self, ctx: &LintContext) {
for symbol_id in ctx.scoping().symbol_ids() {
let name = ctx.scoping().symbol_name(symbol_id);
let decl_span = ctx.scoping().symbol_span(symbol_id);
let is_builtin = self.built_in_globals
&& (GLOBALS["builtin"].contains_key(name) || ctx.globals().is_enabled(name));

if is_builtin {
ctx.diagnostic(no_redeclare_as_builtin_in_diagnostic(name, decl_span));
}
if is_builtin {
ctx.diagnostic(no_redeclare_as_builtin_in_diagnostic(name, decl_span));
}

if ctx.source_type().is_typescript() {
let mut iter = ctx.scoping().symbol_redeclarations(symbol_id).iter().filter(|rd| {
if is_builtin {
if rd.span != decl_span {
ctx.diagnostic(no_redeclare_as_builtin_in_diagnostic(name, rd.span));
if ctx.source_type().is_typescript() {
let mut iter = ctx.scoping().symbol_redeclarations(symbol_id).iter().filter(|rd| {
if is_builtin {
if rd.span != decl_span {
ctx.diagnostic(no_redeclare_as_builtin_in_diagnostic(name, rd.span));
}
return false;
}
return false;
}
if rd.flags.is_function() {
let node = ctx.nodes().get_node(rd.declaration);
if let Some(func) = node.kind().as_function() {
return !func.is_ts_declare_function();
if rd.flags.is_function() {
let node = ctx.nodes().get_node(rd.declaration);
if let Some(func) = node.kind().as_function() {
return !func.is_ts_declare_function();
}
}
true
});

if let Some(first) = iter.next() {
iter.fold(first, |prev, next| {
ctx.diagnostic(no_redeclare_diagnostic(name, prev.span, next.span));
next
});
}
true
});

if let Some(first) = iter.next() {
iter.fold(first, |prev, next| {
ctx.diagnostic(no_redeclare_diagnostic(name, prev.span, next.span));
next
});
continue;
}

return;
}

for windows in ctx.scoping().symbol_redeclarations(symbol_id).windows(2) {
if is_builtin {
ctx.diagnostic(no_redeclare_as_builtin_in_diagnostic(name, windows[1].span));
} else {
ctx.diagnostic(no_redeclare_diagnostic(name, windows[0].span, windows[1].span));
for windows in ctx.scoping().symbol_redeclarations(symbol_id).windows(2) {
if is_builtin {
ctx.diagnostic(no_redeclare_as_builtin_in_diagnostic(name, windows[1].span));
} else {
ctx.diagnostic(no_redeclare_diagnostic(name, windows[0].span, windows[1].span));
}
}
}
}
Expand Down
53 changes: 28 additions & 25 deletions crates/oxc_linter/src/rules/eslint/no_shadow_restricted_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use oxc_syntax::symbol::SymbolId;
use serde_json::Value;

const PRE_DEFINE_VAR: [&str; 5] = ["Infinity", "NaN", "arguments", "eval", "undefined"];
Expand Down Expand Up @@ -93,34 +92,38 @@ impl Rule for NoShadowRestrictedNames {
}))
}

fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) {
let name = ctx.scoping().symbol_name(symbol_id);
fn run_once(&self, ctx: &LintContext) {
for symbol_id in ctx.scoping().symbol_ids() {
let name = ctx.scoping().symbol_name(symbol_id);

if !(PRE_DEFINE_VAR.contains(&name) || self.0.report_global_this && name == "globalThis") {
return;
}

if name == "undefined" {
// Allow to declare `undefined` variable but not allow to assign value to it.
let node_id = ctx.scoping().symbol_declaration(symbol_id);
if let AstKind::VariableDeclarator(declarator) = ctx.nodes().kind(node_id)
&& declarator.init.is_none()
&& ctx
.scoping()
.get_resolved_references(symbol_id)
.all(|reference| !reference.is_write())
if !(PRE_DEFINE_VAR.contains(&name)
|| self.0.report_global_this && name == "globalThis")
{
return;
continue;
}

if name == "undefined" {
// Allow to declare `undefined` variable but not allow to assign value to it.
let node_id = ctx.scoping().symbol_declaration(symbol_id);
if let AstKind::VariableDeclarator(declarator) = ctx.nodes().kind(node_id)
&& declarator.init.is_none()
&& ctx
.scoping()
.get_resolved_references(symbol_id)
.all(|reference| !reference.is_write())
{
continue;
}
}
}

let redeclarations = ctx.scoping().symbol_redeclarations(symbol_id);
if redeclarations.is_empty() {
let span = ctx.scoping().symbol_span(symbol_id);
ctx.diagnostic(no_shadow_restricted_names_diagnostic(name, span));
} else {
for rd in redeclarations {
ctx.diagnostic(no_shadow_restricted_names_diagnostic(name, rd.span));
let redeclarations = ctx.scoping().symbol_redeclarations(symbol_id);
if redeclarations.is_empty() {
let span = ctx.scoping().symbol_span(symbol_id);
ctx.diagnostic(no_shadow_restricted_names_diagnostic(name, span));
} else {
for rd in redeclarations {
ctx.diagnostic(no_shadow_restricted_names_diagnostic(name, rd.span));
}
}
}
}
Expand Down
16 changes: 9 additions & 7 deletions crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::ops::Deref;
use options::{IgnorePattern, NoUnusedVarsOptions};
use oxc_ast::AstKind;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::{AstNode, ScopeFlags, SymbolFlags, SymbolId};
use oxc_semantic::{AstNode, ScopeFlags, SymbolFlags};
use oxc_span::GetSpan;
use symbol::Symbol;

Expand Down Expand Up @@ -205,13 +205,15 @@ impl Rule for NoUnusedVars {
Self(Box::new(NoUnusedVarsOptions::try_from(value).unwrap()))
}

fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) {
let symbol = Symbol::new(ctx, ctx.module_record(), symbol_id);
if Self::should_skip_symbol(&symbol) {
return;
}
fn run_once(&self, ctx: &LintContext) {
for symbol in ctx.scoping().symbol_ids() {
let symbol = Symbol::new(ctx, ctx.module_record(), symbol);
if Self::should_skip_symbol(&symbol) {
continue;
}

self.run_on_symbol_internal(&symbol, ctx);
self.run_on_symbol_internal(&symbol, ctx);
}
}

fn should_run(&self, ctx: &ContextHost) -> bool {
Expand Down
6 changes: 0 additions & 6 deletions crates/oxc_macros/src/declare_all_lint_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,6 @@ pub fn declare_all_lint_rules(metadata: AllLintRulesMeta) -> TokenStream {
}
}

pub(super) fn run_on_symbol<'a>(&self, symbol_id: SymbolId, ctx: &LintContext<'a>) {
match self {
#(Self::#struct_names(rule) => rule.run_on_symbol(symbol_id, ctx)),*
}
}

pub(super) fn run_once<'a>(&self, ctx: &LintContext<'a>) {
match self {
#(Self::#struct_names(rule) => rule.run_once(ctx)),*
Expand Down
1 change: 0 additions & 1 deletion tasks/linter_codegen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ pub fn generate_rule_runner_impls() -> io::Result<()> {
let rule_run_info_init = if rule_run_info.len() == 1 {
match rule_run_info.iter().next().map(String::as_str) {
Some("run") => "RuleRunFunctionsImplemented::Run".to_string(),
Some("run_on_symbol") => "RuleRunFunctionsImplemented::RunOnSymbol".to_string(),
Some("run_once") => "RuleRunFunctionsImplemented::RunOnce".to_string(),
Some("run_on_jest_node") => {
"RuleRunFunctionsImplemented::RunOnJestNode".to_string()
Expand Down
Loading