Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(linter): support shared rule state in ctx #5770

Closed
wants to merge 3 commits into from
Closed
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/oxc_linter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ bitflags = { workspace = true }
convert_case = { workspace = true }
cow-utils = { workspace = true }
dashmap = { workspace = true }
dyn-clone = "1.0.17"
globset = { workspace = true }
itertools = { workspace = true }
json-strip-comments = { workspace = true }
Expand Down
9 changes: 9 additions & 0 deletions crates/oxc_linter/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
disable_directives::{DisableDirectives, DisableDirectivesBuilder},
fixer::{FixKind, Message, RuleFix, RuleFixer},
javascript_globals::GLOBALS,
rule::RuleState,
AllowWarnDeny, FrameworkFlags, OxlintEnv, OxlintGlobals, OxlintSettings,
};

Expand Down Expand Up @@ -58,6 +59,8 @@ pub struct LintContext<'a> {
/// ```
severity: Severity,
frameworks: FrameworkFlags,

rule_state: Option<Box<dyn RuleState>>,
}

impl<'a> LintContext<'a> {
Expand Down Expand Up @@ -91,6 +94,7 @@ impl<'a> LintContext<'a> {
current_rule_fix_capabilities: RuleFixMeta::None,
severity: Severity::Warning,
frameworks: FrameworkFlags::empty(),
rule_state: None,
}
}

Expand All @@ -116,6 +120,11 @@ impl<'a> LintContext<'a> {
self
}

pub fn with_rule_state(mut self, state: Box<dyn RuleState>) -> Self {
self.rule_state = Some(state);
self
}

#[cfg(debug_assertions)]
pub fn with_rule_fix_capabilities(mut self, capabilities: RuleFixMeta) -> Self {
self.current_rule_fix_capabilities = capabilities;
Expand Down
6 changes: 3 additions & 3 deletions crates/oxc_linter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ impl Linter {
let ctx = self.create_ctx(path, semantic);
let semantic = Rc::clone(ctx.semantic());

let rules = self
let mut rules = self
.rules
.iter()
.filter(|rule| rule.should_run(&ctx))
Expand All @@ -130,13 +130,13 @@ impl Linter {
}

for symbol in semantic.symbols().symbol_ids() {
for (rule, ctx) in &rules {
for (rule, ctx) in &mut rules {
rule.run_on_symbol(symbol, ctx);
}
}

for node in semantic.nodes().iter() {
for (rule, ctx) in &rules {
for (rule, ctx) in &mut rules {
rule.run(node, ctx);
}
}
Expand Down
9 changes: 7 additions & 2 deletions crates/oxc_linter/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{
ops::Deref,
};

use dyn_clone::DynClone;
use oxc_semantic::SymbolId;

use crate::{context::LintContext, AllowWarnDeny, AstNode, FixKind, RuleEnum};
Expand All @@ -18,12 +19,12 @@ pub trait Rule: Sized + Default + fmt::Debug {
/// Visit each AST Node
#[expect(unused_variables)]
#[inline]
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {}

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

/// Run only once. Useful for inspecting scopes and trivias etc.
#[expect(unused_variables)]
Expand All @@ -44,6 +45,10 @@ pub trait Rule: Sized + Default + fmt::Debug {
}
}

pub trait RuleState: DynClone {}

dyn_clone::clone_trait_object!(RuleState);

pub trait RuleMeta {
const NAME: &'static str;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl Rule for ArrayCallbackReturn {
Self { check_for_each, allow_implicit_return }
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
let (function_body, always_explicit_return) = match node.kind() {
// Async, generator, and single expression arrow functions
// always have explicit return value
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/constructor_super.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ declare_oxc_lint!(
);

impl Rule for ConstructorSuper {
fn run<'a>(&self, _node: &AstNode<'a>, _ctx: &LintContext<'a>) {}
fn run<'a>(&self, _node: &AstNode<'a>, _ctx: &mut LintContext<'a>) {}
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/default_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl Rule for DefaultCase {
Self(Box::new(cfg))
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
if let AstKind::SwitchStatement(switch) = node.kind() {
let cases = &switch.cases;

Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/default_case_last.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ declare_oxc_lint!(
);

impl Rule for DefaultCaseLast {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
let AstKind::SwitchStatement(switch) = node.kind() else {
return;
};
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/default_param_last.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ declare_oxc_lint!(
);

impl Rule for DefaultParamLast {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
match node.kind() {
AstKind::Function(function) => {
if !function.is_declaration() && !function.is_expression() {
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/eqeqeq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl Rule for Eqeqeq {
}
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
let AstKind::BinaryExpression(binary_expr) = node.kind() else {
return;
};
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/for_direction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ declare_oxc_lint!(
);

impl Rule for ForDirection {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
let AstKind::ForStatement(for_loop) = node.kind() else {
return;
};
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/getter_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ declare_oxc_lint!(
);

impl Rule for GetterReturn {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
// https://eslint.org/docs/latest/rules/getter-return#handled_by_typescript
if ctx.source_type().is_typescript() {
return;
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/guard_for_in.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ declare_oxc_lint!(
);

impl Rule for GuardForIn {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
if let AstKind::ForInStatement(for_in_statement) = node.kind() {
match &for_in_statement.body {
Statement::EmptyStatement(_) | Statement::IfStatement(_) => return,
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/max_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ impl Rule for MaxParams {
}
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
match node.kind() {
AstKind::Function(function) => {
if !function.is_declaration() & !function.is_expression() {
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_alert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ fn is_shadowed<'a>(scope_id: ScopeId, name: &'a str, ctx: &LintContext<'a>) -> b
}

impl Rule for NoAlert {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
let AstKind::CallExpression(call_expr) = node.kind() else {
return;
};
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_array_constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ declare_oxc_lint!(
);

impl Rule for NoArrayConstructor {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
let (span, callee, arguments, type_parameters, optional) = match node.kind() {
AstKind::CallExpression(call_expr) => (
call_expr.span,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ declare_oxc_lint!(
);

impl Rule for NoAsyncPromiseExecutor {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
let AstKind::NewExpression(new_expression) = node.kind() else {
return;
};
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_await_in_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ declare_oxc_lint!(
);

impl Rule for NoAwaitInLoop {
fn run(&self, node: &AstNode, ctx: &LintContext) {
fn run(&self, node: &AstNode, ctx: &mut LintContext) {
// if node is AwaitExpression or AwaitForOfStatement
let span = match node.kind() {
// if the await attr of ForOfStatement is false, return
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_bitwise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl Rule for NoBitwise {
}))
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
match node.kind() {
AstKind::BinaryExpression(bin_expr) => {
let op = bin_expr.operator.as_str();
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_caller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ declare_oxc_lint!(
);

impl Rule for NoCaller {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
if let AstKind::MemberExpression(MemberExpression::StaticMemberExpression(expr)) =
node.kind()
{
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_case_declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ declare_oxc_lint!(
);

impl Rule for NoCaseDeclarations {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
if let AstKind::SwitchCase(switch_case) = node.kind() {
let consequent = &switch_case.consequent;

Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_class_assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ declare_oxc_lint!(
);

impl Rule for NoClassAssign {
fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) {
fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &mut LintContext<'_>) {
let symbol_table = ctx.semantic().symbols();
if symbol_table.get_flags(symbol_id).is_class() {
for reference in symbol_table.get_resolved_references(symbol_id) {
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_compare_neg_zero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ declare_oxc_lint!(
);

impl Rule for NoCompareNegZero {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
let AstKind::BinaryExpression(expr) = node.kind() else {
return;
};
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_cond_assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl Rule for NoCondAssign {
Self { config }
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
match node.kind() {
AstKind::IfStatement(stmt) => self.check_expression(ctx, &stmt.test),
AstKind::WhileStatement(stmt) => self.check_expression(ctx, &stmt.test),
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl Rule for NoConsole {
}))
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
let AstKind::CallExpression(call_expr) = node.kind() else {
return;
};
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_const_assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ declare_oxc_lint!(
);

impl Rule for NoConstAssign {
fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) {
fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &mut LintContext<'_>) {
let symbol_table = ctx.semantic().symbols();
if symbol_table.get_flags(symbol_id).is_const_variable() {
for reference in symbol_table.get_resolved_references(symbol_id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ fn constant_both_always_new(span: Span) -> OxcDiagnostic {
}

impl Rule for NoConstantBinaryExpression {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
match node.kind() {
AstKind::LogicalExpression(expr) => match expr.operator {
LogicalOperator::Or | LogicalOperator::And if expr.left.is_constant(true, ctx) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl Rule for NoConstantCondition {
}
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
match node.kind() {
AstKind::IfStatement(if_stmt) => {
if if_stmt.test.is_constant(true, ctx) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ declare_oxc_lint!(
);

impl Rule for NoConstructorReturn {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
let AstKind::ReturnStatement(ret) = node.kind() else { return };
if ret.argument.is_none() {
return;
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_continue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ declare_oxc_lint!(
);

impl Rule for NoContinue {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
if let AstKind::ContinueStatement(continue_statement) = node.kind() {
ctx.diagnostic(no_continue_diagnostic(Span::new(
continue_statement.span.start,
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_control_regex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ declare_oxc_lint!(
);

impl Rule for NoControlRegex {
fn run<'a>(&self, node: &AstNode<'a>, context: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, context: &mut LintContext<'a>) {
if let Some(RegexPatternData { pattern, flags, span }) = regex_pattern(node) {
let mut violations: Vec<&str> = Vec::new();
let pattern = pattern.as_ref();
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_debugger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ declare_oxc_lint!(
);

impl Rule for NoDebugger {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
if let AstKind::DebuggerStatement(stmt) = node.kind() {
ctx.diagnostic_with_fix(no_debugger_diagnostic(stmt.span), |fixer| {
let Some(parent) = ctx
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_delete_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ declare_oxc_lint!(
);

impl Rule for NoDeleteVar {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
let AstKind::UnaryExpression(expr) = node.kind() else {
return;
};
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_div_regex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ declare_oxc_lint!(
);

impl Rule for NoDivRegex {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
if let AstKind::RegExpLiteral(lit) = node.kind() {
let Some(pattern) = lit.regex.pattern.as_pattern() else { return };
if pattern
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_dupe_else_if.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ declare_oxc_lint!(
);

impl Rule for NoDupeElseIf {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
// if (a) {} else if (a) {}
// ^^ get this if statement
let AstKind::IfStatement(if_stmt) = node.kind() else {
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/eslint/no_dupe_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ declare_oxc_lint!(
);

impl Rule for NoDupeKeys {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &mut LintContext<'a>) {
let AstKind::ObjectExpression(obj_expr) = node.kind() else {
return;
};
Expand Down
Loading