Repository: checkstyle/checkstyle

Problem statement:
GenericWhitespace Check ignores exception to the ">" rule for succeeding whitespaces
Detected at [#14341](https://github.com/checkstyle/checkstyle/pull/14341).
Affects PR #14364 

([Documentation](https://checkstyle.org/checks/whitespace/genericwhitespace.html))

**Test.java**
```java
import java.util.ArrayList;
import java.util.List;

public class Test {
  List a = new ArrayList<> (); // violation expected
}
```

**config.xml**
```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="GenericWhitespace"/>
  </module>
</module>
```

**Actual:**
```
java -jar checkstyle-10.12.7-all.jar -c config.xml Test.java
Starting audit...
Audit done.
```

**Expected:** violation on line 5.
`Test.java:5:26: '>' is followed by whitespace. [GenericWhitespace]`

### Explanation:
- As per the documentation, right angle bracket (`>`) should not be followed by whitespace for diamond operators and when preceding method name or constructor.
- In `List a = new ArrayList<> ();` the right angle bracket (`>`) precedes the constructor, and is also part of the diamond operator (`<>`), hence shouldn't be followed by the whitespace.
- The exact example is provided provided in the [documentation](https://checkstyle.org/checks/whitespace/genericwhitespace.html#Example2-code), showcasing that it is incorrect style and should throw a violation, but this is ignored by the Check. (Also evident in the [referenced PR](https://github.com/checkstyle/checkstyle/pull/14341#issuecomment-1907645027))


Hints:
@sktpy , I understand that the Checkstyle style mentions the said violation and the same has not been implemented. But does the Sun Java style guide and the Google Java Style Guide also include the whitespace as a violation?? If yes, please attach a snapshot of the rule for reference. If not, perhaps that is why the violation has been ignored so far!!

Thanks.
And also do correct me in case I am wrong.
@MANISH-K-07, While Google's Java Style does not have an explicit example stating that this exact thing is not allowed, they cover specifically what _is_ allowed for the case of [Horizontal whitespace](https://google.github.io/styleguide/javaguide.html#s4.6.2-horizontal-whitespace):
> "Beyond where required by the language or other style rules, and apart from literals, comments and Javadoc, a single ASCII space also appears in the following places **_only_**."
- The rule addresses interior space.
- The list provided there defines the **_only_** cases where the occurrence of white-space is valid, except when it falls within the specified excluded scope(i.e. required by language, other style rules, literals, comments and Javadoc).
- Hence implying that any occurrence not listed there is indeed a violation.

<br>As per the Checkstyle [documentation](https://checkstyle.org/checks/whitespace/genericwhitespace.html#Description), this Check adheres to the "typical convention":
> "Checks that the whitespace around the Generic tokens (angle brackets) "<" and ">" are correct to the **_typical convention_**. The convention is not configurable."

<br>When running the Checkstyle with `sun_checks` or `google_checks` config on the given Test, a violation does appear in the expected line but for `MethodParamPad`.
> Test.java:5:28: '(' is preceded with whitespace. [MethodParamPad]

<br>Besides, even if the violation was not covered by any style guide, the Check should behave exactly like it is stated in the documentation, else changes in the documentation should be made to reflect the actual behaviour.
I am on it

Patch:
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/whitespace/GenericWhitespaceCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/whitespace/GenericWhitespaceCheck.java
--- a/src/main/java/com/puppycrawl/tools/checkstyle/checks/whitespace/GenericWhitespaceCheck.java
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/whitespace/GenericWhitespaceCheck.java
@@ -219,7 +219,7 @@ else if (line[after] == ' ') {
      */
     private void processSingleGeneric(DetailAST ast, int[] line, int after) {
         final char charAfter = Character.toChars(line[after])[0];
-        if (isGenericBeforeMethod(ast) || isGenericBeforeCtor(ast)) {
+        if (isGenericBeforeMethod(ast) || isGenericBeforeCtorInvocation(ast)) {
             if (Character.isWhitespace(charAfter)) {
                 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
             }
@@ -230,16 +230,38 @@ else if (!isCharacterValidAfterGenericEnd(charAfter)) {
     }
 
     /**
-     * Checks if generic is before constructor invocation.
+     * Checks if generic is before constructor invocation. Identifies two cases:
+     * <ol>
+     *     <li>{@code new ArrayList<>();}</li>
+     *     <li>{@code new Outer.Inner<>();}</li>
+     * </ol>
      *
      * @param ast ast
-     * @return true if generic before a constructor invocation
+     * @return true if generic is before constructor invocation
      */
-    private static boolean isGenericBeforeCtor(DetailAST ast) {
+    private static boolean isGenericBeforeCtorInvocation(DetailAST ast) {
+        final DetailAST grandParent = ast.getParent().getParent();
+        return grandParent.getType() == TokenTypes.LITERAL_NEW
+                || grandParent.getParent().getType() == TokenTypes.LITERAL_NEW;
+    }
+
+    /**
+     * Checks if generic is after {@code LITERAL_NEW}. Identifies three cases:
+     * <ol>
+     *     <li>{@code new <String>Object();}</li>
+     *     <li>{@code new <String>Outer.Inner();}</li>
+     *     <li>{@code new <@A Outer>@B Inner();}</li>
+     * </ol>
+     *
+     * @param ast ast
+     * @return true if generic after {@code LITERAL_NEW}
+     */
+    private static boolean isGenericAfterNew(DetailAST ast) {
         final DetailAST parent = ast.getParent();
         return parent.getParent().getType() == TokenTypes.LITERAL_NEW
                 && (parent.getNextSibling().getType() == TokenTypes.IDENT
-                    || parent.getNextSibling().getType() == TokenTypes.DOT);
+                    || parent.getNextSibling().getType() == TokenTypes.DOT
+                    || parent.getNextSibling().getType() == TokenTypes.ANNOTATIONS);
     }
 
     /**
@@ -274,25 +296,29 @@ private void processStart(DetailAST ast) {
         final int before = ast.getColumnNo() - 1;
         final int after = ast.getColumnNo() + 1;
 
-        // Need to handle two cases as in:
+        // Checks if generic needs to be preceded by a whitespace or not.
+        // Handles 3 cases as in:
         //
         //   public static <T> Callable<T> callable(Runnable task, T result)
         //                 ^           ^
-        //      ws reqd ---+           +--- whitespace NOT required
+        //   1. ws reqd ---+        2. +--- whitespace NOT required
         //
+        //   new <String>Object()
+        //       ^
+        //    3. +--- ws required
         if (before >= 0) {
-            // Detect if the first case
             final DetailAST parent = ast.getParent();
             final DetailAST grandparent = parent.getParent();
+            // cases (1, 3) where whitespace is required:
             if (grandparent.getType() == TokenTypes.CTOR_DEF
                     || grandparent.getType() == TokenTypes.METHOD_DEF
-                    || isGenericBeforeCtor(ast)) {
-                // Require whitespace
+                    || isGenericAfterNew(ast)) {
+
                 if (!CommonUtil.isCodePointWhitespace(line, before)) {
                     log(ast, MSG_WS_NOT_PRECEDED, OPEN_ANGLE_BRACKET);
                 }
             }
-            // Whitespace not required
+            // case 2 where whitespace is not required:
             else if (CommonUtil.isCodePointWhitespace(line, before)
                 && !containsWhitespaceBefore(before, line)) {
                 log(ast, MSG_WS_PRECEDED, OPEN_ANGLE_BRACKET);


Test patch:
diff --git a/src/it/java/com/google/checkstyle/test/chapter4formatting/rule462horizontalwhitespace/GenericWhitespaceTest.java b/src/it/java/com/google/checkstyle/test/chapter4formatting/rule462horizontalwhitespace/GenericWhitespaceTest.java
--- a/src/it/java/com/google/checkstyle/test/chapter4formatting/rule462horizontalwhitespace/GenericWhitespaceTest.java
+++ b/src/it/java/com/google/checkstyle/test/chapter4formatting/rule462horizontalwhitespace/GenericWhitespaceTest.java
@@ -54,6 +54,7 @@ public void testWhitespaceAroundGenerics() throws Exception {
             "14:46: " + getCheckMessage(messages, msgPreceded, ">"),
             "15:33: " + getCheckMessage(messages, msgFollowed, "<"),
             "15:33: " + getCheckMessage(messages, msgPreceded, "<"),
+            "15:46: " + getCheckMessage(messages, msgFollowed, ">"),
             "15:46: " + getCheckMessage(messages, msgPreceded, ">"),
             "20:39: " + getCheckMessage(messages, msgFollowed, "<"),
             "20:39: " + getCheckMessage(messages, msgPreceded, "<"),
@@ -81,6 +82,7 @@ public void testGenericWhitespace() throws Exception {
             "16:24: " + getCheckMessage(messages, msgPreceded, ">"),
             "16:44: " + getCheckMessage(messages, msgFollowed, "<"),
             "16:44: " + getCheckMessage(messages, msgPreceded, "<"),
+            "16:54: " + getCheckMessage(messages, msgFollowed, ">"),
             "16:54: " + getCheckMessage(messages, msgPreceded, ">"),
             "17:14: " + getCheckMessage(messages, msgFollowed, "<"),
             "17:14: " + getCheckMessage(messages, msgPreceded, "<"),
@@ -95,6 +97,7 @@ public void testGenericWhitespace() throws Exception {
             "17:60: " + getCheckMessage(messages, msgPreceded, "<"),
             "17:70: " + getCheckMessage(messages, msgFollowed, ">"),
             "17:70: " + getCheckMessage(messages, msgPreceded, ">"),
+            "17:72: " + getCheckMessage(messages, msgFollowed, ">"),
             "17:72: " + getCheckMessage(messages, msgPreceded, ">"),
             "30:18: " + getCheckMessage(messages, msgNotPreceded, "<"),
             "30:20: " + getCheckMessage(messages, msgIllegalFollow, ">"),
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/whitespace/GenericWhitespaceCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/whitespace/GenericWhitespaceCheckTest.java
--- a/src/test/java/com/puppycrawl/tools/checkstyle/checks/whitespace/GenericWhitespaceCheckTest.java
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/whitespace/GenericWhitespaceCheckTest.java
@@ -71,6 +71,7 @@ public void testDefault() throws Exception {
             "22:44: " + getCheckMessage(MSG_WS_PRECEDED, "<"),
             "22:44: " + getCheckMessage(MSG_WS_FOLLOWED, "<"),
             "22:54: " + getCheckMessage(MSG_WS_PRECEDED, ">"),
+            "22:54: " + getCheckMessage(MSG_WS_FOLLOWED, ">"),
             "23:14: " + getCheckMessage(MSG_WS_PRECEDED, "<"),
             "23:14: " + getCheckMessage(MSG_WS_FOLLOWED, "<"),
             "23:21: " + getCheckMessage(MSG_WS_PRECEDED, "<"),
@@ -85,6 +86,7 @@ public void testDefault() throws Exception {
             "23:70: " + getCheckMessage(MSG_WS_PRECEDED, ">"),
             "23:70: " + getCheckMessage(MSG_WS_FOLLOWED, ">"),
             "23:72: " + getCheckMessage(MSG_WS_PRECEDED, ">"),
+            "23:72: " + getCheckMessage(MSG_WS_FOLLOWED, ">"),
             "36:18: " + getCheckMessage(MSG_WS_NOT_PRECEDED, "<"),
             "36:20: " + getCheckMessage(MSG_WS_ILLEGAL_FOLLOW, ">"),
             "48:22: " + getCheckMessage(MSG_WS_PRECEDED, "<"),
@@ -173,6 +175,65 @@ public void testGenericWhitespaceWithEmoji() throws Exception {
                 getPath("InputGenericWhitespaceWithEmoji.java"), expected);
     }
 
+    @Test
+    public void testBeforeCtorInvocation() throws Exception {
+        final String[] expected = {
+            "17:31: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "19:56: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "19:56: " + getCheckMessage(MSG_WS_PRECEDED, '>'),
+            "24:25: " + getCheckMessage(MSG_WS_FOLLOWED, '<'),
+            "27:36: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "31:35: " + getCheckMessage(MSG_WS_FOLLOWED, '<'),
+            "31:35: " + getCheckMessage(MSG_WS_PRECEDED, '<'),
+            "31:47: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "31:47: " + getCheckMessage(MSG_WS_PRECEDED, '>'),
+            "38:34: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "39:47: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "40:28: " + getCheckMessage(MSG_WS_PRECEDED, '<'),
+            "40:48: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "47:41: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "50:47: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "52:44: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+        };
+        verifyWithInlineConfigParser(
+                getPath("InputGenericWhitespaceBeforeCtorInvocation.java"), expected);
+    }
+
+    @Test
+    public void testAfterNew() throws Exception {
+        final String[] expected = {
+            "17:30: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "21:12: " + getCheckMessage(MSG_WS_FOLLOWED, '<'),
+            "21:12: " + getCheckMessage(MSG_WS_NOT_PRECEDED, '<'),
+            "21:23: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "21:23: " + getCheckMessage(MSG_WS_PRECEDED, '>'),
+            "28:22: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "33:31: " + getCheckMessage(MSG_WS_FOLLOWED, '<'),
+            "33:31: " + getCheckMessage(MSG_WS_NOT_PRECEDED, '<'),
+            "33:40: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "33:40: " + getCheckMessage(MSG_WS_PRECEDED, '>'),
+            "41:28: " + getCheckMessage(MSG_WS_NOT_PRECEDED, '<'),
+            "41:36: " + getCheckMessage(MSG_WS_FOLLOWED, '<'),
+            "41:56: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "41:61: " + getCheckMessage(MSG_WS_PRECEDED, '>'),
+            "41:63: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "41:65: " + getCheckMessage(MSG_WS_PRECEDED, '>'),
+            "41:66: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+            "41:85: " + getCheckMessage(MSG_WS_PRECEDED, '<'),
+            "41:92: " + getCheckMessage(MSG_WS_FOLLOWED, '>'),
+        };
+        verifyWithInlineConfigParser(
+                getPath("InputGenericWhitespaceAfterNew.java"), expected);
+    }
+
+    @Test
+    public void testBeforeRecordHeader() throws Exception {
+        final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
+        verifyWithInlineConfigParser(
+                getNonCompilablePath("InputGenericWhitespaceBeforeRecordHeader.java"),
+                expected);
+    }
+
     /**
      * Checks if the private field {@code depth} is properly cleared during the
      * start of processing the next file in the check.
diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceBeforeRecordHeader.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceBeforeRecordHeader.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceBeforeRecordHeader.java
@@ -0,0 +1,12 @@
+/*
+GenericWhitespace
+
+
+*/
+
+//non-compiled with javac: Compilable with Java16
+package com.puppycrawl.tools.checkstyle.checks.whitespace.genericwhitespace;
+
+public class InputGenericWhitespaceBeforeRecordHeader {
+    record Session<T>() {}
+}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceAfterNew.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceAfterNew.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceAfterNew.java
@@ -0,0 +1,65 @@
+/*
+GenericWhitespace
+
+
+*/
+
+package com.puppycrawl.tools.checkstyle.checks.whitespace.genericwhitespace;
+
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE_USE;
+
+public class InputGenericWhitespaceAfterNew {
+    public class Inner<T> { }
+
+    Object obj1 = new <String>Object();
+    Object obj2 = new <String> Object(); // violation, ''>' is followed by whitespace.'
+
+    void method() {
+        new <@A Inner>Inner();
+        new< @A Inner > Inner ();
+        // 4 violations above:
+        //                    ''<' is followed by whitespace.'
+        //                    ''<' is not preceded with whitespace.'
+        //                    ''>' is followed by whitespace.'
+        //                    ''>' is preceded with whitespace.'
+
+        new <@A Inner> @A Inner<@A Inner>(); // violation, ''>' is followed by whitespace.'
+    }
+
+    void popCan() {
+        Can.Pop cannot = new <String>Can.Pop();
+        Can.Pop burstCan = new< String > Can.Pop();
+        // 4 violations above:
+        //                    ''<' is followed by whitespace.'
+        //                    ''<' is not preceded with whitespace.'
+        //                    ''>' is followed by whitespace.'
+        //                    ''>' is preceded with whitespace.'
+
+        //                 +       +                   +    + + ++                  +      +
+        Can.must("idk", new<Can<Can< @A Inner<Can<Can<?> [] >>> >> Can.Pop.But.Wont <String> ());
+        // 9 violations above:
+        //                    ''<' is not preceded with whitespace.'
+        //                    ''<' is followed by whitespace.'
+        //                    ''>' is followed by whitespace.'
+        //                    ''>' is preceded with whitespace.'
+        //                    ''>' is followed by whitespace.'
+        //                    ''>' is preceded with whitespace.'
+        //                    ''>' is followed by whitespace.'
+        //                    ''<' is preceded with whitespace.'
+        //                    ''>' is followed by whitespace.'
+    }
+
+    @Target({TYPE_USE}) @interface A {}
+}
+
+class Can<T> {
+    static <T> void must(String why, T whatever) {}
+    static class Pop {
+        static class But {
+            static class Wont<T> {
+            }
+        }
+    }
+}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceBeforeCtorInvocation.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceBeforeCtorInvocation.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceBeforeCtorInvocation.java
@@ -0,0 +1,73 @@
+/*
+GenericWhitespace
+
+
+*/
+
+package com.puppycrawl.tools.checkstyle.checks.whitespace.genericwhitespace;
+
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.lang.annotation.ElementType.TYPE_USE;
+
+public class InputGenericWhitespaceBeforeCtorInvocation {
+    List<String> music = new ArrayList<>();
+    List todo = new ArrayList<> (); // violation, ''>' is followed by whitespace.'
+
+    Gen<String, Integer> eel = new Gen<String, Integer > ();
+    // 2 violations above:
+    //                    ''>' is followed by whitespace.'
+    //                    ''>' is preceded with whitespace.'
+
+    Pipe pipe = new Pipe< String>() { // violation, ''<' is followed by whitespace.'
+    };
+
+    Class<?>[] classy = new Class<?> []{}; // violation, ''>' is followed by whitespace.'
+
+    void method() {
+        new Very.Deep<Integer>();
+        new Very.Deep.Rabbit.Hole <  Integer  > ();
+        // 4 violations above:
+        //                    ''<' is followed by whitespace.'
+        //                    ''<' is preceded with whitespace.'
+        //                    ''>' is followed by whitespace.'
+        //                    ''>' is preceded with whitespace.'
+
+        new @A Gen<@A Gen, @A Gen> (); // violation, ''>' is followed by whitespace.'
+        new java.util.HashMap<Integer, Integer> (); // violation, ''>' is followed by whitespace.'
+        new ArrayList<Very <Gen<String,String>>> ();
+        // 2 violations above:
+        //                    ''<' is preceded with whitespace.'
+        //                    ''>' is followed by whitespace.'
+    }
+
+    static final Very<Gen<String, String>> veryGen =
+            new Very<Gen<String, String>> () {}; // violation, ''>' is followed by whitespace.'
+
+    <T> void mustPatTheCroc(boolean mustPat, T[] crocs) {
+        Very.swampPuppy(new Gen<String, String> ()); // violation, ''>' is followed by whitespace.'
+        if (!mustPat) {
+            mustPatTheCroc(true, new Very<?> []{}); // violation, ''>' is followed by whitespace.'
+        }
+    }
+
+    class Gen<T, U> {}
+
+    static class Very<Q> {
+        static void swampPuppy(Gen<String, String> croc) {}
+        static class Deep<D> {
+            static class Rabbit<R> {
+                static class Hole<H> {
+
+                }
+            }
+        }
+    }
+
+    interface Pipe<T> {
+    }
+
+    @Target({TYPE_USE}) @interface A {}
+}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceDefault.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceDefault.java
--- a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceDefault.java
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/whitespace/genericwhitespace/InputGenericWhitespaceDefault.java
@@ -19,8 +19,8 @@ void meth()
     {
         List<Integer> x = new ArrayList<Integer>();
         List<List<Integer>> y = new ArrayList<List<Integer>>();
-        List < Integer > a = new ArrayList < Integer > (); // 6 violations
-        List < List < Integer > > b = new ArrayList < List < Integer > > (); // 14 violations
+        List < Integer > a = new ArrayList < Integer > (); // 7 violations
+        List < List < Integer > > b = new ArrayList < List < Integer > > (); // 15 violations
     }
     //always 0
     public int compareTo(Object aObject)