{"thread":{"id":"59193","subject":"[PATCH v1 0/3] userdiff: Java updates","startedAt":"2023-02-03T23:23:51Z","lastAt":"2023-02-08T20:55:42Z","messageCount":19,"participants":["Andrei Rybak","Tassilo Horn","Johannes Sixt","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"471466","messageId":"20230203232339.216592-1-rybak.a.v@gmail.com","threadId":"59193","inReplyTo":null,"subject":"[PATCH v1 0/3] userdiff: Java updates","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-03T23:23:36Z","receivedAt":"2023-02-03T23:23:51Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"Three patches to improve builtin userdiff support for Java features.  Some\npretty old features -- type parameters aka generics are actually older than Git,\nand some quite recent features of Java: records and sealed classes, released in\nJava 16 and Java 17 correspondingly, both from 2021.\n\nhttps://en.wikipedia.org/wiki/Java_version_history\n\nAndrei Rybak (3):\n  userdiff: support Java type parameters\n  userdiff: support Java record types\n  userdiff: support Java sealed classes\n\n t/t4018/java-class-type-parameters                     | 6 ++++++\n t/t4018/java-class-type-parameters-implements          | 6 ++++++\n t/t4018/java-interface-type-parameters                 | 6 ++++++\n t/t4018/java-interface-type-parameters-extends         | 6 ++++++\n t/t4018/java-non-sealed                                | 8 ++++++++\n t/t4018/java-record                                    | 6 ++++++\n t/t4018/java-record-type-parameters                    | 6 ++++++\n t/t4018/java-sealed                                    | 7 +++++++\n t/t4018/java-sealed-permits                            | 6 ++++++\n t/t4018/java-sealed-type-parameters                    | 6 ++++++\n t/t4018/java-sealed-type-parameters-implements-permits | 6 ++++++\n t/t4018/java-sealed-type-parameters-permits            | 6 ++++++\n userdiff.c                                             | 2 +-\n 13 files changed, 76 insertions(+), 1 deletion(-)\n create mode 100644 t/t4018/java-class-type-parameters\n create mode 100644 t/t4018/java-class-type-parameters-implements\n create mode 100644 t/t4018/java-interface-type-parameters\n create mode 100644 t/t4018/java-interface-type-parameters-extends\n create mode 100644 t/t4018/java-non-sealed\n create mode 100644 t/t4018/java-record\n create mode 100644 t/t4018/java-record-type-parameters\n create mode 100644 t/t4018/java-sealed\n create mode 100644 t/t4018/java-sealed-permits\n create mode 100644 t/t4018/java-sealed-type-parameters\n create mode 100644 t/t4018/java-sealed-type-parameters-implements-permits\n create mode 100644 t/t4018/java-sealed-type-parameters-permits\n\n-- \n2.39.1\n\n"},{"id":"471467","messageId":"20230203232339.216592-2-rybak.a.v@gmail.com","threadId":"59193","inReplyTo":"20230203232339.216592-1-rybak.a.v@gmail.com","subject":"[PATCH v1 1/3] userdiff: support Java type parameters","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-03T23:23:37Z","receivedAt":"2023-02-03T23:23:55Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"A class or interface in Java [1] can have type parameters immediately\nfollowing the name in the declaration, surrounded by angle brackets\n(paired less than and greater than signs).[2]  Example of a class with\ntype parameters \"A\" and \"N\":\n\n    public class ParameterizedClass<A, N> {\n        private A field1;\n        private N field2;\n    }\n\nSupport matching a parameterized class or interface declaration with\ntype parameters immediately following the name of the type in the\nbuiltin userdiff pattern for Java.  Do so by just allowing matching the\nfirst character after the name of the type to \"<\".\n\nAn alternative approach could be to match both the opening and the\nclosing angle brackets and matching the content between them in various\nways.  Just use the simpler regex for now.\n\n[1] Since Java 5 released in 2004.\n[2] Detailed description is available in the Java Language\n    Specification, sections \"Type Variables\" and \"Parameterized Types\":\n    https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.4\n\nSigned-off-by: Andrei Rybak <rybak.a.v@gmail.com>\n---\n t/t4018/java-class-type-parameters             | 6 ++++++\n t/t4018/java-class-type-parameters-implements  | 6 ++++++\n t/t4018/java-interface-type-parameters         | 6 ++++++\n t/t4018/java-interface-type-parameters-extends | 6 ++++++\n userdiff.c                                     | 2 +-\n 5 files changed, 25 insertions(+), 1 deletion(-)\n create mode 100644 t/t4018/java-class-type-parameters\n create mode 100644 t/t4018/java-class-type-parameters-implements\n create mode 100644 t/t4018/java-interface-type-parameters\n create mode 100644 t/t4018/java-interface-type-parameters-extends\n\ndiff --git a/t/t4018/java-class-type-parameters b/t/t4018/java-class-type-parameters\nnew file mode 100644\nindex 0000000000..579aa7af21\n--- /dev/null\n+++ b/t/t4018/java-class-type-parameters\n@@ -0,0 +1,6 @@\n+class RIGHT<A, B> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private A ChangeMe;\n+}\ndiff --git a/t/t4018/java-class-type-parameters-implements b/t/t4018/java-class-type-parameters-implements\nnew file mode 100644\nindex 0000000000..b8038b1866\n--- /dev/null\n+++ b/t/t4018/java-class-type-parameters-implements\n@@ -0,0 +1,6 @@\n+class RIGHT<A, B> implements List<A> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private A ChangeMe;\n+}\ndiff --git a/t/t4018/java-interface-type-parameters b/t/t4018/java-interface-type-parameters\nnew file mode 100644\nindex 0000000000..a4baa1ae68\n--- /dev/null\n+++ b/t/t4018/java-interface-type-parameters\n@@ -0,0 +1,6 @@\n+interface RIGHT<A, B> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    public B foo(A ChangeMe);\n+}\ndiff --git a/t/t4018/java-interface-type-parameters-extends b/t/t4018/java-interface-type-parameters-extends\nnew file mode 100644\nindex 0000000000..31d7fb3244\n--- /dev/null\n+++ b/t/t4018/java-interface-type-parameters-extends\n@@ -0,0 +1,6 @@\n+interface RIGHT<A, B> extends Function<A, B> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    public B foo(A ChangeMe);\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex d71b82feb7..759e22ffff 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -171,7 +171,7 @@ PATTERNS(\"html\",\n PATTERNS(\"java\",\n \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n \t /* Class, enum, and interface declarations */\n-\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+[A-Za-z][A-Za-z0-9_$]*[ \\t]+.*)$\\n\"\n+\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|<).*)$\\n\"\n \t /* Method definitions; note that constructor signatures are not */\n \t /* matched because they are indistinguishable from method calls. */\n \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n-- \n2.39.1\n\n"},{"id":"471468","messageId":"20230203232339.216592-3-rybak.a.v@gmail.com","threadId":"59193","inReplyTo":"20230203232339.216592-1-rybak.a.v@gmail.com","subject":"[PATCH v1 2/3] userdiff: support Java record types","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-03T23:23:38Z","receivedAt":"2023-02-03T23:23:55Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"A new kind of class was added in Java 16 -- records.[1]  The syntax of\nrecords is similar to regular classes with one important distinction:\nthe name of the record class is followed by a mandatory list of\ncomponents.  The list is enclosed in parentheses, it may be empty, and\nit may immediately follow the name of the class or type parameters, if\nany, without separating whitespace.\n\nCode examples:\n\n    public record Example(int i, String s) {\n    }\n\n    public record WithTypeParameters<A, B>(A a, B b, String s) {\n    }\n\nSupport records in the builtin userdiff pattern for Java.  Add \"record\"\nto the alternatives of keywords for kinds of class, and match an opening\nparenthesis as the first character right after the type name.\n\nAn alternative approach could be to have an optional group that would\nmatch both the opening and the closing parentheses with some way of\nmatching the declarations of the components.  Just use the simpler\nregular expression for now.\n\n[1] detailed description is available in \"JEP 395: Records\"\n    https://openjdk.org/jeps/395\n\nSigned-off-by: Andrei Rybak <rybak.a.v@gmail.com>\n---\n t/t4018/java-record                 | 6 ++++++\n t/t4018/java-record-type-parameters | 6 ++++++\n userdiff.c                          | 2 +-\n 3 files changed, 13 insertions(+), 1 deletion(-)\n create mode 100644 t/t4018/java-record\n create mode 100644 t/t4018/java-record-type-parameters\n\ndiff --git a/t/t4018/java-record b/t/t4018/java-record\nnew file mode 100644\nindex 0000000000..97aa819dd8\n--- /dev/null\n+++ b/t/t4018/java-record\n@@ -0,0 +1,6 @@\n+public record RIGHT(int comp1, double comp2, String comp3) {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    static int ChangeMe;\n+}\ndiff --git a/t/t4018/java-record-type-parameters b/t/t4018/java-record-type-parameters\nnew file mode 100644\nindex 0000000000..f62a035cc8\n--- /dev/null\n+++ b/t/t4018/java-record-type-parameters\n@@ -0,0 +1,6 @@\n+public record RIGHT<A, N extends Number>(A comp1, N comp2, int comp3) {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    static int ChangeMe;\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex 759e22ffff..f92b3029aa 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -171,7 +171,7 @@ PATTERNS(\"html\",\n PATTERNS(\"java\",\n \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n \t /* Class, enum, and interface declarations */\n-\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|<).*)$\\n\"\n+\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface|record)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|[<(]).*)$\\n\"\n \t /* Method definitions; note that constructor signatures are not */\n \t /* matched because they are indistinguishable from method calls. */\n \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n-- \n2.39.1\n\n"},{"id":"471469","messageId":"20230203232339.216592-4-rybak.a.v@gmail.com","threadId":"59193","inReplyTo":"20230203232339.216592-1-rybak.a.v@gmail.com","subject":"[PATCH v1 3/3] userdiff: support Java sealed classes","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-03T23:23:39Z","receivedAt":"2023-02-03T23:23:57Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"A new kind of class was added in Java 17 -- sealed classes.[1]  This\nfeature includes several new keywords that may appear in a declaration\nof a class.  New modifiers before name of the class: \"sealed\" and\n\"non-sealed\", and a clause after name of the class marked by keyword\n\"permits\".\n\nThe current set of regular expressions in userdiff.c already allows the\nmodifier \"sealed\" and the \"permits\" clause, but not the modifier\n\"non-sealed\", which is the first hyphenated keyword in Java.[2]  Allow\nhyphen in the words that precede the name of type to match the\n\"non-sealed\" modifier.\n\nIn new input file \"java-sealed\" for the test t4018-diff-funcname.sh, use\na Java code comment for the marker \"RIGHT\".  This workaround is needed,\nbecause the name of the sealed class appears on the line of code that\nhas the \"ChangeMe\" marker.\n\n[1] Detailed description in \"JEP 409: Sealed Classes\"\n    https://openjdk.org/jeps/409\n[2] \"JEP draft: Keyword Management for the Java Language\"\n    https://openjdk.org/jeps/8223002\n\nSigned-off-by: Andrei Rybak <rybak.a.v@gmail.com>\n---\n t/t4018/java-non-sealed                                | 8 ++++++++\n t/t4018/java-sealed                                    | 7 +++++++\n t/t4018/java-sealed-permits                            | 6 ++++++\n t/t4018/java-sealed-type-parameters                    | 6 ++++++\n t/t4018/java-sealed-type-parameters-implements-permits | 6 ++++++\n t/t4018/java-sealed-type-parameters-permits            | 6 ++++++\n userdiff.c                                             | 2 +-\n 7 files changed, 40 insertions(+), 1 deletion(-)\n create mode 100644 t/t4018/java-non-sealed\n create mode 100644 t/t4018/java-sealed\n create mode 100644 t/t4018/java-sealed-permits\n create mode 100644 t/t4018/java-sealed-type-parameters\n create mode 100644 t/t4018/java-sealed-type-parameters-implements-permits\n create mode 100644 t/t4018/java-sealed-type-parameters-permits\n\ndiff --git a/t/t4018/java-non-sealed b/t/t4018/java-non-sealed\nnew file mode 100644\nindex 0000000000..f68ffd4ff3\n--- /dev/null\n+++ b/t/t4018/java-non-sealed\n@@ -0,0 +1,8 @@\n+public sealed abstract class SealedClass {\n+    public static non-sealed class RIGHT extends SealedClass {\n+        static int ONE;\n+        static int TWO;\n+        static int THREE;\n+        private int ChangeMe;\n+    }\n+}\ndiff --git a/t/t4018/java-sealed b/t/t4018/java-sealed\nnew file mode 100644\nindex 0000000000..e722fee803\n--- /dev/null\n+++ b/t/t4018/java-sealed\n@@ -0,0 +1,7 @@\n+public sealed abstract class Sealed { // RIGHT\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    public final class ChangeMe extends Sealed {\n+    }\n+}\ndiff --git a/t/t4018/java-sealed-permits b/t/t4018/java-sealed-permits\nnew file mode 100644\nindex 0000000000..8573f2a7e8\n--- /dev/null\n+++ b/t/t4018/java-sealed-permits\n@@ -0,0 +1,6 @@\n+public sealed abstract class RIGHT permits PermittedA, PermittedB {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private int ChangeMe;\n+}\ndiff --git a/t/t4018/java-sealed-type-parameters b/t/t4018/java-sealed-type-parameters\nnew file mode 100644\nindex 0000000000..ec31115961\n--- /dev/null\n+++ b/t/t4018/java-sealed-type-parameters\n@@ -0,0 +1,6 @@\n+public sealed abstract class RIGHT<A, B> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private int ChangeMe;\n+}\ndiff --git a/t/t4018/java-sealed-type-parameters-implements-permits b/t/t4018/java-sealed-type-parameters-implements-permits\nnew file mode 100644\nindex 0000000000..9fd4dd5633\n--- /dev/null\n+++ b/t/t4018/java-sealed-type-parameters-implements-permits\n@@ -0,0 +1,6 @@\n+public sealed abstract class RIGHT<A, B> implements List<A> permits PermittedA, PermittedB {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private int ChangeMe;\n+}\ndiff --git a/t/t4018/java-sealed-type-parameters-permits b/t/t4018/java-sealed-type-parameters-permits\nnew file mode 100644\nindex 0000000000..6af2352e46\n--- /dev/null\n+++ b/t/t4018/java-sealed-type-parameters-permits\n@@ -0,0 +1,6 @@\n+public sealed abstract class RIGHT<A, B> permits PermittedA, PermittedB {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private int ChangeMe;\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex f92b3029aa..040deb7439 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -171,7 +171,7 @@ PATTERNS(\"html\",\n PATTERNS(\"java\",\n \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n \t /* Class, enum, and interface declarations */\n-\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface|record)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|[<(]).*)$\\n\"\n+\t \"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|[<(]).*)$\\n\"\n \t /* Method definitions; note that constructor signatures are not */\n \t /* matched because they are indistinguishable from method calls. */\n \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n-- \n2.39.1\n\n"},{"id":"471475","messageId":"877cwxvl3a.fsf@gnu.org","threadId":"59193","inReplyTo":"20230203232339.216592-1-rybak.a.v@gmail.com","subject":"Re: [PATCH v1 0/3] userdiff: Java updates","fromName":"Tassilo Horn","fromEmail":"tsdh@gnu.org","sentAt":"2023-02-04T09:22:18Z","receivedAt":"2023-02-04T09:25:29Z","isPatch":true,"sender":{"key":"tsdh@gnu.org","avatar":"https://avatars.githubusercontent.com/u/103854?v=4"},"body":"Andrei Rybak <rybak.a.v@gmail.com> writes:\n\nHi Andrei,\n\n> Three patches to improve builtin userdiff support for Java features.\n> Some pretty old features -- type parameters aka generics are actually\n> older than Git, and some quite recent features of Java: records and\n> sealed classes, released in Java 16 and Java 17 correspondingly, both\n> from 2021.\n\nThanks for including me being the last contributor to java userdiff.\nThe patches look good from my POV and are safe-guarded with tests, so\nI'm all for it.\n\nBye,\nTassilo\n"},{"id":"471484","messageId":"20230204134329.251451-1-rybak.a.v@gmail.com","threadId":"59193","inReplyTo":"877cwxvl3a.fsf@gnu.org","subject":"[PATCH v2 0/3] userdiff: Java updates","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-04T13:43:26Z","receivedAt":"2023-02-04T13:43:44Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"On 04/02/2023 10:22, Tassilo Horn wrote:\n> Thanks for including me being the last contributor to java userdiff.\n> The patches look good from my POV and are safe-guarded with tests, so\n> I'm all for it.\n\nThank you for review!\n\nI've realized that I've been writing modifiers \"abstract\" and \"sealed\" in a\ntechnically correct, but not the conventional order.  Here's a reroll with the\norder of modifiers following the style of original authors of\nhttps://openjdk.org/jeps/409.  It doesn't matter for the purposes of the test,\nbut it will be less annoying to any future readers :-)\n\nRange diff since v1:\n\n1:  c300745a58 = 1:  c300745a58 userdiff: support Java type parameters\n2:  a0e622a0f8 = 2:  a0e622a0f8 userdiff: support Java record types\n3:  a53fca4d49 ! 3:  b9c6a5dffd userdiff: support Java sealed classes\n    @@ Commit message\n     \n      ## t/t4018/java-non-sealed (new) ##\n     @@\n    -+public sealed abstract class SealedClass {\n    ++public abstract sealed class SealedClass {\n     +    public static non-sealed class RIGHT extends SealedClass {\n     +        static int ONE;\n     +        static int TWO;\n    @@ t/t4018/java-non-sealed (new)\n     \n      ## t/t4018/java-sealed (new) ##\n     @@\n    -+public sealed abstract class Sealed { // RIGHT\n    ++public abstract sealed class Sealed { // RIGHT\n     +    static int ONE;\n     +    static int TWO;\n     +    static int THREE;\n    @@ t/t4018/java-sealed (new)\n     \n      ## t/t4018/java-sealed-permits (new) ##\n     @@\n    -+public sealed abstract class RIGHT permits PermittedA, PermittedB {\n    ++public abstract sealed class RIGHT permits PermittedA, PermittedB {\n     +    static int ONE;\n     +    static int TWO;\n     +    static int THREE;\n    @@ t/t4018/java-sealed-permits (new)\n     \n      ## t/t4018/java-sealed-type-parameters (new) ##\n     @@\n    -+public sealed abstract class RIGHT<A, B> {\n    ++public abstract sealed class RIGHT<A, B> {\n     +    static int ONE;\n     +    static int TWO;\n     +    static int THREE;\n    @@ t/t4018/java-sealed-type-parameters (new)\n     \n      ## t/t4018/java-sealed-type-parameters-implements-permits (new) ##\n     @@\n    -+public sealed abstract class RIGHT<A, B> implements List<A> permits PermittedA, PermittedB {\n    ++public abstract sealed class RIGHT<A, B> implements List<A> permits PermittedA, PermittedB {\n     +    static int ONE;\n     +    static int TWO;\n     +    static int THREE;\n    @@ t/t4018/java-sealed-type-parameters-implements-permits (new)\n     \n      ## t/t4018/java-sealed-type-parameters-permits (new) ##\n     @@\n    -+public sealed abstract class RIGHT<A, B> permits PermittedA, PermittedB {\n    ++public abstract sealed class RIGHT<A, B> permits PermittedA, PermittedB {\n     +    static int ONE;\n     +    static int TWO;\n     +    static int THREE;\n\n\nAndrei Rybak (3):\n  userdiff: support Java type parameters\n  userdiff: support Java record types\n  userdiff: support Java sealed classes\n\n t/t4018/java-class-type-parameters                     | 6 ++++++\n t/t4018/java-class-type-parameters-implements          | 6 ++++++\n t/t4018/java-interface-type-parameters                 | 6 ++++++\n t/t4018/java-interface-type-parameters-extends         | 6 ++++++\n t/t4018/java-non-sealed                                | 8 ++++++++\n t/t4018/java-record                                    | 6 ++++++\n t/t4018/java-record-type-parameters                    | 6 ++++++\n t/t4018/java-sealed                                    | 7 +++++++\n t/t4018/java-sealed-permits                            | 6 ++++++\n t/t4018/java-sealed-type-parameters                    | 6 ++++++\n t/t4018/java-sealed-type-parameters-implements-permits | 6 ++++++\n t/t4018/java-sealed-type-parameters-permits            | 6 ++++++\n userdiff.c                                             | 2 +-\n 13 files changed, 76 insertions(+), 1 deletion(-)\n create mode 100644 t/t4018/java-class-type-parameters\n create mode 100644 t/t4018/java-class-type-parameters-implements\n create mode 100644 t/t4018/java-interface-type-parameters\n create mode 100644 t/t4018/java-interface-type-parameters-extends\n create mode 100644 t/t4018/java-non-sealed\n create mode 100644 t/t4018/java-record\n create mode 100644 t/t4018/java-record-type-parameters\n create mode 100644 t/t4018/java-sealed\n create mode 100644 t/t4018/java-sealed-permits\n create mode 100644 t/t4018/java-sealed-type-parameters\n create mode 100644 t/t4018/java-sealed-type-parameters-implements-permits\n create mode 100644 t/t4018/java-sealed-type-parameters-permits\n\n-- \n2.39.1\n\n"},{"id":"471485","messageId":"20230204134329.251451-2-rybak.a.v@gmail.com","threadId":"59193","inReplyTo":"20230204134329.251451-1-rybak.a.v@gmail.com","subject":"[PATCH v2 1/3] userdiff: support Java type parameters","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-04T13:43:27Z","receivedAt":"2023-02-04T13:43:47Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"A class or interface in Java [1] can have type parameters immediately\nfollowing the name in the declaration, surrounded by angle brackets\n(paired less than and greater than signs).[2]  Example of a class with\ntype parameters \"A\" and \"N\":\n\n    public class ParameterizedClass<A, N> {\n        private A field1;\n        private N field2;\n    }\n\nSupport matching a parameterized class or interface declaration with\ntype parameters immediately following the name of the type in the\nbuiltin userdiff pattern for Java.  Do so by just allowing matching the\nfirst character after the name of the type to \"<\".\n\nAn alternative approach could be to match both the opening and the\nclosing angle brackets and matching the content between them in various\nways.  Just use the simpler regex for now.\n\n[1] Since Java 5 released in 2004.\n[2] Detailed description is available in the Java Language\n    Specification, sections \"Type Variables\" and \"Parameterized Types\":\n    https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.4\n\nSigned-off-by: Andrei Rybak <rybak.a.v@gmail.com>\n---\n t/t4018/java-class-type-parameters             | 6 ++++++\n t/t4018/java-class-type-parameters-implements  | 6 ++++++\n t/t4018/java-interface-type-parameters         | 6 ++++++\n t/t4018/java-interface-type-parameters-extends | 6 ++++++\n userdiff.c                                     | 2 +-\n 5 files changed, 25 insertions(+), 1 deletion(-)\n create mode 100644 t/t4018/java-class-type-parameters\n create mode 100644 t/t4018/java-class-type-parameters-implements\n create mode 100644 t/t4018/java-interface-type-parameters\n create mode 100644 t/t4018/java-interface-type-parameters-extends\n\ndiff --git a/t/t4018/java-class-type-parameters b/t/t4018/java-class-type-parameters\nnew file mode 100644\nindex 0000000000..579aa7af21\n--- /dev/null\n+++ b/t/t4018/java-class-type-parameters\n@@ -0,0 +1,6 @@\n+class RIGHT<A, B> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private A ChangeMe;\n+}\ndiff --git a/t/t4018/java-class-type-parameters-implements b/t/t4018/java-class-type-parameters-implements\nnew file mode 100644\nindex 0000000000..b8038b1866\n--- /dev/null\n+++ b/t/t4018/java-class-type-parameters-implements\n@@ -0,0 +1,6 @@\n+class RIGHT<A, B> implements List<A> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private A ChangeMe;\n+}\ndiff --git a/t/t4018/java-interface-type-parameters b/t/t4018/java-interface-type-parameters\nnew file mode 100644\nindex 0000000000..a4baa1ae68\n--- /dev/null\n+++ b/t/t4018/java-interface-type-parameters\n@@ -0,0 +1,6 @@\n+interface RIGHT<A, B> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    public B foo(A ChangeMe);\n+}\ndiff --git a/t/t4018/java-interface-type-parameters-extends b/t/t4018/java-interface-type-parameters-extends\nnew file mode 100644\nindex 0000000000..31d7fb3244\n--- /dev/null\n+++ b/t/t4018/java-interface-type-parameters-extends\n@@ -0,0 +1,6 @@\n+interface RIGHT<A, B> extends Function<A, B> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    public B foo(A ChangeMe);\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex d71b82feb7..759e22ffff 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -171,7 +171,7 @@ PATTERNS(\"html\",\n PATTERNS(\"java\",\n \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n \t /* Class, enum, and interface declarations */\n-\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+[A-Za-z][A-Za-z0-9_$]*[ \\t]+.*)$\\n\"\n+\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|<).*)$\\n\"\n \t /* Method definitions; note that constructor signatures are not */\n \t /* matched because they are indistinguishable from method calls. */\n \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n-- \n2.39.1\n\n"},{"id":"471486","messageId":"20230204134329.251451-3-rybak.a.v@gmail.com","threadId":"59193","inReplyTo":"20230204134329.251451-1-rybak.a.v@gmail.com","subject":"[PATCH v2 2/3] userdiff: support Java record types","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-04T13:43:28Z","receivedAt":"2023-02-04T13:43:49Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"A new kind of class was added in Java 16 -- records.[1]  The syntax of\nrecords is similar to regular classes with one important distinction:\nthe name of the record class is followed by a mandatory list of\ncomponents.  The list is enclosed in parentheses, it may be empty, and\nit may immediately follow the name of the class or type parameters, if\nany, without separating whitespace.\n\nCode examples:\n\n    public record Example(int i, String s) {\n    }\n\n    public record WithTypeParameters<A, B>(A a, B b, String s) {\n    }\n\nSupport records in the builtin userdiff pattern for Java.  Add \"record\"\nto the alternatives of keywords for kinds of class, and match an opening\nparenthesis as the first character right after the type name.\n\nAn alternative approach could be to have an optional group that would\nmatch both the opening and the closing parentheses with some way of\nmatching the declarations of the components.  Just use the simpler\nregular expression for now.\n\n[1] detailed description is available in \"JEP 395: Records\"\n    https://openjdk.org/jeps/395\n\nSigned-off-by: Andrei Rybak <rybak.a.v@gmail.com>\n---\n t/t4018/java-record                 | 6 ++++++\n t/t4018/java-record-type-parameters | 6 ++++++\n userdiff.c                          | 2 +-\n 3 files changed, 13 insertions(+), 1 deletion(-)\n create mode 100644 t/t4018/java-record\n create mode 100644 t/t4018/java-record-type-parameters\n\ndiff --git a/t/t4018/java-record b/t/t4018/java-record\nnew file mode 100644\nindex 0000000000..97aa819dd8\n--- /dev/null\n+++ b/t/t4018/java-record\n@@ -0,0 +1,6 @@\n+public record RIGHT(int comp1, double comp2, String comp3) {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    static int ChangeMe;\n+}\ndiff --git a/t/t4018/java-record-type-parameters b/t/t4018/java-record-type-parameters\nnew file mode 100644\nindex 0000000000..f62a035cc8\n--- /dev/null\n+++ b/t/t4018/java-record-type-parameters\n@@ -0,0 +1,6 @@\n+public record RIGHT<A, N extends Number>(A comp1, N comp2, int comp3) {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    static int ChangeMe;\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex 759e22ffff..f92b3029aa 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -171,7 +171,7 @@ PATTERNS(\"html\",\n PATTERNS(\"java\",\n \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n \t /* Class, enum, and interface declarations */\n-\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|<).*)$\\n\"\n+\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface|record)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|[<(]).*)$\\n\"\n \t /* Method definitions; note that constructor signatures are not */\n \t /* matched because they are indistinguishable from method calls. */\n \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n-- \n2.39.1\n\n"},{"id":"471487","messageId":"20230204134329.251451-4-rybak.a.v@gmail.com","threadId":"59193","inReplyTo":"20230204134329.251451-1-rybak.a.v@gmail.com","subject":"[PATCH v2 3/3] userdiff: support Java sealed classes","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-04T13:43:29Z","receivedAt":"2023-02-04T13:43:51Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"A new kind of class was added in Java 17 -- sealed classes.[1]  This\nfeature includes several new keywords that may appear in a declaration\nof a class.  New modifiers before name of the class: \"sealed\" and\n\"non-sealed\", and a clause after name of the class marked by keyword\n\"permits\".\n\nThe current set of regular expressions in userdiff.c already allows the\nmodifier \"sealed\" and the \"permits\" clause, but not the modifier\n\"non-sealed\", which is the first hyphenated keyword in Java.[2]  Allow\nhyphen in the words that precede the name of type to match the\n\"non-sealed\" modifier.\n\nIn new input file \"java-sealed\" for the test t4018-diff-funcname.sh, use\na Java code comment for the marker \"RIGHT\".  This workaround is needed,\nbecause the name of the sealed class appears on the line of code that\nhas the \"ChangeMe\" marker.\n\n[1] Detailed description in \"JEP 409: Sealed Classes\"\n    https://openjdk.org/jeps/409\n[2] \"JEP draft: Keyword Management for the Java Language\"\n    https://openjdk.org/jeps/8223002\n\nSigned-off-by: Andrei Rybak <rybak.a.v@gmail.com>\n---\n t/t4018/java-non-sealed                                | 8 ++++++++\n t/t4018/java-sealed                                    | 7 +++++++\n t/t4018/java-sealed-permits                            | 6 ++++++\n t/t4018/java-sealed-type-parameters                    | 6 ++++++\n t/t4018/java-sealed-type-parameters-implements-permits | 6 ++++++\n t/t4018/java-sealed-type-parameters-permits            | 6 ++++++\n userdiff.c                                             | 2 +-\n 7 files changed, 40 insertions(+), 1 deletion(-)\n create mode 100644 t/t4018/java-non-sealed\n create mode 100644 t/t4018/java-sealed\n create mode 100644 t/t4018/java-sealed-permits\n create mode 100644 t/t4018/java-sealed-type-parameters\n create mode 100644 t/t4018/java-sealed-type-parameters-implements-permits\n create mode 100644 t/t4018/java-sealed-type-parameters-permits\n\ndiff --git a/t/t4018/java-non-sealed b/t/t4018/java-non-sealed\nnew file mode 100644\nindex 0000000000..069087c1c6\n--- /dev/null\n+++ b/t/t4018/java-non-sealed\n@@ -0,0 +1,8 @@\n+public abstract sealed class SealedClass {\n+    public static non-sealed class RIGHT extends SealedClass {\n+        static int ONE;\n+        static int TWO;\n+        static int THREE;\n+        private int ChangeMe;\n+    }\n+}\ndiff --git a/t/t4018/java-sealed b/t/t4018/java-sealed\nnew file mode 100644\nindex 0000000000..785fbc62bc\n--- /dev/null\n+++ b/t/t4018/java-sealed\n@@ -0,0 +1,7 @@\n+public abstract sealed class Sealed { // RIGHT\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    public final class ChangeMe extends Sealed {\n+    }\n+}\ndiff --git a/t/t4018/java-sealed-permits b/t/t4018/java-sealed-permits\nnew file mode 100644\nindex 0000000000..18dd4894cf\n--- /dev/null\n+++ b/t/t4018/java-sealed-permits\n@@ -0,0 +1,6 @@\n+public abstract sealed class RIGHT permits PermittedA, PermittedB {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private int ChangeMe;\n+}\ndiff --git a/t/t4018/java-sealed-type-parameters b/t/t4018/java-sealed-type-parameters\nnew file mode 100644\nindex 0000000000..e6530c47c3\n--- /dev/null\n+++ b/t/t4018/java-sealed-type-parameters\n@@ -0,0 +1,6 @@\n+public abstract sealed class RIGHT<A, B> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private int ChangeMe;\n+}\ndiff --git a/t/t4018/java-sealed-type-parameters-implements-permits b/t/t4018/java-sealed-type-parameters-implements-permits\nnew file mode 100644\nindex 0000000000..bd6e6d3582\n--- /dev/null\n+++ b/t/t4018/java-sealed-type-parameters-implements-permits\n@@ -0,0 +1,6 @@\n+public abstract sealed class RIGHT<A, B> implements List<A> permits PermittedA, PermittedB {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private int ChangeMe;\n+}\ndiff --git a/t/t4018/java-sealed-type-parameters-permits b/t/t4018/java-sealed-type-parameters-permits\nnew file mode 100644\nindex 0000000000..25a0da6442\n--- /dev/null\n+++ b/t/t4018/java-sealed-type-parameters-permits\n@@ -0,0 +1,6 @@\n+public abstract sealed class RIGHT<A, B> permits PermittedA, PermittedB {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private int ChangeMe;\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex f92b3029aa..040deb7439 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -171,7 +171,7 @@ PATTERNS(\"html\",\n PATTERNS(\"java\",\n \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n \t /* Class, enum, and interface declarations */\n-\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface|record)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|[<(]).*)$\\n\"\n+\t \"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|[<(]).*)$\\n\"\n \t /* Method definitions; note that constructor signatures are not */\n \t /* matched because they are indistinguishable from method calls. */\n \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n-- \n2.39.1\n\n"},{"id":"471517","messageId":"45830cf4-41c1-0bc1-3e4e-26b9f713f452@kdbg.org","threadId":"59193","inReplyTo":"20230204134329.251451-1-rybak.a.v@gmail.com","subject":"Re: [PATCH v2 0/3] userdiff: Java updates","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2023-02-05T10:09:07Z","receivedAt":"2023-02-05T10:09:22Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 04.02.23 um 14:43 schrieb Andrei Rybak:\n> On 04/02/2023 10:22, Tassilo Horn wrote:\n>> Thanks for including me being the last contributor to java userdiff.\n>> The patches look good from my POV and are safe-guarded with tests, so\n>> I'm all for it.\n> \n> Thank you for review!\n> \n> I've realized that I've been writing modifiers \"abstract\" and \"sealed\" in a\n> technically correct, but not the conventional order.  Here's a reroll with the\n> order of modifiers following the style of original authors of\n> https://openjdk.org/jeps/409.  It doesn't matter for the purposes of the test,\n> but it will be less annoying to any future readers :-)\n\nI've looked through the patches and run the tests, and they all make\nsense to me. By just looking at the patch text I noted that no\nwhitespace between the identifier and the opening angle bracket is\npermitted and whether it should be allowed, but the commit messages make\nquite clear that whitespace is not allowed in this position. Hence:\n\nReviewed-by: Johannes Sixt <j6t@kdbg.org>\n\nThanks,\n-- Hannes\n\n"},{"id":"471537","messageId":"6ca6ebf0-b357-e1d0-4866-dd04a5f987ad@gmail.com","threadId":"59193","inReplyTo":"45830cf4-41c1-0bc1-3e4e-26b9f713f452@kdbg.org","subject":"Re: [PATCH v2 0/3] userdiff: Java updates","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-05T19:27:28Z","receivedAt":"2023-02-05T19:27:37Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"On 2023-02-05T11:09, Johannes Sixt wrote:\n> Am 04.02.23 um 14:43 schrieb Andrei Rybak:\n>> On 04/02/2023 10:22, Tassilo Horn wrote:\n>>> Thanks for including me being the last contributor to java userdiff.\n>>> The patches look good from my POV and are safe-guarded with tests, so\n>>> I'm all for it.\n>>\n>> Thank you for review!\n>>\n>> I've realized that I've been writing modifiers \"abstract\" and \"sealed\" in a\n>> technically correct, but not the conventional order.  Here's a reroll with the\n>> order of modifiers following the style of original authors of\n>> https://openjdk.org/jeps/409.  It doesn't matter for the purposes of the test,\n>> but it will be less annoying to any future readers :-)\n> \n> I've looked through the patches and run the tests, and they all make\n> sense to me. By just looking at the patch text I noted that no\n> whitespace between the identifier and the opening angle bracket is\n> permitted and whether it should be allowed, but the commit messages make\n> quite clear that whitespace is not allowed in this position.\n\nThere is some kind of misunderstanding.  I guess the wording in commit\nmessages of the first and second patches could have been clearer.\n\nIn Java, whitespace is allowed between type name and the brackets.\nIt is permitted both for angle brackets of type parameters:\n\n\tclass SpacesBeforeTypeParameters         <A, B> {\n\t}\n\nand for round brackets of components in records:\n\n\trecord SpacesBeforeComponents      (String comp1, int comp2) {\n\t}\n\nThe common convention, is however, to omit the whitespace before the\nbrackets.\n\nThe regular expression on branch master already allows for whitespace\nafter the name of the type:\n\n\t\"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+[A-Za-z][A-Za-z0-9_$]*[ \\t]+.*)$\\n\"\n\t                                                                          ^^^^^^\nso I didn't need to cover this case.  Note that it requires a non-zero\namount of whitespace. This part of the regular expression was left as\nis (v2 after patch 3/3):\n\n\t\"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|[<(]).*)$\\n\"\n\t                                                                                   ^^^^^^\n\n\nThat being said, I guess it would be an improvement to also allow\nthe name of the type be followed by the end of the line, for users\nwith fairly common code style that puts braces on separate lines:\n\n\tclass WithLineBreakBeforeOpeningBrace\n\t{\n\t}\n\nor `extends` and `implements` clauses after a line break:\n\n\tclass ExtendsOnSeparateLine\n\t\textends Number\n\t\timplements Serializable\n\t{\n\t}\n\neven type parameters:\n\n\tclass TypeParametersOnSeparateLine\n\t\t<A, B>\n\t{\n\t}\n\nSomething like the following:\n\n\t\"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t]+[A-Za-z][A-Za-z0-9_$]*(([ \\t]+|[<(]).*)?)$\\n\"\n\t                                                                                  ^               ^^\nperhaps? Technically, the following is also valid Java:\n\n\tclass WithComment//comment immediately after class name\n\t{\n\t}\n\nbut I'm not sure if allowing it is needed.  If so, we might as well just do this:\n\n\t\"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t]+[A-Za-z][A-Za-z0-9_$]*.*)$\\n\"\n\t                                                                                  ^^\n"},{"id":"471545","messageId":"64601c4b-9ced-672f-a5fd-9a9b3b65859d@kdbg.org","threadId":"59193","inReplyTo":"6ca6ebf0-b357-e1d0-4866-dd04a5f987ad@gmail.com","subject":"Re: [PATCH v2 0/3] userdiff: Java updates","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2023-02-05T21:33:24Z","receivedAt":"2023-02-05T21:33:33Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 05.02.23 um 20:27 schrieb Andrei Rybak:\n> On 2023-02-05T11:09, Johannes Sixt wrote:\n>> Am 04.02.23 um 14:43 schrieb Andrei Rybak:\n>>> On 04/02/2023 10:22, Tassilo Horn wrote:\n>>>> Thanks for including me being the last contributor to java userdiff.\n>>>> The patches look good from my POV and are safe-guarded with tests, so\n>>>> I'm all for it.\n>>>\n>>> Thank you for review!\n>>>\n>>> I've realized that I've been writing modifiers \"abstract\" and\n>>> \"sealed\" in a\n>>> technically correct, but not the conventional order.  Here's a reroll\n>>> with the\n>>> order of modifiers following the style of original authors of\n>>> https://openjdk.org/jeps/409.  It doesn't matter for the purposes of\n>>> the test,\n>>> but it will be less annoying to any future readers :-)\n>>\n>> I've looked through the patches and run the tests, and they all make\n>> sense to me. By just looking at the patch text I noted that no\n>> whitespace between the identifier and the opening angle bracket is\n>> permitted and whether it should be allowed, but the commit messages make\n>> quite clear that whitespace is not allowed in this position.\n> \n> There is some kind of misunderstanding.  I guess the wording in commit\n> messages of the first and second patches could have been clearer.\n> \n> In Java, whitespace is allowed between type name and the brackets.\n> It is permitted both for angle brackets of type parameters:\n> \n>     class SpacesBeforeTypeParameters         <A, B> {\n>     }\n> \n> and for round brackets of components in records:\n> \n>     record SpacesBeforeComponents      (String comp1, int comp2) {\n>     }\n> \n> The common convention, is however, to omit the whitespace before the\n> brackets.\n> \n> The regular expression on branch master already allows for whitespace\n> after the name of the type:\n> \n>     \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[\n> \\t]+[A-Za-z][A-Za-z0-9_$]*[ \\t]+.*)$\\n\"\n>                                                                               ^^^^^^\n> so I didn't need to cover this case.  Note that it requires a non-zero\n> amount of whitespace. This part of the regular expression was left as\n> is (v2 after patch 3/3):\n> \n>     \"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[\n> \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|[<(]).*)$\\n\"\n>                                                                                        ^^^^^^\n> \n> \n> That being said, I guess it would be an improvement to also allow\n> the name of the type be followed by the end of the line, for users\n> with fairly common code style that puts braces on separate lines:\n> \n>     class WithLineBreakBeforeOpeningBrace\n>     {\n>     }\n> \n> or `extends` and `implements` clauses after a line break:\n> \n>     class ExtendsOnSeparateLine\n>         extends Number\n>         implements Serializable\n>     {\n>     }\n> \n> even type parameters:\n> \n>     class TypeParametersOnSeparateLine\n>         <A, B>\n>     {\n>     }\n> \n> Something like the following:\n> \n>     \"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[\n> \\t]+[A-Za-z][A-Za-z0-9_$]*(([ \\t]+|[<(]).*)?)$\\n\"\n>                                                                                       ^               ^^\n> perhaps? Technically, the following is also valid Java:\n> \n>     class WithComment//comment immediately after class name\n>     {\n>     }\n> \n> but I'm not sure if allowing it is needed.  If so, we might as well just\n> do this:\n> \n>     \"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[\n> \\t]+[A-Za-z][A-Za-z0-9_$]*.*)$\\n\"\n>                                                                                       ^^\n\nHaving seen all these examples, I think the following truncated\nexpression might do the right thing for all cases that are valid Java:\n\n\"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t].*)$\"\n\ni.e., we recognize a whitespace in order to identify the keyword, and\nthen capture anything that follows without being specific. My reasoning\nis that \"class\", \"enum\", \"interface\", and \"record\" cannot occur in any\nother context than the beginning of a class definition. (But please do\ncorrect me; I know next to nothing about Java syntax.) As always,\nuserdiff regular expressions can assume that only valid constructs are\ninspected.\n\n-- Hannes\n\n"},{"id":"471738","messageId":"20230207234259.452141-1-rybak.a.v@gmail.com","threadId":"59193","inReplyTo":"64601c4b-9ced-672f-a5fd-9a9b3b65859d@kdbg.org","subject":"[PATCH v3 0/3] userdiff: Java updates","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-07T23:42:56Z","receivedAt":"2023-02-07T23:43:10Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"On 2023-02-05T22:33 Johannes Sixt wrote:\n> Having seen all these examples, I think the following truncated\n> expression might do the right thing for all cases that are valid Java:\n> \n> \"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t].*)$\"\n\nOnly the '\\n' is missing at the end, but otherwise I concur, so here's a v3.\n\n> i.e., we recognize a whitespace in order to identify the keyword, and\n> then capture anything that follows without being specific. My reasoning\n> is that \"class\", \"enum\", \"interface\", and \"record\" cannot occur in any\n> other context than the beginning of a class definition. (But please do\n> correct me; I know next to nothing about Java syntax.)\n\nThe word \"class\" can also occur as part of a class literal, for example:\n\n    Class<String> c = String.class;\n\nbut valid uses of class literals won't interfere with our regex, unless some\nwild formatting is applied.  This is technically valid Java:\n\n    Class<String> c = String.\n    class \n    ;\n\nand with a space after lowercase \"class\", the v3 regex will trip.  Class\nliterals are described in the JLS here:\nhttps://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.8.2\n\n> As always,\n> userdiff regular expressions can assume that only valid constructs are\n> inspected.\n\nChanges since v2:\n\n  - simplified regex that doesn't match class names at all and supports more\n    code styles\n  - updated the comment just above the regex in PATCH 2/3 to mention records\n  - more tests to cover the cases mentioned during review of v2\n  - reworded commit messages to reflect the above items\n\nRange diff since v2:\n\n1:  c300745a58 ! 1:  9e859e3b79 userdiff: support Java type parameters\n    @@ Metadata\n      ## Commit message ##\n         userdiff: support Java type parameters\n     \n    -    A class or interface in Java [1] can have type parameters immediately\n    -    following the name in the declaration, surrounded by angle brackets\n    -    (paired less than and greater than signs).[2]  Example of a class with\n    -    type parameters \"A\" and \"N\":\n    -\n    -        public class ParameterizedClass<A, N> {\n    -            private A field1;\n    -            private N field2;\n    +    A class or interface in Java can have type parameters following the name\n    +    in the declared type, surrounded by angle brackets (paired less than and\n    +    greater than signs).[2]   The type parameters -- `A` and `B` in the\n    +    examples -- may follow the class name immediately:\n    +\n    +        public class ParameterizedClass<A, B> {\n             }\n     \n    -    Support matching a parameterized class or interface declaration with\n    -    type parameters immediately following the name of the type in the\n    -    builtin userdiff pattern for Java.  Do so by just allowing matching the\n    -    first character after the name of the type to \"<\".\n    +    or may be separated by whitespace:\n    +\n    +        public class SpaceBeforeTypeParameters <A, B> {\n    +        }\n     \n    -    An alternative approach could be to match both the opening and the\n    -    closing angle brackets and matching the content between them in various\n    -    ways.  Just use the simpler regex for now.\n    +    A part of the builtin userdiff pattern for Java matches declarations of\n    +    classes, enums, and interfaces.  The regular expression requires at\n    +    least one whitespace character after the name of the declared type.\n    +    This disallows matching for opening angle bracket of type parameters\n    +    immediately after the name of the type.  Mandatory whitespace after the\n    +    name of the type also disallows using the pattern in repositories with a\n    +    fairly common code style that puts braces for the body of a class on\n    +    separate lines:\n    +\n    +        class WithLineBreakBeforeOpeningBrace\n    +        {\n    +        }\n    +\n    +    Support matching Java code in more diverse code styles and declarations\n    +    of classes and interfaces with type parameters immediately following the\n    +    name of the type in the builtin userdiff pattern for Java.  Do so by\n    +    just matching anything until the end of the line after the keywords for\n    +    the kind of type being declared.\n     \n         [1] Since Java 5 released in 2004.\n         [2] Detailed description is available in the Java Language\n    @@ Commit message\n     \n         Signed-off-by: Andrei Rybak <rybak.a.v@gmail.com>\n     \n    + ## t/t4018/java-class-brace-on-separate-line (new) ##\n    +@@\n    ++class RIGHT\n    ++{\n    ++    static int ONE;\n    ++    static int TWO;\n    ++    static int ChangeMe;\n    ++}\n    +\n    + ## t/t4018/java-class-space-before-type-parameters (new) ##\n    +@@\n    ++class RIGHT <TYPE, PARAMS, AFTER, SPACE> {\n    ++    static int ONE;\n    ++    static int TWO;\n    ++    static int THREE;\n    ++    private A ChangeMe;\n    ++}\n    +\n      ## t/t4018/java-class-type-parameters (new) ##\n     @@\n     +class RIGHT<A, B> {\n    @@ userdiff.c: PATTERNS(\"html\",\n      \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n      \t /* Class, enum, and interface declarations */\n     -\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+[A-Za-z][A-Za-z0-9_$]*[ \\t]+.*)$\\n\"\n    -+\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|<).*)$\\n\"\n    ++\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+.*)$\\n\"\n      \t /* Method definitions; note that constructor signatures are not */\n      \t /* matched because they are indistinguishable from method calls. */\n      \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n2:  a0e622a0f8 ! 2:  4f7be5f642 userdiff: support Java record types\n    @@ Commit message\n         the name of the record class is followed by a mandatory list of\n         components.  The list is enclosed in parentheses, it may be empty, and\n         it may immediately follow the name of the class or type parameters, if\n    -    any, without separating whitespace.\n    -\n    -    Code examples:\n    +    any, with or without separating whitespace.  For example:\n     \n             public record Example(int i, String s) {\n             }\n    @@ Commit message\n             public record WithTypeParameters<A, B>(A a, B b, String s) {\n             }\n     \n    +        record SpaceBeforeComponents (String comp1, int comp2) {\n    +        }\n    +\n         Support records in the builtin userdiff pattern for Java.  Add \"record\"\n    -    to the alternatives of keywords for kinds of class, and match an opening\n    -    parenthesis as the first character right after the type name.\n    +    to the alternatives of keywords for kinds of class.\n     \n    -    An alternative approach could be to have an optional group that would\n    -    match both the opening and the closing parentheses with some way of\n    -    matching the declarations of the components.  Just use the simpler\n    -    regular expression for now.\n    +    Allowing matching various possibilities for the type parameters and/or\n    +    list of the components of a record has already been covered by the\n    +    preceding patch.\n     \n         [1] detailed description is available in \"JEP 395: Records\"\n             https://openjdk.org/jeps/395\n    @@ t/t4018/java-record (new)\n     +    static int TWO;\n     +    static int THREE;\n     +    static int ChangeMe;\n    ++}\n    +\n    + ## t/t4018/java-record-space-before-components (new) ##\n    +@@\n    ++public record RIGHT (String components, String after, String space) {\n    ++    static int ONE;\n    ++    static int TWO;\n    ++    static int THREE;\n    ++    static int ChangeMe;\n     +}\n     \n      ## t/t4018/java-record-type-parameters (new) ##\n    @@ t/t4018/java-record-type-parameters (new)\n     \n      ## userdiff.c ##\n     @@ userdiff.c: PATTERNS(\"html\",\n    + \t \"[^<>= \\t]+\"),\n      PATTERNS(\"java\",\n      \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n    - \t /* Class, enum, and interface declarations */\n    --\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|<).*)$\\n\"\n    -+\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface|record)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|[<(]).*)$\\n\"\n    +-\t /* Class, enum, and interface declarations */\n    +-\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+.*)$\\n\"\n    ++\t /* Class, enum, interface, and record declarations */\n    ++\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface|record)[ \\t]+.*)$\\n\"\n      \t /* Method definitions; note that constructor signatures are not */\n      \t /* matched because they are indistinguishable from method calls. */\n      \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n3:  b9c6a5dffd ! 3:  ea6ce671ef userdiff: support Java sealed classes\n    @@ userdiff.c\n     @@ userdiff.c: PATTERNS(\"html\",\n      PATTERNS(\"java\",\n      \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n    - \t /* Class, enum, and interface declarations */\n    --\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface|record)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|[<(]).*)$\\n\"\n    -+\t \"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t]+[A-Za-z][A-Za-z0-9_$]*([ \\t]+|[<(]).*)$\\n\"\n    + \t /* Class, enum, interface, and record declarations */\n    +-\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface|record)[ \\t]+.*)$\\n\"\n    ++\t \"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t]+.*)$\\n\"\n      \t /* Method definitions; note that constructor signatures are not */\n      \t /* matched because they are indistinguishable from method calls. */\n      \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n\nAndrei Rybak (3):\n  userdiff: support Java type parameters\n  userdiff: support Java record types\n  userdiff: support Java sealed classes\n\n t/t4018/java-class-brace-on-separate-line              | 6 ++++++\n t/t4018/java-class-space-before-type-parameters        | 6 ++++++\n t/t4018/java-class-type-parameters                     | 6 ++++++\n t/t4018/java-class-type-parameters-implements          | 6 ++++++\n t/t4018/java-interface-type-parameters                 | 6 ++++++\n t/t4018/java-interface-type-parameters-extends         | 6 ++++++\n t/t4018/java-non-sealed                                | 8 ++++++++\n t/t4018/java-record                                    | 6 ++++++\n t/t4018/java-record-space-before-components            | 6 ++++++\n t/t4018/java-record-type-parameters                    | 6 ++++++\n t/t4018/java-sealed                                    | 7 +++++++\n t/t4018/java-sealed-permits                            | 6 ++++++\n t/t4018/java-sealed-type-parameters                    | 6 ++++++\n t/t4018/java-sealed-type-parameters-implements-permits | 6 ++++++\n t/t4018/java-sealed-type-parameters-permits            | 6 ++++++\n userdiff.c                                             | 4 ++--\n 16 files changed, 95 insertions(+), 2 deletions(-)\n create mode 100644 t/t4018/java-class-brace-on-separate-line\n create mode 100644 t/t4018/java-class-space-before-type-parameters\n create mode 100644 t/t4018/java-class-type-parameters\n create mode 100644 t/t4018/java-class-type-parameters-implements\n create mode 100644 t/t4018/java-interface-type-parameters\n create mode 100644 t/t4018/java-interface-type-parameters-extends\n create mode 100644 t/t4018/java-non-sealed\n create mode 100644 t/t4018/java-record\n create mode 100644 t/t4018/java-record-space-before-components\n create mode 100644 t/t4018/java-record-type-parameters\n create mode 100644 t/t4018/java-sealed\n create mode 100644 t/t4018/java-sealed-permits\n create mode 100644 t/t4018/java-sealed-type-parameters\n create mode 100644 t/t4018/java-sealed-type-parameters-implements-permits\n create mode 100644 t/t4018/java-sealed-type-parameters-permits\n\n-- \n2.39.1\n\n"},{"id":"471739","messageId":"20230207234259.452141-2-rybak.a.v@gmail.com","threadId":"59193","inReplyTo":"20230207234259.452141-1-rybak.a.v@gmail.com","subject":"[PATCH v3 1/3] userdiff: support Java type parameters","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-07T23:42:57Z","receivedAt":"2023-02-07T23:43:12Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"A class or interface in Java can have type parameters following the name\nin the declared type, surrounded by angle brackets (paired less than and\ngreater than signs).[2]   The type parameters -- `A` and `B` in the\nexamples -- may follow the class name immediately:\n\n    public class ParameterizedClass<A, B> {\n    }\n\nor may be separated by whitespace:\n\n    public class SpaceBeforeTypeParameters <A, B> {\n    }\n\nA part of the builtin userdiff pattern for Java matches declarations of\nclasses, enums, and interfaces.  The regular expression requires at\nleast one whitespace character after the name of the declared type.\nThis disallows matching for opening angle bracket of type parameters\nimmediately after the name of the type.  Mandatory whitespace after the\nname of the type also disallows using the pattern in repositories with a\nfairly common code style that puts braces for the body of a class on\nseparate lines:\n\n    class WithLineBreakBeforeOpeningBrace\n    {\n    }\n\nSupport matching Java code in more diverse code styles and declarations\nof classes and interfaces with type parameters immediately following the\nname of the type in the builtin userdiff pattern for Java.  Do so by\njust matching anything until the end of the line after the keywords for\nthe kind of type being declared.\n\n[1] Since Java 5 released in 2004.\n[2] Detailed description is available in the Java Language\n    Specification, sections \"Type Variables\" and \"Parameterized Types\":\n    https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.4\n\nSigned-off-by: Andrei Rybak <rybak.a.v@gmail.com>\n---\n t/t4018/java-class-brace-on-separate-line       | 6 ++++++\n t/t4018/java-class-space-before-type-parameters | 6 ++++++\n t/t4018/java-class-type-parameters              | 6 ++++++\n t/t4018/java-class-type-parameters-implements   | 6 ++++++\n t/t4018/java-interface-type-parameters          | 6 ++++++\n t/t4018/java-interface-type-parameters-extends  | 6 ++++++\n userdiff.c                                      | 2 +-\n 7 files changed, 37 insertions(+), 1 deletion(-)\n create mode 100644 t/t4018/java-class-brace-on-separate-line\n create mode 100644 t/t4018/java-class-space-before-type-parameters\n create mode 100644 t/t4018/java-class-type-parameters\n create mode 100644 t/t4018/java-class-type-parameters-implements\n create mode 100644 t/t4018/java-interface-type-parameters\n create mode 100644 t/t4018/java-interface-type-parameters-extends\n\ndiff --git a/t/t4018/java-class-brace-on-separate-line b/t/t4018/java-class-brace-on-separate-line\nnew file mode 100644\nindex 0000000000..8795acd4cf\n--- /dev/null\n+++ b/t/t4018/java-class-brace-on-separate-line\n@@ -0,0 +1,6 @@\n+class RIGHT\n+{\n+    static int ONE;\n+    static int TWO;\n+    static int ChangeMe;\n+}\ndiff --git a/t/t4018/java-class-space-before-type-parameters b/t/t4018/java-class-space-before-type-parameters\nnew file mode 100644\nindex 0000000000..0bdef1dfbe\n--- /dev/null\n+++ b/t/t4018/java-class-space-before-type-parameters\n@@ -0,0 +1,6 @@\n+class RIGHT <TYPE, PARAMS, AFTER, SPACE> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private A ChangeMe;\n+}\ndiff --git a/t/t4018/java-class-type-parameters b/t/t4018/java-class-type-parameters\nnew file mode 100644\nindex 0000000000..579aa7af21\n--- /dev/null\n+++ b/t/t4018/java-class-type-parameters\n@@ -0,0 +1,6 @@\n+class RIGHT<A, B> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private A ChangeMe;\n+}\ndiff --git a/t/t4018/java-class-type-parameters-implements b/t/t4018/java-class-type-parameters-implements\nnew file mode 100644\nindex 0000000000..b8038b1866\n--- /dev/null\n+++ b/t/t4018/java-class-type-parameters-implements\n@@ -0,0 +1,6 @@\n+class RIGHT<A, B> implements List<A> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private A ChangeMe;\n+}\ndiff --git a/t/t4018/java-interface-type-parameters b/t/t4018/java-interface-type-parameters\nnew file mode 100644\nindex 0000000000..a4baa1ae68\n--- /dev/null\n+++ b/t/t4018/java-interface-type-parameters\n@@ -0,0 +1,6 @@\n+interface RIGHT<A, B> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    public B foo(A ChangeMe);\n+}\ndiff --git a/t/t4018/java-interface-type-parameters-extends b/t/t4018/java-interface-type-parameters-extends\nnew file mode 100644\nindex 0000000000..31d7fb3244\n--- /dev/null\n+++ b/t/t4018/java-interface-type-parameters-extends\n@@ -0,0 +1,6 @@\n+interface RIGHT<A, B> extends Function<A, B> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    public B foo(A ChangeMe);\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex d71b82feb7..bc5f3ed4c3 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -171,7 +171,7 @@ PATTERNS(\"html\",\n PATTERNS(\"java\",\n \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n \t /* Class, enum, and interface declarations */\n-\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+[A-Za-z][A-Za-z0-9_$]*[ \\t]+.*)$\\n\"\n+\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+.*)$\\n\"\n \t /* Method definitions; note that constructor signatures are not */\n \t /* matched because they are indistinguishable from method calls. */\n \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n-- \n2.39.1\n\n"},{"id":"471740","messageId":"20230207234259.452141-3-rybak.a.v@gmail.com","threadId":"59193","inReplyTo":"20230207234259.452141-1-rybak.a.v@gmail.com","subject":"[PATCH v3 2/3] userdiff: support Java record types","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-07T23:42:58Z","receivedAt":"2023-02-07T23:43:13Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"A new kind of class was added in Java 16 -- records.[1]  The syntax of\nrecords is similar to regular classes with one important distinction:\nthe name of the record class is followed by a mandatory list of\ncomponents.  The list is enclosed in parentheses, it may be empty, and\nit may immediately follow the name of the class or type parameters, if\nany, with or without separating whitespace.  For example:\n\n    public record Example(int i, String s) {\n    }\n\n    public record WithTypeParameters<A, B>(A a, B b, String s) {\n    }\n\n    record SpaceBeforeComponents (String comp1, int comp2) {\n    }\n\nSupport records in the builtin userdiff pattern for Java.  Add \"record\"\nto the alternatives of keywords for kinds of class.\n\nAllowing matching various possibilities for the type parameters and/or\nlist of the components of a record has already been covered by the\npreceding patch.\n\n[1] detailed description is available in \"JEP 395: Records\"\n    https://openjdk.org/jeps/395\n\nSigned-off-by: Andrei Rybak <rybak.a.v@gmail.com>\n---\n t/t4018/java-record                         | 6 ++++++\n t/t4018/java-record-space-before-components | 6 ++++++\n t/t4018/java-record-type-parameters         | 6 ++++++\n userdiff.c                                  | 4 ++--\n 4 files changed, 20 insertions(+), 2 deletions(-)\n create mode 100644 t/t4018/java-record\n create mode 100644 t/t4018/java-record-space-before-components\n create mode 100644 t/t4018/java-record-type-parameters\n\ndiff --git a/t/t4018/java-record b/t/t4018/java-record\nnew file mode 100644\nindex 0000000000..97aa819dd8\n--- /dev/null\n+++ b/t/t4018/java-record\n@@ -0,0 +1,6 @@\n+public record RIGHT(int comp1, double comp2, String comp3) {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    static int ChangeMe;\n+}\ndiff --git a/t/t4018/java-record-space-before-components b/t/t4018/java-record-space-before-components\nnew file mode 100644\nindex 0000000000..9827f22583\n--- /dev/null\n+++ b/t/t4018/java-record-space-before-components\n@@ -0,0 +1,6 @@\n+public record RIGHT (String components, String after, String space) {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    static int ChangeMe;\n+}\ndiff --git a/t/t4018/java-record-type-parameters b/t/t4018/java-record-type-parameters\nnew file mode 100644\nindex 0000000000..f62a035cc8\n--- /dev/null\n+++ b/t/t4018/java-record-type-parameters\n@@ -0,0 +1,6 @@\n+public record RIGHT<A, N extends Number>(A comp1, N comp2, int comp3) {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    static int ChangeMe;\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex bc5f3ed4c3..37ac98e177 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -170,8 +170,8 @@ PATTERNS(\"html\",\n \t \"[^<>= \\t]+\"),\n PATTERNS(\"java\",\n \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n-\t /* Class, enum, and interface declarations */\n-\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+.*)$\\n\"\n+\t /* Class, enum, interface, and record declarations */\n+\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface|record)[ \\t]+.*)$\\n\"\n \t /* Method definitions; note that constructor signatures are not */\n \t /* matched because they are indistinguishable from method calls. */\n \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n-- \n2.39.1\n\n"},{"id":"471741","messageId":"20230207234259.452141-4-rybak.a.v@gmail.com","threadId":"59193","inReplyTo":"20230207234259.452141-1-rybak.a.v@gmail.com","subject":"[PATCH v3 3/3] userdiff: support Java sealed classes","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-07T23:42:59Z","receivedAt":"2023-02-07T23:43:23Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"A new kind of class was added in Java 17 -- sealed classes.[1]  This\nfeature includes several new keywords that may appear in a declaration\nof a class.  New modifiers before name of the class: \"sealed\" and\n\"non-sealed\", and a clause after name of the class marked by keyword\n\"permits\".\n\nThe current set of regular expressions in userdiff.c already allows the\nmodifier \"sealed\" and the \"permits\" clause, but not the modifier\n\"non-sealed\", which is the first hyphenated keyword in Java.[2]  Allow\nhyphen in the words that precede the name of type to match the\n\"non-sealed\" modifier.\n\nIn new input file \"java-sealed\" for the test t4018-diff-funcname.sh, use\na Java code comment for the marker \"RIGHT\".  This workaround is needed,\nbecause the name of the sealed class appears on the line of code that\nhas the \"ChangeMe\" marker.\n\n[1] Detailed description in \"JEP 409: Sealed Classes\"\n    https://openjdk.org/jeps/409\n[2] \"JEP draft: Keyword Management for the Java Language\"\n    https://openjdk.org/jeps/8223002\n\nSigned-off-by: Andrei Rybak <rybak.a.v@gmail.com>\n---\n t/t4018/java-non-sealed                                | 8 ++++++++\n t/t4018/java-sealed                                    | 7 +++++++\n t/t4018/java-sealed-permits                            | 6 ++++++\n t/t4018/java-sealed-type-parameters                    | 6 ++++++\n t/t4018/java-sealed-type-parameters-implements-permits | 6 ++++++\n t/t4018/java-sealed-type-parameters-permits            | 6 ++++++\n userdiff.c                                             | 2 +-\n 7 files changed, 40 insertions(+), 1 deletion(-)\n create mode 100644 t/t4018/java-non-sealed\n create mode 100644 t/t4018/java-sealed\n create mode 100644 t/t4018/java-sealed-permits\n create mode 100644 t/t4018/java-sealed-type-parameters\n create mode 100644 t/t4018/java-sealed-type-parameters-implements-permits\n create mode 100644 t/t4018/java-sealed-type-parameters-permits\n\ndiff --git a/t/t4018/java-non-sealed b/t/t4018/java-non-sealed\nnew file mode 100644\nindex 0000000000..069087c1c6\n--- /dev/null\n+++ b/t/t4018/java-non-sealed\n@@ -0,0 +1,8 @@\n+public abstract sealed class SealedClass {\n+    public static non-sealed class RIGHT extends SealedClass {\n+        static int ONE;\n+        static int TWO;\n+        static int THREE;\n+        private int ChangeMe;\n+    }\n+}\ndiff --git a/t/t4018/java-sealed b/t/t4018/java-sealed\nnew file mode 100644\nindex 0000000000..785fbc62bc\n--- /dev/null\n+++ b/t/t4018/java-sealed\n@@ -0,0 +1,7 @@\n+public abstract sealed class Sealed { // RIGHT\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    public final class ChangeMe extends Sealed {\n+    }\n+}\ndiff --git a/t/t4018/java-sealed-permits b/t/t4018/java-sealed-permits\nnew file mode 100644\nindex 0000000000..18dd4894cf\n--- /dev/null\n+++ b/t/t4018/java-sealed-permits\n@@ -0,0 +1,6 @@\n+public abstract sealed class RIGHT permits PermittedA, PermittedB {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private int ChangeMe;\n+}\ndiff --git a/t/t4018/java-sealed-type-parameters b/t/t4018/java-sealed-type-parameters\nnew file mode 100644\nindex 0000000000..e6530c47c3\n--- /dev/null\n+++ b/t/t4018/java-sealed-type-parameters\n@@ -0,0 +1,6 @@\n+public abstract sealed class RIGHT<A, B> {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private int ChangeMe;\n+}\ndiff --git a/t/t4018/java-sealed-type-parameters-implements-permits b/t/t4018/java-sealed-type-parameters-implements-permits\nnew file mode 100644\nindex 0000000000..bd6e6d3582\n--- /dev/null\n+++ b/t/t4018/java-sealed-type-parameters-implements-permits\n@@ -0,0 +1,6 @@\n+public abstract sealed class RIGHT<A, B> implements List<A> permits PermittedA, PermittedB {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private int ChangeMe;\n+}\ndiff --git a/t/t4018/java-sealed-type-parameters-permits b/t/t4018/java-sealed-type-parameters-permits\nnew file mode 100644\nindex 0000000000..25a0da6442\n--- /dev/null\n+++ b/t/t4018/java-sealed-type-parameters-permits\n@@ -0,0 +1,6 @@\n+public abstract sealed class RIGHT<A, B> permits PermittedA, PermittedB {\n+    static int ONE;\n+    static int TWO;\n+    static int THREE;\n+    private int ChangeMe;\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex 37ac98e177..94cca1a2a8 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -171,7 +171,7 @@ PATTERNS(\"html\",\n PATTERNS(\"java\",\n \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n \t /* Class, enum, interface, and record declarations */\n-\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface|record)[ \\t]+.*)$\\n\"\n+\t \"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t]+.*)$\\n\"\n \t /* Method definitions; note that constructor signatures are not */\n \t /* matched because they are indistinguishable from method calls. */\n \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n-- \n2.39.1\n\n"},{"id":"471743","messageId":"152251b7-79a1-9ea9-01de-61a68b729196@gmail.com","threadId":"59193","inReplyTo":"20230207234259.452141-2-rybak.a.v@gmail.com","subject":"Re: [PATCH v3 1/3] userdiff: support Java type parameters","fromName":"Andrei Rybak","fromEmail":"rybak.a.v@gmail.com","sentAt":"2023-02-08T00:04:26Z","receivedAt":"2023-02-08T00:04:36Z","isPatch":true,"sender":{"key":"rybak.a.v@gmail.com","avatar":"https://avatars.githubusercontent.com/u/624072?v=4"},"body":"On 2023-02-08T00:42, Andrei Rybak wrote:\n> A class or interface in Java can have type parameters following the name\n> in the declared type, surrounded by angle brackets (paired less than and\n> greater than signs).[2]   The type parameters -- `A` and `B` in the\n> examples -- may follow the class name immediately:\n> \n>      public class ParameterizedClass<A, B> {\n>      }\n> \n> or may be separated by whitespace:\n> \n>      public class SpaceBeforeTypeParameters <A, B> {\n>      }\n> \n> A part of the builtin userdiff pattern for Java matches declarations of\n> classes, enums, and interfaces.  The regular expression requires at\n> least one whitespace character after the name of the declared type.\n> This disallows matching for opening angle bracket of type parameters\n> immediately after the name of the type.  Mandatory whitespace after the\n> name of the type also disallows using the pattern in repositories with a\n> fairly common code style that puts braces for the body of a class on\n> separate lines:\n> \n>      class WithLineBreakBeforeOpeningBrace\n>      {\n>      }\n> \n> Support matching Java code in more diverse code styles and declarations\n> of classes and interfaces with type parameters immediately following the\n> name of the type in the builtin userdiff pattern for Java.  Do so by\n> just matching anything until the end of the line after the keywords for\n> the kind of type being declared.\n\nThe above explains why removing the mandatory matching for whitespace\nafter the class name is needed, but it doesn't explain why removing\nthe part of the regex that matches the class name itself is OK.\nPerhaps, something like this could be added:\n\n     An possible approach could be to keep matching the name of the\n     type: \"...[ \\t]+[A-Za-z][A-Za-z0-9_$]*.*)$\\n\", but without matching\n     mandatory whitespace after the name of the type, matching the name\n     itself separately isn't useful for our purposes.\n\n?\n\n> [1] Since Java 5 released in 2004.\n> [2] Detailed description is available in the Java Language\n>      Specification, sections \"Type Variables\" and \"Parameterized Types\":\n>      https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.4\n> \n> Signed-off-by: Andrei Rybak <rybak.a.v@gmail.com>\n> ---\n\n[...]\n\n> diff --git a/userdiff.c b/userdiff.c\n> index d71b82feb7..bc5f3ed4c3 100644\n> --- a/userdiff.c\n> +++ b/userdiff.c\n> @@ -171,7 +171,7 @@ PATTERNS(\"html\",\n>   PATTERNS(\"java\",\n>   \t \"!^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\\n\"\n>   \t /* Class, enum, and interface declarations */\n> -\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+[A-Za-z][A-Za-z0-9_$]*[ \\t]+.*)$\\n\"\n> +\t \"^[ \\t]*(([a-z]+[ \\t]+)*(class|enum|interface)[ \\t]+.*)$\\n\"\n>   \t /* Method definitions; note that constructor signatures are not */\n>   \t /* matched because they are indistinguishable from method calls. */\n>   \t \"^[ \\t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_][A-Za-z_0-9]*[ \\t]*\\\\([^;]*)$\",\n"},{"id":"471796","messageId":"e16e4190-9db0-1519-0114-858118882d5d@kdbg.org","threadId":"59193","inReplyTo":"20230207234259.452141-1-rybak.a.v@gmail.com","subject":"Re: [PATCH v3 0/3] userdiff: Java updates","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2023-02-08T20:51:01Z","receivedAt":"2023-02-08T20:51:11Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 08.02.23 um 00:42 schrieb Andrei Rybak:\n> On 2023-02-05T22:33 Johannes Sixt wrote:\n>> Having seen all these examples, I think the following truncated\n>> expression might do the right thing for all cases that are valid Java:\n>>\n>> \"^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t].*)$\"\n> \n> Only the '\\n' is missing at the end, but otherwise I concur, so here's a v3.\n> \n>> i.e., we recognize a whitespace in order to identify the keyword, and\n>> then capture anything that follows without being specific. My reasoning\n>> is that \"class\", \"enum\", \"interface\", and \"record\" cannot occur in any\n>> other context than the beginning of a class definition. (But please do\n>> correct me; I know next to nothing about Java syntax.)\n> \n> The word \"class\" can also occur as part of a class literal, for example:\n> \n>     Class<String> c = String.class;\n> \n> but valid uses of class literals won't interfere with our regex, unless some\n> wild formatting is applied.  This is technically valid Java:\n> \n>     Class<String> c = String.\n>     class \n>     ;\n> \n> and with a space after lowercase \"class\", the v3 regex will trip.\n\nYeah, let's assume that nobody writes code like this.\n\nThis iteration is all good!\n\nReviewed-by: Johannes Sixt <j6t@kdbg.org>\n\n-- Hannes\n\n"},{"id":"471797","messageId":"xmqqedqzri66.fsf@gitster.g","threadId":"59193","inReplyTo":"e16e4190-9db0-1519-0114-858118882d5d@kdbg.org","subject":"Re: [PATCH v3 0/3] userdiff: Java updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-02-08T20:55:29Z","receivedAt":"2023-02-08T20:55:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <j6t@kdbg.org> writes:\n\n> Yeah, let's assume that nobody writes code like this.\n>\n> This iteration is all good!\n>\n> Reviewed-by: Johannes Sixt <j6t@kdbg.org>\n\nThanks.  I think I've queued this round already, but let me amend\nyour Reviewed-by into them.\n\n"}]}