From c49ed63265fc8e0cccea404810a4c5075d396a15 Mon Sep 17 00:00:00 2001 From: Mathias Schreck Date: Fri, 1 Mar 2024 11:25:15 +0100 Subject: [PATCH] feat: update complexity rule for optional chaining & default values (#18152) Both, optional chaining expressions and default values (in function parameters or descructuring expressions) increase the branching of the code and thus the complexity is increased. Fixes: #18060 --- docs/src/rules/complexity.md | 8 +++ lib/rules/complexity.js | 13 +++++ tests/lib/rules/complexity.js | 93 ++++++++++++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/complexity.md b/docs/src/rules/complexity.md index 08dd839f5cd..dda6acb45ea 100644 --- a/docs/src/rules/complexity.md +++ b/docs/src/rules/complexity.md @@ -57,6 +57,14 @@ function b() { foo ||= 1; bar &&= 1; } + +function c(a = {}) { // default parameter -> 2nd path + const { b = 'default' } = a; // default value during destructuring -> 3rd path +} + +function d(a) { + return a?.b?.c; // optional chaining with two optional properties creates two additional branches +} ``` ::: diff --git a/lib/rules/complexity.js b/lib/rules/complexity.js index ac114187b2c..647de7b4d3b 100644 --- a/lib/rules/complexity.js +++ b/lib/rules/complexity.js @@ -109,6 +109,7 @@ module.exports = { IfStatement: increaseComplexity, WhileStatement: increaseComplexity, DoWhileStatement: increaseComplexity, + AssignmentPattern: increaseComplexity, // Avoid `default` "SwitchCase[test]": increaseComplexity, @@ -120,6 +121,18 @@ module.exports = { } }, + MemberExpression(node) { + if (node.optional === true) { + increaseComplexity(); + } + }, + + CallExpression(node) { + if (node.optional === true) { + increaseComplexity(); + } + }, + onCodePathEnd(codePath, node) { const complexity = complexities.pop(); diff --git a/tests/lib/rules/complexity.js b/tests/lib/rules/complexity.js index 0c8eb637482..27ac112fcc2 100644 --- a/tests/lib/rules/complexity.js +++ b/tests/lib/rules/complexity.js @@ -124,7 +124,25 @@ ruleTester.run("complexity", rule, { { code: "class C { static { if (a || b) c = d || e; } }", options: [4], languageOptions: { ecmaVersion: 2022 } }, // object property options - { code: "function b(x) {}", options: [{ max: 1 }] } + { code: "function b(x) {}", options: [{ max: 1 }] }, + + // optional chaining + { + code: "function a(b) { b?.c; }", options: [{ max: 2 }] + }, + + // default function parameter values + { + code: "function a(b = '') {}", options: [{ max: 2 }] + }, + + // default destructuring values + { + code: "function a(b) { const { c = '' } = b; }", options: [{ max: 2 }] + }, + { + code: "function a(b) { const [ c = '' ] = b; }", options: [{ max: 2 }] + } ], invalid: [ { code: "function a(x) {}", options: [0], errors: [makeError("Function 'a'", 1, 0)] }, @@ -522,6 +540,77 @@ ruleTester.run("complexity", rule, { }, // object property options - { code: "function a(x) {}", options: [{ max: 0 }], errors: [makeError("Function 'a'", 1, 0)] } + { code: "function a(x) {}", options: [{ max: 0 }], errors: [makeError("Function 'a'", 1, 0)] }, + + // optional chaining + { + code: "function a(b) { b?.c; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)] + }, + { + code: "function a(b) { b?.['c']; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)] + }, + { + code: "function a(b) { b?.c; d || e; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)] + }, + { + code: "function a(b) { b?.c?.d; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)] + }, + { + code: "function a(b) { b?.['c']?.['d']; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)] + }, + { + code: "function a(b) { b?.c?.['d']; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)] + }, + { + code: "function a(b) { b?.c.d?.e; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)] + }, + { + code: "function a(b) { b?.c?.(); }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)] + }, + { + code: "function a(b) { b?.c?.()?.(); }", + options: [{ max: 3 }], + errors: [makeError("Function 'a'", 4, 3)] + }, + + // default function parameter values + { + code: "function a(b = '') {}", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)] + }, + + // default destructuring values + { + code: "function a(b) { const { c = '' } = b; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)] + }, + { + code: "function a(b) { const [ c = '' ] = b; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)] + }, + { + code: "function a(b) { const [ { c: d = '' } = {} ] = b; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 3, 1)] + } ] });