Repository: checkstyle/checkstyle

Problem statement:
False positive in FinalLocalVariable check on unnamed variables
child #14942
I have read check documentation: https://checkstyle.org/checks/coding/finallocalvariable.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

```
PS D:\CS\test> cat src/Test.java                                                 
import java.util.PriorityQueue;
import java.util.Queue;

public class Test {
    public void testParenPad(Point p) {
        final Queue<Integer> q = new PriorityQueue<>();
        q.add(1);
        q.add(2);
        for (Integer i : q) {
            var _ = q.poll();  // violation
        }
    }
}
PS D:\CS\test> 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="FinalLocalVariable"/>
    </module>
</module>
PS D:\CS\test> java  -jar checkstyle-10.17.0-all.jar -c config.xml  src/Test.java
Starting audit...
[ERROR] D:\CS\test\src\Test.java:10:17: Variable '_' should be declared final. [FinalLocalVariable]
Audit done.
Checkstyle ends with 1 errors.
```

---

**Describe what you expect in detail.**
no violation this is an unnamed variable that is effectively final

---



Hints:
I am good to approve if we add a new property, `validateUnnamedVariables`. @rnveach please share your thoughts and approve if you agree.
> validateUnnamedVariables

[We have 6 "validate" properties.  We have 46 "allow" properties. We have 22 "violate" properties. We have 48 "ignore" properties.](https://rveach.no-ip.org/checkstyle/propertyList.txt)

I was going to suggest "allow", maybe "ignore", but I don't know if it really matters. I guess part of my reason is I like simpler words, but also this seems more like a allow/ignore situation (maybe more ignore). The statistics seemed fun to look at. I am good with what you decide.
I went with `validate` since the other check property is `validateEnhancedForLoopVariable` to be consistent.

Patch:
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/FinalLocalVariableCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/FinalLocalVariableCheck.java
--- a/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/FinalLocalVariableCheck.java
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/FinalLocalVariableCheck.java
@@ -53,6 +53,13 @@
  * Default value is {@code false}.
  * </li>
  * <li>
+ * Property {@code validateUnnamedVariables} - Control whether to check
+ * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html">
+ * unnamed variables</a>.
+ * Type is {@code boolean}.
+ * Default value is {@code false}.
+ * </li>
+ * <li>
  * Property {@code tokens} - tokens to check
  * Type is {@code java.lang.String[]}.
  * Validation type is {@code tokenSet}.
@@ -129,6 +136,13 @@ public class FinalLocalVariableCheck extends AbstractCheck {
      */
     private boolean validateEnhancedForLoopVariable;
 
+    /**
+     * Control whether to check
+     * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html">
+     * unnamed variables</a>.
+     */
+    private boolean validateUnnamedVariables;
+
     /**
      * Setter to control whether to check
      * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2">
@@ -141,6 +155,18 @@ public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedFor
         this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable;
     }
 
+    /**
+     * Setter to control whether to check
+     * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html">
+     * unnamed variables</a>.
+     *
+     * @param validateUnnamedVariables whether to check unnamed variables
+     * @since 10.18.0
+     */
+    public final void setValidateUnnamedVariables(boolean validateUnnamedVariables) {
+        this.validateUnnamedVariables = validateUnnamedVariables;
+    }
+
     @Override
     public int[] getRequiredTokens() {
         return new int[] {
@@ -222,7 +248,8 @@ public void visitToken(DetailAST ast) {
                         && ast.findFirstToken(TokenTypes.MODIFIERS)
                             .findFirstToken(TokenTypes.FINAL) == null
                         && !isVariableInForInit(ast)
-                        && shouldCheckEnhancedForLoopVariable(ast)) {
+                        && shouldCheckEnhancedForLoopVariable(ast)
+                        && shouldCheckUnnamedVariable(ast)) {
                     insertVariable(ast);
                 }
                 break;
@@ -480,6 +507,17 @@ private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) {
                 || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE;
     }
 
+    /**
+     * Determines whether unnamed variable should be checked or not.
+     *
+     * @param ast The ast to compare.
+     * @return true if unnamed variable should be checked.
+     */
+    private boolean shouldCheckUnnamedVariable(DetailAST ast) {
+        return validateUnnamedVariables
+                 || !"_".equals(ast.findFirstToken(TokenTypes.IDENT).getText());
+    }
+
     /**
      * Insert a parameter at the topmost scope stack.
      *


Test patch:
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/FinalLocalVariableCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/FinalLocalVariableCheckTest.java
--- a/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/FinalLocalVariableCheckTest.java
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/FinalLocalVariableCheckTest.java
@@ -405,4 +405,34 @@ public void test() throws Exception {
             getPath("InputFinalLocalVariable3.java"),
             expected);
     }
+
+    @Test
+    public void testValidateUnnamedVariablesTrue() throws Exception {
+        final String[] expected = {
+            "21:22: " + getCheckMessage(MSG_KEY, "i"),
+            "22:17: " + getCheckMessage(MSG_KEY, "_"),
+            "23:17: " + getCheckMessage(MSG_KEY, "__"),
+            "26:13: " + getCheckMessage(MSG_KEY, "_"),
+            "27:13: " + getCheckMessage(MSG_KEY, "_result"),
+            "32:18: " + getCheckMessage(MSG_KEY, "_"),
+            "44:18: " + getCheckMessage(MSG_KEY, "_"),
+            "50:18: " + getCheckMessage(MSG_KEY, "__"),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputFinalLocalVariableValidateUnnamedVariablesTrue.java"),
+            expected);
+    }
+
+    @Test
+    public void testValidateUnnamedVariablesFalse() throws Exception {
+        final String[] expected = {
+            "21:22: " + getCheckMessage(MSG_KEY, "i"),
+            "23:17: " + getCheckMessage(MSG_KEY, "__"),
+            "27:13: " + getCheckMessage(MSG_KEY, "_result"),
+            "50:18: " + getCheckMessage(MSG_KEY, "__"),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputFinalLocalVariableValidateUnnamedVariablesFalse.java"),
+            expected);
+    }
 }
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/finallocalvariable/InputFinalLocalVariableValidateUnnamedVariablesFalse.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/finallocalvariable/InputFinalLocalVariableValidateUnnamedVariablesFalse.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/finallocalvariable/InputFinalLocalVariableValidateUnnamedVariablesFalse.java
@@ -0,0 +1,58 @@
+/*
+FinalLocalVariable
+validateUnnamedVariables = (default)false
+validateEnhancedForLoopVariable = true
+tokens = (default)VARIABLE_DEF
+
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.coding.finallocalvariable;
+
+import java.util.PriorityQueue;
+import java.util.Queue;
+
+public class InputFinalLocalVariableValidateUnnamedVariablesFalse {
+
+    public void testUnnamedVariables() {
+        final Queue<Integer> q = new PriorityQueue<>();
+        q.add(1);
+        q.add(2);
+        for (Integer i : q) {  // violation,'Variable 'i' should be declared final'
+            var _ = q.poll();
+            var __ = q.poll(); // violation,'Variable '__' should be declared final'
+        }
+        final int _ = sideEffect();
+        int _ = sideEffect();
+        int _result = sideEffect(); // violation,'Variable '_result' should be declared final'
+    }
+
+    static
+    {
+        Runnable _ = new Runnable()
+        {
+            public void run()
+            {
+            }
+        };
+    }
+
+    public void testInEnhancedFor() {
+        final int[] squares = {0, 1, 4, 9, 16, 25};
+        for (final int i : squares) {
+        }
+        for (int _ : squares) {
+
+        }
+        for (final int _ : squares) {
+
+        }
+        for (int __ : squares) { // violation,'Variable '__' should be declared final'
+
+        }
+    }
+
+    public int sideEffect() {
+        return 0;
+    }
+}
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/finallocalvariable/InputFinalLocalVariableValidateUnnamedVariablesTrue.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/finallocalvariable/InputFinalLocalVariableValidateUnnamedVariablesTrue.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/coding/finallocalvariable/InputFinalLocalVariableValidateUnnamedVariablesTrue.java
@@ -0,0 +1,58 @@
+/*
+FinalLocalVariable
+validateUnnamedVariables = true
+validateEnhancedForLoopVariable = true
+tokens = (default)VARIABLE_DEF
+
+*/
+
+//non-compiled with javac: Compilable with Java21
+package com.puppycrawl.tools.checkstyle.checks.coding.finallocalvariable;
+
+import java.util.PriorityQueue;
+import java.util.Queue;
+
+public class InputFinalLocalVariableValidateUnnamedVariablesTrue {
+
+    public void testUnnamedVariables() {
+        final Queue<Integer> q = new PriorityQueue<>();
+        q.add(1);
+        q.add(2);
+        for (Integer i : q) {  // violation,'Variable 'i' should be declared final'
+            var _ = q.poll(); // violation,'Variable '_' should be declared final'
+            var __ = q.poll(); // violation,'Variable '__' should be declared final'
+        }
+        final int _ = sideEffect();
+        int _ = sideEffect(); // violation,'Variable '_' should be declared final'
+        int _result = sideEffect(); // violation,'Variable '_result' should be declared final'
+    }
+
+    static
+    {
+        Runnable _ = new Runnable() // violation,'Variable '_' should be declared final'
+        {
+            public void run()
+            {
+            }
+        };
+    }
+
+    public void testInEnhancedFor() {
+        final int[] squares = {0, 1, 4, 9, 16, 25};
+        for (final int i : squares) {
+        }
+        for (int _ : squares) { // violation,'Variable '_' should be declared final'
+
+        }
+        for (final int _ : squares) {
+
+        }
+        for (int __ : squares) { // violation,'Variable '__' should be declared final'
+
+        }
+    }
+
+    public int sideEffect() {
+        return 0;
+    }
+}