mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
### TL;DR Added a `should_run` function to multiple lint rules to determine if a rule should be executed based on the source type. This change optimizes the linting process by avoiding unnecessary rule checks. ### What changed? 1. **New Method**: Introduced the `should_run` method in the `Rule` trait. 2. **Implementation**: Implemented the `should_run` method for various lint rules, particularly those related to React and TypeScript. 3. **Usage**: Updated the `Linter` to use the `should_run` method to filter rules before execution. 4. **Macro Update**: Modified the `declare_all_lint_rules` macro to incorporate the `should_run` method. ### How to test? 1. Run the linter on a project containing React and TypeScript files. 2. Verify that only relevant rules are executed based on the file type (e.g., JSX rules for React files). ### Why make this change? This change improves the performance of the linter by ensuring that only applicable rules are run for a given file type, reducing unnecessary computation and potential false positives. ---
228 lines
6.4 KiB
Rust
228 lines
6.4 KiB
Rust
#![allow(clippy::self_named_module_files)] // for rules.rs
|
|
|
|
#[cfg(test)]
|
|
mod tester;
|
|
|
|
mod ast_util;
|
|
mod config;
|
|
mod context;
|
|
mod disable_directives;
|
|
mod fixer;
|
|
mod frameworks;
|
|
mod globals;
|
|
mod javascript_globals;
|
|
mod options;
|
|
mod rule;
|
|
mod rules;
|
|
mod service;
|
|
mod utils;
|
|
|
|
pub mod partial_loader;
|
|
pub mod table;
|
|
|
|
use std::{io::Write, path::Path, rc::Rc, sync::Arc};
|
|
|
|
use oxc_diagnostics::Error;
|
|
use oxc_semantic::{AstNode, Semantic};
|
|
|
|
pub use crate::{
|
|
config::OxlintConfig,
|
|
context::LintContext,
|
|
fixer::FixKind,
|
|
frameworks::FrameworkFlags,
|
|
options::{AllowWarnDeny, LintOptions},
|
|
rule::{RuleCategory, RuleMeta, RuleWithSeverity},
|
|
service::{LintService, LintServiceOptions},
|
|
};
|
|
use crate::{
|
|
config::{OxlintEnv, OxlintGlobals, OxlintSettings},
|
|
fixer::{Fixer, Message},
|
|
rules::RuleEnum,
|
|
table::RuleTable,
|
|
};
|
|
|
|
#[cfg(target_pointer_width = "64")]
|
|
#[test]
|
|
fn size_asserts() {
|
|
use static_assertions::assert_eq_size;
|
|
|
|
// `RuleEnum` runs in a really tight loop, make sure it is small for CPU cache.
|
|
// A reduction from 168 bytes to 16 results 15% performance improvement.
|
|
// See codspeed in https://github.com/oxc-project/oxc/pull/1783
|
|
assert_eq_size!(RuleEnum, [u8; 16]);
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Linter {
|
|
rules: Vec<RuleWithSeverity>,
|
|
options: LintOptions,
|
|
eslint_config: Arc<OxlintConfig>,
|
|
}
|
|
|
|
impl Default for Linter {
|
|
fn default() -> Self {
|
|
Self::from_options(LintOptions::default()).unwrap()
|
|
}
|
|
}
|
|
|
|
impl Linter {
|
|
/// # Errors
|
|
///
|
|
/// Returns `Err` if there are any errors parsing the configuration file.
|
|
pub fn from_options(options: LintOptions) -> Result<Self, Error> {
|
|
let (rules, eslint_config) = options.derive_rules_and_config()?;
|
|
Ok(Self { rules, options, eslint_config: Arc::new(eslint_config) })
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[must_use]
|
|
pub fn with_rules(mut self, rules: Vec<RuleWithSeverity>) -> Self {
|
|
self.rules = rules;
|
|
self
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn with_eslint_config(mut self, eslint_config: OxlintConfig) -> Self {
|
|
self.eslint_config = Arc::new(eslint_config);
|
|
self
|
|
}
|
|
|
|
/// Set the kind of auto fixes to apply.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// use oxc_linter::{Linter, FixKind};
|
|
///
|
|
/// // turn off all auto fixes. This is default behavior.
|
|
/// Linter::default().with_fix(FixKind::None);
|
|
/// ```
|
|
#[must_use]
|
|
pub fn with_fix(mut self, kind: FixKind) -> Self {
|
|
self.options.fix = kind;
|
|
self
|
|
}
|
|
|
|
pub fn options(&self) -> &LintOptions {
|
|
&self.options
|
|
}
|
|
|
|
pub fn number_of_rules(&self) -> usize {
|
|
self.rules.len()
|
|
}
|
|
|
|
// pub fn run<'a>(&self, ctx: LintContext<'a>) -> Vec<Message<'a>> {
|
|
pub fn run<'a>(&self, path: &Path, semantic: Rc<Semantic<'a>>) -> Vec<Message<'a>> {
|
|
let ctx = self.create_ctx(path, semantic);
|
|
let semantic = Rc::clone(ctx.semantic());
|
|
|
|
let rules = self
|
|
.rules
|
|
.iter()
|
|
.filter(|rule| rule.should_run(&ctx))
|
|
.map(|rule| {
|
|
let rule_name = rule.name();
|
|
let plugin_name = self.map_jest(rule.plugin_name(), rule_name);
|
|
|
|
(
|
|
rule,
|
|
ctx.clone()
|
|
.with_plugin_name(plugin_name)
|
|
.with_rule_name(rule_name)
|
|
.with_severity(rule.severity),
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
for (rule, ctx) in &rules {
|
|
rule.run_once(ctx);
|
|
}
|
|
|
|
for symbol in semantic.symbols().iter() {
|
|
for (rule, ctx) in &rules {
|
|
rule.run_on_symbol(symbol, ctx);
|
|
}
|
|
}
|
|
|
|
for node in semantic.nodes().iter() {
|
|
for (rule, ctx) in &rules {
|
|
rule.run(node, ctx);
|
|
}
|
|
}
|
|
|
|
rules.into_iter().flat_map(|(_, ctx)| ctx.into_message()).collect::<Vec<_>>()
|
|
}
|
|
|
|
/// # Panics
|
|
pub fn print_rules<W: Write>(writer: &mut W) {
|
|
let table = RuleTable::new();
|
|
for section in table.sections {
|
|
writeln!(writer, "{}", section.render_markdown_table()).unwrap();
|
|
}
|
|
writeln!(writer, "Default: {}", table.turned_on_by_default_count).unwrap();
|
|
writeln!(writer, "Total: {}", table.total).unwrap();
|
|
}
|
|
|
|
fn create_ctx<'a>(&self, path: &Path, semantic: Rc<Semantic<'a>>) -> LintContext<'a> {
|
|
let mut ctx = LintContext::new(path.to_path_buf().into_boxed_path(), semantic)
|
|
.with_fix(self.options.fix)
|
|
.with_eslint_config(&self.eslint_config)
|
|
.with_frameworks(self.options.framework_hints);
|
|
|
|
// set file-specific jest/vitest flags
|
|
if self.options.jest_plugin || self.options.vitest_plugin {
|
|
let mut test_flags = FrameworkFlags::empty();
|
|
|
|
if frameworks::is_jestlike_file(path) {
|
|
test_flags.set(FrameworkFlags::Jest, self.options.jest_plugin);
|
|
test_flags.set(FrameworkFlags::Vitest, self.options.vitest_plugin);
|
|
} else if frameworks::has_vitest_imports(ctx.module_record()) {
|
|
test_flags.set(FrameworkFlags::Vitest, true);
|
|
}
|
|
|
|
ctx = ctx.and_frameworks(test_flags);
|
|
}
|
|
|
|
ctx
|
|
}
|
|
|
|
fn map_jest(&self, plugin_name: &'static str, rule_name: &str) -> &'static str {
|
|
if self.options.vitest_plugin
|
|
&& plugin_name == "jest"
|
|
&& utils::is_jest_rule_adapted_to_vitest(rule_name)
|
|
{
|
|
"vitest"
|
|
} else {
|
|
plugin_name
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::{Linter, OxlintConfig};
|
|
|
|
#[test]
|
|
fn print_rules() {
|
|
let mut writer = Vec::new();
|
|
Linter::print_rules(&mut writer);
|
|
assert!(!writer.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_json() {
|
|
use std::fs;
|
|
|
|
use project_root::get_project_root;
|
|
let path = get_project_root().unwrap().join("npm/oxlint/configuration_schema.json");
|
|
let schema = schemars::schema_for!(OxlintConfig);
|
|
let json = serde_json::to_string_pretty(&schema).unwrap();
|
|
let existing_json = fs::read_to_string(&path).unwrap_or_default();
|
|
if existing_json != json {
|
|
std::fs::write(&path, &json).unwrap();
|
|
}
|
|
insta::with_settings!({ prepend_module_to_snapshot => false }, {
|
|
insta::assert_snapshot!(json);
|
|
});
|
|
}
|
|
}
|