Repository: checkstyle/checkstyle

Problem statement:
Add Check Support for Java 21 Pattern Matching for Switch Syntax: NPathComplexity
child of https://github.com/checkstyle/checkstyle/issues/14961

I have read check documentation:https://checkstyle.org/checks/metrics/npathcomplexity.html
I have downloaded the latest checkstyle from: https://checkstyle.org/cmdline.html#Download_and_Run
I have executed the cli and showed it below, as cli describes the problem better than 1,000 words

---

```
c cat config.xml                               
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
        "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
        "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
    <property name="charset" value="UTF-8"/>
    <module name="TreeWalker">
        <module name="NPathComplexity">
            <property name="max" value="1"/>
        </module>
    </module>
</module>
➜  src cat Test.java                                
record ColoredPoint(int p, int x, boolean c) { }
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) { }

public class Test {
    void test(Object obj) {
        switch (obj) {
            case ColoredPoint(int a, int b, _) when (a >= b) : {} break;
            case ColoredPoint(int a, int b, _) when (b > 100) : {} break;
            case ColoredPoint(int a, int b, _) when (b != 1000) : {} break;
            case ColoredPoint(int a, int b, _) when (b != 100000) : {} break;
            case Rectangle(_,_) : {}
            default : System.out.println("none");
        };
    }

    void test2(Object obj) {
        switch (obj) {
            case ColoredPoint(int a, int b, _) when (a >= b) -> {}
            case ColoredPoint(int a, int b, _) when (b > 100) -> {}
            case ColoredPoint(int a, int b, _) when (b != 1000) -> {}
            case ColoredPoint(int a, int b, _) when (b != 100000) -> {}
            case Rectangle(_,_) -> {}
            default -> System.out.println("none");
        };
    }
}
➜  src javac --enable-preview --release 21 Test.java                        
Note: Test.java uses preview features of Java SE 21.
Note: Recompile with -Xlint:preview for details.
➜  src java -jar checkstyle-10.18.1-SNAPSHOT-all.jar -c config.xml Test.java
Starting audit...
[ERROR] /home/nick/IdeaProjects/tester/src/Test.java:5:5: NPath Complexity is 6 (max allowed is 1). [NPathComplexity]
[ERROR] /home/nick/IdeaProjects/tester/src/Test.java:16:5: NPath Complexity is 2 (max allowed is 1). [NPathComplexity]
Audit done.
Checkstyle ends with 2 errors.


```

---

Describe what you want in detail

I expect both methods to have the same NPathComplexity. There is a problem in counting when case labels are in the switch rule `(->)`.The colon syntax ( `:` ) is the correct one in terms of NPath counting


Hints:
Looks like a false negative, approving this one.
https://checkstyle.org/checks/metrics/npathcomplexity.html#Description
> Expressions 	Number of && and || operators in expression. No operators - 0

This check also counts expressions, so fixing `when` may cause other effects here.
OP is updated, same result as before, false negative on `->` syntax.

Patch:
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
--- a/src/main/java/com/puppycrawl/tools/checkstyle/checks/metrics/NPathComplexityCheck.java
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/metrics/NPathComplexityCheck.java
@@ -139,6 +139,14 @@ public final class NPathComplexityCheck extends AbstractCheck {
      */
     public static final String MSG_KEY = "npathComplexity";
 
+    /** Tokens that are considered as case labels. */
+    private static final int[] CASE_LABEL_TOKENS = {
+        TokenTypes.EXPR,
+        TokenTypes.PATTERN_DEF,
+        TokenTypes.PATTERN_VARIABLE_DEF,
+        TokenTypes.RECORD_PATTERN_DEF,
+    };
+
     /** Default allowed complexity. */
     private static final int DEFAULT_MAX = 200;
 
@@ -508,17 +516,21 @@ private static int countCaseTokens(DetailAST ast) {
     }
 
     /**
-     * Counts number of case constants (EXPR) tokens in a switch labeled rule.
+     * Counts number of case constants tokens in a switch labeled rule.
      *
      * @param ast switch rule token.
-     * @return number of case constant (EXPR) tokens.
+     * @return number of case constant tokens.
      */
     private static int countCaseConstants(DetailAST ast) {
         final AtomicInteger counter = new AtomicInteger();
         final DetailAST literalCase = ast.getFirstChild();
 
-        TokenUtil.forEachChild(literalCase,
-            TokenTypes.EXPR, node -> counter.getAndIncrement());
+        for (DetailAST node = literalCase.getFirstChild(); node != null;
+                    node = node.getNextSibling()) {
+            if (TokenUtil.isOfType(node, CASE_LABEL_TOKENS)) {
+                counter.getAndIncrement();
+            }
+        }
 
         return counter.get();
     }


Test patch:
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
--- a/src/test/java/com/puppycrawl/tools/checkstyle/checks/metrics/NPathComplexityCheckTest.java
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/metrics/NPathComplexityCheckTest.java
@@ -249,6 +249,28 @@ public void testCount() throws Exception {
             expected);
     }
 
+    @Test
+    public void testPatternMatchingForSwitch() throws Exception {
+
+        final String[] expected = {
+            "14:5: " + getCheckMessage(MSG_KEY, 3, 1),
+            "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),
+            "68:5: " + getCheckMessage(MSG_KEY, 4, 1),
+            "76:5: " + getCheckMessage(MSG_KEY, 4, 1),
+            "86:5: " + getCheckMessage(MSG_KEY, 3, 1),
+            "95:5: " + getCheckMessage(MSG_KEY, 3, 1),
+        };
+
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputNPathComplexityPatternMatchingForSwitch.java"),
+            expected);
+
+    }
+
     @Test
     public void testGetAcceptableTokens() {
         final NPathComplexityCheck npathComplexityCheckObj = new NPathComplexityCheck();
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
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/metrics/npathcomplexity/InputNPathComplexityPatternMatchingForSwitch.java
@@ -0,0 +1,105 @@
+/*
+NPathComplexity
+max = 1
+
+
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.metrics.npathcomplexity;
+
+public class InputNPathComplexityPatternMatchingForSwitch {
+
+    // violation below, 'NPath Complexity is 3 (max allowed is 1)'
+    void testPatternWithRule(Object o) {
+        switch (o) {
+            case Integer _ -> {}
+            case String _ -> {}
+            default -> {}
+        }
+    }
+
+    // violation below, 'NPath Complexity is 3 (max allowed is 1)'
+    void testPatternWithStatement(Object o) {
+        switch (o) {
+            case Integer _: {}
+            case String _: {}
+            default : {}
+        }
+    }
+
+    // violation below, 'NPath Complexity is 3 (max allowed is 1)'
+    void testRecordPatternWithRule(Object o) {
+        switch (o) {
+            case A(int x) -> {}
+            case B(String s) -> {}
+            default -> {}
+        }
+    }
+
+    // violation below, 'NPath Complexity is 3 (max allowed is 1)'
+    void testRecordPatternWithStatement(Object o) {
+        switch (o) {
+            case A(int x) : {} break;
+            case B(String s) : {}
+            default : {}
+        }
+    }
+
+    // violation below, 'NPath Complexity is 3 (max allowed is 1)'
+    void testGuardsInRule(Object o) {
+        switch (o) {
+            case Integer i when i > 0 -> {}
+            case String s when s.length() > 0 -> {}
+            default -> {}
+        }
+    }
+
+    // violation below, 'NPath Complexity is 3 (max allowed is 1)'
+    void testGuardsInStatement(Object o) {
+        switch (o) {
+            case Integer i when i > 0 : {} break;
+            case String s when s.length() > 0 : {}
+            default : {}
+        }
+    }
+
+    // violation below, 'NPath Complexity is 4 (max allowed is 1)'
+    void testMultiCaseLabelWithRule(Object o) {
+        switch (o) {
+            case Integer _, String _ , Double _ -> {}
+            default -> {}
+        }
+    }
+
+    // violation below, 'NPath Complexity is 4 (max allowed is 1)'
+    void testMultiCaseLabelWithStatement(Object o) {
+        switch (o) {
+            case Integer _ :
+            case String _  :
+            case Double _  : {}
+            default : {}
+        }
+    }
+
+    // violation below, 'NPath Complexity is 3 (max allowed is 1)'
+    void testExprInRule(int x) {
+        switch (x) {
+            case 1 -> {}
+            case 2 -> {}
+            default -> {}
+        }
+    }
+
+    // violation below, 'NPath Complexity is 3 (max allowed is 1)'
+    void testExprInStatement(int x) {
+        switch (x) {
+            case 1 : {}
+            case 2 : {}
+            default : {}
+        }
+    }
+
+    record A(int x) {}
+    record B(String s) {}
+}