Repository: checkstyle/checkstyle

Problem statement:
RedundantModifier should include RECORD_DEF in acceptable tokens
Issue property reported by @Vyom-Yadav

I have read check documentation: http://checkstyle.sourceforge.net/config_modifier.html#RedundantModifier
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

```bash
/var/tmp $ javac Test.java

/var/tmp $ cat config.xml
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
        "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
        "https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
    <module name="TreeWalker">
        <module name="RedundantModifier"/>
    </module>
</module>

/var/tmp $ cat Test.java
final record foo() {

    static final record hoo() {
    }
}


/var/tmp $ RUN_LOCALE="-Duser.language=en -Duser.country=US"
/var/tmp $ java $RUN_LOCALE -jar checkstyle-10.0-all.jar -c config.xml YOUR_FILE.java
Starting audit...
Audit done.
```

---

**Describe what you expect in detail.**
```
/var/tmp $ RUN_LOCALE="-Duser.language=en -Duser.country=US"
/var/tmp $ java $RUN_LOCALE -jar checkstyle-10.0-all.jar -c config.xml YOUR_FILE.java
Starting audit...
[ERROR] /var/tmp/Test.java:1:1: Redundant 'final' modifier. [RedundantModifier]
[ERROR] /var/tmp/Test.java:3:5: Redundant 'static' modifier. [RedundantModifier]
[ERROR] /var/tmp/Test.java:3:12: Redundant 'final' modifier. [RedundantModifier]
Audit done.
Checkstyle ends with 3 errors.
```

Related info https://docs.oracle.com/en/java/javase/17/language/records.html




Hints:
It would be a good idea to wait for resolution of https://github.com/checkstyle/checkstyle/issues/10798 before working on this issue. 

Edit: on second thought, this should not matter, since we are only adding type definition token and checking for static modifier.
@nmancus1 Though both the issues are related to the same check, I feel that we can go ahead with this issue as this one isn't dependent on the java version (afaik) unlike the other issue.
> It would be a good idea to wait for resolution of #10798 before working on this issue.

I do not think it is necessary, this issue can be fixed separately.
Records are also implicitly final, will be fixed in the same PR.

Patch:
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/modifier/RedundantModifierCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/modifier/RedundantModifierCheck.java
--- a/src/main/java/com/puppycrawl/tools/checkstyle/checks/modifier/RedundantModifierCheck.java
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/modifier/RedundantModifierCheck.java
@@ -56,6 +56,10 @@
  * <li>
  * Nested {@code enum} definitions that are declared as {@code static}.
  * </li>
+ * <li>
+ * {@code record} definitions that are declared as {@code final} and nested
+ * {@code record} definitions that are declared as {@code static}.
+ * </li>
  * </ol>
  * <p>
  * interfaces by definition are abstract so the {@code abstract} modifier is redundant on them.
@@ -73,6 +77,12 @@
  * automatically public, static and final just as their
  * annotation fields are automatically public and abstract.</p>
  *
+ * <p>A record class is implicitly final and cannot be abstract, these restrictions emphasize
+ * that the API of a record class is defined solely by its state description, and
+ * cannot be enhanced later by another class. Nested records are implicitly static. This avoids an
+ * immediately enclosing instance which would silently add state to the record class.
+ * See <a href="https://openjdk.java.net/jeps/395">JEP 395</a> for more info.</p>
+ *
  * <p>Enums by definition are static implicit subclasses of java.lang.Enum&#60;E&#62;.
  * So, the {@code static} modifier on the enums is redundant. In addition,
  * if enum is inside of interface, {@code public} modifier is also redundant.</p>
@@ -155,7 +165,9 @@
  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RESOURCE">
  * RESOURCE</a>,
  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">
- * ANNOTATION_DEF</a>.
+ * ANNOTATION_DEF</a>,
+ * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
+ * RECORD_DEF</a>.
  * </li>
  * </ul>
  * <p>
@@ -226,6 +238,7 @@ public int[] getAcceptableTokens() {
             TokenTypes.ENUM_DEF,
             TokenTypes.RESOURCE,
             TokenTypes.ANNOTATION_DEF,
+            TokenTypes.RECORD_DEF,
         };
     }
 
@@ -248,6 +261,9 @@ public void visitToken(DetailAST ast) {
             case TokenTypes.RESOURCE:
                 processResources(ast);
                 break;
+            case TokenTypes.RECORD_DEF:
+                checkForRedundantModifier(ast, TokenTypes.FINAL, TokenTypes.LITERAL_STATIC);
+                break;
             case TokenTypes.CLASS_DEF:
             case TokenTypes.VARIABLE_DEF:
             case TokenTypes.ANNOTATION_FIELD_DEF:
@@ -412,14 +428,17 @@ private void processResources(DetailAST ast) {
      * Checks if given ast has a redundant modifier.
      *
      * @param ast ast
-     * @param modifierType The modifier to check for.
+     * @param modifierTypes The modifiers to check for.
      */
-    private void checkForRedundantModifier(DetailAST ast, int modifierType) {
+    private void checkForRedundantModifier(DetailAST ast, int... modifierTypes) {
         Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
             .ifPresent(modifiers -> {
-                TokenUtil.forEachChild(modifiers, modifierType, modifier -> {
-                    log(modifier, MSG_KEY, modifier.getText());
-                });
+                for (DetailAST childAst = modifiers.getFirstChild();
+                     childAst != null; childAst = childAst.getNextSibling()) {
+                    if (TokenUtil.isOfType(childAst, modifierTypes)) {
+                        log(childAst, MSG_KEY, childAst.getText());
+                    }
+                }
             });
     }
 


Test patch:
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/modifier/RedundantModifierCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/modifier/RedundantModifierCheckTest.java
--- a/src/test/java/com/puppycrawl/tools/checkstyle/checks/modifier/RedundantModifierCheckTest.java
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/modifier/RedundantModifierCheckTest.java
@@ -153,6 +153,7 @@ public void testGetAcceptableTokens() {
             TokenTypes.ENUM_DEF,
             TokenTypes.RESOURCE,
             TokenTypes.ANNOTATION_DEF,
+            TokenTypes.RECORD_DEF,
         };
         assertWithMessage("Invalid acceptable tokens")
             .that(actual)
@@ -312,4 +313,24 @@ public void testNestedDef() throws Exception {
         verifyWithInlineConfigParser(getPath(
                 "InputRedundantModifierNestedDef.java"), expected);
     }
+
+    @Test
+    public void testRecords() throws Exception {
+        final String[] expected = {
+            "12:5: " + getCheckMessage(MSG_KEY, "static"),
+            "16:9: " + getCheckMessage(MSG_KEY, "final"),
+            "16:15: " + getCheckMessage(MSG_KEY, "static"),
+            "21:9: " + getCheckMessage(MSG_KEY, "static"),
+            "27:9: " + getCheckMessage(MSG_KEY, "final"),
+            "27:15: " + getCheckMessage(MSG_KEY, "static"),
+            "32:13: " + getCheckMessage(MSG_KEY, "static"),
+            "38:1: " + getCheckMessage(MSG_KEY, "final"),
+            "40:5: " + getCheckMessage(MSG_KEY, "final"),
+            "43:5: " + getCheckMessage(MSG_KEY, "static"),
+            "47:9: " + getCheckMessage(MSG_KEY, "final"),
+            "47:15: " + getCheckMessage(MSG_KEY, "static"),
+        };
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputRedundantModifierRecords.java"), expected);
+    }
 }
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/modifier/redundantmodifier/InputRedundantModifierRecords.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/modifier/redundantmodifier/InputRedundantModifierRecords.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/modifier/redundantmodifier/InputRedundantModifierRecords.java
@@ -0,0 +1,56 @@
+/*
+RedundantModifier
+
+
+*/
+
+package com.puppycrawl.tools.checkstyle.checks.modifier.redundantmodifier;
+
+//non-compiled with javac: Compilable with Java16
+public class InputRedundantModifierRecords {
+
+    static record testRecord(int a) { // violation 'Redundant 'static' modifier'
+    }
+
+    interface foo {
+        final static record testRecords(int a) { // 2 violations
+        }
+    }
+
+    class b {
+        static record testRecord(int a) { // violation 'Redundant 'static' modifier'
+        }
+    }
+
+    enum testEnum {
+        ONE;
+        final static record testRecord() { // 2 violations
+
+        }
+
+        class b {
+            static record testRecord() { // violation 'Redundant 'static' modifier'
+            }
+        }
+    }
+}
+
+final record testRecord(int a) { // violation 'Redundant 'final' modifier'
+
+    final record anotherRecord(int b) { // violation 'Redundant 'final' modifier'
+    }
+
+    static record anotherTestRecord(int c) { // violation 'Redundant 'static' modifier'
+    }
+
+    @interface hoo {
+        final static record someRecord() { // 2 violations
+        }
+    }
+}
+
+record anotherRecord(int b) { // ok
+
+    record nestedRecord(int a, int p) { // ok
+    }
+}