From 901f80cfa437c590b32d1b6468e8cfc9bd597c06 Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Wed, 31 Dec 2025 16:35:01 -0500 Subject: [PATCH 01/15] Initialize eslint complexity code --- .../src/generated/rule_runner_impls.rs | 5 + crates/oxc_linter/src/rules.rs | 2 + .../oxc_linter/src/rules/eslint/complexity.rs | 399 ++++++++++++++++++ 3 files changed, 406 insertions(+) create mode 100644 crates/oxc_linter/src/rules/eslint/complexity.rs diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 22cc2d7791d2a..d4b9ef51036a2 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -51,6 +51,11 @@ impl RuleRunner for crate::rules::eslint::class_methods_use_this::ClassMethodsUs const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } +impl RuleRunner for crate::rules::eslint::complexity::Complexity { + const NODE_TYPES: Option<&AstTypesBitset> = None; + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; +} + impl RuleRunner for crate::rules::eslint::constructor_super::ConstructorSuper { const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[AstType::Class])); diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 9217d61935025..fb610aa247b27 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -46,6 +46,7 @@ pub(crate) mod eslint { pub mod block_scoped_var; pub mod capitalized_comments; pub mod class_methods_use_this; + pub mod complexity; pub mod constructor_super; pub mod curly; pub mod default_case; @@ -704,6 +705,7 @@ oxc_macros::declare_all_lint_rules! { eslint::block_scoped_var, eslint::capitalized_comments, eslint::class_methods_use_this, + eslint::complexity, eslint::constructor_super, eslint::curly, eslint::default_case, diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs new file mode 100644 index 0000000000000..b1cd9ca73b78a --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -0,0 +1,399 @@ +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::{ + AstNode, + context::LintContext, + fixer::{RuleFix, RuleFixer}, + rule::{DefaultRuleConfig, Rule}, +}; + +fn complexity_diagnostic(span: Span) -> OxcDiagnostic { + // See for details + OxcDiagnostic::warn("Should be an imperative statement about what is wrong.") + .with_help("Should be a command-like statement that tells the user how to fix the issue.") + .with_label(span) +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(untagged, rename_all = "camelCase")] +enum ConfigElement0 { + Unlabeled1(i32), + Unlabeled2(ConfigElement00), +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(rename_all = "camelCase")] +struct ConfigElement00 { + max: i32, + maximum: i32, + variant: Variant, +} +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(untagged, rename_all = "camelCase")] +enum Variant { + #[default] + Classic, + Modified, +} + +#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema)] +pub struct Complexity(ConfigElement0); + +// See for documentation details. +declare_oxc_lint!( + /// ### What it does + /// + /// Briefly describe the rule's purpose. + /// + /// ### Why is this bad? + /// + /// Explain why violating this rule is problematic. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// ``` + Complexity, + eslint, + nursery, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` + // See for details + pending, // TODO: describe fix capabilities. Remove if no fix can be done, + // keep at 'pending' if you think one could be added but don't know how. + // Options are 'fix', 'fix_dangerous', 'suggestion', and 'conditional_fix_suggestion' + config = Complexity, +); + +impl Rule for Complexity { + fn from_configuration(value: serde_json::Value) -> Result { + Ok(serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner()) + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {} +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("function a(x) {}", None), + ("function b(x) {}", Some(serde_json::json!([1]))), + ("function a(x) {if (true) {return x;}}", Some(serde_json::json!([2]))), + ("function a(x) {if (true) {return x;} else {return x+1;}}", Some(serde_json::json!([2]))), + ( + "function a(x) {if (true) {return x;} else if (false) {return x+1;} else {return 4;}}", + Some(serde_json::json!([3])), + ), + ( + "function a(x) {for(var i = 0; i < 5; i ++) {x ++;} return x;}", + Some(serde_json::json!([2])), + ), + ("function a(obj) {for(var i in obj) {obj[i] = 3;}}", Some(serde_json::json!([2]))), + ( + "function a(x) {for(var i = 0; i < 5; i ++) {if(i % 2 === 0) {x ++;}} return x;}", + Some(serde_json::json!([3])), + ), + ( + "function a(obj) {if(obj){ for(var x in obj) {try {x.getThis();} catch (e) {x.getThat();}}} else {return false;}}", + Some(serde_json::json!([4])), + ), + ( + "function a(x) {try {x.getThis();} catch (e) {x.getThat();}}", + Some(serde_json::json!([2])), + ), + ("function a(x) {return x === 4 ? 3 : 5;}", Some(serde_json::json!([2]))), + ("function a(x) {return x === 4 ? 3 : (x === 3 ? 2 : 1);}", Some(serde_json::json!([3]))), + ("function a(x) {return x || 4;}", Some(serde_json::json!([2]))), + ("function a(x) {x && 4;}", Some(serde_json::json!([2]))), + ("function a(x) {x ?? 4;}", Some(serde_json::json!([2]))), + ("function a(x) {x ||= 4;}", Some(serde_json::json!([2]))), + ("function a(x) {x &&= 4;}", Some(serde_json::json!([2]))), + ("function a(x) {x ??= 4;}", Some(serde_json::json!([2]))), + ("function a(x) {x = 4;}", Some(serde_json::json!([1]))), + ("function a(x) {x |= 4;}", Some(serde_json::json!([1]))), + ("function a(x) {x &= 4;}", Some(serde_json::json!([1]))), + ("function a(x) {x += 4;}", Some(serde_json::json!([1]))), + ("function a(x) {x >>= 4;}", Some(serde_json::json!([1]))), + ("function a(x) {x >>>= 4;}", Some(serde_json::json!([1]))), + ("function a(x) {x == 4;}", Some(serde_json::json!([1]))), + ("function a(x) {x === 4;}", Some(serde_json::json!([1]))), + ( + "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}}", + Some(serde_json::json!([3])), + ), + ( + "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}}", + Some(serde_json::json!([4])), + ), + ("function a(x) {while(true) {'foo';}}", Some(serde_json::json!([2]))), + ("function a(x) {do {'foo';} while (true)}", Some(serde_json::json!([2]))), + ("if (foo) { bar(); }", Some(serde_json::json!([3]))), + ("var a = (x) => {do {'foo';} while (true)}", Some(serde_json::json!([2]))), // { "ecmaVersion": 6 }, + ( + "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}}", + Some(serde_json::json!([{ "max": 2, "variant": "modified" }])), + ), + ( + "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}}", + Some(serde_json::json!([{ "max": 3, "variant": "modified" }])), + ), + ("function foo() { class C { x = a || b; y = c || d; } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ( + "function foo() { class C { static x = a || b; static y = c || d; } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ( + "function foo() { class C { x = a || b; y = c || d; } e || f; }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ( + "function foo() { a || b; class C { x = c || d; y = e || f; } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ("function foo() { class C { [x || y] = a || b; } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = a || b; y() { c || d; } z = e || f; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x() { a || b; } y = c || d; z() { e || f; } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = (() => { a || b }) || (() => { c || d }) }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = () => { a || b }; y = () => { c || d } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = a || (() => { b || c }); }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = class { y = a || b; z = c || d; }; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = a || class { y = b || c; z = d || e; }; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x; y = a; static z; static q = b; }", Some(serde_json::json!([1]))), // { "ecmaVersion": 2022 }, + ( + "function foo() { class C { static { a || b; } static { c || d; } } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ("function foo() { a || b; class C { static { c || d; } } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("function foo() { class C { static { a || b; } } c || d; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ( + "function foo() { class C { static { a || b; } } class D { static { c || d; } } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ("class C { static { a || b; } static { c || d; } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ( + "class C { static { a || b; } static { c || d; } static { e || f; } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ("class C { static { () => a || b; c || d; } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ( + "class C { static { a || b; () => c || d; } static { c || d; } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ("class C { static { a } }", Some(serde_json::json!([1]))), // { "ecmaVersion": 2022 }, + ("class C { static { a } static { b } }", Some(serde_json::json!([1]))), // { "ecmaVersion": 2022 }, + ( + "class C { static { a || b; } } class D { static { c || d; } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ("class C { static { a || b; } static c = d || e; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { static a = b || c; static { c || d; } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { static { a || b; } c = d || e; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { a = b || c; static { d || e; } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { static { a || b; c || d; } }", Some(serde_json::json!([3]))), // { "ecmaVersion": 2022 }, + ("class C { static { if (a || b) c = d || e; } }", Some(serde_json::json!([4]))), // { "ecmaVersion": 2022 }, + ("function b(x) {}", Some(serde_json::json!([{ "max": 1 }]))), + ("function a(b) { b?.c; }", Some(serde_json::json!([{ "max": 2 }]))), + ("function a(b = '') {}", Some(serde_json::json!([{ "max": 2 }]))), + ("function a(b) { const { c = '' } = b; }", Some(serde_json::json!([{ "max": 2 }]))), + ("function a(b) { const [ c = '' ] = b; }", Some(serde_json::json!([{ "max": 2 }]))), + ]; + + let fail = vec![ + ("function a(x) {}", Some(serde_json::json!([0]))), + ( + "function foo(x) {if (x > 10) {return 'x is greater than 10';} else if (x > 5) {return 'x is greater than 5';} else {return 'x is less than 5';}}", + Some(serde_json::json!([2])), + ), + ("var func = function () {}", Some(serde_json::json!([0]))), + ("var obj = { a(x) {} }", Some(serde_json::json!([0]))), // { "ecmaVersion": 6 }, + ("class Test { a(x) {} }", Some(serde_json::json!([0]))), // { "ecmaVersion": 6 }, + ("var a = (x) => {if (true) {return x;}}", Some(serde_json::json!([1]))), // { "ecmaVersion": 6 }, + ("function a(x) {if (true) {return x;}}", Some(serde_json::json!([1]))), + ("function a(x) {if (true) {return x;} else {return x+1;}}", Some(serde_json::json!([1]))), + ( + "function a(x) {if (true) {return x;} else if (false) {return x+1;} else {return 4;}}", + Some(serde_json::json!([2])), + ), + ( + "function a(x) {for(var i = 0; i < 5; i ++) {x ++;} return x;}", + Some(serde_json::json!([1])), + ), + ("function a(obj) {for(var i in obj) {obj[i] = 3;}}", Some(serde_json::json!([1]))), + ("function a(obj) {for(var i of obj) {obj[i] = 3;}}", Some(serde_json::json!([1]))), // { "ecmaVersion": 6 }, + ( + "function a(x) {for(var i = 0; i < 5; i ++) {if(i % 2 === 0) {x ++;}} return x;}", + Some(serde_json::json!([2])), + ), + ( + "function a(obj) {if(obj){ for(var x in obj) {try {x.getThis();} catch (e) {x.getThat();}}} else {return false;}}", + Some(serde_json::json!([3])), + ), + ( + "function a(x) {try {x.getThis();} catch (e) {x.getThat();}}", + Some(serde_json::json!([1])), + ), + ("function a(x) {return x === 4 ? 3 : 5;}", Some(serde_json::json!([1]))), + ("function a(x) {return x === 4 ? 3 : (x === 3 ? 2 : 1);}", Some(serde_json::json!([2]))), + ("function a(x) {return x || 4;}", Some(serde_json::json!([1]))), + ("function a(x) {x && 4;}", Some(serde_json::json!([1]))), + ("function a(x) {x ?? 4;}", Some(serde_json::json!([1]))), + ("function a(x) {x ||= 4;}", Some(serde_json::json!([1]))), + ("function a(x) {x &&= 4;}", Some(serde_json::json!([1]))), + ("function a(x) {x ??= 4;}", Some(serde_json::json!([1]))), + ( + "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}}", + Some(serde_json::json!([2])), + ), + ( + "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}}", + Some(serde_json::json!([3])), + ), + ("function a(x) {while(true) {'foo';}}", Some(serde_json::json!([1]))), + ("function a(x) {do {'foo';} while (true)}", Some(serde_json::json!([1]))), + ( + "function a(x) {(function() {while(true){'foo';}})(); (function() {while(true){'bar';}})();}", + Some(serde_json::json!([1])), + ), + ( + "function a(x) {(function() {while(true){'foo';}})(); (function() {'bar';})();}", + Some(serde_json::json!([1])), + ), + ("var obj = { a(x) { return x ? 0 : 1; } };", Some(serde_json::json!([1]))), // { "ecmaVersion": 6 }, + ("var obj = { a: function b(x) { return x ? 0 : 1; } };", Some(serde_json::json!([1]))), + ( + "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}}", + Some(serde_json::json!([{ "max": 1, "variant": "modified" }])), + ), + ( + "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}}", + Some(serde_json::json!([{ "max": 2, "variant": "modified" }])), + ), + ("function foo () { a || b; class C { x; } c || d; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("function foo () { a || b; class C { x = c; } d || e; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("function foo () { a || b; class C { [x || y]; } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("function foo () { a || b; class C { [x || y] = c; } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("function foo () { class C { [x || y]; } a || b; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("function foo () { class C { [x || y] = a; } b || c; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("function foo () { class C { [x || y]; [z || q]; } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ( + "function foo () { class C { [x || y] = a; [z || q] = b; } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ( + "function foo () { a || b; class C { x = c || d; } e || f; }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ( + "class C { x(){ a || b; } y = c || d || e; z() { f || g; } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ("class C { x = a || b; y() { c || d || e; } z = f || g; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x; y() { c || d || e; } z; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = a || b; }", Some(serde_json::json!([1]))), // { "ecmaVersion": 2022 }, + ("(class { x = a || b; })", Some(serde_json::json!([1]))), // { "ecmaVersion": 2022 }, + ("class C { static x = a || b; }", Some(serde_json::json!([1]))), // { "ecmaVersion": 2022 }, + ("(class { x = a ? b : c; })", Some(serde_json::json!([1]))), // { "ecmaVersion": 2022 }, + ("class C { x = a || b || c; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = a || b; y = b || c || d; z = e || f; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = a || b || c; y = d || e; z = f || g || h; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = () => a || b || c; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = (() => a || b || c) || d; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = () => a || b || c; y = d || e; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { x = () => a || b || c; y = d || e || f; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ( + "class C { x = function () { a || b }; y = function () { c || d }; }", + Some(serde_json::json!([1])), + ), // { "ecmaVersion": 2022 }, + ("class C { x = class { [y || z]; }; }", Some(serde_json::json!([1]))), // { "ecmaVersion": 2022 }, + ("class C { x = class { [y || z] = a; }; }", Some(serde_json::json!([1]))), // { "ecmaVersion": 2022 }, + ("class C { x = class { y = a || b; }; }", Some(serde_json::json!([1]))), // { "ecmaVersion": 2022 }, + ("function foo () { a || b; class C { static {} } c || d; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ( + "function foo () { a || b; class C { static { c || d; } } e || f; }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ("class C { static { a || b; } }", Some(serde_json::json!([1]))), // { "ecmaVersion": 2022 }, + ("class C { static { a || b || c; } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { static { a || b; c || d; } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("class C { static { a || b; c || d; e || f; } }", Some(serde_json::json!([3]))), // { "ecmaVersion": 2022 }, + ("class C { static { a || b; c || d; { e || f; } } }", Some(serde_json::json!([3]))), // { "ecmaVersion": 2022 }, + ("class C { static { if (a || b) c = d || e; } }", Some(serde_json::json!([3]))), // { "ecmaVersion": 2022 }, + ( + "class C { static { if (a || b) c = (d => e || f)() || (g => h || i)(); } }", + Some(serde_json::json!([3])), + ), // { "ecmaVersion": 2022 }, + ( + "class C { x(){ a || b; } static { c || d || e; } z() { f || g; } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ( + "class C { x = a || b; static { c || d || e; } y = f || g; }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ( + "class C { static x = a || b; static { c || d || e; } static y = f || g; }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ( + "class C { static { a || b; } static(){ c || d || e; } static { f || g; } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ( + "class C { static { a || b; } static static(){ c || d || e; } static { f || g; } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ( + "class C { static { a || b; } static x = c || d || e; static { f || g; } }", + Some(serde_json::json!([2])), + ), // { "ecmaVersion": 2022 }, + ( + "class C { static { a || b || c || d; } static { e || f || g; } }", + Some(serde_json::json!([3])), + ), // { "ecmaVersion": 2022 }, + ( + "class C { static { a || b || c; } static { d || e || f || g; } }", + Some(serde_json::json!([3])), + ), // { "ecmaVersion": 2022 }, + ( + "class C { static { a || b || c || d; } static { e || f || g || h; } }", + Some(serde_json::json!([3])), + ), // { "ecmaVersion": 2022 }, + ("class C { x = () => a || b || c; y = f || g || h; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, + ("function a(x) {}", Some(serde_json::json!([{ "max": 0 }]))), + ( + "const obj = { b: (a) => a?.b?.c, c: function (a) { return a?.b?.c; } };", + Some(serde_json::json!([{ "max": 2 }])), + ), + ("function a(b) { b?.c; }", Some(serde_json::json!([{ "max": 1 }]))), + ("function a(b) { b?.['c']; }", Some(serde_json::json!([{ "max": 1 }]))), + ("function a(b) { b?.c; d || e; }", Some(serde_json::json!([{ "max": 2 }]))), + ("function a(b) { b?.c?.d; }", Some(serde_json::json!([{ "max": 2 }]))), + ("function a(b) { b?.['c']?.['d']; }", Some(serde_json::json!([{ "max": 2 }]))), + ("function a(b) { b?.c?.['d']; }", Some(serde_json::json!([{ "max": 2 }]))), + ("function a(b) { b?.c.d?.e; }", Some(serde_json::json!([{ "max": 2 }]))), + ("function a(b) { b?.c?.(); }", Some(serde_json::json!([{ "max": 2 }]))), + ("function a(b) { b?.c?.()?.(); }", Some(serde_json::json!([{ "max": 3 }]))), + ("function a(b = '') {}", Some(serde_json::json!([{ "max": 1 }]))), + ("function a(b) { const { c = '' } = b; }", Some(serde_json::json!([{ "max": 1 }]))), + ("function a(b) { const [ c = '' ] = b; }", Some(serde_json::json!([{ "max": 1 }]))), + ( + "function a(b) { const [ { c: d = '' } = {} ] = b; }", + Some(serde_json::json!([{ "max": 1 }])), + ), + ]; + + Tester::new(Complexity::NAME, Complexity::PLUGIN, pass, fail).test_and_snapshot(); +} From fe70a31003699e6d884df007af9d136ad13194ab Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Wed, 31 Dec 2025 19:16:57 -0500 Subject: [PATCH 02/15] ComplexityConfig naming and setup --- .../oxc_linter/src/rules/eslint/complexity.rs | 60 +++++++++++++------ 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index b1cd9ca73b78a..ee965e8930a3a 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -3,6 +3,8 @@ use oxc_macros::declare_oxc_lint; use oxc_span::Span; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::ops::Deref; use crate::{ AstNode, @@ -18,30 +20,40 @@ fn complexity_diagnostic(span: Span) -> OxcDiagnostic { .with_label(span) } -#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(untagged, rename_all = "camelCase")] -enum ConfigElement0 { - Unlabeled1(i32), - Unlabeled2(ConfigElement00), -} +const THRESHOLD_DEFAULT: usize = 20; -#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase", default)] #[schemars(rename_all = "camelCase")] -struct ConfigElement00 { - max: i32, - maximum: i32, +pub struct ComplexityConfig { + max: usize, variant: Variant, } -#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] + +impl Default for ComplexityConfig { + fn default() -> Self { + Self { max: THRESHOLD_DEFAULT, variant: Variant::Classic } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] #[schemars(untagged, rename_all = "camelCase")] -enum Variant { - #[default] +pub enum Variant { Classic, Modified, } #[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema)] -pub struct Complexity(ConfigElement0); +pub struct Complexity(Box); + +impl Deref for Complexity { + type Target = ComplexityConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} // See for documentation details. declare_oxc_lint!( @@ -76,12 +88,24 @@ declare_oxc_lint!( impl Rule for Complexity { fn from_configuration(value: serde_json::Value) -> Result { - Ok(serde_json::from_value::>(value) - .unwrap_or_default() - .into_inner()) + // dbg!(&value); + if let Some(max) = value + .get(0) + .and_then(Value::as_number) + .and_then(serde_json::Number::as_u64) + .and_then(|v| usize::try_from(v).ok()) + { + Ok(Self(Box::new(ComplexityConfig { max, variant: Variant::Classic }))) + } else { + Ok(serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner()) + } } - fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {} + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + // dbg!(self.max, self.variant); + } } #[test] From 960f1e0fdd563d701eba16425cc23263754031e3 Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Thu, 1 Jan 2026 10:25:02 -0500 Subject: [PATCH 03/15] Cases now pass for now - messages, span, improvements to go --- .../src/generated/rule_runner_impls.rs | 2 +- .../oxc_linter/src/rules/eslint/complexity.rs | 205 +++++- .../src/snapshots/eslint_complexity.snap | 608 ++++++++++++++++++ 3 files changed, 804 insertions(+), 11 deletions(-) create mode 100644 crates/oxc_linter/src/snapshots/eslint_complexity.snap diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index d4b9ef51036a2..b588b823e0efa 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -53,7 +53,7 @@ impl RuleRunner for crate::rules::eslint::class_methods_use_this::ClassMethodsUs impl RuleRunner for crate::rules::eslint::complexity::Complexity { const NODE_TYPES: Option<&AstTypesBitset> = None; - const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; } impl RuleRunner for crate::rules::eslint::constructor_super::ConstructorSuper { diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index ee965e8930a3a..dc436244a55ec 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -1,6 +1,9 @@ +use oxc_ast_visit::Visit; +use oxc_ast_visit::walk; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{GetSpan, Span}; +use oxc_syntax::operator::AssignmentOperator; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -13,11 +16,12 @@ use crate::{ rule::{DefaultRuleConfig, Rule}, }; -fn complexity_diagnostic(span: Span) -> OxcDiagnostic { +fn complexity_diagnostic(span: Span, name: &str, complexity: usize, max: usize) -> OxcDiagnostic { // See for details - OxcDiagnostic::warn("Should be an imperative statement about what is wrong.") - .with_help("Should be a command-like statement that tells the user how to fix the issue.") - .with_label(span) + OxcDiagnostic::warn(format!( + "{name} has a complexity of {complexity}. Maximum allowed is {max}." + )) + .with_label(span) } const THRESHOLD_DEFAULT: usize = 20; @@ -36,7 +40,7 @@ impl Default for ComplexityConfig { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(untagged, rename_all = "camelCase")] pub enum Variant { @@ -83,12 +87,11 @@ declare_oxc_lint!( pending, // TODO: describe fix capabilities. Remove if no fix can be done, // keep at 'pending' if you think one could be added but don't know how. // Options are 'fix', 'fix_dangerous', 'suggestion', and 'conditional_fix_suggestion' - config = Complexity, + config = ComplexityConfig, ); impl Rule for Complexity { fn from_configuration(value: serde_json::Value) -> Result { - // dbg!(&value); if let Some(max) = value .get(0) .and_then(Value::as_number) @@ -103,8 +106,190 @@ impl Rule for Complexity { } } - fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - // dbg!(self.max, self.variant); + fn run_once<'a>(&self, ctx: &LintContext<'a>) { + let mut visitor = ComplexityVisitor::new(self.max, self.variant); + + visitor.visit_program(ctx.nodes().program()); + + for (diagnostic, complexity, span) in visitor.diagnostics { + ctx.diagnostic(complexity_diagnostic(span, "idk", complexity, self.max)); + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum DiagnosticType { + ClassPropertyInitializer, + ClassStaticBlock, + Function, +} + +struct ComplexityVisitor { + threshold: usize, + variant: Variant, + + complexity_stack: Vec, + diagnostics: Vec<(DiagnosticType, usize, Span)>, +} + +impl ComplexityVisitor { + fn new(threshold: usize, variant: Variant) -> Self { + Self { threshold, variant, complexity_stack: Vec::new(), diagnostics: Vec::new() } + } + + fn increase_complexity(&mut self) { + if let Some(complexity) = self.complexity_stack.last_mut() { + *complexity += 1; + } + } + + fn start(&mut self) { + self.complexity_stack.push(1); + } + + fn end(&mut self, diagnostic_type: DiagnosticType, span: Span) { + if let Some(complexity) = self.complexity_stack.pop() { + if complexity > self.threshold { + self.diagnostics.push((diagnostic_type, complexity, span)); + } + } + } +} + +impl Visit<'_> for ComplexityVisitor { + fn visit_catch_clause(&mut self, it: &oxc_ast::ast::CatchClause<'_>) { + self.increase_complexity(); + walk::walk_catch_clause(self, it); + } + + fn visit_conditional_expression(&mut self, it: &oxc_ast::ast::ConditionalExpression<'_>) { + self.increase_complexity(); + walk::walk_conditional_expression(self, it); + } + + fn visit_logical_expression(&mut self, it: &oxc_ast::ast::LogicalExpression<'_>) { + self.increase_complexity(); + walk::walk_logical_expression(self, it); + } + + fn visit_for_statement(&mut self, it: &oxc_ast::ast::ForStatement<'_>) { + self.increase_complexity(); + walk::walk_for_statement(self, it); + } + + fn visit_for_in_statement(&mut self, it: &oxc_ast::ast::ForInStatement<'_>) { + self.increase_complexity(); + walk::walk_for_in_statement(self, it); + } + + fn visit_for_of_statement(&mut self, it: &oxc_ast::ast::ForOfStatement<'_>) { + self.increase_complexity(); + walk::walk_for_of_statement(self, it); + } + + fn visit_if_statement(&mut self, it: &oxc_ast::ast::IfStatement<'_>) { + self.increase_complexity(); + walk::walk_if_statement(self, it); + } + + fn visit_while_statement(&mut self, it: &oxc_ast::ast::WhileStatement<'_>) { + self.increase_complexity(); + walk::walk_while_statement(self, it); + } + + fn visit_do_while_statement(&mut self, it: &oxc_ast::ast::DoWhileStatement<'_>) { + self.increase_complexity(); + walk::walk_do_while_statement(self, it); + } + + fn visit_assignment_pattern(&mut self, it: &oxc_ast::ast::AssignmentPattern<'_>) { + self.increase_complexity(); + walk::walk_assignment_pattern(self, it); + } + + fn visit_formal_parameter(&mut self, it: &oxc_ast::ast::FormalParameter<'_>) { + if let Some(_) = &it.initializer { + self.increase_complexity(); + } + walk::walk_formal_parameter(self, it); + } + + fn visit_switch_case(&mut self, it: &oxc_ast::ast::SwitchCase<'_>) { + if self.variant == Variant::Classic && it.test.is_some() { + self.increase_complexity(); + } + walk::walk_switch_case(self, it); + } + + fn visit_switch_statement(&mut self, it: &oxc_ast::ast::SwitchStatement<'_>) { + if self.variant == Variant::Modified { + self.increase_complexity(); + } + walk::walk_switch_statement(self, it); + } + + fn visit_assignment_expression(&mut self, it: &oxc_ast::ast::AssignmentExpression<'_>) { + if matches!( + it.operator, + AssignmentOperator::LogicalAnd + | AssignmentOperator::LogicalOr + | AssignmentOperator::LogicalNullish + ) { + self.increase_complexity(); + } + walk::walk_assignment_expression(self, it); + } + + fn visit_member_expression(&mut self, it: &oxc_ast::ast::MemberExpression<'_>) { + if it.optional() { + self.increase_complexity(); + } + walk::walk_member_expression(self, it); + } + + fn visit_call_expression(&mut self, it: &oxc_ast::ast::CallExpression<'_>) { + if it.optional { + self.increase_complexity(); + } + walk::walk_call_expression(self, it); + } + + fn visit_function(&mut self, it: &oxc_ast::ast::Function<'_>, flags: oxc_semantic::ScopeFlags) { + self.start(); + walk::walk_function(self, it, flags); + // let report_span = Span::new(it.span.start, it.params.span.start); + self.end(DiagnosticType::Function, it.span); + } + + fn visit_arrow_function_expression(&mut self, it: &oxc_ast::ast::ArrowFunctionExpression<'_>) { + self.start(); + walk::walk_arrow_function_expression(self, it); + // let report_span = Span::new(it.params.span.end + 1, it.body.span.start - 1); + self.end(DiagnosticType::Function, it.span); + } + + fn visit_static_block(&mut self, it: &oxc_ast::ast::StaticBlock<'_>) { + self.start(); + walk::walk_static_block(self, it); + // let report_span = Span::new(it.span.start, it.span.start + 6); + self.end(DiagnosticType::ClassStaticBlock, it.span); + } + + fn visit_property_definition(&mut self, it: &oxc_ast::ast::PropertyDefinition<'_>) { + let kind = oxc_ast::AstKind::PropertyDefinition(self.alloc(it)); + self.enter_node(kind); + self.visit_span(&it.span); + self.visit_decorators(&it.decorators); + self.visit_property_key(&it.key); + if let Some(type_annotation) = &it.type_annotation { + self.visit_ts_type_annotation(type_annotation); + } + if let Some(value) = &it.value { + self.start(); + self.visit_expression(value); + self.end(DiagnosticType::ClassPropertyInitializer, value.span()); + } + self.leave_node(kind); } } diff --git a/crates/oxc_linter/src/snapshots/eslint_complexity.snap b/crates/oxc_linter/src/snapshots/eslint_complexity.snap new file mode 100644 index 0000000000000..aa2bdb5421072 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_complexity.snap @@ -0,0 +1,608 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint(complexity): idk has a complexity of 1. Maximum allowed is 0. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {} + · ──────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function foo(x) {if (x > 10) {return 'x is greater than 10';} else if (x > 5) {return 'x is greater than 5';} else {return 'x is less than 5';}} + · ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 1. Maximum allowed is 0. + ╭─[complexity.tsx:1:12] + 1 │ var func = function () {} + · ────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 1. Maximum allowed is 0. + ╭─[complexity.tsx:1:14] + 1 │ var obj = { a(x) {} } + · ────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 1. Maximum allowed is 0. + ╭─[complexity.tsx:1:15] + 1 │ class Test { a(x) {} } + · ────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:9] + 1 │ var a = (x) => {if (true) {return x;}} + · ────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {if (true) {return x;}} + · ───────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {if (true) {return x;} else {return x+1;}} + · ──────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {if (true) {return x;} else if (false) {return x+1;} else {return 4;}} + · ──────────────────────────────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {for(var i = 0; i < 5; i ++) {x ++;} return x;} + · ───────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(obj) {for(var i in obj) {obj[i] = 3;}} + · ───────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(obj) {for(var i of obj) {obj[i] = 3;}} + · ───────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {for(var i = 0; i < 5; i ++) {if(i % 2 === 0) {x ++;}} return x;} + · ─────────────────────────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ╭─[complexity.tsx:1:1] + 1 │ function a(obj) {if(obj){ for(var x in obj) {try {x.getThis();} catch (e) {x.getThat();}}} else {return false;}} + · ──────────────────────────────────────────────────────────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {try {x.getThis();} catch (e) {x.getThat();}} + · ─────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {return x === 4 ? 3 : 5;} + · ─────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {return x === 4 ? 3 : (x === 3 ? 2 : 1);} + · ─────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {return x || 4;} + · ────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {x && 4;} + · ─────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {x ?? 4;} + · ─────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {x ||= 4;} + · ──────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {x &&= 4;} + · ──────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {x ??= 4;} + · ──────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}} + · ────────────────────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}} + · ──────────────────────────────────────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {while(true) {'foo';}} + · ──────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {do {'foo';} while (true)} + · ──────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:17] + 1 │ function a(x) {(function() {while(true){'foo';}})(); (function() {while(true){'bar';}})();} + · ──────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:55] + 1 │ function a(x) {(function() {while(true){'foo';}})(); (function() {while(true){'bar';}})();} + · ──────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:17] + 1 │ function a(x) {(function() {while(true){'foo';}})(); (function() {'bar';})();} + · ──────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:14] + 1 │ var obj = { a(x) { return x ? 0 : 1; } }; + · ───────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:16] + 1 │ var obj = { a: function b(x) { return x ? 0 : 1; } }; + · ─────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}} + · ────────────────────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}} + · ──────────────────────────────────────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function foo () { a || b; class C { x; } c || d; } + · ────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function foo () { a || b; class C { x = c; } d || e; } + · ────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function foo () { a || b; class C { [x || y]; } } + · ───────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function foo () { a || b; class C { [x || y] = c; } } + · ───────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function foo () { class C { [x || y]; } a || b; } + · ───────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function foo () { class C { [x || y] = a; } b || c; } + · ───────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function foo () { class C { [x || y]; [z || q]; } } + · ─────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function foo () { class C { [x || y] = a; [z || q] = b; } } + · ─────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function foo () { a || b; class C { x = c || d; } e || f; } + · ─────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:30] + 1 │ class C { x(){ a || b; } y = c || d || e; z() { f || g; } } + · ─────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:24] + 1 │ class C { x = a || b; y() { c || d || e; } z = f || g; } + · ─────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:15] + 1 │ class C { x; y() { c || d || e; } z; } + · ─────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:15] + 1 │ class C { x = a || b; } + · ────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:14] + 1 │ (class { x = a || b; }) + · ────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:22] + 1 │ class C { static x = a || b; } + · ────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:14] + 1 │ (class { x = a ? b : c; }) + · ───────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:15] + 1 │ class C { x = a || b || c; } + · ─────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:27] + 1 │ class C { x = a || b; y = b || c || d; z = e || f; } + · ─────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:15] + 1 │ class C { x = a || b || c; y = d || e; z = f || g || h; } + · ─────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:44] + 1 │ class C { x = a || b || c; y = d || e; z = f || g || h; } + · ─────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:15] + 1 │ class C { x = () => a || b || c; } + · ───────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:16] + 1 │ class C { x = (() => a || b || c) || d; } + · ───────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:15] + 1 │ class C { x = () => a || b || c; y = d || e; } + · ───────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:15] + 1 │ class C { x = () => a || b || c; y = d || e || f; } + · ───────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:38] + 1 │ class C { x = () => a || b || c; y = d || e || f; } + · ─────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:15] + 1 │ class C { x = function () { a || b }; y = function () { c || d }; } + · ────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:43] + 1 │ class C { x = function () { a || b }; y = function () { c || d }; } + · ────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:15] + 1 │ class C { x = class { [y || z]; }; } + · ─────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:15] + 1 │ class C { x = class { [y || z] = a; }; } + · ─────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:27] + 1 │ class C { x = class { y = a || b; }; } + · ────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function foo () { a || b; class C { static {} } c || d; } + · ───────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function foo () { a || b; class C { static { c || d; } } e || f; } + · ────────────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:11] + 1 │ class C { static { a || b; } } + · ────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:11] + 1 │ class C { static { a || b || c; } } + · ─────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:11] + 1 │ class C { static { a || b; c || d; } } + · ────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ╭─[complexity.tsx:1:11] + 1 │ class C { static { a || b; c || d; e || f; } } + · ────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ╭─[complexity.tsx:1:11] + 1 │ class C { static { a || b; c || d; { e || f; } } } + · ────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ╭─[complexity.tsx:1:11] + 1 │ class C { static { if (a || b) c = d || e; } } + · ────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ╭─[complexity.tsx:1:11] + 1 │ class C { static { if (a || b) c = (d => e || f)() || (g => h || i)(); } } + · ────────────────────────────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:26] + 1 │ class C { x(){ a || b; } static { c || d || e; } z() { f || g; } } + · ─────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:23] + 1 │ class C { x = a || b; static { c || d || e; } y = f || g; } + · ─────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:30] + 1 │ class C { static x = a || b; static { c || d || e; } static y = f || g; } + · ─────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:36] + 1 │ class C { static { a || b; } static(){ c || d || e; } static { f || g; } } + · ────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:43] + 1 │ class C { static { a || b; } static static(){ c || d || e; } static { f || g; } } + · ────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:41] + 1 │ class C { static { a || b; } static x = c || d || e; static { f || g; } } + · ─────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ╭─[complexity.tsx:1:11] + 1 │ class C { static { a || b || c || d; } static { e || f || g; } } + · ──────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ╭─[complexity.tsx:1:35] + 1 │ class C { static { a || b || c; } static { d || e || f || g; } } + · ──────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ╭─[complexity.tsx:1:11] + 1 │ class C { static { a || b || c || d; } static { e || f || g || h; } } + · ──────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ╭─[complexity.tsx:1:40] + 1 │ class C { static { a || b || c || d; } static { e || f || g || h; } } + · ──────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:15] + 1 │ class C { x = () => a || b || c; y = f || g || h; } + · ───────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:38] + 1 │ class C { x = () => a || b || c; y = f || g || h; } + · ─────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 1. Maximum allowed is 0. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {} + · ──────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:18] + 1 │ const obj = { b: (a) => a?.b?.c, c: function (a) { return a?.b?.c; } }; + · ────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:37] + 1 │ const obj = { b: (a) => a?.b?.c, c: function (a) { return a?.b?.c; } }; + · ──────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(b) { b?.c; } + · ─────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(b) { b?.['c']; } + · ─────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function a(b) { b?.c; d || e; } + · ─────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function a(b) { b?.c?.d; } + · ────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function a(b) { b?.['c']?.['d']; } + · ────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function a(b) { b?.c?.['d']; } + · ────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function a(b) { b?.c.d?.e; } + · ──────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ╭─[complexity.tsx:1:1] + 1 │ function a(b) { b?.c?.(); } + · ─────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ╭─[complexity.tsx:1:1] + 1 │ function a(b) { b?.c?.()?.(); } + · ─────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(b = '') {} + · ───────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(b) { const { c = '' } = b; } + · ─────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(b) { const [ c = '' ] = b; } + · ─────────────────────────────────────── + ╰──── + + ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(b) { const [ { c: d = '' } = {} ] = b; } + · ─────────────────────────────────────────────────── + ╰──── From d7a5c415e27f09cae2acfe3641333a49a8f6e97e Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Thu, 1 Jan 2026 16:56:16 -0500 Subject: [PATCH 04/15] Error message uses correct name --- .../src/generated/rule_runner_impls.rs | 9 +- .../oxc_linter/src/rules/eslint/complexity.rs | 174 ++++++++------- .../src/snapshots/eslint_complexity.snap | 202 +++++++++--------- 3 files changed, 210 insertions(+), 175 deletions(-) diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index b588b823e0efa..1d268f762098a 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -52,8 +52,13 @@ impl RuleRunner for crate::rules::eslint::class_methods_use_this::ClassMethodsUs } impl RuleRunner for crate::rules::eslint::complexity::Complexity { - const NODE_TYPES: Option<&AstTypesBitset> = None; - const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; + const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ + AstType::Function, + AstType::ArrowFunctionExpression, + AstType::StaticBlock, + AstType::PropertyDefinition, + ])); + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } impl RuleRunner for crate::rules::eslint::constructor_super::ConstructorSuper { diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index dc436244a55ec..93bf857b87c50 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -1,7 +1,9 @@ +use oxc_ast::AstKind; use oxc_ast_visit::Visit; use oxc_ast_visit::walk; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; +use oxc_semantic::ScopeFlags; use oxc_span::{GetSpan, Span}; use oxc_syntax::operator::AssignmentOperator; use schemars::JsonSchema; @@ -11,6 +13,7 @@ use std::ops::Deref; use crate::{ AstNode, + ast_util::get_function_name_with_kind, context::LintContext, fixer::{RuleFix, RuleFixer}, rule::{DefaultRuleConfig, Rule}, @@ -106,13 +109,45 @@ impl Rule for Complexity { } } - fn run_once<'a>(&self, ctx: &LintContext<'a>) { - let mut visitor = ComplexityVisitor::new(self.max, self.variant); - - visitor.visit_program(ctx.nodes().program()); - - for (diagnostic, complexity, span) in visitor.diagnostics { - ctx.diagnostic(complexity_diagnostic(span, "idk", complexity, self.max)); + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let mut visitor = ComplexityVisitor::new(self.variant); + let (complexity, span, diagnostic_type) = match node.kind() { + AstKind::Function(func) => { + visitor.visit_function(func, ScopeFlags::Function); + (visitor.complexity, func.span, DiagnosticType::Function) + } + AstKind::ArrowFunctionExpression(func) => { + visitor.visit_arrow_function_expression(func); + (visitor.complexity, func.span, DiagnosticType::Function) + } + AstKind::StaticBlock(block) => { + visitor.visit_static_block(block); + (visitor.complexity, block.span, DiagnosticType::ClassStaticBlock) + } + AstKind::PropertyDefinition(prop_def) => { + if let Some(expr) = &prop_def.value { + visitor.visit_property_definition(prop_def); + (visitor.complexity, expr.span(), DiagnosticType::ClassPropertyInitializer) + } else { + return; + } + } + _ => { + return; + } + }; + + if complexity > self.max { + let name = match diagnostic_type { + DiagnosticType::ClassStaticBlock => "class static block", + DiagnosticType::ClassPropertyInitializer => "class field initializer", + DiagnosticType::Function => { + let parent_node = ctx.nodes().parent_node(node.id()); + &get_function_name_with_kind(node, parent_node) + } + }; + + ctx.diagnostic(complexity_diagnostic(span, name, complexity, self.max)); } } } @@ -125,105 +160,85 @@ enum DiagnosticType { } struct ComplexityVisitor { - threshold: usize, variant: Variant, - - complexity_stack: Vec, - diagnostics: Vec<(DiagnosticType, usize, Span)>, + complexity: usize, + has_entered_complexity_evaluation: bool, } impl ComplexityVisitor { - fn new(threshold: usize, variant: Variant) -> Self { - Self { threshold, variant, complexity_stack: Vec::new(), diagnostics: Vec::new() } - } - - fn increase_complexity(&mut self) { - if let Some(complexity) = self.complexity_stack.last_mut() { - *complexity += 1; - } - } - - fn start(&mut self) { - self.complexity_stack.push(1); - } - - fn end(&mut self, diagnostic_type: DiagnosticType, span: Span) { - if let Some(complexity) = self.complexity_stack.pop() { - if complexity > self.threshold { - self.diagnostics.push((diagnostic_type, complexity, span)); - } - } + fn new(variant: Variant) -> Self { + Self { variant, complexity: 1, has_entered_complexity_evaluation: false } } } impl Visit<'_> for ComplexityVisitor { fn visit_catch_clause(&mut self, it: &oxc_ast::ast::CatchClause<'_>) { - self.increase_complexity(); + self.complexity += 1; walk::walk_catch_clause(self, it); } fn visit_conditional_expression(&mut self, it: &oxc_ast::ast::ConditionalExpression<'_>) { - self.increase_complexity(); + self.complexity += 1; walk::walk_conditional_expression(self, it); } fn visit_logical_expression(&mut self, it: &oxc_ast::ast::LogicalExpression<'_>) { - self.increase_complexity(); + self.complexity += 1; walk::walk_logical_expression(self, it); } fn visit_for_statement(&mut self, it: &oxc_ast::ast::ForStatement<'_>) { - self.increase_complexity(); + self.complexity += 1; walk::walk_for_statement(self, it); } fn visit_for_in_statement(&mut self, it: &oxc_ast::ast::ForInStatement<'_>) { - self.increase_complexity(); + self.complexity += 1; walk::walk_for_in_statement(self, it); } fn visit_for_of_statement(&mut self, it: &oxc_ast::ast::ForOfStatement<'_>) { - self.increase_complexity(); + self.complexity += 1; walk::walk_for_of_statement(self, it); } fn visit_if_statement(&mut self, it: &oxc_ast::ast::IfStatement<'_>) { - self.increase_complexity(); + self.complexity += 1; walk::walk_if_statement(self, it); } fn visit_while_statement(&mut self, it: &oxc_ast::ast::WhileStatement<'_>) { - self.increase_complexity(); + self.complexity += 1; walk::walk_while_statement(self, it); } fn visit_do_while_statement(&mut self, it: &oxc_ast::ast::DoWhileStatement<'_>) { - self.increase_complexity(); + self.complexity += 1; walk::walk_do_while_statement(self, it); } fn visit_assignment_pattern(&mut self, it: &oxc_ast::ast::AssignmentPattern<'_>) { - self.increase_complexity(); + self.complexity += 1; walk::walk_assignment_pattern(self, it); } fn visit_formal_parameter(&mut self, it: &oxc_ast::ast::FormalParameter<'_>) { if let Some(_) = &it.initializer { - self.increase_complexity(); + self.complexity += 1; } walk::walk_formal_parameter(self, it); } fn visit_switch_case(&mut self, it: &oxc_ast::ast::SwitchCase<'_>) { if self.variant == Variant::Classic && it.test.is_some() { - self.increase_complexity(); + self.complexity += 1; } walk::walk_switch_case(self, it); } fn visit_switch_statement(&mut self, it: &oxc_ast::ast::SwitchStatement<'_>) { if self.variant == Variant::Modified { - self.increase_complexity(); + self.complexity += 1; } walk::walk_switch_statement(self, it); } @@ -235,61 +250,76 @@ impl Visit<'_> for ComplexityVisitor { | AssignmentOperator::LogicalOr | AssignmentOperator::LogicalNullish ) { - self.increase_complexity(); + self.complexity += 1; } walk::walk_assignment_expression(self, it); } fn visit_member_expression(&mut self, it: &oxc_ast::ast::MemberExpression<'_>) { if it.optional() { - self.increase_complexity(); + self.complexity += 1; } walk::walk_member_expression(self, it); } fn visit_call_expression(&mut self, it: &oxc_ast::ast::CallExpression<'_>) { if it.optional { - self.increase_complexity(); + self.complexity += 1; } walk::walk_call_expression(self, it); } fn visit_function(&mut self, it: &oxc_ast::ast::Function<'_>, flags: oxc_semantic::ScopeFlags) { - self.start(); - walk::walk_function(self, it, flags); - // let report_span = Span::new(it.span.start, it.params.span.start); - self.end(DiagnosticType::Function, it.span); + if !self.has_entered_complexity_evaluation { + self.has_entered_complexity_evaluation = true; + walk::walk_function(self, it, flags); + } + // Do not enter function if we already started evaluating complexity } fn visit_arrow_function_expression(&mut self, it: &oxc_ast::ast::ArrowFunctionExpression<'_>) { - self.start(); - walk::walk_arrow_function_expression(self, it); - // let report_span = Span::new(it.params.span.end + 1, it.body.span.start - 1); - self.end(DiagnosticType::Function, it.span); + if !self.has_entered_complexity_evaluation { + self.has_entered_complexity_evaluation = true; + walk::walk_arrow_function_expression(self, it); + } + // Do not enter function if we already started evaluating complexity } fn visit_static_block(&mut self, it: &oxc_ast::ast::StaticBlock<'_>) { - self.start(); - walk::walk_static_block(self, it); - // let report_span = Span::new(it.span.start, it.span.start + 6); - self.end(DiagnosticType::ClassStaticBlock, it.span); + if !self.has_entered_complexity_evaluation { + self.has_entered_complexity_evaluation = true; + walk::walk_static_block(self, it); + } + // Do not enter static block if we already started evaluating complexity } fn visit_property_definition(&mut self, it: &oxc_ast::ast::PropertyDefinition<'_>) { - let kind = oxc_ast::AstKind::PropertyDefinition(self.alloc(it)); - self.enter_node(kind); - self.visit_span(&it.span); - self.visit_decorators(&it.decorators); - self.visit_property_key(&it.key); - if let Some(type_annotation) = &it.type_annotation { - self.visit_ts_type_annotation(type_annotation); - } - if let Some(value) = &it.value { - self.start(); - self.visit_expression(value); - self.end(DiagnosticType::ClassPropertyInitializer, value.span()); + if !self.has_entered_complexity_evaluation { + if let Some(value) = &it.value { + self.has_entered_complexity_evaluation = true; + self.visit_expression(value); + } else { + // Do not visit any other node if there is no value expression to + // evaluate - only visit all other nodes if it is part of another + // function / static block evaluation + unreachable!(); + } + } else { + // Visit all other nodes except for value expression if part of another + // function / static block's complexity evaluation + let kind = oxc_ast::AstKind::PropertyDefinition(self.alloc(it)); + self.enter_node(kind); + self.visit_span(&it.span); + self.visit_decorators(&it.decorators); + self.visit_property_key(&it.key); + if let Some(type_annotation) = &it.type_annotation { + self.visit_ts_type_annotation(type_annotation); + } + if let Some(value) = &it.value { + // Do not enter value expression if we already started evaluating complexity + } + self.leave_node(kind); } - self.leave_node(kind); } } diff --git a/crates/oxc_linter/src/snapshots/eslint_complexity.snap b/crates/oxc_linter/src/snapshots/eslint_complexity.snap index aa2bdb5421072..720f333624f1b 100644 --- a/crates/oxc_linter/src/snapshots/eslint_complexity.snap +++ b/crates/oxc_linter/src/snapshots/eslint_complexity.snap @@ -1,607 +1,607 @@ --- source: crates/oxc_linter/src/tester.rs --- - ⚠ eslint(complexity): idk has a complexity of 1. Maximum allowed is 0. + ⚠ eslint(complexity): function `a` has a complexity of 1. Maximum allowed is 0. ╭─[complexity.tsx:1:1] 1 │ function a(x) {} · ──────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `foo` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function foo(x) {if (x > 10) {return 'x is greater than 10';} else if (x > 5) {return 'x is greater than 5';} else {return 'x is less than 5';}} · ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 1. Maximum allowed is 0. + ⚠ eslint(complexity): function has a complexity of 1. Maximum allowed is 0. ╭─[complexity.tsx:1:12] 1 │ var func = function () {} · ────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 1. Maximum allowed is 0. + ⚠ eslint(complexity): function `a` has a complexity of 1. Maximum allowed is 0. ╭─[complexity.tsx:1:14] 1 │ var obj = { a(x) {} } · ────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 1. Maximum allowed is 0. + ⚠ eslint(complexity): method `a` has a complexity of 1. Maximum allowed is 0. ╭─[complexity.tsx:1:15] 1 │ class Test { a(x) {} } · ────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:9] 1 │ var a = (x) => {if (true) {return x;}} · ────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {if (true) {return x;}} · ───────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {if (true) {return x;} else {return x+1;}} · ──────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `a` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function a(x) {if (true) {return x;} else if (false) {return x+1;} else {return 4;}} · ──────────────────────────────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {for(var i = 0; i < 5; i ++) {x ++;} return x;} · ───────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(obj) {for(var i in obj) {obj[i] = 3;}} · ───────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(obj) {for(var i of obj) {obj[i] = 3;}} · ───────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `a` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function a(x) {for(var i = 0; i < 5; i ++) {if(i % 2 === 0) {x ++;}} return x;} · ─────────────────────────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ⚠ eslint(complexity): function `a` has a complexity of 4. Maximum allowed is 3. ╭─[complexity.tsx:1:1] 1 │ function a(obj) {if(obj){ for(var x in obj) {try {x.getThis();} catch (e) {x.getThat();}}} else {return false;}} · ──────────────────────────────────────────────────────────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {try {x.getThis();} catch (e) {x.getThat();}} · ─────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {return x === 4 ? 3 : 5;} · ─────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `a` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function a(x) {return x === 4 ? 3 : (x === 3 ? 2 : 1);} · ─────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {return x || 4;} · ────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {x && 4;} · ─────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {x ?? 4;} · ─────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {x ||= 4;} · ──────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {x &&= 4;} · ──────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {x ??= 4;} · ──────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `a` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}} · ────────────────────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ⚠ eslint(complexity): function `a` has a complexity of 4. Maximum allowed is 3. ╭─[complexity.tsx:1:1] 1 │ function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}} · ──────────────────────────────────────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {while(true) {'foo';}} · ──────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {do {'foo';} while (true)} · ──────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:17] 1 │ function a(x) {(function() {while(true){'foo';}})(); (function() {while(true){'bar';}})();} · ──────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:55] 1 │ function a(x) {(function() {while(true){'foo';}})(); (function() {while(true){'bar';}})();} · ──────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:17] 1 │ function a(x) {(function() {while(true){'foo';}})(); (function() {'bar';})();} · ──────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:14] 1 │ var obj = { a(x) { return x ? 0 : 1; } }; · ───────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:16] 1 │ var obj = { a: function b(x) { return x ? 0 : 1; } }; · ─────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}} · ────────────────────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `a` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}} · ──────────────────────────────────────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `foo` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function foo () { a || b; class C { x; } c || d; } · ────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `foo` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function foo () { a || b; class C { x = c; } d || e; } · ────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `foo` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function foo () { a || b; class C { [x || y]; } } · ───────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `foo` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function foo () { a || b; class C { [x || y] = c; } } · ───────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `foo` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function foo () { class C { [x || y]; } a || b; } · ───────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `foo` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function foo () { class C { [x || y] = a; } b || c; } · ───────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `foo` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function foo () { class C { [x || y]; [z || q]; } } · ─────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `foo` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function foo () { class C { [x || y] = a; [z || q] = b; } } · ─────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `foo` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function foo () { a || b; class C { x = c || d; } e || f; } · ─────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class field initializer has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:30] 1 │ class C { x(){ a || b; } y = c || d || e; z() { f || g; } } · ─────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): method `y` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:24] 1 │ class C { x = a || b; y() { c || d || e; } z = f || g; } · ─────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): method `y` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:15] 1 │ class C { x; y() { c || d || e; } z; } · ─────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): class field initializer has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:15] 1 │ class C { x = a || b; } · ────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): class field initializer has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:14] 1 │ (class { x = a || b; }) · ────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): class field initializer has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:22] 1 │ class C { static x = a || b; } · ────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): class field initializer has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:14] 1 │ (class { x = a ? b : c; }) · ───────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class field initializer has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:15] 1 │ class C { x = a || b || c; } · ─────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class field initializer has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:27] 1 │ class C { x = a || b; y = b || c || d; z = e || f; } · ─────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class field initializer has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:15] 1 │ class C { x = a || b || c; y = d || e; z = f || g || h; } · ─────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class field initializer has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:44] 1 │ class C { x = a || b || c; y = d || e; z = f || g || h; } · ─────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): method `x` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:15] 1 │ class C { x = () => a || b || c; } · ───────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:16] 1 │ class C { x = (() => a || b || c) || d; } · ───────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): method `x` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:15] 1 │ class C { x = () => a || b || c; y = d || e; } · ───────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): method `x` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:15] 1 │ class C { x = () => a || b || c; y = d || e || f; } · ───────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class field initializer has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:38] 1 │ class C { x = () => a || b || c; y = d || e || f; } · ─────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): method `x` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:15] 1 │ class C { x = function () { a || b }; y = function () { c || d }; } · ────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): method `y` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:43] 1 │ class C { x = function () { a || b }; y = function () { c || d }; } · ────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): class field initializer has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:15] 1 │ class C { x = class { [y || z]; }; } · ─────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): class field initializer has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:15] 1 │ class C { x = class { [y || z] = a; }; } · ─────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): class field initializer has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:27] 1 │ class C { x = class { y = a || b; }; } · ────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `foo` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function foo () { a || b; class C { static {} } c || d; } · ───────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `foo` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function foo () { a || b; class C { static { c || d; } } e || f; } · ────────────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): class static block has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:11] 1 │ class C { static { a || b; } } · ────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class static block has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:11] 1 │ class C { static { a || b || c; } } · ─────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class static block has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:11] 1 │ class C { static { a || b; c || d; } } · ────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ⚠ eslint(complexity): class static block has a complexity of 4. Maximum allowed is 3. ╭─[complexity.tsx:1:11] 1 │ class C { static { a || b; c || d; e || f; } } · ────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ⚠ eslint(complexity): class static block has a complexity of 4. Maximum allowed is 3. ╭─[complexity.tsx:1:11] 1 │ class C { static { a || b; c || d; { e || f; } } } · ────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ⚠ eslint(complexity): class static block has a complexity of 4. Maximum allowed is 3. ╭─[complexity.tsx:1:11] 1 │ class C { static { if (a || b) c = d || e; } } · ────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ⚠ eslint(complexity): class static block has a complexity of 4. Maximum allowed is 3. ╭─[complexity.tsx:1:11] 1 │ class C { static { if (a || b) c = (d => e || f)() || (g => h || i)(); } } · ────────────────────────────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class static block has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:26] 1 │ class C { x(){ a || b; } static { c || d || e; } z() { f || g; } } · ─────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class static block has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:23] 1 │ class C { x = a || b; static { c || d || e; } y = f || g; } · ─────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class static block has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:30] 1 │ class C { static x = a || b; static { c || d || e; } static y = f || g; } · ─────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): method `static` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:36] 1 │ class C { static { a || b; } static(){ c || d || e; } static { f || g; } } · ────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): static method `static` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:43] 1 │ class C { static { a || b; } static static(){ c || d || e; } static { f || g; } } · ────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class field initializer has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:41] 1 │ class C { static { a || b; } static x = c || d || e; static { f || g; } } · ─────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ⚠ eslint(complexity): class static block has a complexity of 4. Maximum allowed is 3. ╭─[complexity.tsx:1:11] 1 │ class C { static { a || b || c || d; } static { e || f || g; } } · ──────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ⚠ eslint(complexity): class static block has a complexity of 4. Maximum allowed is 3. ╭─[complexity.tsx:1:35] 1 │ class C { static { a || b || c; } static { d || e || f || g; } } · ──────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ⚠ eslint(complexity): class static block has a complexity of 4. Maximum allowed is 3. ╭─[complexity.tsx:1:11] 1 │ class C { static { a || b || c || d; } static { e || f || g || h; } } · ──────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ⚠ eslint(complexity): class static block has a complexity of 4. Maximum allowed is 3. ╭─[complexity.tsx:1:40] 1 │ class C { static { a || b || c || d; } static { e || f || g || h; } } · ──────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): method `x` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:15] 1 │ class C { x = () => a || b || c; y = f || g || h; } · ───────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): class field initializer has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:38] 1 │ class C { x = () => a || b || c; y = f || g || h; } · ─────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 1. Maximum allowed is 0. + ⚠ eslint(complexity): function `a` has a complexity of 1. Maximum allowed is 0. ╭─[complexity.tsx:1:1] 1 │ function a(x) {} · ──────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `b` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:18] 1 │ const obj = { b: (a) => a?.b?.c, c: function (a) { return a?.b?.c; } }; · ────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `c` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:37] 1 │ const obj = { b: (a) => a?.b?.c, c: function (a) { return a?.b?.c; } }; · ──────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(b) { b?.c; } · ─────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(b) { b?.['c']; } · ─────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `a` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function a(b) { b?.c; d || e; } · ─────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `a` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function a(b) { b?.c?.d; } · ────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `a` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function a(b) { b?.['c']?.['d']; } · ────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `a` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function a(b) { b?.c?.['d']; } · ────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `a` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function a(b) { b?.c.d?.e; } · ──────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 2. + ⚠ eslint(complexity): function `a` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:1] 1 │ function a(b) { b?.c?.(); } · ─────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 4. Maximum allowed is 3. + ⚠ eslint(complexity): function `a` has a complexity of 4. Maximum allowed is 3. ╭─[complexity.tsx:1:1] 1 │ function a(b) { b?.c?.()?.(); } · ─────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(b = '') {} · ───────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(b) { const { c = '' } = b; } · ─────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 2. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(b) { const [ c = '' ] = b; } · ─────────────────────────────────────── ╰──── - ⚠ eslint(complexity): idk has a complexity of 3. Maximum allowed is 1. + ⚠ eslint(complexity): function `a` has a complexity of 3. Maximum allowed is 1. ╭─[complexity.tsx:1:1] 1 │ function a(b) { const [ { c: d = '' } = {} ] = b; } · ─────────────────────────────────────────────────── From 0f3e5cc1ddeea29cc0b4138353e7be561e69a345 Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Thu, 1 Jan 2026 17:13:01 -0500 Subject: [PATCH 05/15] Reduce repetition for complexity --- crates/oxc_linter/src/rules/eslint/complexity.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index 93bf857b87c50..1ceafdfe2b195 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -111,23 +111,23 @@ impl Rule for Complexity { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { let mut visitor = ComplexityVisitor::new(self.variant); - let (complexity, span, diagnostic_type) = match node.kind() { + let (span, diagnostic_type) = match node.kind() { AstKind::Function(func) => { visitor.visit_function(func, ScopeFlags::Function); - (visitor.complexity, func.span, DiagnosticType::Function) + (func.span, DiagnosticType::Function) } AstKind::ArrowFunctionExpression(func) => { visitor.visit_arrow_function_expression(func); - (visitor.complexity, func.span, DiagnosticType::Function) + (func.span, DiagnosticType::Function) } AstKind::StaticBlock(block) => { visitor.visit_static_block(block); - (visitor.complexity, block.span, DiagnosticType::ClassStaticBlock) + (block.span, DiagnosticType::ClassStaticBlock) } AstKind::PropertyDefinition(prop_def) => { if let Some(expr) = &prop_def.value { visitor.visit_property_definition(prop_def); - (visitor.complexity, expr.span(), DiagnosticType::ClassPropertyInitializer) + (expr.span(), DiagnosticType::ClassPropertyInitializer) } else { return; } @@ -137,7 +137,7 @@ impl Rule for Complexity { } }; - if complexity > self.max { + if visitor.complexity > self.max { let name = match diagnostic_type { DiagnosticType::ClassStaticBlock => "class static block", DiagnosticType::ClassPropertyInitializer => "class field initializer", @@ -147,7 +147,7 @@ impl Rule for Complexity { } }; - ctx.diagnostic(complexity_diagnostic(span, name, complexity, self.max)); + ctx.diagnostic(complexity_diagnostic(span, name, visitor.complexity, self.max)); } } } From f057e3a8371073c17066c174e28688b9a0dc6069 Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Thu, 1 Jan 2026 17:49:04 -0500 Subject: [PATCH 06/15] Add documentation --- .../oxc_linter/src/rules/eslint/complexity.rs | 65 +++++++++++++++---- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index 1ceafdfe2b195..e48b123ac2cf0 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -66,30 +66,73 @@ impl Deref for Complexity { declare_oxc_lint!( /// ### What it does /// - /// Briefly describe the rule's purpose. + /// Enforces a maximum cyclomatic complexity in a program, which is the number + /// of linearly independent paths in a program. /// /// ### Why is this bad? /// - /// Explain why violating this rule is problematic. + /// Having high code complexity reduces code readability. This rule + /// aims to make the code easier to follow by reducing the number of branches + /// in the program. /// /// ### Examples /// - /// Examples of **incorrect** code for this rule: + /// Examples of **incorrect** code for this rule with `{ "max": 2 }` /// ```js - /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// function foo() { + /// if (foo1) { + /// return x1; // 1st path + /// } else if (foo2) { + /// return x2; // 2nd path + /// } else { + /// return x3; // 3rd path + /// } + /// } + /// + /// function bar() { + /// // there are 2 paths - when bar1 is falsy, and when bar1 is truthy, in which bar1 = bar1 && bar2; + /// bar1 &&= bar2; + /// // there are 2 paths - when bar3 is truthy, and when bar3 is falsy, in which bar3 = 4; + /// bar3 ||= 4; + /// } + /// + /// // there are 2 paths - when baz1 is defined, and when baz1 is undefined and is assigned 'a' + /// function baz(baz1 = 'a') { + /// const { baz2 = 'b' } = baz3; // there are 2 additional paths - when baz2 is defined and when baz2 is not + /// } + /// + /// function d() { + /// d1 = d2?.d3?.(); // optional chaining creates 2 paths each - when object is defined and when it is not + /// } /// ``` /// - /// Examples of **correct** code for this rule: + /// Examples of **correct** code for this rule with `{ "max": 2 }` /// ```js - /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// // This example is taken directly from ESLint documentation + /// function foo() { // this function has complexity = 1 + /// class C { + /// x = a + b; // this initializer has complexity = 1 + /// y = c || d; // this initializer has complexity = 2 + /// z = e && f; // this initializer has complexity = 2 + /// + /// static p = g || h; // this initializer has complexity = 2 + /// static q = i ? j : k; // this initializer has complexity = 2 + /// + /// static { // this static block has complexity = 2 + /// if (foo) { + /// baz = bar; + /// } + /// } + /// + /// static { // this static block has complexity = 2 + /// qux = baz || quux; + /// } + /// } + /// } /// ``` Complexity, eslint, - nursery, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` - // See for details - pending, // TODO: describe fix capabilities. Remove if no fix can be done, - // keep at 'pending' if you think one could be added but don't know how. - // Options are 'fix', 'fix_dangerous', 'suggestion', and 'conditional_fix_suggestion' + style, config = ComplexityConfig, ); From c27c5745af792569c7a4d4f7a18406819ecf1c9d Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Thu, 1 Jan 2026 18:34:31 -0500 Subject: [PATCH 07/15] Add doc strings to config --- crates/oxc_linter/src/rules/eslint/complexity.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index e48b123ac2cf0..64478a5ac89ee 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -33,7 +33,9 @@ const THRESHOLD_DEFAULT: usize = 20; #[serde(rename_all = "camelCase", default)] #[schemars(rename_all = "camelCase")] pub struct ComplexityConfig { + /// Maximum amount of cyclomatic complexity max: usize, + /// The cyclomatic complexity variant to use variant: Variant, } @@ -47,7 +49,10 @@ impl Default for ComplexityConfig { #[serde(rename_all = "camelCase")] #[schemars(untagged, rename_all = "camelCase")] pub enum Variant { + /// Classic means McCabe cyclomatic complexity Classic, + /// Modified means classic cyclomatic complexity but a switch statement increases + /// complexity by 1 irrespective of the number of `case` statements Modified, } @@ -345,7 +350,7 @@ impl Visit<'_> for ComplexityVisitor { // Do not visit any other node if there is no value expression to // evaluate - only visit all other nodes if it is part of another // function / static block evaluation - unreachable!(); + return; } } else { // Visit all other nodes except for value expression if part of another From 398b566d5d13a5b8e3f0b51b63f4e0288b5ed76f Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Thu, 1 Jan 2026 18:44:56 -0500 Subject: [PATCH 08/15] Warnings and clippy stuff --- .../oxc_linter/src/rules/eslint/complexity.rs | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index 64478a5ac89ee..0d10bc7473f88 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -15,7 +15,6 @@ use crate::{ AstNode, ast_util::get_function_name_with_kind, context::LintContext, - fixer::{RuleFix, RuleFixer}, rule::{DefaultRuleConfig, Rule}, }; @@ -271,7 +270,7 @@ impl Visit<'_> for ComplexityVisitor { } fn visit_formal_parameter(&mut self, it: &oxc_ast::ast::FormalParameter<'_>) { - if let Some(_) = &it.initializer { + if it.initializer.is_some() { self.complexity += 1; } walk::walk_formal_parameter(self, it); @@ -342,17 +341,7 @@ impl Visit<'_> for ComplexityVisitor { } fn visit_property_definition(&mut self, it: &oxc_ast::ast::PropertyDefinition<'_>) { - if !self.has_entered_complexity_evaluation { - if let Some(value) = &it.value { - self.has_entered_complexity_evaluation = true; - self.visit_expression(value); - } else { - // Do not visit any other node if there is no value expression to - // evaluate - only visit all other nodes if it is part of another - // function / static block evaluation - return; - } - } else { + if self.has_entered_complexity_evaluation { // Visit all other nodes except for value expression if part of another // function / static block's complexity evaluation let kind = oxc_ast::AstKind::PropertyDefinition(self.alloc(it)); @@ -363,10 +352,17 @@ impl Visit<'_> for ComplexityVisitor { if let Some(type_annotation) = &it.type_annotation { self.visit_ts_type_annotation(type_annotation); } - if let Some(value) = &it.value { + if it.value.is_some() { // Do not enter value expression if we already started evaluating complexity } self.leave_node(kind); + } else if let Some(value) = &it.value { + self.has_entered_complexity_evaluation = true; + self.visit_expression(value); + } else { + // Do not visit any other node if there is no value expression to + // evaluate - only visit all other nodes if it is part of another + // function / static block evaluation } } } From f4647c3ced51789abd93ad290ef98b8ee8da0d10 Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Thu, 1 Jan 2026 19:08:16 -0500 Subject: [PATCH 09/15] That's weird, I thought node.kind covered this already --- crates/oxc_linter/src/generated/rule_runner_impls.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index fdcf971c66a1f..7f8caaa3fe196 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -52,12 +52,7 @@ impl RuleRunner for crate::rules::eslint::class_methods_use_this::ClassMethodsUs } impl RuleRunner for crate::rules::eslint::complexity::Complexity { - const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ - AstType::Function, - AstType::ArrowFunctionExpression, - AstType::StaticBlock, - AstType::PropertyDefinition, - ])); + const NODE_TYPES: Option<&AstTypesBitset> = None; const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } From e8874618b53cd1d011defabc0eec5c4080669e8f Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Thu, 1 Jan 2026 20:21:40 -0500 Subject: [PATCH 10/15] rule_runner_impls is now correct --- .../src/generated/rule_runner_impls.rs | 7 +- .../oxc_linter/src/rules/eslint/complexity.rs | 75 ++++++++++++------- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 7f8caaa3fe196..50e5293e2e112 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -52,7 +52,12 @@ impl RuleRunner for crate::rules::eslint::class_methods_use_this::ClassMethodsUs } impl RuleRunner for crate::rules::eslint::complexity::Complexity { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ + AstType::ArrowFunctionExpression, + AstType::Function, + AstType::PropertyDefinition, + AstType::StaticBlock, + ])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index 0d10bc7473f88..dcbd40812f25a 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -157,44 +157,69 @@ impl Rule for Complexity { } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - let mut visitor = ComplexityVisitor::new(self.variant); - let (span, diagnostic_type) = match node.kind() { + match node.kind() { AstKind::Function(func) => { + let mut visitor = ComplexityVisitor::new(self.variant); visitor.visit_function(func, ScopeFlags::Function); - (func.span, DiagnosticType::Function) + if visitor.complexity > self.max { + let name = { + let parent_node = ctx.nodes().parent_node(node.id()); + &get_function_name_with_kind(node, parent_node) + }; + ctx.diagnostic(complexity_diagnostic( + func.span, + name, + visitor.complexity, + self.max, + )); + } } AstKind::ArrowFunctionExpression(func) => { + let mut visitor = ComplexityVisitor::new(self.variant); visitor.visit_arrow_function_expression(func); - (func.span, DiagnosticType::Function) + if visitor.complexity > self.max { + let name = { + let parent_node = ctx.nodes().parent_node(node.id()); + &get_function_name_with_kind(node, parent_node) + }; + ctx.diagnostic(complexity_diagnostic( + func.span, + name, + visitor.complexity, + self.max, + )); + } } AstKind::StaticBlock(block) => { + let mut visitor = ComplexityVisitor::new(self.variant); visitor.visit_static_block(block); - (block.span, DiagnosticType::ClassStaticBlock) + if visitor.complexity > self.max { + let name = "class static block"; + ctx.diagnostic(complexity_diagnostic( + block.span, + name, + visitor.complexity, + self.max, + )); + } } AstKind::PropertyDefinition(prop_def) => { - if let Some(expr) = &prop_def.value { - visitor.visit_property_definition(prop_def); - (expr.span(), DiagnosticType::ClassPropertyInitializer) - } else { + let mut visitor = ComplexityVisitor::new(self.variant); + let Some(expr) = &prop_def.value else { return; + }; + visitor.visit_property_definition(prop_def); + if visitor.complexity > self.max { + let name = "class field initializer"; + ctx.diagnostic(complexity_diagnostic( + expr.span(), + name, + visitor.complexity, + self.max, + )); } } - _ => { - return; - } - }; - - if visitor.complexity > self.max { - let name = match diagnostic_type { - DiagnosticType::ClassStaticBlock => "class static block", - DiagnosticType::ClassPropertyInitializer => "class field initializer", - DiagnosticType::Function => { - let parent_node = ctx.nodes().parent_node(node.id()); - &get_function_name_with_kind(node, parent_node) - } - }; - - ctx.diagnostic(complexity_diagnostic(span, name, visitor.complexity, self.max)); + _ => {} } } } From 4805b4a94061c11b50bae454e44f7d8f804c3467 Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Thu, 1 Jan 2026 20:27:08 -0500 Subject: [PATCH 11/15] Dead code --- crates/oxc_linter/src/rules/eslint/complexity.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index dcbd40812f25a..a028f8c0c1007 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -224,13 +224,6 @@ impl Rule for Complexity { } } -#[derive(Clone, Copy, PartialEq, Eq)] -enum DiagnosticType { - ClassPropertyInitializer, - ClassStaticBlock, - Function, -} - struct ComplexityVisitor { variant: Variant, complexity: usize, From 69c9cd0460bc977fcaff649fca4fe7f324b98ab0 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Fri, 2 Jan 2026 13:02:20 +0000 Subject: [PATCH 12/15] minor clean up --- crates/oxc_linter/src/rules/eslint/complexity.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index a028f8c0c1007..c6ca2540b7da9 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -1,6 +1,5 @@ use oxc_ast::AstKind; -use oxc_ast_visit::Visit; -use oxc_ast_visit::walk; +use oxc_ast_visit::{Visit, walk}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_semantic::ScopeFlags; @@ -19,7 +18,6 @@ use crate::{ }; fn complexity_diagnostic(span: Span, name: &str, complexity: usize, max: usize) -> OxcDiagnostic { - // See for details OxcDiagnostic::warn(format!( "{name} has a complexity of {complexity}. Maximum allowed is {max}." )) @@ -44,11 +42,12 @@ impl Default for ComplexityConfig { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[schemars(untagged, rename_all = "camelCase")] pub enum Variant { /// Classic means McCabe cyclomatic complexity + #[default] Classic, /// Modified means classic cyclomatic complexity but a switch statement increases /// complexity by 1 irrespective of the number of `case` statements @@ -66,7 +65,6 @@ impl Deref for Complexity { } } -// See for documentation details. declare_oxc_lint!( /// ### What it does /// @@ -370,9 +368,8 @@ impl Visit<'_> for ComplexityVisitor { if let Some(type_annotation) = &it.type_annotation { self.visit_ts_type_annotation(type_annotation); } - if it.value.is_some() { - // Do not enter value expression if we already started evaluating complexity - } + // Intentionally skip `it.value` - do not enter value expression + // if we already started evaluating complexity self.leave_node(kind); } else if let Some(value) = &it.value { self.has_entered_complexity_evaluation = true; From 7f2a6e525dcf96c56daef21683c12d559e71ee32 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 2 Jan 2026 13:05:27 +0000 Subject: [PATCH 13/15] Update crates/oxc_linter/src/rules/eslint/complexity.rs Signed-off-by: Cameron --- crates/oxc_linter/src/rules/eslint/complexity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index c6ca2540b7da9..4fce7f6b25dde 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -134,7 +134,7 @@ declare_oxc_lint!( /// ``` Complexity, eslint, - style, + restriction config = ComplexityConfig, ); From 1f3fd88e4d71504f48dd5dbe5fb00ab8972442b8 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Fri, 2 Jan 2026 13:08:33 +0000 Subject: [PATCH 14/15] u --- crates/oxc_linter/src/rules/eslint/complexity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index 4fce7f6b25dde..0f55ed0c16db0 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -134,7 +134,7 @@ declare_oxc_lint!( /// ``` Complexity, eslint, - restriction + restriction, config = ComplexityConfig, ); From 32c3a5ad0e0c05c8fb2328740044b66cfb8bd66e Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Fri, 2 Jan 2026 13:14:00 +0000 Subject: [PATCH 15/15] add support for `maximum`/`max` aliasing in config --- crates/oxc_linter/src/rules/eslint/complexity.rs | 5 +++++ .../oxc_linter/src/snapshots/eslint_complexity.snap | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/crates/oxc_linter/src/rules/eslint/complexity.rs b/crates/oxc_linter/src/rules/eslint/complexity.rs index 0f55ed0c16db0..946e021e7f414 100644 --- a/crates/oxc_linter/src/rules/eslint/complexity.rs +++ b/crates/oxc_linter/src/rules/eslint/complexity.rs @@ -31,6 +31,7 @@ const THRESHOLD_DEFAULT: usize = 20; #[schemars(rename_all = "camelCase")] pub struct ComplexityConfig { /// Maximum amount of cyclomatic complexity + #[serde(alias = "maximum")] max: usize, /// The cyclomatic complexity variant to use variant: Variant, @@ -503,6 +504,8 @@ fn test() { ("class C { static { a || b; c || d; } }", Some(serde_json::json!([3]))), // { "ecmaVersion": 2022 }, ("class C { static { if (a || b) c = d || e; } }", Some(serde_json::json!([4]))), // { "ecmaVersion": 2022 }, ("function b(x) {}", Some(serde_json::json!([{ "max": 1 }]))), + ("function b(x) {}", Some(serde_json::json!([{ "maximum": 1 }]))), + ("function a(x) {if (true) {return x;}}", Some(serde_json::json!([{ "maximum": 2 }]))), ("function a(b) { b?.c; }", Some(serde_json::json!([{ "max": 2 }]))), ("function a(b = '') {}", Some(serde_json::json!([{ "max": 2 }]))), ("function a(b) { const { c = '' } = b; }", Some(serde_json::json!([{ "max": 2 }]))), @@ -671,6 +674,8 @@ fn test() { ), // { "ecmaVersion": 2022 }, ("class C { x = () => a || b || c; y = f || g || h; }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 }, ("function a(x) {}", Some(serde_json::json!([{ "max": 0 }]))), + ("function a(x) {}", Some(serde_json::json!([{ "maximum": 0 }]))), + ("function a(x) {if (true) {return x;}}", Some(serde_json::json!([{ "maximum": 1 }]))), ( "const obj = { b: (a) => a?.b?.c, c: function (a) { return a?.b?.c; } };", Some(serde_json::json!([{ "max": 2 }])), diff --git a/crates/oxc_linter/src/snapshots/eslint_complexity.snap b/crates/oxc_linter/src/snapshots/eslint_complexity.snap index 720f333624f1b..2d45dd64ffe49 100644 --- a/crates/oxc_linter/src/snapshots/eslint_complexity.snap +++ b/crates/oxc_linter/src/snapshots/eslint_complexity.snap @@ -517,6 +517,18 @@ source: crates/oxc_linter/src/tester.rs · ──────────────── ╰──── + ⚠ eslint(complexity): function `a` has a complexity of 1. Maximum allowed is 0. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {} + · ──────────────── + ╰──── + + ⚠ eslint(complexity): function `a` has a complexity of 2. Maximum allowed is 1. + ╭─[complexity.tsx:1:1] + 1 │ function a(x) {if (true) {return x;}} + · ───────────────────────────────────── + ╰──── + ⚠ eslint(complexity): function `b` has a complexity of 3. Maximum allowed is 2. ╭─[complexity.tsx:1:18] 1 │ const obj = { b: (a) => a?.b?.c, c: function (a) { return a?.b?.c; } };