perf(linter): disable lint rules by file type (#4380)

### 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.

---
This commit is contained in:
DonIsaac 2024-07-21 15:22:54 +00:00
parent 44a10c4b91
commit f7da22da18
56 changed files with 229 additions and 11 deletions

View file

@ -116,10 +116,10 @@ impl Linter {
let ctx = self.create_ctx(path, semantic);
let semantic = Rc::clone(ctx.semantic());
let ctx = ctx.with_fix(self.options.fix).with_eslint_config(&self.eslint_config);
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);

View file

@ -22,6 +22,17 @@ pub trait Rule: Sized + Default + fmt::Debug {
/// Run only once. Useful for inspecting scopes and trivias etc.
fn run_once(&self, _ctx: &LintContext) {}
/// Check if a rule should be run at all.
///
/// You usually do not need to implement this function. If you do, use it to
/// enable rules on a file-by-file basis. Do not check if plugins are
/// enabled/disabled; this is handled by the [`linter`].
///
/// [`linter`]: crate::Linter
fn should_run(&self, _ctx: &LintContext) -> bool {
true
}
}
pub trait RuleMeta {

View file

@ -146,6 +146,10 @@ impl Rule for ButtonHasType {
.unwrap_or(true),
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
impl ButtonHasType {

View file

@ -63,6 +63,10 @@ impl Rule for JsxKey {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
enum InsideArrayOrIterator {

View file

@ -60,6 +60,10 @@ impl Rule for JsxNoCommentTextnodes {
ctx.diagnostic(jsx_no_comment_textnodes_diagnostic(jsx_text.span));
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
fn control_patterns(pattern: &str) -> bool {

View file

@ -69,6 +69,10 @@ impl Rule for JsxNoDuplicateProps {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[test]

View file

@ -243,6 +243,10 @@ impl Rule for JsxNoTargetBlank {
.unwrap_or(false),
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
fn check_is_external_link(link: &str) -> bool {

View file

@ -72,6 +72,10 @@ impl Rule for JsxNoUndef {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[test]

View file

@ -74,6 +74,10 @@ impl Rule for JsxNoUselessFragment {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
impl JsxNoUselessFragment {

View file

@ -85,6 +85,10 @@ impl Rule for NoChildrenProp {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[test]

View file

@ -74,6 +74,10 @@ impl Rule for NoDanger {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[test]

View file

@ -117,6 +117,10 @@ impl Rule for NoDirectMutationState {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
// check current node is this.state.xx

View file

@ -66,6 +66,10 @@ impl Rule for NoFindDomNode {
};
ctx.diagnostic(no_find_dom_node_diagnostic(span));
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[test]

View file

@ -66,6 +66,10 @@ impl Rule for NoIsMounted {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[test]

View file

@ -78,6 +78,10 @@ impl Rule for NoRenderReturnValue {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[test]

View file

@ -71,6 +71,10 @@ impl Rule for NoSetState {
ctx.diagnostic(no_set_state_diagnostic(call_expr.callee.span()));
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[test]

View file

@ -127,6 +127,10 @@ impl Rule for NoStringRefs {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[test]

View file

@ -61,6 +61,10 @@ impl Rule for NoUnescapedEntities {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
pub const DEFAULTS: Map<char, &'static [&'static str]> = phf_map! {

View file

@ -529,6 +529,10 @@ impl Rule for NoUnknownProperty {
);
});
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[test]

View file

@ -74,6 +74,10 @@ impl Rule for PreferEs6Class {
));
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[derive(Debug, Default, Clone)]

View file

@ -54,6 +54,10 @@ impl Rule for ReactInJsxScope {
ctx.diagnostic(react_in_jsx_scope_diagnostic(node_span));
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[test]

View file

@ -78,6 +78,10 @@ impl Rule for RequireRenderReturn {
};
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]

View file

@ -137,6 +137,10 @@ impl Rule for VoidDomElementsNoChildren {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
#[test]

View file

@ -42,6 +42,10 @@ impl Rule for JsxNoJsxAsProp {
check_jsx_element(jsx_elem, ctx);
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
fn check_jsx_element<'a>(jsx_elem: &JSXElement<'a>, ctx: &LintContext<'a>) {

View file

@ -50,6 +50,10 @@ impl Rule for JsxNoNewArrayAsProp {
check_jsx_element(jsx_elem, ctx);
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
fn check_jsx_element<'a>(jsx_elem: &JSXElement<'a>, ctx: &LintContext<'a>) {

View file

@ -45,6 +45,10 @@ impl Rule for JsxNoNewFunctionAsProp {
check_jsx_element(jsx_elem, ctx);
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
fn check_jsx_element<'a>(jsx_elem: &JSXElement<'a>, ctx: &LintContext<'a>) {

View file

@ -49,6 +49,10 @@ impl Rule for JsxNoNewObjectAsProp {
check_jsx_element(jsx_elem, ctx);
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_jsx()
}
}
fn check_jsx_element<'a>(jsx_elem: &JSXElement<'a>, ctx: &LintContext<'a>) {

View file

@ -319,6 +319,10 @@ impl Rule for AdjacentOverloadSignatures {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -126,6 +126,10 @@ impl Rule for ArrayType {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
fn check(

View file

@ -204,6 +204,10 @@ impl Rule for BanTsComment {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
impl BanTsComment {

View file

@ -83,6 +83,10 @@ impl Rule for BanTypes {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -244,6 +244,10 @@ impl Rule for ConsistentIndexedObjectStyle {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -214,6 +214,10 @@ impl Rule for ConsistentTypeDefinitions {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -266,6 +266,10 @@ impl Rule for ConsistentTypeImports {
);
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
// Given an array of words, returns an English-friendly concatenation, separated with commas, with

View file

@ -249,6 +249,10 @@ impl Rule for ExplicitFunctionReturnType {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
impl ExplicitFunctionReturnType {

View file

@ -92,6 +92,10 @@ impl Rule for NoConfusingNonNullAssertion {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -64,6 +64,10 @@ impl Rule for NoDuplicateEnumValues {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -68,6 +68,10 @@ impl Rule for NoEmptyInterface {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -112,6 +112,10 @@ impl Rule for NoExplicitAny {
Self { fix_to_unknown, ignore_rest_args }
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
impl NoExplicitAny {

View file

@ -64,6 +64,10 @@ impl Rule for NoExtraNonNullAssertion {
ctx.diagnostic(no_extra_non_null_assertion_diagnostic(Span::new(end, end)));
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -109,6 +109,10 @@ impl Rule for NoImportTypeSideEffects {
},
);
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -98,6 +98,10 @@ impl Rule for NoNamespace {
ctx.diagnostic(no_namespace_diagnostic(span));
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
fn is_declaration(node: &AstNode, ctx: &LintContext) -> bool {

View file

@ -47,6 +47,10 @@ impl Rule for NoNonNullAssertedNullishCoalescing {
ctx.diagnostic(no_non_null_asserted_nullish_coalescing_diagnostic(ts_non_null_expr.span));
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
fn has_assignment_before_node(
symbol_id: SymbolId,

View file

@ -88,6 +88,10 @@ impl Rule for NoNonNullAssertedOptionalChain {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
fn is_parent_member_or_call(node: &AstNode<'_>, ctx: &LintContext<'_>) -> bool {

View file

@ -36,6 +36,10 @@ impl Rule for NoNonNullAssertion {
let AstKind::TSNonNullExpression(expr) = node.kind() else { return };
ctx.diagnostic(no_non_null_assertion_diagnostic(expr.span));
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -87,9 +87,6 @@ impl Rule for NoThisAlias {
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if !ctx.source_type().is_typescript() {
return;
}
match node.kind() {
AstKind::VariableDeclarator(decl) => {
let Some(init) = &decl.init else { return };
@ -146,6 +143,9 @@ impl Rule for NoThisAlias {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
fn rhs_is_this_reference(rhs_expression: &Expression) -> bool {

View file

@ -67,6 +67,10 @@ impl Rule for NoUnnecessaryTypeConstraint {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -36,10 +36,6 @@ declare_oxc_lint!(
impl Rule for NoUnsafeDeclarationMerging {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if !ctx.source_type().is_typescript() {
return;
}
match node.kind() {
AstKind::Class(decl) => {
if let Some(ident) = decl.id.as_ref() {
@ -64,6 +60,10 @@ impl Rule for NoUnsafeDeclarationMerging {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
fn check_and_diagnostic(

View file

@ -34,9 +34,6 @@ declare_oxc_lint!(
impl Rule for NoVarRequires {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if !ctx.source_type().is_typescript() {
return;
}
let AstKind::CallExpression(expr) = node.kind() else {
return;
};
@ -68,6 +65,10 @@ impl Rule for NoVarRequires {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -80,6 +80,10 @@ impl Rule for PreferAsConst {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
fn check_and_report(

View file

@ -51,6 +51,10 @@ impl Rule for PreferEnumInitializers {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -392,6 +392,10 @@ impl Rule for PreferFunctionType {
_ => {}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -108,6 +108,10 @@ impl Rule for PreferLiteralEnumMember {
ctx.diagnostic(prefer_literal_enum_member_diagnostic(decl.span));
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
#[test]

View file

@ -71,6 +71,10 @@ impl Rule for PreferTsExpectError {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
fn get_last_comment_line(comment: CommentKind, raw: &str) -> String {

View file

@ -162,6 +162,10 @@ impl Rule for TripleSlashReference {
}
}
}
fn should_run(&self, ctx: &LintContext) -> bool {
ctx.source_type().is_typescript()
}
}
fn get_attr_key_and_value(raw: &str) -> Option<(String, String)> {

View file

@ -118,6 +118,12 @@ pub fn declare_all_lint_rules(metadata: AllLintRulesMeta) -> TokenStream {
#(Self::#struct_names(rule) => rule.run_once(ctx)),*
}
}
pub(super) fn should_run(&self, ctx: &LintContext) -> bool {
match self {
#(Self::#struct_names(rule) => rule.should_run(ctx)),*
}
}
}
impl std::hash::Hash for RuleEnum {