Skip to content
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ memoffset = "0.9.1"
miette = { version = "7.2.0", features = ["fancy-no-syscall"] }
mimalloc = "0.1.43"
mime_guess = "2.0.5"
nohash-hasher = "0.2.0"
nonmax = "0.5.5"
num-bigint = "0.4.6"
num-traits = "0.2.19"
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_linter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ language-tags = { workspace = true }
lazy_static = { workspace = true }
memchr = { workspace = true }
mime_guess = { workspace = true }
nohash-hasher = { workspace = true }
once_cell = { workspace = true }
phf = { workspace = true, features = ["macros"] }
rayon = { workspace = true }
Expand Down
8 changes: 7 additions & 1 deletion crates/oxc_linter/src/context/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
utils, FrameworkFlags, RuleWithSeverity,
};

use super::{plugin_name_to_prefix, LintContext};
use super::{plugin_name_to_prefix, LintContext, RuleState};

/// Stores shared information about a file being linted.
///
Expand Down Expand Up @@ -46,6 +46,8 @@ pub struct ContextHost<'a> {
pub(super) config: Arc<LintConfig>,
pub(super) frameworks: FrameworkFlags,
pub(super) plugins: LintPlugins,

pub(super) state: RuleState,
}

impl<'a> ContextHost<'a> {
Expand All @@ -55,6 +57,7 @@ impl<'a> ContextHost<'a> {
file_path: P,
semantic: Rc<Semantic<'a>>,
options: LintOptions,
rules: &Vec<RuleWithSeverity>,
) -> Self {
// We should always check for `semantic.cfg()` being `Some` since we depend on it and it is
// unwrapped without any runtime checks after construction.
Expand All @@ -77,6 +80,7 @@ impl<'a> ContextHost<'a> {
config: Arc::new(LintConfig::default()),
frameworks: options.framework_hints,
plugins: options.plugins,
state: RuleState::new(rules),
}
.sniff_for_frameworks()
}
Expand Down Expand Up @@ -116,6 +120,7 @@ impl<'a> ContextHost<'a> {
LintContext {
parent: self,
diagnostics: RefCell::new(Vec::with_capacity(DIAGNOSTICS_INITIAL_CAPACITY)),
current_rule_id: rule.id(),
current_rule_name: rule_name,
current_plugin_name: plugin_name,
current_plugin_prefix: plugin_name_to_prefix(plugin_name),
Expand All @@ -132,6 +137,7 @@ impl<'a> ContextHost<'a> {
LintContext {
parent: Rc::clone(&self),
diagnostics: RefCell::new(Vec::with_capacity(DIAGNOSTICS_INITIAL_CAPACITY)),
current_rule_id: 0,
current_rule_name: "",
current_plugin_name: "eslint",
current_plugin_prefix: "eslint",
Expand Down
17 changes: 15 additions & 2 deletions crates/oxc_linter/src/context/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
#![allow(rustdoc::private_intra_doc_links)] // useful for intellisense
mod host;
mod rule_state;

use std::{cell::RefCell, path::Path, rc::Rc};
use std::{
cell::{RefCell, RefMut},
path::Path,
rc::Rc,
};

use oxc_cfg::ControlFlowGraph;
use oxc_diagnostics::{OxcDiagnostic, Severity};
Expand All @@ -15,10 +20,11 @@ use crate::{
disable_directives::DisableDirectives,
fixer::{FixKind, Message, RuleFix, RuleFixer},
javascript_globals::GLOBALS,
AllowWarnDeny, FrameworkFlags, OxlintEnv, OxlintGlobals, OxlintSettings,
AllowWarnDeny, FrameworkFlags, OxlintEnv, OxlintGlobals, OxlintSettings, RuleMeta,
};

pub use host::ContextHost;
pub(crate) use rule_state::RuleState;

#[derive(Clone)]
#[must_use]
Expand All @@ -32,6 +38,7 @@ pub struct LintContext<'a> {
diagnostics: RefCell<Vec<Message<'a>>>,

// states
current_rule_id: usize,
current_plugin_name: &'static str,
current_plugin_prefix: &'static str,
current_rule_name: &'static str,
Expand Down Expand Up @@ -134,6 +141,12 @@ impl<'a> LintContext<'a> {
&self.parent.config.globals
}

#[inline]
#[must_use]
pub fn state<R: RuleMeta + 'static>(&self) -> RefMut<'_, R::State> {
self.parent.state.get_mut::<R>(self.current_rule_id)
}

/// Runtime environments turned on/off by the user.
///
/// Examples of environments are `builtin`, `browser`, `node`, etc.
Expand Down
40 changes: 40 additions & 0 deletions crates/oxc_linter/src/context/rule_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::{
any::Any,
cell::{RefCell, RefMut},
};

use nohash_hasher::IntMap;

use crate::{rules::RuleEnum, RuleMeta};

#[must_use]
pub struct RuleState {
inner: IntMap</* RuleId */ usize, Box<RefCell<dyn Any>>>,
}

impl RuleState {
pub fn new<I, R>(rules: I) -> Self
where
R: AsRef<RuleEnum>,
I: IntoIterator<Item = R>,
{
let inner = rules
.into_iter()
.map(|rule| {
let rule = rule.as_ref();
(rule.id(), rule.new_state())
})
.collect();
Self { inner }
}

pub fn get_raw_mut(&self, rule_id: usize) -> RefMut<'_, dyn Any> {
self.inner[&rule_id].borrow_mut()
}

pub fn get_mut<R: RuleMeta + 'static>(&self, rule_id: usize) -> RefMut<'_, R::State> {
RefMut::map(self.get_raw_mut(rule_id), |state| {
state.downcast_mut().expect("downcast failed")
})
}
}
5 changes: 3 additions & 2 deletions crates/oxc_linter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@ impl Linter {
}

pub fn run<'a>(&self, path: &Path, semantic: Rc<Semantic<'a>>) -> Vec<Message<'a>> {
let ctx_host =
Rc::new(ContextHost::new(path, semantic, self.options).with_config(&self.config));
let ctx_host = Rc::new(
ContextHost::new(path, semantic, self.options, &self.rules).with_config(&self.config),
);

let rules = self
.rules
Expand Down
13 changes: 13 additions & 0 deletions crates/oxc_linter/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ pub trait RuleMeta {
/// What kind of auto-fixing can this rule do?
const FIX: RuleFixMeta = RuleFixMeta::None;

/// Data stored between each call to [run](`Rule::run`),
/// [run_on_symbol](`Rule::run_on_symbol`), and
/// [run_once](`Rule::run_once`). State is reset between files.
///
/// By default, this is the unit type (aka `()`).
type State: Default + 'static;

fn documentation() -> Option<&'static str> {
None
}
Expand Down Expand Up @@ -261,6 +268,12 @@ impl Deref for RuleWithSeverity {
}
}

impl AsRef<RuleEnum> for RuleWithSeverity {
fn as_ref(&self) -> &RuleEnum {
&self.rule
}
}

impl RuleWithSeverity {
pub fn new(rule: RuleEnum, severity: AllowWarnDeny) -> Self {
Self { rule, severity }
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/react/jsx_boolean_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ declare_oxc_lint!(
/// ```
JsxBooleanValue,
style,
fix,
fix
);

impl Rule for JsxBooleanValue {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ declare_oxc_lint!(
/// ```
PreferStructuredClone,
style,
pending,
pending
);

impl Rule for PreferStructuredClone {
Expand Down
9 changes: 7 additions & 2 deletions crates/oxc_linter/src/utils/jest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,13 @@ mod test {
let semantic_ret = Rc::new(semantic_ret);

let build_ctx = |path: &'static str| {
Rc::new(ContextHost::new(path, Rc::clone(&semantic_ret), LintOptions::default()))
.spawn_for_test()
Rc::new(ContextHost::new(
path,
Rc::clone(&semantic_ret),
LintOptions::default(),
&vec![],
))
.spawn_for_test()
};

let ctx = build_ctx("foo.js");
Expand Down
6 changes: 6 additions & 0 deletions crates/oxc_macros/src/declare_all_lint_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ pub fn declare_all_lint_rules(metadata: AllLintRulesMeta) -> TokenStream {
#(Self::#struct_names(rule) => rule.should_run(ctx)),*
}
}

pub(crate) fn new_state(&self) -> Box<std::cell::RefCell<dyn std::any::Any>> {
match self {
#(Self::#struct_names(_) => Box::new(std::cell::RefCell::new(<#struct_names as RuleMeta>::State::default()))),*
}
}
}

impl std::hash::Hash for RuleEnum {
Expand Down
20 changes: 18 additions & 2 deletions crates/oxc_macros/src/declare_oxc_lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct LintRuleMeta {
/// Describes what auto-fixing capabilities the rule has
fix: Option<Ident>,
documentation: String,
state: Option<Ident>,
pub used_in_test: bool,
}

Expand Down Expand Up @@ -45,10 +46,17 @@ impl Parse for LintRuleMeta {
None
};

let state = if input.peek(Token!(,)) {
input.parse::<Token!(,)>()?;
Some(input.parse()?)
} else {
None
};

// Ignore the rest
input.parse::<proc_macro2::TokenStream>()?;

Ok(Self { name: struct_name, category, fix, documentation, used_in_test: false })
Ok(Self { name: struct_name, category, fix, documentation, state, used_in_test: false })
}
}

Expand All @@ -57,7 +65,7 @@ fn rule_name_converter() -> Converter {
}

pub fn declare_oxc_lint(metadata: LintRuleMeta) -> TokenStream {
let LintRuleMeta { name, category, fix, documentation, used_in_test } = metadata;
let LintRuleMeta { name, category, fix, documentation, state, used_in_test } = metadata;

let canonical_name = rule_name_converter().convert(name.to_string());
let category = match category.to_string().as_str() {
Expand All @@ -83,6 +91,12 @@ pub fn declare_oxc_lint(metadata: LintRuleMeta) -> TokenStream {
Some(quote! { use crate::{rule::{RuleCategory, RuleMeta, RuleFixMeta}, fixer::FixKind}; })
};

let state_type = if let Some(ty) = state {
quote! { type State = #ty; }
} else {
quote! { type State = (); }
};

let output = quote! {
#import_statement

Expand All @@ -93,6 +107,8 @@ pub fn declare_oxc_lint(metadata: LintRuleMeta) -> TokenStream {

#fix

#state_type

fn documentation() -> Option<&'static str> {
Some(#documentation)
}
Expand Down