diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/metrics/NPathComplexityCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/metrics/NPathComplexityCheck.java index 951d61eed29..da17e67d4f9 100644 --- a/src/main/java/com/puppycrawl/tools/checkstyle/checks/metrics/NPathComplexityCheck.java +++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/metrics/NPathComplexityCheck.java @@ -91,6 +91,7 @@ * NP(for-range) + NP(expr1)+ NP(expr2) + NP(expr3) + 1 * switch ([expr]) { case : [case-range] default: [default-range] } * S(i=1:i=n)NP(case-range[i]) + NP(default-range) + NP(expr) + * when[expr]NP(expr) + 1 * [expr1] ? [expr2] : [expr3]NP(expr1) + NP(expr2) + NP(expr3) + 2 * goto label1break1 * Expressions @@ -220,6 +221,7 @@ public int[] getRequiredTokens() { TokenTypes.LITERAL_DEFAULT, TokenTypes.COMPACT_CTOR_DEF, TokenTypes.SWITCH_RULE, + TokenTypes.LITERAL_WHEN, }; } @@ -249,6 +251,9 @@ public void visitToken(DetailAST ast) { case TokenTypes.LITERAL_RETURN: visitUnitaryOperator(ast, 0); break; + case TokenTypes.LITERAL_WHEN: + visitWhenExpression(ast, 1); + break; case TokenTypes.CASE_GROUP: final int caseNumber = countCaseTokens(ast); branchVisited = true; @@ -291,6 +296,7 @@ public void leaveToken(DetailAST ast) { case TokenTypes.LITERAL_FOR: case TokenTypes.LITERAL_IF: case TokenTypes.LITERAL_SWITCH: + case TokenTypes.LITERAL_WHEN: leaveConditional(); break; case TokenTypes.LITERAL_TRY: @@ -343,6 +349,19 @@ private void visitConditional(DetailAST ast, int basicBranchingFactor) { pushValue(expressionValue); } + /** + * Visits when expression token. There is no guarantee that when expression will be + * bracketed, so we don't use visitConditional method. + * + * @param ast visited token. + * @param basicBranchingFactor default number of branches added. + */ + private void visitWhenExpression(DetailAST ast, int basicBranchingFactor) { + final int expressionValue = basicBranchingFactor + countConditionalOperators(ast); + processingTokenEnd.setToken(getLastToken(ast)); + pushValue(expressionValue); + } + /** * Visits ternary operator (?:) and return tokens. They differ from those processed by * visitConditional method in that their expression isn't bracketed. diff --git a/src/main/resources/com/puppycrawl/tools/checkstyle/meta/checks/metrics/NPathComplexityCheck.xml b/src/main/resources/com/puppycrawl/tools/checkstyle/meta/checks/metrics/NPathComplexityCheck.xml index cf97e7a6457..8efa3720f18 100644 --- a/src/main/resources/com/puppycrawl/tools/checkstyle/meta/checks/metrics/NPathComplexityCheck.xml +++ b/src/main/resources/com/puppycrawl/tools/checkstyle/meta/checks/metrics/NPathComplexityCheck.xml @@ -64,6 +64,7 @@ <td>NP(for-range) + NP(expr1)+ NP(expr2) + NP(expr3) + 1</td></tr> <tr><td>switch ([expr]) { case : [case-range] default: [default-range] }</td> <td>S(i=1:i=n)NP(case-range[i]) + NP(default-range) + NP(expr)</td></tr> + <tr><td>when[expr]</td><td>NP(expr) + 1</td></tr> <tr><td>[expr1] ? [expr2] : [expr3]</td><td>NP(expr1) + NP(expr2) + NP(expr3) + 2</td></tr> <tr><td>goto label</td><td>1</td></tr><tr><td>break</td><td>1</td></tr> <tr><td>Expressions</td> diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/metrics/NPathComplexityCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/metrics/NPathComplexityCheckTest.java index 2733a57950b..fa263cd88fe 100644 --- a/src/test/java/com/puppycrawl/tools/checkstyle/checks/metrics/NPathComplexityCheckTest.java +++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/metrics/NPathComplexityCheckTest.java @@ -257,8 +257,8 @@ public void testPatternMatchingForSwitch() throws Exception { "23:5: " + getCheckMessage(MSG_KEY, 3, 1), "32:5: " + getCheckMessage(MSG_KEY, 3, 1), "41:5: " + getCheckMessage(MSG_KEY, 3, 1), - "50:5: " + getCheckMessage(MSG_KEY, 3, 1), - "59:5: " + getCheckMessage(MSG_KEY, 3, 1), + "50:5: " + getCheckMessage(MSG_KEY, 5, 1), + "59:5: " + getCheckMessage(MSG_KEY, 5, 1), "68:5: " + getCheckMessage(MSG_KEY, 4, 1), "76:5: " + getCheckMessage(MSG_KEY, 4, 1), "86:5: " + getCheckMessage(MSG_KEY, 3, 1), @@ -271,6 +271,28 @@ public void testPatternMatchingForSwitch() throws Exception { } + @Test + public void testWhenExpression() throws Exception { + + final String[] expected = { + "14:5: " + getCheckMessage(MSG_KEY, 3, 1), + "20:5: " + getCheckMessage(MSG_KEY, 3, 1), + "28:5: " + getCheckMessage(MSG_KEY, 3, 1), + "36:5: " + getCheckMessage(MSG_KEY, 4, 1), + "44:5: " + getCheckMessage(MSG_KEY, 4, 1), + "52:5: " + getCheckMessage(MSG_KEY, 5, 1), + "60:5: " + getCheckMessage(MSG_KEY, 7, 1), + "69:5: " + getCheckMessage(MSG_KEY, 5, 1), + "77:5: " + getCheckMessage(MSG_KEY, 5, 1), + "85:5: " + getCheckMessage(MSG_KEY, 6, 1), + }; + + verifyWithInlineConfigParser( + getNonCompilablePath("InputNPathComplexityWhenExpression.java"), + expected); + + } + @Test public void testGetAcceptableTokens() { final NPathComplexityCheck npathComplexityCheckObj = new NPathComplexityCheck(); @@ -294,6 +316,7 @@ public void testGetAcceptableTokens() { TokenTypes.LITERAL_DEFAULT, TokenTypes.COMPACT_CTOR_DEF, TokenTypes.SWITCH_RULE, + TokenTypes.LITERAL_WHEN, }; assertWithMessage("Acceptable tokens should not be null") .that(actual) @@ -326,6 +349,7 @@ public void testGetRequiredTokens() { TokenTypes.LITERAL_DEFAULT, TokenTypes.COMPACT_CTOR_DEF, TokenTypes.SWITCH_RULE, + TokenTypes.LITERAL_WHEN, }; assertWithMessage("Required tokens should not be null") .that(actual) diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/metrics/npathcomplexity/InputNPathComplexityPatternMatchingForSwitch.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/metrics/npathcomplexity/InputNPathComplexityPatternMatchingForSwitch.java index 4508acae857..41933ccfd37 100644 --- a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/metrics/npathcomplexity/InputNPathComplexityPatternMatchingForSwitch.java +++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/metrics/npathcomplexity/InputNPathComplexityPatternMatchingForSwitch.java @@ -46,7 +46,7 @@ case B(String s) : {} } } - // violation below, 'NPath Complexity is 3 (max allowed is 1)' + // violation below, 'NPath Complexity is 5 (max allowed is 1)' void testGuardsInRule(Object o) { switch (o) { case Integer i when i > 0 -> {} @@ -55,7 +55,7 @@ void testGuardsInRule(Object o) { } } - // violation below, 'NPath Complexity is 3 (max allowed is 1)' + // violation below, 'NPath Complexity is 5 (max allowed is 1)' void testGuardsInStatement(Object o) { switch (o) { case Integer i when i > 0 : {} break; diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/metrics/npathcomplexity/InputNPathComplexityWhenExpression.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/metrics/npathcomplexity/InputNPathComplexityWhenExpression.java new file mode 100644 index 00000000000..37d5146c8db --- /dev/null +++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/metrics/npathcomplexity/InputNPathComplexityWhenExpression.java @@ -0,0 +1,91 @@ +/* +NPathComplexity +max = 1 + + +*/ + +//non-compiled with javac: Compilable with Java21 +package com.puppycrawl.tools.checkstyle.checks.metrics.npathcomplexity; + +public class InputNPathComplexityWhenExpression { + + // violation below, 'NPath Complexity is 3 (max allowed is 1)' + void m(Object o) { + + if (o instanceof String s && !s.isEmpty()) { } + } + + // violation below, 'NPath Complexity is 3 (max allowed is 1)' + void m2(Object o) { + switch (o) { + case String s when !s.isEmpty() -> { } + default -> { } + } + } + + // violation below, 'NPath Complexity is 3 (max allowed is 1)' + void m3(Object o) { + switch (o) { + case String s when (!s.isEmpty()) -> { } + default -> { } + } + } + + // violation below, 'NPath Complexity is 4 (max allowed is 1)' + void m4(Object o, boolean b) { + switch (o) { + case String s when !s.isEmpty() && b -> { } + default -> { } + } + } + + // violation below, 'NPath Complexity is 4 (max allowed is 1)' + void m5(Object o, boolean b) { + switch (o) { + case String s when (!s.isEmpty() && b) -> { } + default -> { } + } + } + + // violation below, 'NPath Complexity is 5 (max allowed is 1)' + void m6(Object o, boolean b, boolean c) { + switch (o) { + case String s when (!s.isEmpty() && b) && c -> { } + default -> { } + } + } + + // violation below, 'NPath Complexity is 7 (max allowed is 1)' + void m7(Object o, boolean b, boolean c) { + switch (o) { + case String s when !s.isEmpty() -> { } + case Integer i when (i == 0) && c || b -> { } + default -> { } + } + } + + // violation below, 'NPath Complexity is 5 (max allowed is 1)' + void m8(Object o, boolean b, boolean c) { + switch (o) { + case String s when (b ? true : c) -> { } + default -> { } + } + } + + // violation below, 'NPath Complexity is 5 (max allowed is 1)' + void m9(Object o, boolean b, boolean c) { + switch (o) { + case String s when b ? true : c -> { } + default -> { } + } + } + + // violation below, 'NPath Complexity is 6 (max allowed is 1)' + void m10(Object o, boolean b, boolean c) { + switch (o) { + case String s when (b ? true : c) && c == true -> { } + default -> { } + } + } +} diff --git a/src/xdocs/checks/metrics/npathcomplexity.xml b/src/xdocs/checks/metrics/npathcomplexity.xml index 0feb9d18152..954e988ecc8 100644 --- a/src/xdocs/checks/metrics/npathcomplexity.xml +++ b/src/xdocs/checks/metrics/npathcomplexity.xml @@ -75,6 +75,7 @@ switch ([expr]) { case : [case-range] default: [default-range] } S(i=1:i=n)NP(case-range[i]) + NP(default-range) + NP(expr) + when[expr]NP(expr) + 1 [expr1] ? [expr2] : [expr3]NP(expr1) + NP(expr2) + NP(expr3) + 2 goto label1 diff --git a/src/xdocs/checks/metrics/npathcomplexity.xml.template b/src/xdocs/checks/metrics/npathcomplexity.xml.template index c889e4a3c3a..cdc9e71f678 100644 --- a/src/xdocs/checks/metrics/npathcomplexity.xml.template +++ b/src/xdocs/checks/metrics/npathcomplexity.xml.template @@ -75,6 +75,7 @@ switch ([expr]) { case : [case-range] default: [default-range] } S(i=1:i=n)NP(case-range[i]) + NP(default-range) + NP(expr) + when[expr]NP(expr) + 1 [expr1] ? [expr2] : [expr3]NP(expr1) + NP(expr2) + NP(expr3) + 2 goto label1