{"thread":{"id":"60993","subject":"[PATCH] userdiff: better method/property matching for C#","startedAt":"2024-02-25T17:33:47Z","lastAt":"2024-04-05T22:10:10Z","messageCount":14,"participants":["Steven Jeuris via GitGitGadget","Junio C Hamano","Linus Arver","Johannes Sixt","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"489318","messageId":"pull.1682.git.git.1708882423691.gitgitgadget@gmail.com","threadId":"60993","inReplyTo":null,"subject":"[PATCH] userdiff: better method/property matching for C#","fromName":"Steven Jeuris via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-02-25T17:33:43Z","receivedAt":"2024-02-25T17:33:47Z","isPatch":true,"sender":{"key":"name:Steven Jeuris","avatar":null},"body":"From: Steven Jeuris <steven.jeuris@3shape.com>\n\n- Support multi-line methods by not requiring closing parenthesis.\n- Support multiple generics (comma was missing before).\n- Add missing `foreach`, `from`, `lock` and  `fixed` keywords to skip over.\n- Remove `instanceof` keyword, which isn't C#.\n- Also detect non-method keywords not positioned at the start of a line.\n- Added tests; none existed before.\n\nThe overall strategy is to focus more on what isn't expected for\nmethod/property definitions, instead of what is, but is fully optional.\n\nSigned-off-by: Steven Jeuris <steven.jeuris@gmail.com>\n---\n    userdiff: better method/property matching for C#\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1682%2FWhathecode%2Fmaster-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1682/Whathecode/master-v1\nPull-Request: https://github.com/git/git/pull/1682\n\n t/t4018/csharp-method               |  10 +++\n t/t4018/csharp-method-explicit      |  12 +++\n t/t4018/csharp-method-generics      |  11 +++\n t/t4018/csharp-method-modifiers     |  13 ++++\n t/t4018/csharp-method-multiline     |  10 +++\n t/t4018/csharp-method-params        |  10 +++\n t/t4018/csharp-method-skip-body     | 115 ++++++++++++++++++++++++++++\n t/t4018/csharp-method-special-chars |  11 +++\n t/t4018/csharp-method-with-spacing  |  10 +++\n t/t4018/csharp-property             |  11 +++\n userdiff.c                          |  16 ++--\n 11 files changed, 224 insertions(+), 5 deletions(-)\n create mode 100644 t/t4018/csharp-method\n create mode 100644 t/t4018/csharp-method-explicit\n create mode 100644 t/t4018/csharp-method-generics\n create mode 100644 t/t4018/csharp-method-modifiers\n create mode 100644 t/t4018/csharp-method-multiline\n create mode 100644 t/t4018/csharp-method-params\n create mode 100644 t/t4018/csharp-method-skip-body\n create mode 100644 t/t4018/csharp-method-special-chars\n create mode 100644 t/t4018/csharp-method-with-spacing\n create mode 100644 t/t4018/csharp-property\n\ndiff --git a/t/t4018/csharp-method b/t/t4018/csharp-method\nnew file mode 100644\nindex 00000000000..85ff0cb8b5b\n--- /dev/null\n+++ b/t/t4018/csharp-method\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+\tstring Method(int RIGHT)\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\treturn \"ChangeMe\";\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-explicit b/t/t4018/csharp-method-explicit\nnew file mode 100644\nindex 00000000000..083aa094ce2\n--- /dev/null\n+++ b/t/t4018/csharp-method-explicit\n@@ -0,0 +1,12 @@\n+using System;\n+\n+class Example : IDisposable\n+{\n+\tvoid IDisposable.Dispose() // RIGHT\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\t// ChangeMe\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-generics b/t/t4018/csharp-method-generics\nnew file mode 100644\nindex 00000000000..c472d4a18df\n--- /dev/null\n+++ b/t/t4018/csharp-method-generics\n@@ -0,0 +1,11 @@\n+class Example<T1, T2>\n+{\n+\tExample<int, string> Method<TA, TB>(TA RIGHT, TB b)\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\t// ChangeMe\n+\t\treturn null;\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-modifiers b/t/t4018/csharp-method-modifiers\nnew file mode 100644\nindex 00000000000..f1c008a4749\n--- /dev/null\n+++ b/t/t4018/csharp-method-modifiers\n@@ -0,0 +1,13 @@\n+using System.Threading.Tasks;\n+\n+class Example\n+{\n+\tstatic internal async Task Method(int RIGHT)\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\t// ChangeMe\n+\t\tawait Task.Delay(1);\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-multiline b/t/t4018/csharp-method-multiline\nnew file mode 100644\nindex 00000000000..0a20b0cb49c\n--- /dev/null\n+++ b/t/t4018/csharp-method-multiline\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+\tstring Method_RIGHT(\n+\t\tint a,\n+\t\tint b,\n+\t\tint c)\n+\t{\n+\t\treturn \"ChangeMe\";\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-params b/t/t4018/csharp-method-params\nnew file mode 100644\nindex 00000000000..18598449008\n--- /dev/null\n+++ b/t/t4018/csharp-method-params\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+\tstring Method(int RIGHT, int b, int c = 42)\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\treturn \"ChangeMe\";\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-skip-body b/t/t4018/csharp-method-skip-body\nnew file mode 100644\nindex 00000000000..95e93ea995c\n--- /dev/null\n+++ b/t/t4018/csharp-method-skip-body\n@@ -0,0 +1,115 @@\n+using System.Linq;\n+using System;\n+\n+class Example : IDisposable\n+{\n+\tstring Method(int RIGHT)\n+\t{\n+\t\t// Method calls\n+\t\tMethodCall();\n+\t\tMethodCall(1, 2);\n+\t\tMethodCall(\n+\t\t\t1, 2);\n+\t\t\n+\t\t// Assignments\n+\t\tvar constantAssignment = \"test\";\n+\t\tvar methodAssignment = MethodCall();\n+\t\tvar multiLineMethodAssignment = MethodCall(\n+\t\t\t);\n+\t\t\n+\t\t// Initializations/disposal\n+\t\tnew Example();\n+\t\tnew Example(\n+\t\t\t);\n+\t\tnew Example { };\n+\t\tusing (this) \n+\t\t{\n+\t\t}\n+\t\tvar def =\n+\t\t\tthis is default(\n+\t\t\t\tExample);\n+\t\t\n+\t\t// Iteration statements\n+\t\tdo { } while (true);\n+\t\tdo MethodCall(\n+\t\t\t); while (true);\n+\t\twhile (true);\n+\t\twhile (true) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tfor (int i = 0; i < 10; ++i)\n+\t\t{\n+\t\t}\n+\t\tforeach (int i in Enumerable.Range(0, 10))\n+\t\t{\n+\t\t}\n+\t\tint[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n+\t\tvar test =\n+\t\t\tfrom num in Numbers(\n+\t\t \t) select num;\n+\t\t\n+\t\t// Control\n+\t\tif (false)\n+\t\t{\n+\t\t\treturn \"out\";\n+\t\t}\n+\t\telse { }\n+\t\tif (true) MethodCall(\n+\t\t\t);\n+\t\telse MethodCall(\n+\t\t\t);\n+\t\tswitch (\"test\")\n+\t\t{\n+\t\t\tcase \"one\":\n+\t\t\t\treturn MethodCall(\n+\t\t\t\t\t);\n+\t\t\tcase \"two\":\n+\t\t\t\tbreak;\n+\t\t}\n+\t\t(int, int) tuple = (1, 4);\n+\t\tswitch (tuple)\n+\t\t{\n+\t\t\tcase (1, 4):\n+\t\t\t\tMethodCall();\n+\t\t}\n+\t\t\n+\t\t// Exceptions\n+\t\ttry\n+\t\t{\n+\t\t\tthrow new Exception(\"fail\");\n+\t\t}\n+\t\tcatch (Exception)\n+\t\t{\n+\t\t}\n+\t\tfinally\n+\t\t{\n+\t\t}\n+\t\ttry { } catch (Exception) {}\n+\t\ttry\n+\t\t{\n+\t\t\tthrow GetException(\n+\t\t\t\t);\n+\t\t}\n+\t\tcatch (Exception) { }\n+\t\t\n+\t\t// Others\n+\t\tlock (this)\n+\t\t{\n+\t\t}\n+\t\tunsafe\n+\t\t{\n+\t\t\tbyte[] bytes = [1, 2, 3];\n+\t\t\tfixed (byte* pointerToFirst = bytes)\n+\t\t\t{\n+\t\t\t}\n+\t\t}\n+\t\t\n+\t\treturn \"ChangeMe\";\n+\t}\n+\t\n+\tpublic void Dispose() {}\n+\t\n+\tstring MethodCall(int a = 0, int b = 0) => \"test\";\n+\tException GetException() => new Exception(\"fail\");\n+\tint[] Numbers() => [0, 1];\n+}\ndiff --git a/t/t4018/csharp-method-special-chars b/t/t4018/csharp-method-special-chars\nnew file mode 100644\nindex 00000000000..ec3565fd000\n--- /dev/null\n+++ b/t/t4018/csharp-method-special-chars\n@@ -0,0 +1,11 @@\n+class @Some_Type\n+{\n+\t@Some_Type @Method_With_Underscore(int RIGHT)\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\t// ChangeMe\n+\t\treturn new @Some_Type();\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-with-spacing b/t/t4018/csharp-method-with-spacing\nnew file mode 100644\nindex 00000000000..4143929a711\n--- /dev/null\n+++ b/t/t4018/csharp-method-with-spacing\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+\t\tstring   Method \t( int \tRIGHT )\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\treturn \"ChangeMe\";\n+\t}\n+}\ndiff --git a/t/t4018/csharp-property b/t/t4018/csharp-property\nnew file mode 100644\nindex 00000000000..1792117f964\n--- /dev/null\n+++ b/t/t4018/csharp-property\n@@ -0,0 +1,11 @@\n+class Example\n+{\n+\tpublic bool RIGHT\n+    {\n+        get { return true; }\n+        set\n+        {\n+            // ChangeMe\n+        }\n+    }\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex e399543823b..a6a26a97e4c 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -89,12 +89,18 @@ PATTERNS(\"cpp\",\n \t \"|\\\\.[0-9][0-9]*([Ee][-+]?[0-9]+)?[fFlL]?\"\n \t \"|[-+*/<>%&^|=!]=|--|\\\\+\\\\+|<<=?|>>=?|&&|\\\\|\\\\||::|->\\\\*?|\\\\.\\\\*|<=>\"),\n PATTERNS(\"csharp\",\n-\t /* Keywords */\n-\t \"!^[ \\t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\\n\"\n-\t /* Methods and constructors */\n-\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[<>@._[:alnum:]]+[ \\t]*\\\\(.*\\\\))[ \\t]*$\\n\"\n+\t /*\n+\t  * Jump over keywords not used by methods which can be followed by parentheses without special characters in between,\n+\t  * making them look like methods.\n+\t  */\n+\t \"!(^|[ \\t]+)(do|while|for|foreach|from|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \\t(]+|$)\\n\"\n+\t /* Methods/constructors:\n+\t  * the strategy is to identify a minimum of two groups (any combination of keywords/type/name),\n+\t  * without intermediate or final characters which can't be part of method definitions before the opening parenthesis.\n+\t  */\n+\t \"^[ \\t]*(([][[:alnum:]@_<>.,]*[^=:{ \\t][ \\t]+[][[:alnum:]@_<>.,]*)+\\\\([^;]*)$\\n\"\n \t /* Properties */\n-\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[@._[:alnum:]]+)[ \\t]*$\\n\"\n+\t \"^[ \\t]*((([][[:alnum:]@_<>.,]+)[ \\t]+[][[:alnum:]@_]*)+[^=:;,()]*)$\\n\"\n \t /* Type definitions */\n \t \"^[ \\t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \\t]+)*(class|enum|interface|struct|record)[ \\t]+.*)$\\n\"\n \t /* Namespace */\n\nbase-commit: f41f85c9ec8d4d46de0fd5fded88db94d3ec8c11\n-- \ngitgitgadget\n"},{"id":"490111","messageId":"pull.1682.v2.git.git.1709756493673.gitgitgadget@gmail.com","threadId":"60993","inReplyTo":"pull.1682.git.git.1708882423691.gitgitgadget@gmail.com","subject":"[PATCH v2] userdiff: better method/property matching for C#","fromName":"Steven Jeuris via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-06T20:21:33Z","receivedAt":"2024-03-06T20:21:36Z","isPatch":true,"sender":{"key":"name:Steven Jeuris","avatar":null},"body":"From: Steven Jeuris <steven.jeuris@3shape.com>\n\n- Support multi-line methods by not requiring closing parenthesis.\n- Support multiple generics (comma was missing before).\n- Add missing `foreach`, `lock` and  `fixed` keywords to skip over.\n- Remove `instanceof` keyword, which isn't C#.\n- Also detect non-method keywords not positioned at the start of a line.\n- Added tests; none existed before.\n\nThe overall strategy is to focus more on what isn't expected for\nmethod/property definitions, instead of what is, but is fully optional.\n\nSigned-off-by: Steven Jeuris <steven.jeuris@gmail.com>\n---\n    userdiff: better method/property matching for C#\n    \n    Change since v1: I removed \"from\" from the list of keywords to skip.\n    First, I considered adding \"await\", but I discovered both \"await\" and\n    \"from\" are \"contextual keywords\", which unlike the other keywords\n    currently listed, aren't reserved, and can thus cause false negatives.\n    I.e., it is valid to have a method named \"await\" or \"from\". In edge\n    cases, this may lead to false positives, but a different exclusion rule\n    will need to be added to handle these.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1682%2FWhathecode%2Fmaster-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1682/Whathecode/master-v2\nPull-Request: https://github.com/git/git/pull/1682\n\nRange-diff vs v1:\n\n 1:  cdd8dd4d871 ! 1:  00315519014 userdiff: better method/property matching for C#\n     @@ Commit message\n      \n          - Support multi-line methods by not requiring closing parenthesis.\n          - Support multiple generics (comma was missing before).\n     -    - Add missing `foreach`, `from`, `lock` and  `fixed` keywords to skip over.\n     +    - Add missing `foreach`, `lock` and  `fixed` keywords to skip over.\n          - Remove `instanceof` keyword, which isn't C#.\n          - Also detect non-method keywords not positioned at the start of a line.\n          - Added tests; none existed before.\n     @@ t/t4018/csharp-method-skip-body (new)\n      +\t\t{\n      +\t\t}\n      +\t\tint[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n     -+\t\tvar test =\n     -+\t\t\tfrom num in Numbers(\n     -+\t\t \t) select num;\n      +\t\t\n      +\t\t// Control\n      +\t\tif (false)\n     @@ userdiff.c: PATTERNS(\"cpp\",\n      +\t  * Jump over keywords not used by methods which can be followed by parentheses without special characters in between,\n      +\t  * making them look like methods.\n      +\t  */\n     -+\t \"!(^|[ \\t]+)(do|while|for|foreach|from|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \\t(]+|$)\\n\"\n     ++\t \"!(^|[ \\t]+)(do|while|for|foreach|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \\t(]+|$)\\n\"\n      +\t /* Methods/constructors:\n      +\t  * the strategy is to identify a minimum of two groups (any combination of keywords/type/name),\n      +\t  * without intermediate or final characters which can't be part of method definitions before the opening parenthesis.\n\n\n t/t4018/csharp-method               |  10 +++\n t/t4018/csharp-method-explicit      |  12 +++\n t/t4018/csharp-method-generics      |  11 +++\n t/t4018/csharp-method-modifiers     |  13 ++++\n t/t4018/csharp-method-multiline     |  10 +++\n t/t4018/csharp-method-params        |  10 +++\n t/t4018/csharp-method-skip-body     | 112 ++++++++++++++++++++++++++++\n t/t4018/csharp-method-special-chars |  11 +++\n t/t4018/csharp-method-with-spacing  |  10 +++\n t/t4018/csharp-property             |  11 +++\n userdiff.c                          |  16 ++--\n 11 files changed, 221 insertions(+), 5 deletions(-)\n create mode 100644 t/t4018/csharp-method\n create mode 100644 t/t4018/csharp-method-explicit\n create mode 100644 t/t4018/csharp-method-generics\n create mode 100644 t/t4018/csharp-method-modifiers\n create mode 100644 t/t4018/csharp-method-multiline\n create mode 100644 t/t4018/csharp-method-params\n create mode 100644 t/t4018/csharp-method-skip-body\n create mode 100644 t/t4018/csharp-method-special-chars\n create mode 100644 t/t4018/csharp-method-with-spacing\n create mode 100644 t/t4018/csharp-property\n\ndiff --git a/t/t4018/csharp-method b/t/t4018/csharp-method\nnew file mode 100644\nindex 00000000000..85ff0cb8b5b\n--- /dev/null\n+++ b/t/t4018/csharp-method\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+\tstring Method(int RIGHT)\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\treturn \"ChangeMe\";\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-explicit b/t/t4018/csharp-method-explicit\nnew file mode 100644\nindex 00000000000..083aa094ce2\n--- /dev/null\n+++ b/t/t4018/csharp-method-explicit\n@@ -0,0 +1,12 @@\n+using System;\n+\n+class Example : IDisposable\n+{\n+\tvoid IDisposable.Dispose() // RIGHT\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\t// ChangeMe\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-generics b/t/t4018/csharp-method-generics\nnew file mode 100644\nindex 00000000000..c472d4a18df\n--- /dev/null\n+++ b/t/t4018/csharp-method-generics\n@@ -0,0 +1,11 @@\n+class Example<T1, T2>\n+{\n+\tExample<int, string> Method<TA, TB>(TA RIGHT, TB b)\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\t// ChangeMe\n+\t\treturn null;\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-modifiers b/t/t4018/csharp-method-modifiers\nnew file mode 100644\nindex 00000000000..f1c008a4749\n--- /dev/null\n+++ b/t/t4018/csharp-method-modifiers\n@@ -0,0 +1,13 @@\n+using System.Threading.Tasks;\n+\n+class Example\n+{\n+\tstatic internal async Task Method(int RIGHT)\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\t// ChangeMe\n+\t\tawait Task.Delay(1);\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-multiline b/t/t4018/csharp-method-multiline\nnew file mode 100644\nindex 00000000000..0a20b0cb49c\n--- /dev/null\n+++ b/t/t4018/csharp-method-multiline\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+\tstring Method_RIGHT(\n+\t\tint a,\n+\t\tint b,\n+\t\tint c)\n+\t{\n+\t\treturn \"ChangeMe\";\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-params b/t/t4018/csharp-method-params\nnew file mode 100644\nindex 00000000000..18598449008\n--- /dev/null\n+++ b/t/t4018/csharp-method-params\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+\tstring Method(int RIGHT, int b, int c = 42)\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\treturn \"ChangeMe\";\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-skip-body b/t/t4018/csharp-method-skip-body\nnew file mode 100644\nindex 00000000000..c8c9621634d\n--- /dev/null\n+++ b/t/t4018/csharp-method-skip-body\n@@ -0,0 +1,112 @@\n+using System.Linq;\n+using System;\n+\n+class Example : IDisposable\n+{\n+\tstring Method(int RIGHT)\n+\t{\n+\t\t// Method calls\n+\t\tMethodCall();\n+\t\tMethodCall(1, 2);\n+\t\tMethodCall(\n+\t\t\t1, 2);\n+\t\t\n+\t\t// Assignments\n+\t\tvar constantAssignment = \"test\";\n+\t\tvar methodAssignment = MethodCall();\n+\t\tvar multiLineMethodAssignment = MethodCall(\n+\t\t\t);\n+\t\t\n+\t\t// Initializations/disposal\n+\t\tnew Example();\n+\t\tnew Example(\n+\t\t\t);\n+\t\tnew Example { };\n+\t\tusing (this) \n+\t\t{\n+\t\t}\n+\t\tvar def =\n+\t\t\tthis is default(\n+\t\t\t\tExample);\n+\t\t\n+\t\t// Iteration statements\n+\t\tdo { } while (true);\n+\t\tdo MethodCall(\n+\t\t\t); while (true);\n+\t\twhile (true);\n+\t\twhile (true) {\n+\t\t\tbreak;\n+\t\t}\n+\t\tfor (int i = 0; i < 10; ++i)\n+\t\t{\n+\t\t}\n+\t\tforeach (int i in Enumerable.Range(0, 10))\n+\t\t{\n+\t\t}\n+\t\tint[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n+\t\t\n+\t\t// Control\n+\t\tif (false)\n+\t\t{\n+\t\t\treturn \"out\";\n+\t\t}\n+\t\telse { }\n+\t\tif (true) MethodCall(\n+\t\t\t);\n+\t\telse MethodCall(\n+\t\t\t);\n+\t\tswitch (\"test\")\n+\t\t{\n+\t\t\tcase \"one\":\n+\t\t\t\treturn MethodCall(\n+\t\t\t\t\t);\n+\t\t\tcase \"two\":\n+\t\t\t\tbreak;\n+\t\t}\n+\t\t(int, int) tuple = (1, 4);\n+\t\tswitch (tuple)\n+\t\t{\n+\t\t\tcase (1, 4):\n+\t\t\t\tMethodCall();\n+\t\t}\n+\t\t\n+\t\t// Exceptions\n+\t\ttry\n+\t\t{\n+\t\t\tthrow new Exception(\"fail\");\n+\t\t}\n+\t\tcatch (Exception)\n+\t\t{\n+\t\t}\n+\t\tfinally\n+\t\t{\n+\t\t}\n+\t\ttry { } catch (Exception) {}\n+\t\ttry\n+\t\t{\n+\t\t\tthrow GetException(\n+\t\t\t\t);\n+\t\t}\n+\t\tcatch (Exception) { }\n+\t\t\n+\t\t// Others\n+\t\tlock (this)\n+\t\t{\n+\t\t}\n+\t\tunsafe\n+\t\t{\n+\t\t\tbyte[] bytes = [1, 2, 3];\n+\t\t\tfixed (byte* pointerToFirst = bytes)\n+\t\t\t{\n+\t\t\t}\n+\t\t}\n+\t\t\n+\t\treturn \"ChangeMe\";\n+\t}\n+\t\n+\tpublic void Dispose() {}\n+\t\n+\tstring MethodCall(int a = 0, int b = 0) => \"test\";\n+\tException GetException() => new Exception(\"fail\");\n+\tint[] Numbers() => [0, 1];\n+}\ndiff --git a/t/t4018/csharp-method-special-chars b/t/t4018/csharp-method-special-chars\nnew file mode 100644\nindex 00000000000..ec3565fd000\n--- /dev/null\n+++ b/t/t4018/csharp-method-special-chars\n@@ -0,0 +1,11 @@\n+class @Some_Type\n+{\n+\t@Some_Type @Method_With_Underscore(int RIGHT)\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\t// ChangeMe\n+\t\treturn new @Some_Type();\n+\t}\n+}\ndiff --git a/t/t4018/csharp-method-with-spacing b/t/t4018/csharp-method-with-spacing\nnew file mode 100644\nindex 00000000000..4143929a711\n--- /dev/null\n+++ b/t/t4018/csharp-method-with-spacing\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+\t\tstring   Method \t( int \tRIGHT )\n+\t{\n+\t\t// Filler\n+\t\t// Filler\n+\t\t\n+\t\treturn \"ChangeMe\";\n+\t}\n+}\ndiff --git a/t/t4018/csharp-property b/t/t4018/csharp-property\nnew file mode 100644\nindex 00000000000..1792117f964\n--- /dev/null\n+++ b/t/t4018/csharp-property\n@@ -0,0 +1,11 @@\n+class Example\n+{\n+\tpublic bool RIGHT\n+    {\n+        get { return true; }\n+        set\n+        {\n+            // ChangeMe\n+        }\n+    }\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex e399543823b..5a9e8a0ef55 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -89,12 +89,18 @@ PATTERNS(\"cpp\",\n \t \"|\\\\.[0-9][0-9]*([Ee][-+]?[0-9]+)?[fFlL]?\"\n \t \"|[-+*/<>%&^|=!]=|--|\\\\+\\\\+|<<=?|>>=?|&&|\\\\|\\\\||::|->\\\\*?|\\\\.\\\\*|<=>\"),\n PATTERNS(\"csharp\",\n-\t /* Keywords */\n-\t \"!^[ \\t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\\n\"\n-\t /* Methods and constructors */\n-\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[<>@._[:alnum:]]+[ \\t]*\\\\(.*\\\\))[ \\t]*$\\n\"\n+\t /*\n+\t  * Jump over keywords not used by methods which can be followed by parentheses without special characters in between,\n+\t  * making them look like methods.\n+\t  */\n+\t \"!(^|[ \\t]+)(do|while|for|foreach|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \\t(]+|$)\\n\"\n+\t /* Methods/constructors:\n+\t  * the strategy is to identify a minimum of two groups (any combination of keywords/type/name),\n+\t  * without intermediate or final characters which can't be part of method definitions before the opening parenthesis.\n+\t  */\n+\t \"^[ \\t]*(([][[:alnum:]@_<>.,]*[^=:{ \\t][ \\t]+[][[:alnum:]@_<>.,]*)+\\\\([^;]*)$\\n\"\n \t /* Properties */\n-\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[@._[:alnum:]]+)[ \\t]*$\\n\"\n+\t \"^[ \\t]*((([][[:alnum:]@_<>.,]+)[ \\t]+[][[:alnum:]@_]*)+[^=:;,()]*)$\\n\"\n \t /* Type definitions */\n \t \"^[ \\t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \\t]+)*(class|enum|interface|struct|record)[ \\t]+.*)$\\n\"\n \t /* Namespace */\n\nbase-commit: f41f85c9ec8d4d46de0fd5fded88db94d3ec8c11\n-- \ngitgitgadget\n"},{"id":"490132","messageId":"xmqqv85yokoc.fsf@gitster.g","threadId":"60993","inReplyTo":"pull.1682.v2.git.git.1709756493673.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] userdiff: better method/property matching for C#","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-07T02:11:31Z","receivedAt":"2024-03-07T02:11:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Steven Jeuris via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Steven Jeuris <steven.jeuris@3shape.com>\n>\n> - Support multi-line methods by not requiring closing parenthesis.\n> - Support multiple generics (comma was missing before).\n> - Add missing `foreach`, `lock` and  `fixed` keywords to skip over.\n> - Remove `instanceof` keyword, which isn't C#.\n> - Also detect non-method keywords not positioned at the start of a line.\n> - Added tests; none existed before.\n>\n> The overall strategy is to focus more on what isn't expected for\n> method/property definitions, instead of what is, but is fully optional.\n>\n\nRoughly in other words, we assume that any file the end user throws\nat us is a well formed program, so instead of enumerating all valid\nkeywords and limit the match exactly to them, use a pattern that\nwould match valid keywords (both currently known ones, and anything\nthe language might add in the future that we do not know about), to\nmatch with anything syntactically plausible to be a definition?\n\nIt does make sense to start by assuming that the end user data is a\nvalid C# program.\n\n> Signed-off-by: Steven Jeuris <steven.jeuris@gmail.com>\n> ---\n\n> diff --git a/userdiff.c b/userdiff.c\n> index e399543823b..5a9e8a0ef55 100644\n> --- a/userdiff.c\n> +++ b/userdiff.c\n> @@ -89,12 +89,18 @@ PATTERNS(\"cpp\",\n>  \t \"|\\\\.[0-9][0-9]*([Ee][-+]?[0-9]+)?[fFlL]?\"\n>  \t \"|[-+*/<>%&^|=!]=|--|\\\\+\\\\+|<<=?|>>=?|&&|\\\\|\\\\||::|->\\\\*?|\\\\.\\\\*|<=>\"),\n>  PATTERNS(\"csharp\",\n> -\t /* Keywords */\n> -\t \"!^[ \\t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\\n\"\n> -\t /* Methods and constructors */\n> -\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[<>@._[:alnum:]]+[ \\t]*\\\\(.*\\\\))[ \\t]*$\\n\"\n> +\t /*\n> +\t  * Jump over keywords not used by methods which can be followed by parentheses without special characters in between,\n> +\t  * making them look like methods.\n> +\t  */\n\nOverly long comments (I'll wrap them while queuing).\n\n> +\t \"!(^|[ \\t]+)(do|while|for|foreach|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \\t(]+|$)\\n\"\n> +\t /* Methods/constructors:\n> +\t  * the strategy is to identify a minimum of two groups (any combination of keywords/type/name),\n> +\t  * without intermediate or final characters which can't be part of method definitions before the opening parenthesis.\n> +\t  */\n> +\t \"^[ \\t]*(([][[:alnum:]@_<>.,]*[^=:{ \\t][ \\t]+[][[:alnum:]@_<>.,]*)+\\\\([^;]*)$\\n\"\n>  \t /* Properties */\n> -\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[@._[:alnum:]]+)[ \\t]*$\\n\"\n> +\t \"^[ \\t]*((([][[:alnum:]@_<>.,]+)[ \\t]+[][[:alnum:]@_]*)+[^=:;,()]*)$\\n\"\n>  \t /* Type definitions */\n>  \t \"^[ \\t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \\t]+)*(class|enum|interface|struct|record)[ \\t]+.*)$\\n\"\n>  \t /* Namespace */\n>\n> base-commit: f41f85c9ec8d4d46de0fd5fded88db94d3ec8c11\n"},{"id":"490792","messageId":"owlyy1ai2gdk.fsf@fine.c.googlers.com","threadId":"60993","inReplyTo":"pull.1682.v2.git.git.1709756493673.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] userdiff: better method/property matching for C#","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-03-16T18:14:47Z","receivedAt":"2024-03-16T18:14:49Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"\"Steven Jeuris via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Steven Jeuris <steven.jeuris@3shape.com>\n>\n> - Support multi-line methods by not requiring closing parenthesis.\n> - Support multiple generics (comma was missing before).\n> - Add missing `foreach`, `lock` and  `fixed` keywords to skip over.\n> - Remove `instanceof` keyword, which isn't C#.\n> - Also detect non-method keywords not positioned at the start of a line.\n> - Added tests; none existed before.\n>\n> The overall strategy is to focus more on what isn't expected for\n> method/property definitions, instead of what is, but is fully optional.\n\nIt would make it easier to review if you broke this patch up into smaller\nchunks. For example, you could do it like this:\n\n  (1) add tests to capture existing behavior\n  (2a) change method/property matching behavior, one thing at a time\n  (2b) adjust or add tests along the way, to account for the change in (2a)\n  (3) repeat step (2) as needed for each of the bullet points you've\n      outlined already\n\nNot being familiar with this area, I don't know if (2b) is easily\ndoable. But if it is, I think the overall effort of breaking this patch\ndown would be worthwhile.\n\nOf course, nothing stops other reviewers (who are more familiar with\nthis area) from chiming in and LGTM'ing this patch as is.\n\nThanks.\n"},{"id":"491609","messageId":"xmqqfrwc8yhq.fsf@gitster.g","threadId":"60993","inReplyTo":"pull.1682.v2.git.git.1709756493673.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] userdiff: better method/property matching for C#","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-26T21:38:41Z","receivedAt":"2024-03-26T21:38:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Steven Jeuris via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Steven Jeuris <steven.jeuris@3shape.com>\n>\n> - Support multi-line methods by not requiring closing parenthesis.\n> - Support multiple generics (comma was missing before).\n> - Add missing `foreach`, `lock` and  `fixed` keywords to skip over.\n> - Remove `instanceof` keyword, which isn't C#.\n> - Also detect non-method keywords not positioned at the start of a line.\n> - Added tests; none existed before.\n>\n> The overall strategy is to focus more on what isn't expected for\n> method/property definitions, instead of what is, but is fully optional.\n>\n> Signed-off-by: Steven Jeuris <steven.jeuris@gmail.com>\n> ---\n>     userdiff: better method/property matching for C#\n>     \n>     Change since v1: I removed \"from\" from the list of keywords to skip.\n>     First, I considered adding \"await\", but I discovered both \"await\" and\n>     \"from\" are \"contextual keywords\", which unlike the other keywords\n>     currently listed, aren't reserved, and can thus cause false negatives.\n>     I.e., it is valid to have a method named \"await\" or \"from\". In edge\n>     cases, this may lead to false positives, but a different exclusion rule\n>     will need to be added to handle these.\n\nIt seems that this has seen no reviews.  I somehow find it doubtful\nthat Ævar or Peff would be writing too much C# to be familiar with\nthe language to judge the quality of the patch, but can somebody\nwith C# background (I hear that its most common use is for\ndeveloping Windows applications etc. there) chip in?\n\nThanks.\n"},{"id":"491649","messageId":"1facab70-4c8a-4cf4-b464-b27ab90b1339@kdbg.org","threadId":"60993","inReplyTo":"pull.1682.v2.git.git.1709756493673.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] userdiff: better method/property matching for C#","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2024-03-27T07:30:41Z","receivedAt":"2024-03-27T08:18:44Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 06.03.24 um 21:21 schrieb Steven Jeuris via GitGitGadget:\n> From: Steven Jeuris <steven.jeuris@3shape.com>\n> \n> - Support multi-line methods by not requiring closing parenthesis.\n> - Support multiple generics (comma was missing before).\n> - Add missing `foreach`, `lock` and  `fixed` keywords to skip over.\n> - Remove `instanceof` keyword, which isn't C#.\n> - Also detect non-method keywords not positioned at the start of a line.\n> - Added tests; none existed before.\n> \n> The overall strategy is to focus more on what isn't expected for\n> method/property definitions, instead of what is, but is fully optional.\n> \n> Signed-off-by: Steven Jeuris <steven.jeuris@gmail.com>\n> ---\n\nI like the comprehensive test cases that are added. However, I found one\nmajor point in the patterns that must be considered. See below.\n\n>     userdiff: better method/property matching for C#\n>     \n>     Change since v1: I removed \"from\" from the list of keywords to skip.\n>     First, I considered adding \"await\", but I discovered both \"await\" and\n>     \"from\" are \"contextual keywords\", which unlike the other keywords\n>     currently listed, aren't reserved, and can thus cause false negatives.\n>     I.e., it is valid to have a method named \"await\" or \"from\". In edge\n>     cases, this may lead to false positives, but a different exclusion rule\n>     will need to be added to handle these.\n\nSo, this patch makes the choice to have some false positives instead of\nsome false negatives. I have no experience in C#, so I trust your\njudgement that users are better served with this choice rather than the\nopposite.\n\n> \n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1682%2FWhathecode%2Fmaster-v2\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1682/Whathecode/master-v2\n> Pull-Request: https://github.com/git/git/pull/1682\n> \n> Range-diff vs v1:\n> \n>  1:  cdd8dd4d871 ! 1:  00315519014 userdiff: better method/property matching for C#\n>      @@ Commit message\n>       \n>           - Support multi-line methods by not requiring closing parenthesis.\n>           - Support multiple generics (comma was missing before).\n>      -    - Add missing `foreach`, `from`, `lock` and  `fixed` keywords to skip over.\n>      +    - Add missing `foreach`, `lock` and  `fixed` keywords to skip over.\n>           - Remove `instanceof` keyword, which isn't C#.\n>           - Also detect non-method keywords not positioned at the start of a line.\n>           - Added tests; none existed before.\n>      @@ t/t4018/csharp-method-skip-body (new)\n>       +\t\t{\n>       +\t\t}\n>       +\t\tint[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n>      -+\t\tvar test =\n>      -+\t\t\tfrom num in Numbers(\n>      -+\t\t \t) select num;\n>       +\t\t\n>       +\t\t// Control\n>       +\t\tif (false)\n>      @@ userdiff.c: PATTERNS(\"cpp\",\n>       +\t  * Jump over keywords not used by methods which can be followed by parentheses without special characters in between,\n>       +\t  * making them look like methods.\n>       +\t  */\n>      -+\t \"!(^|[ \\t]+)(do|while|for|foreach|from|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \\t(]+|$)\\n\"\n>      ++\t \"!(^|[ \\t]+)(do|while|for|foreach|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \\t(]+|$)\\n\"\n>       +\t /* Methods/constructors:\n>       +\t  * the strategy is to identify a minimum of two groups (any combination of keywords/type/name),\n>       +\t  * without intermediate or final characters which can't be part of method definitions before the opening parenthesis.\n> \n> \n>  t/t4018/csharp-method               |  10 +++\n>  t/t4018/csharp-method-explicit      |  12 +++\n>  t/t4018/csharp-method-generics      |  11 +++\n>  t/t4018/csharp-method-modifiers     |  13 ++++\n>  t/t4018/csharp-method-multiline     |  10 +++\n>  t/t4018/csharp-method-params        |  10 +++\n>  t/t4018/csharp-method-skip-body     | 112 ++++++++++++++++++++++++++++\n>  t/t4018/csharp-method-special-chars |  11 +++\n>  t/t4018/csharp-method-with-spacing  |  10 +++\n>  t/t4018/csharp-property             |  11 +++\n>  userdiff.c                          |  16 ++--\n>  11 files changed, 221 insertions(+), 5 deletions(-)\n>  create mode 100644 t/t4018/csharp-method\n>  create mode 100644 t/t4018/csharp-method-explicit\n>  create mode 100644 t/t4018/csharp-method-generics\n>  create mode 100644 t/t4018/csharp-method-modifiers\n>  create mode 100644 t/t4018/csharp-method-multiline\n>  create mode 100644 t/t4018/csharp-method-params\n>  create mode 100644 t/t4018/csharp-method-skip-body\n>  create mode 100644 t/t4018/csharp-method-special-chars\n>  create mode 100644 t/t4018/csharp-method-with-spacing\n>  create mode 100644 t/t4018/csharp-property\n> \n> diff --git a/t/t4018/csharp-method b/t/t4018/csharp-method\n> new file mode 100644\n> index 00000000000..85ff0cb8b5b\n> --- /dev/null\n> +++ b/t/t4018/csharp-method\n> @@ -0,0 +1,10 @@\n> +class Example\n> +{\n> +\tstring Method(int RIGHT)\n> +\t{\n> +\t\t// Filler\n> +\t\t// Filler\n> +\t\t\n> +\t\treturn \"ChangeMe\";\n> +\t}\n> +}\n> diff --git a/t/t4018/csharp-method-explicit b/t/t4018/csharp-method-explicit\n> new file mode 100644\n> index 00000000000..083aa094ce2\n> --- /dev/null\n> +++ b/t/t4018/csharp-method-explicit\n> @@ -0,0 +1,12 @@\n> +using System;\n> +\n> +class Example : IDisposable\n> +{\n> +\tvoid IDisposable.Dispose() // RIGHT\n> +\t{\n> +\t\t// Filler\n> +\t\t// Filler\n> +\t\t\n> +\t\t// ChangeMe\n> +\t}\n> +}\n> diff --git a/t/t4018/csharp-method-generics b/t/t4018/csharp-method-generics\n> new file mode 100644\n> index 00000000000..c472d4a18df\n> --- /dev/null\n> +++ b/t/t4018/csharp-method-generics\n> @@ -0,0 +1,11 @@\n> +class Example<T1, T2>\n> +{\n> +\tExample<int, string> Method<TA, TB>(TA RIGHT, TB b)\n> +\t{\n> +\t\t// Filler\n> +\t\t// Filler\n> +\t\t\n> +\t\t// ChangeMe\n> +\t\treturn null;\n> +\t}\n> +}\n> diff --git a/t/t4018/csharp-method-modifiers b/t/t4018/csharp-method-modifiers\n> new file mode 100644\n> index 00000000000..f1c008a4749\n> --- /dev/null\n> +++ b/t/t4018/csharp-method-modifiers\n> @@ -0,0 +1,13 @@\n> +using System.Threading.Tasks;\n> +\n> +class Example\n> +{\n> +\tstatic internal async Task Method(int RIGHT)\n> +\t{\n> +\t\t// Filler\n> +\t\t// Filler\n> +\t\t\n> +\t\t// ChangeMe\n> +\t\tawait Task.Delay(1);\n> +\t}\n> +}\n> diff --git a/t/t4018/csharp-method-multiline b/t/t4018/csharp-method-multiline\n> new file mode 100644\n> index 00000000000..0a20b0cb49c\n> --- /dev/null\n> +++ b/t/t4018/csharp-method-multiline\n> @@ -0,0 +1,10 @@\n> +class Example\n> +{\n> +\tstring Method_RIGHT(\n> +\t\tint a,\n> +\t\tint b,\n> +\t\tint c)\n> +\t{\n> +\t\treturn \"ChangeMe\";\n> +\t}\n> +}\n> diff --git a/t/t4018/csharp-method-params b/t/t4018/csharp-method-params\n> new file mode 100644\n> index 00000000000..18598449008\n> --- /dev/null\n> +++ b/t/t4018/csharp-method-params\n> @@ -0,0 +1,10 @@\n> +class Example\n> +{\n> +\tstring Method(int RIGHT, int b, int c = 42)\n> +\t{\n> +\t\t// Filler\n> +\t\t// Filler\n> +\t\t\n> +\t\treturn \"ChangeMe\";\n> +\t}\n> +}\n> diff --git a/t/t4018/csharp-method-skip-body b/t/t4018/csharp-method-skip-body\n> new file mode 100644\n> index 00000000000..c8c9621634d\n> --- /dev/null\n> +++ b/t/t4018/csharp-method-skip-body\n> @@ -0,0 +1,112 @@\n> +using System.Linq;\n> +using System;\n> +\n> +class Example : IDisposable\n> +{\n> +\tstring Method(int RIGHT)\n> +\t{\n> +\t\t// Method calls\n> +\t\tMethodCall();\n> +\t\tMethodCall(1, 2);\n> +\t\tMethodCall(\n> +\t\t\t1, 2);\n> +\t\t\n> +\t\t// Assignments\n> +\t\tvar constantAssignment = \"test\";\n> +\t\tvar methodAssignment = MethodCall();\n> +\t\tvar multiLineMethodAssignment = MethodCall(\n> +\t\t\t);\n> +\t\t\n> +\t\t// Initializations/disposal\n> +\t\tnew Example();\n> +\t\tnew Example(\n> +\t\t\t);\n> +\t\tnew Example { };\n> +\t\tusing (this) \n> +\t\t{\n> +\t\t}\n> +\t\tvar def =\n> +\t\t\tthis is default(\n> +\t\t\t\tExample);\n> +\t\t\n> +\t\t// Iteration statements\n> +\t\tdo { } while (true);\n> +\t\tdo MethodCall(\n> +\t\t\t); while (true);\n> +\t\twhile (true);\n> +\t\twhile (true) {\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\tfor (int i = 0; i < 10; ++i)\n> +\t\t{\n> +\t\t}\n> +\t\tforeach (int i in Enumerable.Range(0, 10))\n> +\t\t{\n> +\t\t}\n> +\t\tint[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n> +\t\t\n> +\t\t// Control\n> +\t\tif (false)\n> +\t\t{\n> +\t\t\treturn \"out\";\n> +\t\t}\n> +\t\telse { }\n> +\t\tif (true) MethodCall(\n> +\t\t\t);\n> +\t\telse MethodCall(\n> +\t\t\t);\n> +\t\tswitch (\"test\")\n> +\t\t{\n> +\t\t\tcase \"one\":\n> +\t\t\t\treturn MethodCall(\n> +\t\t\t\t\t);\n> +\t\t\tcase \"two\":\n> +\t\t\t\tbreak;\n> +\t\t}\n> +\t\t(int, int) tuple = (1, 4);\n> +\t\tswitch (tuple)\n> +\t\t{\n> +\t\t\tcase (1, 4):\n> +\t\t\t\tMethodCall();\n> +\t\t}\n> +\t\t\n> +\t\t// Exceptions\n> +\t\ttry\n> +\t\t{\n> +\t\t\tthrow new Exception(\"fail\");\n> +\t\t}\n> +\t\tcatch (Exception)\n> +\t\t{\n> +\t\t}\n> +\t\tfinally\n> +\t\t{\n> +\t\t}\n> +\t\ttry { } catch (Exception) {}\n> +\t\ttry\n> +\t\t{\n> +\t\t\tthrow GetException(\n> +\t\t\t\t);\n> +\t\t}\n> +\t\tcatch (Exception) { }\n> +\t\t\n> +\t\t// Others\n> +\t\tlock (this)\n> +\t\t{\n> +\t\t}\n> +\t\tunsafe\n> +\t\t{\n> +\t\t\tbyte[] bytes = [1, 2, 3];\n> +\t\t\tfixed (byte* pointerToFirst = bytes)\n> +\t\t\t{\n> +\t\t\t}\n> +\t\t}\n> +\t\t\n> +\t\treturn \"ChangeMe\";\n> +\t}\n\nNice! This tests the exlusion patterns.\n\n> +\t\n> +\tpublic void Dispose() {}\n> +\t\n> +\tstring MethodCall(int a = 0, int b = 0) => \"test\";\n> +\tException GetException() => new Exception(\"fail\");\n> +\tint[] Numbers() => [0, 1];\n\nAre these also patterns that should not be picked up? If so, should the\n\"ChangeMe\" not be later in the file? As written, they would never be\ncandidates because there is no diff hunk later in the file.\n\n> +}\n> diff --git a/t/t4018/csharp-method-special-chars b/t/t4018/csharp-method-special-chars\n> new file mode 100644\n> index 00000000000..ec3565fd000\n> --- /dev/null\n> +++ b/t/t4018/csharp-method-special-chars\n> @@ -0,0 +1,11 @@\n> +class @Some_Type\n> +{\n> +\t@Some_Type @Method_With_Underscore(int RIGHT)\n> +\t{\n> +\t\t// Filler\n> +\t\t// Filler\n> +\t\t\n> +\t\t// ChangeMe\n> +\t\treturn new @Some_Type();\n> +\t}\n> +}\n> diff --git a/t/t4018/csharp-method-with-spacing b/t/t4018/csharp-method-with-spacing\n> new file mode 100644\n> index 00000000000..4143929a711\n> --- /dev/null\n> +++ b/t/t4018/csharp-method-with-spacing\n> @@ -0,0 +1,10 @@\n> +class Example\n> +{\n> +\t\tstring   Method \t( int \tRIGHT )\n> +\t{\n> +\t\t// Filler\n> +\t\t// Filler\n> +\t\t\n> +\t\treturn \"ChangeMe\";\n> +\t}\n> +}\n> diff --git a/t/t4018/csharp-property b/t/t4018/csharp-property\n> new file mode 100644\n> index 00000000000..1792117f964\n> --- /dev/null\n> +++ b/t/t4018/csharp-property\n> @@ -0,0 +1,11 @@\n> +class Example\n> +{\n> +\tpublic bool RIGHT\n> +    {\n> +        get { return true; }\n> +        set\n> +        {\n> +            // ChangeMe\n> +        }\n> +    }\n> +}\n> diff --git a/userdiff.c b/userdiff.c\n> index e399543823b..5a9e8a0ef55 100644\n> --- a/userdiff.c\n> +++ b/userdiff.c\n> @@ -89,12 +89,18 @@ PATTERNS(\"cpp\",\n>  \t \"|\\\\.[0-9][0-9]*([Ee][-+]?[0-9]+)?[fFlL]?\"\n>  \t \"|[-+*/<>%&^|=!]=|--|\\\\+\\\\+|<<=?|>>=?|&&|\\\\|\\\\||::|->\\\\*?|\\\\.\\\\*|<=>\"),\n>  PATTERNS(\"csharp\",\n> -\t /* Keywords */\n> -\t \"!^[ \\t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\\n\"\n> -\t /* Methods and constructors */\n> -\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[<>@._[:alnum:]]+[ \\t]*\\\\(.*\\\\))[ \\t]*$\\n\"\n> +\t /*\n> +\t  * Jump over keywords not used by methods which can be followed by parentheses without special characters in between,\n> +\t  * making them look like methods.\n> +\t  */\n> +\t \"!(^|[ \\t]+)(do|while|for|foreach|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \\t(]+|$)\\n\"\n> +\t /* Methods/constructors:\n> +\t  * the strategy is to identify a minimum of two groups (any combination of keywords/type/name),\n> +\t  * without intermediate or final characters which can't be part of method definitions before the opening parenthesis.\n> +\t  */\n\nI would have appreciated if the comment lines were not so long. We\nalready have many long lines of code in this file, but comment lines\ngenerally stay below the 80 character limit. Also watch out for the\nmulti-line comment style (put the opening /* on a line by itself, like\nyou did in the comment above).\n\n> +\t \"^[ \\t]*(([][[:alnum:]@_<>.,]*[^=:{ \\t][ \\t]+[][[:alnum:]@_<>.,]*)+\\\\([^;]*)$\\n\"\n\nI am not 100% sure about the meaning of this expression. But would it\nnot match this line, which looks like part of a multi-line expression:\n\n\t+ func(x)\n\nand produce a false positive? [Testing myself...] Yes it does. I do not\nknow if it is worth fixing as I do not do C#. But if this were patterns\nfor C or C++, I would consider it a major problem.\n\n>  \t /* Properties */\n> -\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[@._[:alnum:]]+)[ \\t]*$\\n\"\n> +\t \"^[ \\t]*((([][[:alnum:]@_<>.,]+)[ \\t]+[][[:alnum:]@_]*)+[^=:;,()]*)$\\n\"\n\nOK. Since the second of the two words is optional and almost the same as\nthe first, I have a gut feeling that the pattern can be expensive to\nmatch. But since I do not have data to back up my suspicion, let's leave\nit as it is.\n\n>  \t /* Type definitions */\n>  \t \"^[ \\t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \\t]+)*(class|enum|interface|struct|record)[ \\t]+.*)$\\n\"\n>  \t /* Namespace */>\n> base-commit: f41f85c9ec8d4d46de0fd5fded88db94d3ec8c11\n\n-- Hannes\n\n"},{"id":"491654","messageId":"20240327084005.GE830163@coredump.intra.peff.net","threadId":"60993","inReplyTo":"xmqqfrwc8yhq.fsf@gitster.g","subject":"Re: [PATCH v2] userdiff: better method/property matching for C#","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-03-27T08:40:05Z","receivedAt":"2024-03-27T08:40:07Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Mar 26, 2024 at 02:38:41PM -0700, Junio C Hamano wrote:\n\n> >     userdiff: better method/property matching for C#\n> >     \n> >     Change since v1: I removed \"from\" from the list of keywords to skip.\n> >     First, I considered adding \"await\", but I discovered both \"await\" and\n> >     \"from\" are \"contextual keywords\", which unlike the other keywords\n> >     currently listed, aren't reserved, and can thus cause false negatives.\n> >     I.e., it is valid to have a method named \"await\" or \"from\". In edge\n> >     cases, this may lead to false positives, but a different exclusion rule\n> >     will need to be added to handle these.\n> \n> It seems that this has seen no reviews.  I somehow find it doubtful\n> that Ævar or Peff would be writing too much C# to be familiar with\n> the language to judge the quality of the patch, but can somebody\n> with C# background (I hear that its most common use is for\n> developing Windows applications etc. there) chip in?\n\nYeah, sorry, I have never written a line of C# in my life, so I have\nbeen dutifully ignoring this series. :)\n\n-Peff\n"},{"id":"491727","messageId":"pull.1682.v3.git.git.1711613220277.gitgitgadget@gmail.com","threadId":"60993","inReplyTo":"pull.1682.v2.git.git.1709756493673.gitgitgadget@gmail.com","subject":"[PATCH v3] userdiff: better method/property matching for C#","fromName":"Steven Jeuris via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-28T08:07:00Z","receivedAt":"2024-03-28T08:07:05Z","isPatch":true,"sender":{"key":"name:Steven Jeuris","avatar":null},"body":"From: Steven Jeuris <steven.jeuris@3shape.com>\n\n- Support multi-line methods by not requiring closing parenthesis.\n- Support multiple generics (comma was missing before).\n- Add missing `foreach`, `lock` and  `fixed` keywords to skip over.\n- Remove `instanceof` keyword, which isn't C#.\n- Also detect non-method keywords not positioned at the start of a line.\n- Added tests; none existed before.\n\nThe overall strategy is to focus more on what isn't expected for\nmethod/property definitions, instead of what is, but is fully optional.\n\nSigned-off-by: Steven Jeuris <steven.jeuris@gmail.com>\n---\n    userdiff: better method/property matching for C#\n    \n    Change since v1: I removed \"from\" from the list of keywords to skip.\n    First, I considered adding \"await\", but I discovered both \"await\" and\n    \"from\" are \"contextual keywords\", which unlike the other keywords\n    currently listed, aren't reserved, and can thus cause false negatives.\n    I.e., it is valid to have a method named \"await\" or \"from\". In edge\n    cases, this may lead to false positives, but a different exclusion rule\n    will need to be added to handle these.\n    \n    Change since v2:\n    \n     * Corrected comment formatting.\n     * Added csharp-property-skip-body test.\n     * Added comments in test code to explain sections not part of the test.\n     * Elaborated regex comments.\n     * Excluded math operators (+-*/%) in method pattern to not catch\n       multiline operations, and tested for this in the -skip-body tests.\n       Catching \"-\" only worked when it was defined at the end of the\n       exclusion block for some reason. The regex matcher seems quite\n       bugged.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1682%2FWhathecode%2Fmaster-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1682/Whathecode/master-v3\nPull-Request: https://github.com/git/git/pull/1682\n\nRange-diff vs v2:\n\n 1:  00315519014 ! 1:  9b6c76f5342 userdiff: better method/property matching for C#\n     @@ t/t4018/csharp-method (new)\n      @@\n      +class Example\n      +{\n     -+\tstring Method(int RIGHT)\n     -+\t{\n     -+\t\t// Filler\n     -+\t\t// Filler\n     -+\t\t\n     -+\t\treturn \"ChangeMe\";\n     -+\t}\n     ++    string Method(int RIGHT)\n     ++    {\n     ++        // Filler\n     ++        // Filler\n     ++\n     ++        return \"ChangeMe\";\n     ++    }\n      +}\n      \n       ## t/t4018/csharp-method-explicit (new) ##\n     @@ t/t4018/csharp-method-explicit (new)\n      +\n      +class Example : IDisposable\n      +{\n     -+\tvoid IDisposable.Dispose() // RIGHT\n     -+\t{\n     -+\t\t// Filler\n     -+\t\t// Filler\n     -+\t\t\n     -+\t\t// ChangeMe\n     -+\t}\n     ++    void IDisposable.Dispose() // RIGHT\n     ++    {\n     ++        // Filler\n     ++        // Filler\n     ++        \n     ++        // ChangeMe\n     ++    }\n      +}\n      \n       ## t/t4018/csharp-method-generics (new) ##\n      @@\n      +class Example<T1, T2>\n      +{\n     -+\tExample<int, string> Method<TA, TB>(TA RIGHT, TB b)\n     -+\t{\n     -+\t\t// Filler\n     -+\t\t// Filler\n     -+\t\t\n     -+\t\t// ChangeMe\n     -+\t\treturn null;\n     -+\t}\n     ++    Example<int, string> Method<TA, TB>(TA RIGHT, TB b)\n     ++    {\n     ++        // Filler\n     ++        // Filler\n     ++        \n     ++        // ChangeMe\n     ++        return null;\n     ++    }\n      +}\n      \n       ## t/t4018/csharp-method-modifiers (new) ##\n     @@ t/t4018/csharp-method-modifiers (new)\n      +\n      +class Example\n      +{\n     -+\tstatic internal async Task Method(int RIGHT)\n     -+\t{\n     -+\t\t// Filler\n     -+\t\t// Filler\n     -+\t\t\n     -+\t\t// ChangeMe\n     -+\t\tawait Task.Delay(1);\n     -+\t}\n     ++    static internal async Task Method(int RIGHT)\n     ++    {\n     ++        // Filler\n     ++        // Filler\n     ++        \n     ++        // ChangeMe\n     ++        await Task.Delay(1);\n     ++    }\n      +}\n      \n       ## t/t4018/csharp-method-multiline (new) ##\n      @@\n      +class Example\n      +{\n     -+\tstring Method_RIGHT(\n     -+\t\tint a,\n     -+\t\tint b,\n     -+\t\tint c)\n     -+\t{\n     -+\t\treturn \"ChangeMe\";\n     -+\t}\n     ++    string Method_RIGHT(\n     ++        int a,\n     ++        int b,\n     ++        int c)\n     ++    {\n     ++        return \"ChangeMe\";\n     ++    }\n      +}\n      \n       ## t/t4018/csharp-method-params (new) ##\n      @@\n      +class Example\n      +{\n     -+\tstring Method(int RIGHT, int b, int c = 42)\n     -+\t{\n     -+\t\t// Filler\n     -+\t\t// Filler\n     -+\t\t\n     -+\t\treturn \"ChangeMe\";\n     -+\t}\n     ++    string Method(int RIGHT, int b, int c = 42)\n     ++    {\n     ++        // Filler\n     ++        // Filler\n     ++        \n     ++        return \"ChangeMe\";\n     ++    }\n      +}\n      \n       ## t/t4018/csharp-method-skip-body (new) ##\n     @@ t/t4018/csharp-method-skip-body (new)\n      +\n      +class Example : IDisposable\n      +{\n     -+\tstring Method(int RIGHT)\n     -+\t{\n     -+\t\t// Method calls\n     -+\t\tMethodCall();\n     -+\t\tMethodCall(1, 2);\n     -+\t\tMethodCall(\n     -+\t\t\t1, 2);\n     -+\t\t\n     -+\t\t// Assignments\n     -+\t\tvar constantAssignment = \"test\";\n     -+\t\tvar methodAssignment = MethodCall();\n     -+\t\tvar multiLineMethodAssignment = MethodCall(\n     -+\t\t\t);\n     -+\t\t\n     -+\t\t// Initializations/disposal\n     -+\t\tnew Example();\n     -+\t\tnew Example(\n     -+\t\t\t);\n     -+\t\tnew Example { };\n     -+\t\tusing (this) \n     -+\t\t{\n     -+\t\t}\n     -+\t\tvar def =\n     -+\t\t\tthis is default(\n     -+\t\t\t\tExample);\n     -+\t\t\n     -+\t\t// Iteration statements\n     -+\t\tdo { } while (true);\n     -+\t\tdo MethodCall(\n     -+\t\t\t); while (true);\n     -+\t\twhile (true);\n     -+\t\twhile (true) {\n     -+\t\t\tbreak;\n     -+\t\t}\n     -+\t\tfor (int i = 0; i < 10; ++i)\n     -+\t\t{\n     -+\t\t}\n     -+\t\tforeach (int i in Enumerable.Range(0, 10))\n     -+\t\t{\n     -+\t\t}\n     -+\t\tint[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n     -+\t\t\n     -+\t\t// Control\n     -+\t\tif (false)\n     -+\t\t{\n     -+\t\t\treturn \"out\";\n     -+\t\t}\n     -+\t\telse { }\n     -+\t\tif (true) MethodCall(\n     -+\t\t\t);\n     -+\t\telse MethodCall(\n     -+\t\t\t);\n     -+\t\tswitch (\"test\")\n     -+\t\t{\n     -+\t\t\tcase \"one\":\n     -+\t\t\t\treturn MethodCall(\n     -+\t\t\t\t\t);\n     -+\t\t\tcase \"two\":\n     -+\t\t\t\tbreak;\n     -+\t\t}\n     -+\t\t(int, int) tuple = (1, 4);\n     -+\t\tswitch (tuple)\n     -+\t\t{\n     -+\t\t\tcase (1, 4):\n     -+\t\t\t\tMethodCall();\n     -+\t\t}\n     -+\t\t\n     -+\t\t// Exceptions\n     -+\t\ttry\n     -+\t\t{\n     -+\t\t\tthrow new Exception(\"fail\");\n     -+\t\t}\n     -+\t\tcatch (Exception)\n     -+\t\t{\n     -+\t\t}\n     -+\t\tfinally\n     -+\t\t{\n     -+\t\t}\n     -+\t\ttry { } catch (Exception) {}\n     -+\t\ttry\n     -+\t\t{\n     -+\t\t\tthrow GetException(\n     -+\t\t\t\t);\n     -+\t\t}\n     -+\t\tcatch (Exception) { }\n     -+\t\t\n     -+\t\t// Others\n     -+\t\tlock (this)\n     -+\t\t{\n     -+\t\t}\n     -+\t\tunsafe\n     -+\t\t{\n     -+\t\t\tbyte[] bytes = [1, 2, 3];\n     -+\t\t\tfixed (byte* pointerToFirst = bytes)\n     -+\t\t\t{\n     -+\t\t\t}\n     -+\t\t}\n     -+\t\t\n     -+\t\treturn \"ChangeMe\";\n     -+\t}\n     -+\t\n     -+\tpublic void Dispose() {}\n     -+\t\n     -+\tstring MethodCall(int a = 0, int b = 0) => \"test\";\n     -+\tException GetException() => new Exception(\"fail\");\n     -+\tint[] Numbers() => [0, 1];\n     ++    string Method(int RIGHT)\n     ++    {\n     ++        // Method calls\n     ++        MethodCall();\n     ++        MethodCall(1, 2);\n     ++        MethodCall(\n     ++            1, 2);\n     ++\n     ++        // Assignments\n     ++        var constantAssignment = \"test\";\n     ++        var methodAssignment = MethodCall();\n     ++        var multiLineMethodAssignment = MethodCall(\n     ++        );\n     ++        var multiLine = \"first\"\n     ++            + MethodCall()\n     ++            - MethodCall()\n     ++            + MethodCall();\n     ++\n     ++        // Initializations/disposal\n     ++        new Example();\n     ++        new Example(\n     ++            );\n     ++        new Example { };\n     ++        using (this)\n     ++        {\n     ++        }\n     ++        var def =\n     ++            this is default(\n     ++                Example);\n     ++\n     ++        // Iteration statements\n     ++        do { } while (true);\n     ++        do MethodCall(\n     ++        ); while (true);\n     ++        while (true);\n     ++        while (true) {\n     ++            break;\n     ++        }\n     ++        for (int i = 0; i < 10; ++i)\n     ++        {\n     ++        }\n     ++        foreach (int i in Enumerable.Range(0, 10))\n     ++        {\n     ++        }\n     ++        int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n     ++\n     ++        // Control\n     ++        if (false)\n     ++        {\n     ++            return \"out\";\n     ++        }\n     ++        else { }\n     ++        if (true) MethodCall(\n     ++        );\n     ++        else MethodCall(\n     ++        );\n     ++        switch (\"test\")\n     ++        {\n     ++            case \"one\":\n     ++            return MethodCall(\n     ++            );\n     ++            case \"two\":\n     ++            break;\n     ++        }\n     ++        (int, int) tuple = (1, 4);\n     ++        switch (tuple)\n     ++        {\n     ++            case (1, 4):\n     ++            MethodCall();\n     ++        }\n     ++\n     ++        // Exceptions\n     ++        try\n     ++        {\n     ++            throw new Exception(\"fail\");\n     ++        }\n     ++        catch (Exception)\n     ++        {\n     ++        }\n     ++        finally\n     ++        {\n     ++        }\n     ++        try { } catch (Exception) {}\n     ++        try\n     ++        {\n     ++            throw GetException(\n     ++            );\n     ++        }\n     ++        catch (Exception) { }\n     ++\n     ++        // Others\n     ++        lock (this)\n     ++        {\n     ++        }\n     ++        unsafe\n     ++        {\n     ++            byte[] bytes = [1, 2, 3];\n     ++            fixed (byte* pointerToFirst = bytes)\n     ++            {\n     ++            }\n     ++        }\n     ++\n     ++        return \"ChangeMe\";\n     ++    }\n     ++\n     ++    // Supporting methods to make the tested method above valid C#.\n     ++    public void Dispose() {}\n     ++    string MethodCall(int a = 0, int b = 0) => \"test\";\n     ++    Exception GetException() => new Exception(\"fail\");\n     ++    int[] Numbers() => [0, 1];\n      +}\n      \n       ## t/t4018/csharp-method-special-chars (new) ##\n      @@\n      +class @Some_Type\n      +{\n     -+\t@Some_Type @Method_With_Underscore(int RIGHT)\n     -+\t{\n     -+\t\t// Filler\n     -+\t\t// Filler\n     -+\t\t\n     -+\t\t// ChangeMe\n     -+\t\treturn new @Some_Type();\n     -+\t}\n     ++    @Some_Type @Method_With_Underscore(int RIGHT)\n     ++    {\n     ++        // Filler\n     ++        // Filler\n     ++        \n     ++        // ChangeMe\n     ++        return new @Some_Type();\n     ++    }\n      +}\n      \n       ## t/t4018/csharp-method-with-spacing (new) ##\n     @@ t/t4018/csharp-method-with-spacing (new)\n      +{\n      +\t\tstring   Method \t( int \tRIGHT )\n      +\t{\n     -+\t\t// Filler\n     -+\t\t// Filler\n     ++        // Filler\n     ++        // Filler\n      +\t\t\n     -+\t\treturn \"ChangeMe\";\n     -+\t}\n     ++        return \"ChangeMe\";\n     ++    }\n      +}\n      \n       ## t/t4018/csharp-property (new) ##\n      @@\n      +class Example\n      +{\n     -+\tpublic bool RIGHT\n     ++    public bool RIGHT\n      +    {\n      +        get { return true; }\n      +        set\n     @@ t/t4018/csharp-property (new)\n      +            // ChangeMe\n      +        }\n      +    }\n     ++}\n     +\n     + ## t/t4018/csharp-property-skip-body (new) ##\n     +@@\n     ++using System.Linq;\n     ++using System;\n     ++\n     ++class Example : IDisposable\n     ++{\n     ++    public string RIGHT\n     ++    {\n     ++        get\n     ++        {\n     ++            // Method calls\n     ++            MethodCall();\n     ++            MethodCall(1, 2);\n     ++            MethodCall(\n     ++                1, 2);\n     ++\n     ++            // Assignments\n     ++            var constantAssignment = \"test\";\n     ++            var methodAssignment = MethodCall();\n     ++            var multiLineMethodAssignment = MethodCall(\n     ++                );\n     ++            var multiLine = \"first\"\n     ++                + MethodCall()\n     ++                - MethodCall()\n     ++                + MethodCall();\n     ++\n     ++            // Initializations/disposal\n     ++            new Example();\n     ++            new Example(\n     ++                );\n     ++            new Example { };\n     ++            using (this)\n     ++            {\n     ++            }\n     ++            var def =\n     ++                this is default(\n     ++                    Example);\n     ++\n     ++            // Iteration statements\n     ++            do { } while (true);\n     ++            do MethodCall(\n     ++            ); while (true);\n     ++            while (true);\n     ++            while (true) {\n     ++                break;\n     ++            }\n     ++            for (int i = 0; i < 10; ++i)\n     ++            {\n     ++            }\n     ++            foreach (int i in Enumerable.Range(0, 10))\n     ++            {\n     ++            }\n     ++            int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n     ++\n     ++            // Control\n     ++            if (false)\n     ++            {\n     ++                return \"out\";\n     ++            }\n     ++            else { }\n     ++            if (true) MethodCall(\n     ++            );\n     ++            else MethodCall(\n     ++            );\n     ++            switch (\"test\")\n     ++            {\n     ++                case \"one\":\n     ++                return MethodCall(\n     ++                );\n     ++                case \"two\":\n     ++                break;\n     ++            }\n     ++            (int, int) tuple = (1, 4);\n     ++            switch (tuple)\n     ++            {\n     ++                case (1, 4):\n     ++                MethodCall();\n     ++            }\n     ++\n     ++            // Exceptions\n     ++            try\n     ++            {\n     ++                throw new Exception(\"fail\");\n     ++            }\n     ++            catch (Exception)\n     ++            {\n     ++            }\n     ++            finally\n     ++            {\n     ++            }\n     ++            try { } catch (Exception) {}\n     ++            try\n     ++            {\n     ++                throw GetException(\n     ++                );\n     ++            }\n     ++            catch (Exception) { }\n     ++\n     ++            // Others\n     ++            lock (this)\n     ++            {\n     ++            }\n     ++            unsafe\n     ++            {\n     ++                byte[] bytes = [1, 2, 3];\n     ++                fixed (byte* pointerToFirst = bytes)\n     ++                {\n     ++                }\n     ++            }\n     ++\n     ++            return \"ChangeMe\";\n     ++        }\n     ++        set { }\n     ++    }\n     ++\n     ++    // Supporting methods to make the tested property above valid C#.\n     ++    public void Dispose() {}\n     ++    string MethodCall(int a = 0, int b = 0) => \"test\";\n     ++    Exception GetException() => new Exception(\"fail\");\n     ++    int[] Numbers() => [0, 1];\n      +}\n      \n       ## userdiff.c ##\n     @@ userdiff.c: PATTERNS(\"cpp\",\n      -\t \"!^[ \\t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\\n\"\n      -\t /* Methods and constructors */\n      -\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[<>@._[:alnum:]]+[ \\t]*\\\\(.*\\\\))[ \\t]*$\\n\"\n     +-\t /* Properties */\n     +-\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[@._[:alnum:]]+)[ \\t]*$\\n\"\n      +\t /*\n     -+\t  * Jump over keywords not used by methods which can be followed by parentheses without special characters in between,\n     ++\t  * Jump over reserved keywords which are illegal method names, but which\n     ++\t  * can be followed by parentheses without special characters in between,\n      +\t  * making them look like methods.\n      +\t  */\n      +\t \"!(^|[ \\t]+)(do|while|for|foreach|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \\t(]+|$)\\n\"\n     -+\t /* Methods/constructors:\n     -+\t  * the strategy is to identify a minimum of two groups (any combination of keywords/type/name),\n     -+\t  * without intermediate or final characters which can't be part of method definitions before the opening parenthesis.\n     ++\t /*\n     ++\t  * Methods/constructors:\n     ++\t  * The strategy is to identify a minimum of two groups (any combination\n     ++\t  * of keywords/type/name), without intermediate characters which can't be\n     ++\t  * part of method definitions before the opening parenthesis, and without\n     ++\t  * final unexpected characters, normally only used in ordinary statements.\n     ++\t  * \"=\" is excluded to ignore assignments, but as a result rules out\n     ++\t  * methods with expression bodies. However, since those fit on 1-2 lines,\n     ++\t  * a chunk header isn't useful either way.\n      +\t  */\n     -+\t \"^[ \\t]*(([][[:alnum:]@_<>.,]*[^=:{ \\t][ \\t]+[][[:alnum:]@_<>.,]*)+\\\\([^;]*)$\\n\"\n     - \t /* Properties */\n     --\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[@._[:alnum:]]+)[ \\t]*$\\n\"\n     -+\t \"^[ \\t]*((([][[:alnum:]@_<>.,]+)[ \\t]+[][[:alnum:]@_]*)+[^=:;,()]*)$\\n\"\n     ++\t \"^[ \\t]*(([][[:alnum:]@_<>.,]*[^=:{ \\t+*%\\\\/\\\\-][ \\t]+[][[:alnum:]@_<>.,]*)+\\\\([^;]*)$\\n\"\n     ++\t /*\n     ++\t  * Properties:\n     ++\t  * As with methods, expect a minimum of two groups. And, the vast majority\n     ++\t  * of properties long enough to be worth showing a chunk header for don't\n     ++\t  * include \"=:;,()\" on the line they are defined.\n     ++\t  */\n     ++\t \"^[ \\t]*([][[:alnum:]@_<>.,]+[ \\t]+[][[:alnum:]@_.]+[^=:;,()]*)$\\n\"\n       \t /* Type definitions */\n       \t \"^[ \\t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \\t]+)*(class|enum|interface|struct|record)[ \\t]+.*)$\\n\"\n       \t /* Namespace */\n\n\n t/t4018/csharp-method               |  10 +++\n t/t4018/csharp-method-explicit      |  12 +++\n t/t4018/csharp-method-generics      |  11 +++\n t/t4018/csharp-method-modifiers     |  13 +++\n t/t4018/csharp-method-multiline     |  10 +++\n t/t4018/csharp-method-params        |  10 +++\n t/t4018/csharp-method-skip-body     | 116 +++++++++++++++++++++++++++\n t/t4018/csharp-method-special-chars |  11 +++\n t/t4018/csharp-method-with-spacing  |  10 +++\n t/t4018/csharp-property             |  11 +++\n t/t4018/csharp-property-skip-body   | 120 ++++++++++++++++++++++++++++\n userdiff.c                          |  30 +++++--\n 12 files changed, 358 insertions(+), 6 deletions(-)\n create mode 100644 t/t4018/csharp-method\n create mode 100644 t/t4018/csharp-method-explicit\n create mode 100644 t/t4018/csharp-method-generics\n create mode 100644 t/t4018/csharp-method-modifiers\n create mode 100644 t/t4018/csharp-method-multiline\n create mode 100644 t/t4018/csharp-method-params\n create mode 100644 t/t4018/csharp-method-skip-body\n create mode 100644 t/t4018/csharp-method-special-chars\n create mode 100644 t/t4018/csharp-method-with-spacing\n create mode 100644 t/t4018/csharp-property\n create mode 100644 t/t4018/csharp-property-skip-body\n\ndiff --git a/t/t4018/csharp-method b/t/t4018/csharp-method\nnew file mode 100644\nindex 00000000000..16b367aca2b\n--- /dev/null\n+++ b/t/t4018/csharp-method\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        // Filler\n+        // Filler\n+\n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-explicit b/t/t4018/csharp-method-explicit\nnew file mode 100644\nindex 00000000000..5a710116cc4\n--- /dev/null\n+++ b/t/t4018/csharp-method-explicit\n@@ -0,0 +1,12 @@\n+using System;\n+\n+class Example : IDisposable\n+{\n+    void IDisposable.Dispose() // RIGHT\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-generics b/t/t4018/csharp-method-generics\nnew file mode 100644\nindex 00000000000..b3216bfb2a7\n--- /dev/null\n+++ b/t/t4018/csharp-method-generics\n@@ -0,0 +1,11 @@\n+class Example<T1, T2>\n+{\n+    Example<int, string> Method<TA, TB>(TA RIGHT, TB b)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+        return null;\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-modifiers b/t/t4018/csharp-method-modifiers\nnew file mode 100644\nindex 00000000000..caefa8ee99c\n--- /dev/null\n+++ b/t/t4018/csharp-method-modifiers\n@@ -0,0 +1,13 @@\n+using System.Threading.Tasks;\n+\n+class Example\n+{\n+    static internal async Task Method(int RIGHT)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+        await Task.Delay(1);\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-multiline b/t/t4018/csharp-method-multiline\nnew file mode 100644\nindex 00000000000..3983ff42f51\n--- /dev/null\n+++ b/t/t4018/csharp-method-multiline\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    string Method_RIGHT(\n+        int a,\n+        int b,\n+        int c)\n+    {\n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-params b/t/t4018/csharp-method-params\nnew file mode 100644\nindex 00000000000..3f00410ba1f\n--- /dev/null\n+++ b/t/t4018/csharp-method-params\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    string Method(int RIGHT, int b, int c = 42)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-skip-body b/t/t4018/csharp-method-skip-body\nnew file mode 100644\nindex 00000000000..b3c7194eb74\n--- /dev/null\n+++ b/t/t4018/csharp-method-skip-body\n@@ -0,0 +1,116 @@\n+using System.Linq;\n+using System;\n+\n+class Example : IDisposable\n+{\n+    string Method(int RIGHT)\n+    {\n+        // Method calls\n+        MethodCall();\n+        MethodCall(1, 2);\n+        MethodCall(\n+            1, 2);\n+\n+        // Assignments\n+        var constantAssignment = \"test\";\n+        var methodAssignment = MethodCall();\n+        var multiLineMethodAssignment = MethodCall(\n+        );\n+        var multiLine = \"first\"\n+            + MethodCall()\n+            - MethodCall()\n+            + MethodCall();\n+\n+        // Initializations/disposal\n+        new Example();\n+        new Example(\n+            );\n+        new Example { };\n+        using (this)\n+        {\n+        }\n+        var def =\n+            this is default(\n+                Example);\n+\n+        // Iteration statements\n+        do { } while (true);\n+        do MethodCall(\n+        ); while (true);\n+        while (true);\n+        while (true) {\n+            break;\n+        }\n+        for (int i = 0; i < 10; ++i)\n+        {\n+        }\n+        foreach (int i in Enumerable.Range(0, 10))\n+        {\n+        }\n+        int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n+\n+        // Control\n+        if (false)\n+        {\n+            return \"out\";\n+        }\n+        else { }\n+        if (true) MethodCall(\n+        );\n+        else MethodCall(\n+        );\n+        switch (\"test\")\n+        {\n+            case \"one\":\n+            return MethodCall(\n+            );\n+            case \"two\":\n+            break;\n+        }\n+        (int, int) tuple = (1, 4);\n+        switch (tuple)\n+        {\n+            case (1, 4):\n+            MethodCall();\n+        }\n+\n+        // Exceptions\n+        try\n+        {\n+            throw new Exception(\"fail\");\n+        }\n+        catch (Exception)\n+        {\n+        }\n+        finally\n+        {\n+        }\n+        try { } catch (Exception) {}\n+        try\n+        {\n+            throw GetException(\n+            );\n+        }\n+        catch (Exception) { }\n+\n+        // Others\n+        lock (this)\n+        {\n+        }\n+        unsafe\n+        {\n+            byte[] bytes = [1, 2, 3];\n+            fixed (byte* pointerToFirst = bytes)\n+            {\n+            }\n+        }\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    // Supporting methods to make the tested method above valid C#.\n+    public void Dispose() {}\n+    string MethodCall(int a = 0, int b = 0) => \"test\";\n+    Exception GetException() => new Exception(\"fail\");\n+    int[] Numbers() => [0, 1];\n+}\ndiff --git a/t/t4018/csharp-method-special-chars b/t/t4018/csharp-method-special-chars\nnew file mode 100644\nindex 00000000000..e6c7bc01a18\n--- /dev/null\n+++ b/t/t4018/csharp-method-special-chars\n@@ -0,0 +1,11 @@\n+class @Some_Type\n+{\n+    @Some_Type @Method_With_Underscore(int RIGHT)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+        return new @Some_Type();\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-with-spacing b/t/t4018/csharp-method-with-spacing\nnew file mode 100644\nindex 00000000000..da0c9b7e60c\n--- /dev/null\n+++ b/t/t4018/csharp-method-with-spacing\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+\t\tstring   Method \t( int \tRIGHT )\n+\t{\n+        // Filler\n+        // Filler\n+\t\t\n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-property b/t/t4018/csharp-property\nnew file mode 100644\nindex 00000000000..e56dfce34c1\n--- /dev/null\n+++ b/t/t4018/csharp-property\n@@ -0,0 +1,11 @@\n+class Example\n+{\n+    public bool RIGHT\n+    {\n+        get { return true; }\n+        set\n+        {\n+            // ChangeMe\n+        }\n+    }\n+}\ndiff --git a/t/t4018/csharp-property-skip-body b/t/t4018/csharp-property-skip-body\nnew file mode 100644\nindex 00000000000..ad9d96007a8\n--- /dev/null\n+++ b/t/t4018/csharp-property-skip-body\n@@ -0,0 +1,120 @@\n+using System.Linq;\n+using System;\n+\n+class Example : IDisposable\n+{\n+    public string RIGHT\n+    {\n+        get\n+        {\n+            // Method calls\n+            MethodCall();\n+            MethodCall(1, 2);\n+            MethodCall(\n+                1, 2);\n+\n+            // Assignments\n+            var constantAssignment = \"test\";\n+            var methodAssignment = MethodCall();\n+            var multiLineMethodAssignment = MethodCall(\n+                );\n+            var multiLine = \"first\"\n+                + MethodCall()\n+                - MethodCall()\n+                + MethodCall();\n+\n+            // Initializations/disposal\n+            new Example();\n+            new Example(\n+                );\n+            new Example { };\n+            using (this)\n+            {\n+            }\n+            var def =\n+                this is default(\n+                    Example);\n+\n+            // Iteration statements\n+            do { } while (true);\n+            do MethodCall(\n+            ); while (true);\n+            while (true);\n+            while (true) {\n+                break;\n+            }\n+            for (int i = 0; i < 10; ++i)\n+            {\n+            }\n+            foreach (int i in Enumerable.Range(0, 10))\n+            {\n+            }\n+            int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n+\n+            // Control\n+            if (false)\n+            {\n+                return \"out\";\n+            }\n+            else { }\n+            if (true) MethodCall(\n+            );\n+            else MethodCall(\n+            );\n+            switch (\"test\")\n+            {\n+                case \"one\":\n+                return MethodCall(\n+                );\n+                case \"two\":\n+                break;\n+            }\n+            (int, int) tuple = (1, 4);\n+            switch (tuple)\n+            {\n+                case (1, 4):\n+                MethodCall();\n+            }\n+\n+            // Exceptions\n+            try\n+            {\n+                throw new Exception(\"fail\");\n+            }\n+            catch (Exception)\n+            {\n+            }\n+            finally\n+            {\n+            }\n+            try { } catch (Exception) {}\n+            try\n+            {\n+                throw GetException(\n+                );\n+            }\n+            catch (Exception) { }\n+\n+            // Others\n+            lock (this)\n+            {\n+            }\n+            unsafe\n+            {\n+                byte[] bytes = [1, 2, 3];\n+                fixed (byte* pointerToFirst = bytes)\n+                {\n+                }\n+            }\n+\n+            return \"ChangeMe\";\n+        }\n+        set { }\n+    }\n+\n+    // Supporting methods to make the tested property above valid C#.\n+    public void Dispose() {}\n+    string MethodCall(int a = 0, int b = 0) => \"test\";\n+    Exception GetException() => new Exception(\"fail\");\n+    int[] Numbers() => [0, 1];\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex e399543823b..5440ccf2de5 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -89,12 +89,30 @@ PATTERNS(\"cpp\",\n \t \"|\\\\.[0-9][0-9]*([Ee][-+]?[0-9]+)?[fFlL]?\"\n \t \"|[-+*/<>%&^|=!]=|--|\\\\+\\\\+|<<=?|>>=?|&&|\\\\|\\\\||::|->\\\\*?|\\\\.\\\\*|<=>\"),\n PATTERNS(\"csharp\",\n-\t /* Keywords */\n-\t \"!^[ \\t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\\n\"\n-\t /* Methods and constructors */\n-\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[<>@._[:alnum:]]+[ \\t]*\\\\(.*\\\\))[ \\t]*$\\n\"\n-\t /* Properties */\n-\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[@._[:alnum:]]+)[ \\t]*$\\n\"\n+\t /*\n+\t  * Jump over reserved keywords which are illegal method names, but which\n+\t  * can be followed by parentheses without special characters in between,\n+\t  * making them look like methods.\n+\t  */\n+\t \"!(^|[ \\t]+)(do|while|for|foreach|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \\t(]+|$)\\n\"\n+\t /*\n+\t  * Methods/constructors:\n+\t  * The strategy is to identify a minimum of two groups (any combination\n+\t  * of keywords/type/name), without intermediate characters which can't be\n+\t  * part of method definitions before the opening parenthesis, and without\n+\t  * final unexpected characters, normally only used in ordinary statements.\n+\t  * \"=\" is excluded to ignore assignments, but as a result rules out\n+\t  * methods with expression bodies. However, since those fit on 1-2 lines,\n+\t  * a chunk header isn't useful either way.\n+\t  */\n+\t \"^[ \\t]*(([][[:alnum:]@_<>.,]*[^=:{ \\t+*%\\\\/\\\\-][ \\t]+[][[:alnum:]@_<>.,]*)+\\\\([^;]*)$\\n\"\n+\t /*\n+\t  * Properties:\n+\t  * As with methods, expect a minimum of two groups. And, the vast majority\n+\t  * of properties long enough to be worth showing a chunk header for don't\n+\t  * include \"=:;,()\" on the line they are defined.\n+\t  */\n+\t \"^[ \\t]*([][[:alnum:]@_<>.,]+[ \\t]+[][[:alnum:]@_.]+[^=:;,()]*)$\\n\"\n \t /* Type definitions */\n \t \"^[ \\t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \\t]+)*(class|enum|interface|struct|record)[ \\t]+.*)$\\n\"\n \t /* Namespace */\n\nbase-commit: f41f85c9ec8d4d46de0fd5fded88db94d3ec8c11\n-- \ngitgitgadget\n"},{"id":"491779","messageId":"pull.1682.v4.git.git.1711653257043.gitgitgadget@gmail.com","threadId":"60993","inReplyTo":"pull.1682.v3.git.git.1711613220277.gitgitgadget@gmail.com","subject":"[PATCH v4] userdiff: better method/property matching for C#","fromName":"Steven Jeuris via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-28T19:14:16Z","receivedAt":"2024-03-28T19:14:21Z","isPatch":true,"sender":{"key":"name:Steven Jeuris","avatar":null},"body":"From: Steven Jeuris <steven.jeuris@3shape.com>\n\n- Support multi-line methods by not requiring closing parenthesis.\n- Support multiple generics (comma was missing before).\n- Add missing `foreach`, `lock` and  `fixed` keywords to skip over.\n- Remove `instanceof` keyword, which isn't C#.\n- Also detect non-method keywords not positioned at the start of a line.\n- Added tests; none existed before.\n\nThe overall strategy is to focus more on what isn't expected for\nmethod/property definitions, instead of what is, but is fully optional.\n\nSigned-off-by: Steven Jeuris <steven.jeuris@gmail.com>\n---\n    userdiff: better method/property matching for C#\n    \n    Change since v1: I removed \"from\" from the list of keywords to skip.\n    First, I considered adding \"await\", but I discovered both \"await\" and\n    \"from\" are \"contextual keywords\", which unlike the other keywords\n    currently listed, aren't reserved, and can thus cause false negatives.\n    I.e., it is valid to have a method named \"await\" or \"from\". In edge\n    cases, this may lead to false positives, but a different exclusion rule\n    will need to be added to handle these.\n    \n    Change since v2:\n    \n     * Corrected comment formatting.\n     * Added csharp-property-skip-body test.\n     * Added comments in test code to explain sections not part of the test.\n     * Elaborated regex comments.\n     * Excluded math operators (+-*/%) in method pattern to not catch\n       multiline operations, and tested for this in the -skip-body tests.\n       Catching \"-\" only worked when it was defined at the end of the\n       exclusion block for some reason. The regex matcher seems quite\n       bugged.\n    \n    Change since v3:\n    \n     * Changed regex to better handle whitespace in types, making use of the\n       fact that it only appears after commas.\n     * Split regex into multiple lines with comments explaining structure.\n     * Split the \"skip body\" tests into more narrow csharp-exclude- tests.\n     * Added a test for generic methods:\n       csharp-exclude-generic-method-calls.\n     * Added a test for array types used in methods: csharp-method-array.\n     * Added an addition property test: csharp-property-braces-same-line.\n     * Included a test for \"( func(x)\" case identified by Johannes in\n       csharp-exclude-assignments.\n    \n    As before, I ran into many regex limitations (no possessive quantifiers,\n    no lookahead). It also seems different regex evaluators are used on\n    different test runs. Which one does git diff use? Maybe it is about time\n    to update this? E.g., if speed is a concern, possessive quantifiers can\n    speed up search.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1682%2FWhathecode%2Fmaster-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1682/Whathecode/master-v4\nPull-Request: https://github.com/git/git/pull/1682\n\nRange-diff vs v3:\n\n 1:  9b6c76f5342 ! 1:  2feb84beaa0 userdiff: better method/property matching for C#\n     @@ Commit message\n      \n          Signed-off-by: Steven Jeuris <steven.jeuris@gmail.com>\n      \n     - ## t/t4018/csharp-method (new) ##\n     + ## t/t4018/csharp-exclude-assignments (new) ##\n      @@\n      +class Example\n      +{\n      +    string Method(int RIGHT)\n      +    {\n     -+        // Filler\n     -+        // Filler\n     ++        var constantAssignment = \"test\";\n     ++        var methodAssignment = MethodCall();\n     ++        var multiLineMethodAssignment = MethodCall(\n     ++        );\n     ++        var multiLine = \"first\"\n     ++            + MethodCall()\n     ++            +\n     ++            ( MethodCall()\n     ++            )\n     ++            + MethodCall();\n      +\n      +        return \"ChangeMe\";\n      +    }\n     -+}\n     -\n     - ## t/t4018/csharp-method-explicit (new) ##\n     -@@\n     -+using System;\n      +\n     -+class Example : IDisposable\n     -+{\n     -+    void IDisposable.Dispose() // RIGHT\n     -+    {\n     -+        // Filler\n     -+        // Filler\n     -+        \n     -+        // ChangeMe\n     -+    }\n     -+}\n     -\n     - ## t/t4018/csharp-method-generics (new) ##\n     -@@\n     -+class Example<T1, T2>\n     -+{\n     -+    Example<int, string> Method<TA, TB>(TA RIGHT, TB b)\n     -+    {\n     -+        // Filler\n     -+        // Filler\n     -+        \n     -+        // ChangeMe\n     -+        return null;\n     -+    }\n     ++    string MethodCall(int a = 0, int b = 0) => \"test\";\n      +}\n      \n     - ## t/t4018/csharp-method-modifiers (new) ##\n     + ## t/t4018/csharp-exclude-control-statements (new) ##\n      @@\n     -+using System.Threading.Tasks;\n     -+\n      +class Example\n      +{\n     -+    static internal async Task Method(int RIGHT)\n     ++    string Method(int RIGHT)\n      +    {\n     -+        // Filler\n     -+        // Filler\n     -+        \n     -+        // ChangeMe\n     -+        await Task.Delay(1);\n     ++        if (false)\n     ++        {\n     ++            return \"out\";\n     ++        }\n     ++        else { }\n     ++        if (true) MethodCall(\n     ++        );\n     ++        else MethodCall(\n     ++        );\n     ++        switch (\"test\")\n     ++        {\n     ++            case \"one\":\n     ++            return MethodCall(\n     ++            );\n     ++            case \"two\":\n     ++            break;\n     ++        }\n     ++        (int, int) tuple = (1, 4);\n     ++        switch (tuple)\n     ++        {\n     ++            case (1, 4):\n     ++              MethodCall();\n     ++\t\t      break;\n     ++        }\n     ++\n     ++        return \"ChangeMe\";\n      +    }\n     ++\n     ++    string MethodCall(int a = 0, int b = 0) => \"test\";\n      +}\n      \n     - ## t/t4018/csharp-method-multiline (new) ##\n     + ## t/t4018/csharp-exclude-exceptions (new) ##\n      @@\n     ++using System;\n     ++\n      +class Example\n      +{\n     -+    string Method_RIGHT(\n     -+        int a,\n     -+        int b,\n     -+        int c)\n     ++    string Method(int RIGHT)\n      +    {\n     ++        try\n     ++        {\n     ++            throw new Exception(\"fail\");\n     ++        }\n     ++        catch (Exception)\n     ++        {\n     ++        }\n     ++        finally\n     ++        {\n     ++        }\n     ++        try { } catch (Exception) {}\n     ++        try\n     ++        {\n     ++            throw GetException(\n     ++            );\n     ++        }\n     ++        catch (Exception) { }\n     ++\n      +        return \"ChangeMe\";\n      +    }\n     ++\n     ++    Exception GetException() => new Exception(\"fail\");\n      +}\n      \n     - ## t/t4018/csharp-method-params (new) ##\n     + ## t/t4018/csharp-exclude-generic-method-calls (new) ##\n      @@\n      +class Example\n      +{\n     -+    string Method(int RIGHT, int b, int c = 42)\n     ++    string Method(int RIGHT)\n      +    {\n     -+        // Filler\n     -+        // Filler\n     -+        \n     ++        GenericMethodCall<int, int>(\n     ++            );\n     ++\n      +        return \"ChangeMe\";\n      +    }\n     ++\n     ++    string GenericMethodCall<T, T2>() => \"test\";\n      +}\n      \n     - ## t/t4018/csharp-method-skip-body (new) ##\n     + ## t/t4018/csharp-exclude-init-dispose (new) ##\n      @@\n     -+using System.Linq;\n      +using System;\n      +\n      +class Example : IDisposable\n      +{\n      +    string Method(int RIGHT)\n      +    {\n     -+        // Method calls\n     -+        MethodCall();\n     -+        MethodCall(1, 2);\n     -+        MethodCall(\n     -+            1, 2);\n     -+\n     -+        // Assignments\n     -+        var constantAssignment = \"test\";\n     -+        var methodAssignment = MethodCall();\n     -+        var multiLineMethodAssignment = MethodCall(\n     -+        );\n     -+        var multiLine = \"first\"\n     -+            + MethodCall()\n     -+            - MethodCall()\n     -+            + MethodCall();\n     -+\n     -+        // Initializations/disposal\n      +        new Example();\n      +        new Example(\n      +            );\n     @@ t/t4018/csharp-method-skip-body (new)\n      +            this is default(\n      +                Example);\n      +\n     -+        // Iteration statements\n     ++        return \"ChangeMe\";\n     ++    }\n     ++\n     ++    public void Dispose() {}\n     ++}\n     +\n     + ## t/t4018/csharp-exclude-iterations (new) ##\n     +@@\n     ++using System.Linq;\n     ++\n     ++class Example\n     ++{\n     ++    string Method(int RIGHT)\n     ++    {\n      +        do { } while (true);\n      +        do MethodCall(\n      +        ); while (true);\n     @@ t/t4018/csharp-method-skip-body (new)\n      +        }\n      +        int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n      +\n     -+        // Control\n     -+        if (false)\n     -+        {\n     -+            return \"out\";\n     -+        }\n     -+        else { }\n     -+        if (true) MethodCall(\n     -+        );\n     -+        else MethodCall(\n     -+        );\n     -+        switch (\"test\")\n     -+        {\n     -+            case \"one\":\n     -+            return MethodCall(\n     -+            );\n     -+            case \"two\":\n     -+            break;\n     -+        }\n     -+        (int, int) tuple = (1, 4);\n     -+        switch (tuple)\n     -+        {\n     -+            case (1, 4):\n     -+            MethodCall();\n     -+        }\n     ++        return \"ChangeMe\";\n     ++    }\n      +\n     -+        // Exceptions\n     -+        try\n     -+        {\n     -+            throw new Exception(\"fail\");\n     -+        }\n     -+        catch (Exception)\n     -+        {\n     -+        }\n     -+        finally\n     -+        {\n     -+        }\n     -+        try { } catch (Exception) {}\n     -+        try\n     -+        {\n     -+            throw GetException(\n     -+            );\n     -+        }\n     -+        catch (Exception) { }\n     ++    string MethodCall(int a = 0, int b = 0) => \"test\";\n     ++}\n     +\n     + ## t/t4018/csharp-exclude-method-calls (new) ##\n     +@@\n     ++class Example\n     ++{\n     ++    string Method(int RIGHT)\n     ++    {\n     ++        MethodCall();\n     ++        MethodCall(1, 2);\n     ++        MethodCall(\n     ++            1, 2);\n      +\n     -+        // Others\n     ++        return \"ChangeMe\";\n     ++    }\n     ++\n     ++    string MethodCall(int a = 0, int b = 0) => \"test\";\n     ++    string GenericMethodCall<T, T2>() => \"test\";\n     ++}\n     +\n     + ## t/t4018/csharp-exclude-other (new) ##\n     +@@\n     ++class Example\n     ++{\n     ++    string Method(int RIGHT)\n     ++    {\n      +        lock (this)\n      +        {\n      +        }\n     @@ t/t4018/csharp-method-skip-body (new)\n      +\n      +        return \"ChangeMe\";\n      +    }\n     ++}\n     +\n     + ## t/t4018/csharp-method (new) ##\n     +@@\n     ++class Example\n     ++{\n     ++    string Method(int RIGHT)\n     ++    {\n     ++        // Filler\n     ++        // Filler\n      +\n     -+    // Supporting methods to make the tested method above valid C#.\n     -+    public void Dispose() {}\n     -+    string MethodCall(int a = 0, int b = 0) => \"test\";\n     -+    Exception GetException() => new Exception(\"fail\");\n     -+    int[] Numbers() => [0, 1];\n     ++        return \"ChangeMe\";\n     ++    }\n     ++}\n     +\n     + ## t/t4018/csharp-method-array (new) ##\n     +@@\n     ++class Example\n     ++{\n     ++    string[] Method(int RIGHT)\n     ++    {\n     ++        // Filler\n     ++        // Filler\n     ++\n     ++        return [\"ChangeMe\"];\n     ++    }\n     ++}\n     +\n     + ## t/t4018/csharp-method-explicit (new) ##\n     +@@\n     ++using System;\n     ++\n     ++class Example : IDisposable\n     ++{\n     ++    void IDisposable.Dispose() // RIGHT\n     ++    {\n     ++        // Filler\n     ++        // Filler\n     ++        \n     ++        // ChangeMe\n     ++    }\n     ++}\n     +\n     + ## t/t4018/csharp-method-generics (new) ##\n     +@@\n     ++class Example<T1, T2>\n     ++{\n     ++    Example<int, string> Method<TA, TB>(TA RIGHT, TB b)\n     ++    {\n     ++        // Filler\n     ++        // Filler\n     ++        \n     ++        // ChangeMe\n     ++        return null;\n     ++    }\n     ++}\n     +\n     + ## t/t4018/csharp-method-modifiers (new) ##\n     +@@\n     ++using System.Threading.Tasks;\n     ++\n     ++class Example\n     ++{\n     ++    static internal async Task Method(int RIGHT)\n     ++    {\n     ++        // Filler\n     ++        // Filler\n     ++        \n     ++        // ChangeMe\n     ++        await Task.Delay(1);\n     ++    }\n     ++}\n     +\n     + ## t/t4018/csharp-method-multiline (new) ##\n     +@@\n     ++class Example\n     ++{\n     ++    string Method_RIGHT(\n     ++        int a,\n     ++        int b,\n     ++        int c)\n     ++    {\n     ++        return \"ChangeMe\";\n     ++    }\n     ++}\n     +\n     + ## t/t4018/csharp-method-params (new) ##\n     +@@\n     ++class Example\n     ++{\n     ++    string Method(int RIGHT, int b, int c = 42)\n     ++    {\n     ++        // Filler\n     ++        // Filler\n     ++        \n     ++        return \"ChangeMe\";\n     ++    }\n      +}\n      \n       ## t/t4018/csharp-method-special-chars (new) ##\n     @@ t/t4018/csharp-method-with-spacing (new)\n      @@\n      +class Example\n      +{\n     -+\t\tstring   Method \t( int \tRIGHT )\n     -+\t{\n     ++    \tstring   Method \t( int \tRIGHT )\n     ++    {\n      +        // Filler\n      +        // Filler\n     -+\t\t\n     ++\n      +        return \"ChangeMe\";\n      +    }\n      +}\n     @@ t/t4018/csharp-property (new)\n      +    }\n      +}\n      \n     - ## t/t4018/csharp-property-skip-body (new) ##\n     + ## t/t4018/csharp-property-braces-same-line (new) ##\n      @@\n     -+using System.Linq;\n     -+using System;\n     -+\n     -+class Example : IDisposable\n     ++class Example\n      +{\n     -+    public string RIGHT\n     -+    {\n     -+        get\n     ++    public bool RIGHT {\n     ++        get { return true; }\n     ++        set\n      +        {\n     -+            // Method calls\n     -+            MethodCall();\n     -+            MethodCall(1, 2);\n     -+            MethodCall(\n     -+                1, 2);\n     -+\n     -+            // Assignments\n     -+            var constantAssignment = \"test\";\n     -+            var methodAssignment = MethodCall();\n     -+            var multiLineMethodAssignment = MethodCall(\n     -+                );\n     -+            var multiLine = \"first\"\n     -+                + MethodCall()\n     -+                - MethodCall()\n     -+                + MethodCall();\n     -+\n     -+            // Initializations/disposal\n     -+            new Example();\n     -+            new Example(\n     -+                );\n     -+            new Example { };\n     -+            using (this)\n     -+            {\n     -+            }\n     -+            var def =\n     -+                this is default(\n     -+                    Example);\n     -+\n     -+            // Iteration statements\n     -+            do { } while (true);\n     -+            do MethodCall(\n     -+            ); while (true);\n     -+            while (true);\n     -+            while (true) {\n     -+                break;\n     -+            }\n     -+            for (int i = 0; i < 10; ++i)\n     -+            {\n     -+            }\n     -+            foreach (int i in Enumerable.Range(0, 10))\n     -+            {\n     -+            }\n     -+            int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n     -+\n     -+            // Control\n     -+            if (false)\n     -+            {\n     -+                return \"out\";\n     -+            }\n     -+            else { }\n     -+            if (true) MethodCall(\n     -+            );\n     -+            else MethodCall(\n     -+            );\n     -+            switch (\"test\")\n     -+            {\n     -+                case \"one\":\n     -+                return MethodCall(\n     -+                );\n     -+                case \"two\":\n     -+                break;\n     -+            }\n     -+            (int, int) tuple = (1, 4);\n     -+            switch (tuple)\n     -+            {\n     -+                case (1, 4):\n     -+                MethodCall();\n     -+            }\n     -+\n     -+            // Exceptions\n     -+            try\n     -+            {\n     -+                throw new Exception(\"fail\");\n     -+            }\n     -+            catch (Exception)\n     -+            {\n     -+            }\n     -+            finally\n     -+            {\n     -+            }\n     -+            try { } catch (Exception) {}\n     -+            try\n     -+            {\n     -+                throw GetException(\n     -+                );\n     -+            }\n     -+            catch (Exception) { }\n     -+\n     -+            // Others\n     -+            lock (this)\n     -+            {\n     -+            }\n     -+            unsafe\n     -+            {\n     -+                byte[] bytes = [1, 2, 3];\n     -+                fixed (byte* pointerToFirst = bytes)\n     -+                {\n     -+                }\n     -+            }\n     -+\n     -+            return \"ChangeMe\";\n     ++            // ChangeMe\n      +        }\n     -+        set { }\n      +    }\n     -+\n     -+    // Supporting methods to make the tested property above valid C#.\n     -+    public void Dispose() {}\n     -+    string MethodCall(int a = 0, int b = 0) => \"test\";\n     -+    Exception GetException() => new Exception(\"fail\");\n     -+    int[] Numbers() => [0, 1];\n      +}\n      \n       ## userdiff.c ##\n     @@ userdiff.c: PATTERNS(\"cpp\",\n      +\t  * can be followed by parentheses without special characters in between,\n      +\t  * making them look like methods.\n      +\t  */\n     -+\t \"!(^|[ \\t]+)(do|while|for|foreach|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \\t(]+|$)\\n\"\n     ++\t \"!(^|[ \\t]+)\" /* Start of line or whitespace. */\n     ++\t\t\"(do|while|for|foreach|if|else|new|default|return|switch|case|throw\"\n     ++\t\t\"|catch|using|lock|fixed)\"\n     ++\t\t\"([ \\t(]+|$)\\n\" /* Whitespace, \"(\", or end of line. */\n      +\t /*\n      +\t  * Methods/constructors:\n      +\t  * The strategy is to identify a minimum of two groups (any combination\n     -+\t  * of keywords/type/name), without intermediate characters which can't be\n     -+\t  * part of method definitions before the opening parenthesis, and without\n     ++\t  * of keywords/type/name) before the opening parenthesis, and without\n      +\t  * final unexpected characters, normally only used in ordinary statements.\n     -+\t  * \"=\" is excluded to ignore assignments, but as a result rules out\n     -+\t  * methods with expression bodies. However, since those fit on 1-2 lines,\n     -+\t  * a chunk header isn't useful either way.\n      +\t  */\n     -+\t \"^[ \\t]*(([][[:alnum:]@_<>.,]*[^=:{ \\t+*%\\\\/\\\\-][ \\t]+[][[:alnum:]@_<>.,]*)+\\\\([^;]*)$\\n\"\n     ++\t \"^[ \\t]*\" /* Remove leading whitespace. */\n     ++\t\t\"(\" /* Start chunk header capture. */\n     ++\t\t\"(\" /* Group of keywords/type/names. */\n     ++\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+\" /* Space only allowed after \",\". */\n     ++\t\t\"[ \\t]+\" /* One required space forces a minimum of two items. */\n     ++\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+\"\n     ++\t\t\"[ \\t]*\" /* Optional space before parameters start. */\n     ++\t\t\")+\"\n     ++\t\t\"\\\\(\" /* Start of method parameters. */\n     ++\t\t\"[^;]*\" /* Allow complex parameters, but exclude statements (;). */\n     ++\t\t\")$\\n\" /* Close chunk header capture. */\n      +\t /*\n      +\t  * Properties:\n     -+\t  * As with methods, expect a minimum of two groups. And, the vast majority\n     -+\t  * of properties long enough to be worth showing a chunk header for don't\n     -+\t  * include \"=:;,()\" on the line they are defined.\n     ++\t  * As with methods, expect a minimum of two groups. But, more trivial than\n     ++\t  * methods, the vast majority of properties long enough to be worth\n     ++\t  * showing a chunk header for don't include \"=:;,()\" on the line they are\n     ++\t  * defined, since they don't have a parameter list.\n      +\t  */\n     -+\t \"^[ \\t]*([][[:alnum:]@_<>.,]+[ \\t]+[][[:alnum:]@_.]+[^=:;,()]*)$\\n\"\n     ++\t \"^[ \\t]*(\"\n     ++\t\t\"(\"\n     ++\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+[ \\t]+\"\n     ++\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+[ \\t]*\"\n     ++\t\t\")+\" /* Up to here, same as methods regex. */\n     ++\t\t\"[^;=:,()]*\" /* Compared to methods, no parameter list allowed. */\n     ++\t\t\")$\\n\"\n       \t /* Type definitions */\n       \t \"^[ \\t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \\t]+)*(class|enum|interface|struct|record)[ \\t]+.*)$\\n\"\n       \t /* Namespace */\n\n\n t/t4018/csharp-exclude-assignments          | 20 +++++++++\n t/t4018/csharp-exclude-control-statements   | 34 +++++++++++++++\n t/t4018/csharp-exclude-exceptions           | 29 +++++++++++++\n t/t4018/csharp-exclude-generic-method-calls | 12 ++++++\n t/t4018/csharp-exclude-init-dispose         | 22 ++++++++++\n t/t4018/csharp-exclude-iterations           | 26 ++++++++++++\n t/t4018/csharp-exclude-method-calls         | 15 +++++++\n t/t4018/csharp-exclude-other                | 18 ++++++++\n t/t4018/csharp-method                       | 10 +++++\n t/t4018/csharp-method-array                 | 10 +++++\n t/t4018/csharp-method-explicit              | 12 ++++++\n t/t4018/csharp-method-generics              | 11 +++++\n t/t4018/csharp-method-modifiers             | 13 ++++++\n t/t4018/csharp-method-multiline             | 10 +++++\n t/t4018/csharp-method-params                | 10 +++++\n t/t4018/csharp-method-special-chars         | 11 +++++\n t/t4018/csharp-method-with-spacing          | 10 +++++\n t/t4018/csharp-property                     | 11 +++++\n t/t4018/csharp-property-braces-same-line    | 10 +++++\n userdiff.c                                  | 46 ++++++++++++++++++---\n 20 files changed, 334 insertions(+), 6 deletions(-)\n create mode 100644 t/t4018/csharp-exclude-assignments\n create mode 100644 t/t4018/csharp-exclude-control-statements\n create mode 100644 t/t4018/csharp-exclude-exceptions\n create mode 100644 t/t4018/csharp-exclude-generic-method-calls\n create mode 100644 t/t4018/csharp-exclude-init-dispose\n create mode 100644 t/t4018/csharp-exclude-iterations\n create mode 100644 t/t4018/csharp-exclude-method-calls\n create mode 100644 t/t4018/csharp-exclude-other\n create mode 100644 t/t4018/csharp-method\n create mode 100644 t/t4018/csharp-method-array\n create mode 100644 t/t4018/csharp-method-explicit\n create mode 100644 t/t4018/csharp-method-generics\n create mode 100644 t/t4018/csharp-method-modifiers\n create mode 100644 t/t4018/csharp-method-multiline\n create mode 100644 t/t4018/csharp-method-params\n create mode 100644 t/t4018/csharp-method-special-chars\n create mode 100644 t/t4018/csharp-method-with-spacing\n create mode 100644 t/t4018/csharp-property\n create mode 100644 t/t4018/csharp-property-braces-same-line\n\ndiff --git a/t/t4018/csharp-exclude-assignments b/t/t4018/csharp-exclude-assignments\nnew file mode 100644\nindex 00000000000..239f312963b\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-assignments\n@@ -0,0 +1,20 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        var constantAssignment = \"test\";\n+        var methodAssignment = MethodCall();\n+        var multiLineMethodAssignment = MethodCall(\n+        );\n+        var multiLine = \"first\"\n+            + MethodCall()\n+            +\n+            ( MethodCall()\n+            )\n+            + MethodCall();\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    string MethodCall(int a = 0, int b = 0) => \"test\";\n+}\ndiff --git a/t/t4018/csharp-exclude-control-statements b/t/t4018/csharp-exclude-control-statements\nnew file mode 100644\nindex 00000000000..3a0f404ee10\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-control-statements\n@@ -0,0 +1,34 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        if (false)\n+        {\n+            return \"out\";\n+        }\n+        else { }\n+        if (true) MethodCall(\n+        );\n+        else MethodCall(\n+        );\n+        switch (\"test\")\n+        {\n+            case \"one\":\n+            return MethodCall(\n+            );\n+            case \"two\":\n+            break;\n+        }\n+        (int, int) tuple = (1, 4);\n+        switch (tuple)\n+        {\n+            case (1, 4):\n+              MethodCall();\n+\t\t      break;\n+        }\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    string MethodCall(int a = 0, int b = 0) => \"test\";\n+}\ndiff --git a/t/t4018/csharp-exclude-exceptions b/t/t4018/csharp-exclude-exceptions\nnew file mode 100644\nindex 00000000000..b1e64256cfe\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-exceptions\n@@ -0,0 +1,29 @@\n+using System;\n+\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        try\n+        {\n+            throw new Exception(\"fail\");\n+        }\n+        catch (Exception)\n+        {\n+        }\n+        finally\n+        {\n+        }\n+        try { } catch (Exception) {}\n+        try\n+        {\n+            throw GetException(\n+            );\n+        }\n+        catch (Exception) { }\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    Exception GetException() => new Exception(\"fail\");\n+}\ndiff --git a/t/t4018/csharp-exclude-generic-method-calls b/t/t4018/csharp-exclude-generic-method-calls\nnew file mode 100644\nindex 00000000000..31af546665d\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-generic-method-calls\n@@ -0,0 +1,12 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        GenericMethodCall<int, int>(\n+            );\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    string GenericMethodCall<T, T2>() => \"test\";\n+}\ndiff --git a/t/t4018/csharp-exclude-init-dispose b/t/t4018/csharp-exclude-init-dispose\nnew file mode 100644\nindex 00000000000..2bc8e194e20\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-init-dispose\n@@ -0,0 +1,22 @@\n+using System;\n+\n+class Example : IDisposable\n+{\n+    string Method(int RIGHT)\n+    {\n+        new Example();\n+        new Example(\n+            );\n+        new Example { };\n+        using (this)\n+        {\n+        }\n+        var def =\n+            this is default(\n+                Example);\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    public void Dispose() {}\n+}\ndiff --git a/t/t4018/csharp-exclude-iterations b/t/t4018/csharp-exclude-iterations\nnew file mode 100644\nindex 00000000000..960aa182ae2\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-iterations\n@@ -0,0 +1,26 @@\n+using System.Linq;\n+\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        do { } while (true);\n+        do MethodCall(\n+        ); while (true);\n+        while (true);\n+        while (true) {\n+            break;\n+        }\n+        for (int i = 0; i < 10; ++i)\n+        {\n+        }\n+        foreach (int i in Enumerable.Range(0, 10))\n+        {\n+        }\n+        int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    string MethodCall(int a = 0, int b = 0) => \"test\";\n+}\ndiff --git a/t/t4018/csharp-exclude-method-calls b/t/t4018/csharp-exclude-method-calls\nnew file mode 100644\nindex 00000000000..8afe2a54638\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-method-calls\n@@ -0,0 +1,15 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        MethodCall();\n+        MethodCall(1, 2);\n+        MethodCall(\n+            1, 2);\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    string MethodCall(int a = 0, int b = 0) => \"test\";\n+    string GenericMethodCall<T, T2>() => \"test\";\n+}\ndiff --git a/t/t4018/csharp-exclude-other b/t/t4018/csharp-exclude-other\nnew file mode 100644\nindex 00000000000..4d5581cf3e1\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-other\n@@ -0,0 +1,18 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        lock (this)\n+        {\n+        }\n+        unsafe\n+        {\n+            byte[] bytes = [1, 2, 3];\n+            fixed (byte* pointerToFirst = bytes)\n+            {\n+            }\n+        }\n+\n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-method b/t/t4018/csharp-method\nnew file mode 100644\nindex 00000000000..16b367aca2b\n--- /dev/null\n+++ b/t/t4018/csharp-method\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        // Filler\n+        // Filler\n+\n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-array b/t/t4018/csharp-method-array\nnew file mode 100644\nindex 00000000000..1126de8201d\n--- /dev/null\n+++ b/t/t4018/csharp-method-array\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    string[] Method(int RIGHT)\n+    {\n+        // Filler\n+        // Filler\n+\n+        return [\"ChangeMe\"];\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-explicit b/t/t4018/csharp-method-explicit\nnew file mode 100644\nindex 00000000000..5a710116cc4\n--- /dev/null\n+++ b/t/t4018/csharp-method-explicit\n@@ -0,0 +1,12 @@\n+using System;\n+\n+class Example : IDisposable\n+{\n+    void IDisposable.Dispose() // RIGHT\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-generics b/t/t4018/csharp-method-generics\nnew file mode 100644\nindex 00000000000..b3216bfb2a7\n--- /dev/null\n+++ b/t/t4018/csharp-method-generics\n@@ -0,0 +1,11 @@\n+class Example<T1, T2>\n+{\n+    Example<int, string> Method<TA, TB>(TA RIGHT, TB b)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+        return null;\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-modifiers b/t/t4018/csharp-method-modifiers\nnew file mode 100644\nindex 00000000000..caefa8ee99c\n--- /dev/null\n+++ b/t/t4018/csharp-method-modifiers\n@@ -0,0 +1,13 @@\n+using System.Threading.Tasks;\n+\n+class Example\n+{\n+    static internal async Task Method(int RIGHT)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+        await Task.Delay(1);\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-multiline b/t/t4018/csharp-method-multiline\nnew file mode 100644\nindex 00000000000..3983ff42f51\n--- /dev/null\n+++ b/t/t4018/csharp-method-multiline\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    string Method_RIGHT(\n+        int a,\n+        int b,\n+        int c)\n+    {\n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-params b/t/t4018/csharp-method-params\nnew file mode 100644\nindex 00000000000..3f00410ba1f\n--- /dev/null\n+++ b/t/t4018/csharp-method-params\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    string Method(int RIGHT, int b, int c = 42)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-special-chars b/t/t4018/csharp-method-special-chars\nnew file mode 100644\nindex 00000000000..e6c7bc01a18\n--- /dev/null\n+++ b/t/t4018/csharp-method-special-chars\n@@ -0,0 +1,11 @@\n+class @Some_Type\n+{\n+    @Some_Type @Method_With_Underscore(int RIGHT)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+        return new @Some_Type();\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-with-spacing b/t/t4018/csharp-method-with-spacing\nnew file mode 100644\nindex 00000000000..233bb976cc2\n--- /dev/null\n+++ b/t/t4018/csharp-method-with-spacing\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    \tstring   Method \t( int \tRIGHT )\n+    {\n+        // Filler\n+        // Filler\n+\n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-property b/t/t4018/csharp-property\nnew file mode 100644\nindex 00000000000..e56dfce34c1\n--- /dev/null\n+++ b/t/t4018/csharp-property\n@@ -0,0 +1,11 @@\n+class Example\n+{\n+    public bool RIGHT\n+    {\n+        get { return true; }\n+        set\n+        {\n+            // ChangeMe\n+        }\n+    }\n+}\ndiff --git a/t/t4018/csharp-property-braces-same-line b/t/t4018/csharp-property-braces-same-line\nnew file mode 100644\nindex 00000000000..608131d3d31\n--- /dev/null\n+++ b/t/t4018/csharp-property-braces-same-line\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    public bool RIGHT {\n+        get { return true; }\n+        set\n+        {\n+            // ChangeMe\n+        }\n+    }\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex e399543823b..35e0e1183f7 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -89,12 +89,46 @@ PATTERNS(\"cpp\",\n \t \"|\\\\.[0-9][0-9]*([Ee][-+]?[0-9]+)?[fFlL]?\"\n \t \"|[-+*/<>%&^|=!]=|--|\\\\+\\\\+|<<=?|>>=?|&&|\\\\|\\\\||::|->\\\\*?|\\\\.\\\\*|<=>\"),\n PATTERNS(\"csharp\",\n-\t /* Keywords */\n-\t \"!^[ \\t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\\n\"\n-\t /* Methods and constructors */\n-\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[<>@._[:alnum:]]+[ \\t]*\\\\(.*\\\\))[ \\t]*$\\n\"\n-\t /* Properties */\n-\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[@._[:alnum:]]+)[ \\t]*$\\n\"\n+\t /*\n+\t  * Jump over reserved keywords which are illegal method names, but which\n+\t  * can be followed by parentheses without special characters in between,\n+\t  * making them look like methods.\n+\t  */\n+\t \"!(^|[ \\t]+)\" /* Start of line or whitespace. */\n+\t\t\"(do|while|for|foreach|if|else|new|default|return|switch|case|throw\"\n+\t\t\"|catch|using|lock|fixed)\"\n+\t\t\"([ \\t(]+|$)\\n\" /* Whitespace, \"(\", or end of line. */\n+\t /*\n+\t  * Methods/constructors:\n+\t  * The strategy is to identify a minimum of two groups (any combination\n+\t  * of keywords/type/name) before the opening parenthesis, and without\n+\t  * final unexpected characters, normally only used in ordinary statements.\n+\t  */\n+\t \"^[ \\t]*\" /* Remove leading whitespace. */\n+\t\t\"(\" /* Start chunk header capture. */\n+\t\t\"(\" /* Group of keywords/type/names. */\n+\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+\" /* Space only allowed after \",\". */\n+\t\t\"[ \\t]+\" /* One required space forces a minimum of two items. */\n+\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+\"\n+\t\t\"[ \\t]*\" /* Optional space before parameters start. */\n+\t\t\")+\"\n+\t\t\"\\\\(\" /* Start of method parameters. */\n+\t\t\"[^;]*\" /* Allow complex parameters, but exclude statements (;). */\n+\t\t\")$\\n\" /* Close chunk header capture. */\n+\t /*\n+\t  * Properties:\n+\t  * As with methods, expect a minimum of two groups. But, more trivial than\n+\t  * methods, the vast majority of properties long enough to be worth\n+\t  * showing a chunk header for don't include \"=:;,()\" on the line they are\n+\t  * defined, since they don't have a parameter list.\n+\t  */\n+\t \"^[ \\t]*(\"\n+\t\t\"(\"\n+\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+[ \\t]+\"\n+\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+[ \\t]*\"\n+\t\t\")+\" /* Up to here, same as methods regex. */\n+\t\t\"[^;=:,()]*\" /* Compared to methods, no parameter list allowed. */\n+\t\t\")$\\n\"\n \t /* Type definitions */\n \t \"^[ \\t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \\t]+)*(class|enum|interface|struct|record)[ \\t]+.*)$\\n\"\n \t /* Namespace */\n\nbase-commit: f41f85c9ec8d4d46de0fd5fded88db94d3ec8c11\n-- \ngitgitgadget\n"},{"id":"491781","messageId":"xmqqv856tamc.fsf@gitster.g","threadId":"60993","inReplyTo":"pull.1682.v4.git.git.1711653257043.gitgitgadget@gmail.com","subject":"Re: [PATCH v4] userdiff: better method/property matching for C#","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-28T19:33:15Z","receivedAt":"2024-03-28T19:33:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Steven Jeuris via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>     As before, I ran into many regex limitations (no possessive quantifiers,\n>     no lookahead). It also seems different regex evaluators are used on\n>     different test runs. Which one does git diff use? Maybe it is about time\n>     to update this? E.g., if speed is a concern, possessive quantifiers can\n>     speed up search.\n\nWhen you make regcomp(3) and regexec(3) calls, you'll be using the\nsystem library that supplies them.  IOW, we use whatever is on the\nsystem.\n"},{"id":"491898","messageId":"bb4fe56f-f45d-404a-834e-31ba877641c7@kdbg.org","threadId":"60993","inReplyTo":"pull.1682.v4.git.git.1711653257043.gitgitgadget@gmail.com","subject":"Re: [PATCH v4] userdiff: better method/property matching for C#","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2024-03-30T18:49:02Z","receivedAt":"2024-03-30T18:49:31Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 28.03.24 um 20:14 schrieb Steven Jeuris via GitGitGadget:\n> diff --git a/userdiff.c b/userdiff.c\n> index e399543823b..35e0e1183f7 100644\n> --- a/userdiff.c\n> +++ b/userdiff.c\n> @@ -89,12 +89,46 @@ PATTERNS(\"cpp\",\n>  \t \"|\\\\.[0-9][0-9]*([Ee][-+]?[0-9]+)?[fFlL]?\"\n>  \t \"|[-+*/<>%&^|=!]=|--|\\\\+\\\\+|<<=?|>>=?|&&|\\\\|\\\\||::|->\\\\*?|\\\\.\\\\*|<=>\"),\n>  PATTERNS(\"csharp\",\n> -\t /* Keywords */\n> -\t \"!^[ \\t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\\n\"\n> -\t /* Methods and constructors */\n> -\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[<>@._[:alnum:]]+[ \\t]*\\\\(.*\\\\))[ \\t]*$\\n\"\n> -\t /* Properties */\n> -\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[@._[:alnum:]]+)[ \\t]*$\\n\"\n> +\t /*\n> +\t  * Jump over reserved keywords which are illegal method names, but which\n> +\t  * can be followed by parentheses without special characters in between,\n> +\t  * making them look like methods.\n> +\t  */\n> +\t \"!(^|[ \\t]+)\" /* Start of line or whitespace. */\n> +\t\t\"(do|while|for|foreach|if|else|new|default|return|switch|case|throw\"\n> +\t\t\"|catch|using|lock|fixed)\"\n> +\t\t\"([ \\t(]+|$)\\n\" /* Whitespace, \"(\", or end of line. */\n> +\t /*\n> +\t  * Methods/constructors:\n> +\t  * The strategy is to identify a minimum of two groups (any combination\n> +\t  * of keywords/type/name) before the opening parenthesis, and without\n> +\t  * final unexpected characters, normally only used in ordinary statements.\n> +\t  */\n> +\t \"^[ \\t]*\" /* Remove leading whitespace. */\n> +\t\t\"(\" /* Start chunk header capture. */\n> +\t\t\"(\" /* Group of keywords/type/names. */\n> +\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+\" /* Space only allowed after \",\". */\n\nMental note: This pattern means:\n\n   a sequence of at least one of\n   (\n       one character of a certain set\n       OR\n       a comma\n          followed by SP\n          optionally followed by whitespace or |\n   )\n\n> +\t\t\"[ \\t]+\" /* One required space forces a minimum of two items. */\n\nOK.\n\n> +\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+\"\n\nSame here.\n\nIs it the case of t4018/csharp-method-generics?\n\n    Example<int, string> Method<TA, TB>(TA RIGHT, TB b)\n\nLet me see if I make sense of this. The idea is to treat the ', '\nsequence as a single \"character\" so that the SP in this sequence does\nnot count as the word separator that we otherwise have between two plain\nwords.\n\nI am unsure whether this is a workable solution. The set of symbols that\ncan occur before a method definition overlaps too much with the symbols\nthat can occur elsewhere. For example, if you have these lines of code:\n\n    something(arg,\n        meth, func(x),\n        prop, y,\n        more);\n\nyou have a false positive in the lines in the middle.\n\nI have the feeling that it is impossible to distinguish method\ndefinitions from other code reliably. We can go back and forth a while,\nbut at some point we have to stop and accept that there are false\npositives. Where that point is, I cannot tell, because I do not know\nwhat is common and what is uncommon in typical C# code. It is your call.\n\n> +\t\t\"[ \\t]*\" /* Optional space before parameters start. */\n> +\t\t\")+\"\n> +\t\t\"\\\\(\" /* Start of method parameters. */\n> +\t\t\"[^;]*\" /* Allow complex parameters, but exclude statements (;). */\n> +\t\t\")$\\n\" /* Close chunk header capture. */\n> +\t /*\n> +\t  * Properties:\n> +\t  * As with methods, expect a minimum of two groups. But, more trivial than\n> +\t  * methods, the vast majority of properties long enough to be worth\n> +\t  * showing a chunk header for don't include \"=:;,()\" on the line they are\n> +\t  * defined, since they don't have a parameter list.\n> +\t  */\n> +\t \"^[ \\t]*(\"\n> +\t\t\"(\"\n> +\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+[ \\t]+\"\n> +\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+[ \\t]*\"\n> +\t\t\")+\" /* Up to here, same as methods regex. */\n\nIs the intent to match pairs of words? Or is the intent to find at least\ntwo words? If the latter, then the preceding 4 lines are better written as\n\n\t\t       \"([][[:alnum:]@_<>.]|,[ \\t]+)+\"\n\t\t\"([ \\t]+([][[:alnum:]@_<>.]|,[ \\t]+)+)+\"\n\nIf the former, then the space after the second word must not be\noptional, because it would match\n\n    a bc d\n\nas two pairs\n\n    (a b)(c d)\n\nBTW that there is an | symbol in the potential space after the comma is\ncertainly an oversight, right? And, why is a tab after the comma not\npermitted?\n\nWhen you construct regular expressions, make sure that repeated parts\nalways begin with a mandatory part and that this mandatory part cannot\nbe an optional part at the end of the preceding or the repeated pattern.\nOtherwise, in a non-matching case you send the matcher into an expensive\nbacktracking loop.\n\n> +\t\t\"[^;=:,()]*\" /* Compared to methods, no parameter list allowed. */\n> +\t\t\")$\\n\"\n>  \t /* Type definitions */\n>  \t \"^[ \\t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \\t]+)*(class|enum|interface|struct|record)[ \\t]+.*)$\\n\"\n>  \t /* Namespace */\n> \n> base-commit: f41f85c9ec8d4d46de0fd5fded88db94d3ec8c11\n"},{"id":"492191","messageId":"pull.1682.v5.git.git.1712180564927.gitgitgadget@gmail.com","threadId":"60993","inReplyTo":"pull.1682.v4.git.git.1711653257043.gitgitgadget@gmail.com","subject":"[PATCH v5] userdiff: better method/property matching for C#","fromName":"Steven Jeuris via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-03T21:42:44Z","receivedAt":"2024-04-03T21:42:50Z","isPatch":true,"sender":{"key":"name:Steven Jeuris","avatar":null},"body":"From: Steven Jeuris <steven.jeuris@3shape.com>\n\n- Support multi-line methods by not requiring closing parenthesis.\n- Support multiple generics (comma was missing before).\n- Add missing `foreach`, `lock` and  `fixed` keywords to skip over.\n- Remove `instanceof` keyword, which isn't C#.\n- Also detect non-method keywords not positioned at the start of a line.\n- Added tests; none existed before.\n\nThe overall strategy is to focus more on what isn't expected for\nmethod/property definitions, instead of what is, but is fully optional.\n\nSigned-off-by: Steven Jeuris <steven.jeuris@gmail.com>\n---\n    userdiff: better method/property matching for C#\n    \n    Change since v1: I removed \"from\" from the list of keywords to skip.\n    First, I considered adding \"await\", but I discovered both \"await\" and\n    \"from\" are \"contextual keywords\", which unlike the other keywords\n    currently listed, aren't reserved, and can thus cause false negatives.\n    I.e., it is valid to have a method named \"await\" or \"from\". In edge\n    cases, this may lead to false positives, but a different exclusion rule\n    will need to be added to handle these.\n    \n    Change since v2:\n    \n     * Corrected comment formatting.\n     * Added csharp-property-skip-body test.\n     * Added comments in test code to explain sections not part of the test.\n     * Elaborated regex comments.\n     * Excluded math operators (+-*/%) in method pattern to not catch\n       multiline operations, and tested for this in the -skip-body tests.\n       Catching \"-\" only worked when it was defined at the end of the\n       exclusion block for some reason. The regex matcher seems quite\n       bugged.\n    \n    Change since v3:\n    \n     * Changed regex to better handle whitespace in types, making use of the\n       fact that it only appears after commas.\n     * Split regex into multiple lines with comments explaining structure.\n     * Split the \"skip body\" tests into more narrow csharp-exclude- tests.\n     * Added a test for generic methods:\n       csharp-exclude-generic-method-calls.\n     * Added a test for array types used in methods: csharp-method-array.\n     * Added an addition property test: csharp-property-braces-same-line.\n     * Included a test for \"( func(x)\" case identified by Johannes in\n       csharp-exclude-assignments.\n    \n    As before, I ran into many regex limitations (no possessive quantifiers,\n    no lookahead). It also seems different regex evaluators are used on\n    different test runs. Which one does git diff use? Maybe it is about time\n    to update this? E.g., if speed is a concern, possessive quantifiers can\n    speed up search.\n    \n    Change since v4:\n    \n     * Better matching of at least two \"words\".\n     * Better handling of generics by restricting commas within < ... >.\n     * Allow any spaces around commas in generics.\n     * Because of stricter use of comma, Johannes' identified failing cases\n       now pass.\n     * Updated tests to cover all of the above.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1682%2FWhathecode%2Fmaster-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1682/Whathecode/master-v5\nPull-Request: https://github.com/git/git/pull/1682\n\nRange-diff vs v4:\n\n 1:  2feb84beaa0 ! 1:  00191ef695a userdiff: better method/property matching for C#\n     @@ t/t4018/csharp-exclude-method-calls (new)\n      +        MethodCall(1, 2);\n      +        MethodCall(\n      +            1, 2);\n     ++        MethodCall(\n     ++            1, 2,\n     ++            3);\n     ++        MethodCall(\n     ++            1, MethodCall(),\n     ++            2);\n      +\n      +        return \"ChangeMe\";\n      +    }\n      +\n     -+    string MethodCall(int a = 0, int b = 0) => \"test\";\n     -+    string GenericMethodCall<T, T2>() => \"test\";\n     ++    int MethodCall(int a = 0, int b = 0, int c = 0) => 42;\n      +}\n      \n       ## t/t4018/csharp-exclude-other (new) ##\n     @@ t/t4018/csharp-method-generics (new)\n      +        // ChangeMe\n      +        return null;\n      +    }\n     ++}\n     +\n     + ## t/t4018/csharp-method-generics-alternate-spaces (new) ##\n     +@@\n     ++class Example<T1, T2>\n     ++{\n     ++    Example<int,string> Method<TA ,TB>(TA RIGHT, TB b)\n     ++    {\n     ++        // Filler\n     ++        // Filler\n     ++        \n     ++        // ChangeMe\n     ++        return null;\n     ++    }\n      +}\n      \n       ## t/t4018/csharp-method-modifiers (new) ##\n     @@ userdiff.c: PATTERNS(\"cpp\",\n      +\t  */\n      +\t \"^[ \\t]*\" /* Remove leading whitespace. */\n      +\t\t\"(\" /* Start chunk header capture. */\n     -+\t\t\"(\" /* Group of keywords/type/names. */\n     -+\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+\" /* Space only allowed after \",\". */\n     -+\t\t\"[ \\t]+\" /* One required space forces a minimum of two items. */\n     -+\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+\"\n     -+\t\t\"[ \\t]*\" /* Optional space before parameters start. */\n     ++\t\t\"(\" /* First group. */\n     ++\t\t\t\"[][[:alnum:]@_.]\" /* Name. */\n     ++\t\t\t\"(<[][[:alnum:]@_, \\t<>]+>)?\" /* Optional generic parameters. */\n      +\t\t\")+\"\n     ++\t\t\"([ \\t]+\" /* Subsequent groups, prepended with space. */\n     ++\t\t\t\"([][[:alnum:]@_.](<[][[:alnum:]@_, \\t<>]+>)?)+\"\n     ++\t\t\")+\"\n     ++\t\t\"[ \\t]*\" /* Optional space before parameters start. */\n      +\t\t\"\\\\(\" /* Start of method parameters. */\n      +\t\t\"[^;]*\" /* Allow complex parameters, but exclude statements (;). */\n      +\t\t\")$\\n\" /* Close chunk header capture. */\n     @@ userdiff.c: PATTERNS(\"cpp\",\n      +\t  * defined, since they don't have a parameter list.\n      +\t  */\n      +\t \"^[ \\t]*(\"\n     -+\t\t\"(\"\n     -+\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+[ \\t]+\"\n     -+\t\t\"([][[:alnum:]@_<>.]|, [ |\\t]*)+[ \\t]*\"\n     ++\t\t\"([][[:alnum:]@_.](<[][[:alnum:]@_, \\t<>]+>)?)+\"\n     ++\t\t\"([ \\t]+\"\n     ++\t\t\t\"([][[:alnum:]@_.](<[][[:alnum:]@_, \\t<>]+>)?)+\"\n      +\t\t\")+\" /* Up to here, same as methods regex. */\n      +\t\t\"[^;=:,()]*\" /* Compared to methods, no parameter list allowed. */\n      +\t\t\")$\\n\"\n\n\n t/t4018/csharp-exclude-assignments            | 20 ++++++++\n t/t4018/csharp-exclude-control-statements     | 34 +++++++++++++\n t/t4018/csharp-exclude-exceptions             | 29 +++++++++++\n t/t4018/csharp-exclude-generic-method-calls   | 12 +++++\n t/t4018/csharp-exclude-init-dispose           | 22 +++++++++\n t/t4018/csharp-exclude-iterations             | 26 ++++++++++\n t/t4018/csharp-exclude-method-calls           | 20 ++++++++\n t/t4018/csharp-exclude-other                  | 18 +++++++\n t/t4018/csharp-method                         | 10 ++++\n t/t4018/csharp-method-array                   | 10 ++++\n t/t4018/csharp-method-explicit                | 12 +++++\n t/t4018/csharp-method-generics                | 11 +++++\n .../csharp-method-generics-alternate-spaces   | 11 +++++\n t/t4018/csharp-method-modifiers               | 13 +++++\n t/t4018/csharp-method-multiline               | 10 ++++\n t/t4018/csharp-method-params                  | 10 ++++\n t/t4018/csharp-method-special-chars           | 11 +++++\n t/t4018/csharp-method-with-spacing            | 10 ++++\n t/t4018/csharp-property                       | 11 +++++\n t/t4018/csharp-property-braces-same-line      | 10 ++++\n userdiff.c                                    | 48 ++++++++++++++++---\n 21 files changed, 352 insertions(+), 6 deletions(-)\n create mode 100644 t/t4018/csharp-exclude-assignments\n create mode 100644 t/t4018/csharp-exclude-control-statements\n create mode 100644 t/t4018/csharp-exclude-exceptions\n create mode 100644 t/t4018/csharp-exclude-generic-method-calls\n create mode 100644 t/t4018/csharp-exclude-init-dispose\n create mode 100644 t/t4018/csharp-exclude-iterations\n create mode 100644 t/t4018/csharp-exclude-method-calls\n create mode 100644 t/t4018/csharp-exclude-other\n create mode 100644 t/t4018/csharp-method\n create mode 100644 t/t4018/csharp-method-array\n create mode 100644 t/t4018/csharp-method-explicit\n create mode 100644 t/t4018/csharp-method-generics\n create mode 100644 t/t4018/csharp-method-generics-alternate-spaces\n create mode 100644 t/t4018/csharp-method-modifiers\n create mode 100644 t/t4018/csharp-method-multiline\n create mode 100644 t/t4018/csharp-method-params\n create mode 100644 t/t4018/csharp-method-special-chars\n create mode 100644 t/t4018/csharp-method-with-spacing\n create mode 100644 t/t4018/csharp-property\n create mode 100644 t/t4018/csharp-property-braces-same-line\n\ndiff --git a/t/t4018/csharp-exclude-assignments b/t/t4018/csharp-exclude-assignments\nnew file mode 100644\nindex 00000000000..239f312963b\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-assignments\n@@ -0,0 +1,20 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        var constantAssignment = \"test\";\n+        var methodAssignment = MethodCall();\n+        var multiLineMethodAssignment = MethodCall(\n+        );\n+        var multiLine = \"first\"\n+            + MethodCall()\n+            +\n+            ( MethodCall()\n+            )\n+            + MethodCall();\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    string MethodCall(int a = 0, int b = 0) => \"test\";\n+}\ndiff --git a/t/t4018/csharp-exclude-control-statements b/t/t4018/csharp-exclude-control-statements\nnew file mode 100644\nindex 00000000000..3a0f404ee10\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-control-statements\n@@ -0,0 +1,34 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        if (false)\n+        {\n+            return \"out\";\n+        }\n+        else { }\n+        if (true) MethodCall(\n+        );\n+        else MethodCall(\n+        );\n+        switch (\"test\")\n+        {\n+            case \"one\":\n+            return MethodCall(\n+            );\n+            case \"two\":\n+            break;\n+        }\n+        (int, int) tuple = (1, 4);\n+        switch (tuple)\n+        {\n+            case (1, 4):\n+              MethodCall();\n+\t\t      break;\n+        }\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    string MethodCall(int a = 0, int b = 0) => \"test\";\n+}\ndiff --git a/t/t4018/csharp-exclude-exceptions b/t/t4018/csharp-exclude-exceptions\nnew file mode 100644\nindex 00000000000..b1e64256cfe\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-exceptions\n@@ -0,0 +1,29 @@\n+using System;\n+\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        try\n+        {\n+            throw new Exception(\"fail\");\n+        }\n+        catch (Exception)\n+        {\n+        }\n+        finally\n+        {\n+        }\n+        try { } catch (Exception) {}\n+        try\n+        {\n+            throw GetException(\n+            );\n+        }\n+        catch (Exception) { }\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    Exception GetException() => new Exception(\"fail\");\n+}\ndiff --git a/t/t4018/csharp-exclude-generic-method-calls b/t/t4018/csharp-exclude-generic-method-calls\nnew file mode 100644\nindex 00000000000..31af546665d\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-generic-method-calls\n@@ -0,0 +1,12 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        GenericMethodCall<int, int>(\n+            );\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    string GenericMethodCall<T, T2>() => \"test\";\n+}\ndiff --git a/t/t4018/csharp-exclude-init-dispose b/t/t4018/csharp-exclude-init-dispose\nnew file mode 100644\nindex 00000000000..2bc8e194e20\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-init-dispose\n@@ -0,0 +1,22 @@\n+using System;\n+\n+class Example : IDisposable\n+{\n+    string Method(int RIGHT)\n+    {\n+        new Example();\n+        new Example(\n+            );\n+        new Example { };\n+        using (this)\n+        {\n+        }\n+        var def =\n+            this is default(\n+                Example);\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    public void Dispose() {}\n+}\ndiff --git a/t/t4018/csharp-exclude-iterations b/t/t4018/csharp-exclude-iterations\nnew file mode 100644\nindex 00000000000..960aa182ae2\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-iterations\n@@ -0,0 +1,26 @@\n+using System.Linq;\n+\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        do { } while (true);\n+        do MethodCall(\n+        ); while (true);\n+        while (true);\n+        while (true) {\n+            break;\n+        }\n+        for (int i = 0; i < 10; ++i)\n+        {\n+        }\n+        foreach (int i in Enumerable.Range(0, 10))\n+        {\n+        }\n+        int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    string MethodCall(int a = 0, int b = 0) => \"test\";\n+}\ndiff --git a/t/t4018/csharp-exclude-method-calls b/t/t4018/csharp-exclude-method-calls\nnew file mode 100644\nindex 00000000000..51e2dc20407\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-method-calls\n@@ -0,0 +1,20 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        MethodCall();\n+        MethodCall(1, 2);\n+        MethodCall(\n+            1, 2);\n+        MethodCall(\n+            1, 2,\n+            3);\n+        MethodCall(\n+            1, MethodCall(),\n+            2);\n+\n+        return \"ChangeMe\";\n+    }\n+\n+    int MethodCall(int a = 0, int b = 0, int c = 0) => 42;\n+}\ndiff --git a/t/t4018/csharp-exclude-other b/t/t4018/csharp-exclude-other\nnew file mode 100644\nindex 00000000000..4d5581cf3e1\n--- /dev/null\n+++ b/t/t4018/csharp-exclude-other\n@@ -0,0 +1,18 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        lock (this)\n+        {\n+        }\n+        unsafe\n+        {\n+            byte[] bytes = [1, 2, 3];\n+            fixed (byte* pointerToFirst = bytes)\n+            {\n+            }\n+        }\n+\n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-method b/t/t4018/csharp-method\nnew file mode 100644\nindex 00000000000..16b367aca2b\n--- /dev/null\n+++ b/t/t4018/csharp-method\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    string Method(int RIGHT)\n+    {\n+        // Filler\n+        // Filler\n+\n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-array b/t/t4018/csharp-method-array\nnew file mode 100644\nindex 00000000000..1126de8201d\n--- /dev/null\n+++ b/t/t4018/csharp-method-array\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    string[] Method(int RIGHT)\n+    {\n+        // Filler\n+        // Filler\n+\n+        return [\"ChangeMe\"];\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-explicit b/t/t4018/csharp-method-explicit\nnew file mode 100644\nindex 00000000000..5a710116cc4\n--- /dev/null\n+++ b/t/t4018/csharp-method-explicit\n@@ -0,0 +1,12 @@\n+using System;\n+\n+class Example : IDisposable\n+{\n+    void IDisposable.Dispose() // RIGHT\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-generics b/t/t4018/csharp-method-generics\nnew file mode 100644\nindex 00000000000..b3216bfb2a7\n--- /dev/null\n+++ b/t/t4018/csharp-method-generics\n@@ -0,0 +1,11 @@\n+class Example<T1, T2>\n+{\n+    Example<int, string> Method<TA, TB>(TA RIGHT, TB b)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+        return null;\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-generics-alternate-spaces b/t/t4018/csharp-method-generics-alternate-spaces\nnew file mode 100644\nindex 00000000000..95836217430\n--- /dev/null\n+++ b/t/t4018/csharp-method-generics-alternate-spaces\n@@ -0,0 +1,11 @@\n+class Example<T1, T2>\n+{\n+    Example<int,string> Method<TA ,TB>(TA RIGHT, TB b)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+        return null;\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-modifiers b/t/t4018/csharp-method-modifiers\nnew file mode 100644\nindex 00000000000..caefa8ee99c\n--- /dev/null\n+++ b/t/t4018/csharp-method-modifiers\n@@ -0,0 +1,13 @@\n+using System.Threading.Tasks;\n+\n+class Example\n+{\n+    static internal async Task Method(int RIGHT)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+        await Task.Delay(1);\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-multiline b/t/t4018/csharp-method-multiline\nnew file mode 100644\nindex 00000000000..3983ff42f51\n--- /dev/null\n+++ b/t/t4018/csharp-method-multiline\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    string Method_RIGHT(\n+        int a,\n+        int b,\n+        int c)\n+    {\n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-params b/t/t4018/csharp-method-params\nnew file mode 100644\nindex 00000000000..3f00410ba1f\n--- /dev/null\n+++ b/t/t4018/csharp-method-params\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    string Method(int RIGHT, int b, int c = 42)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-special-chars b/t/t4018/csharp-method-special-chars\nnew file mode 100644\nindex 00000000000..e6c7bc01a18\n--- /dev/null\n+++ b/t/t4018/csharp-method-special-chars\n@@ -0,0 +1,11 @@\n+class @Some_Type\n+{\n+    @Some_Type @Method_With_Underscore(int RIGHT)\n+    {\n+        // Filler\n+        // Filler\n+        \n+        // ChangeMe\n+        return new @Some_Type();\n+    }\n+}\ndiff --git a/t/t4018/csharp-method-with-spacing b/t/t4018/csharp-method-with-spacing\nnew file mode 100644\nindex 00000000000..233bb976cc2\n--- /dev/null\n+++ b/t/t4018/csharp-method-with-spacing\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    \tstring   Method \t( int \tRIGHT )\n+    {\n+        // Filler\n+        // Filler\n+\n+        return \"ChangeMe\";\n+    }\n+}\ndiff --git a/t/t4018/csharp-property b/t/t4018/csharp-property\nnew file mode 100644\nindex 00000000000..e56dfce34c1\n--- /dev/null\n+++ b/t/t4018/csharp-property\n@@ -0,0 +1,11 @@\n+class Example\n+{\n+    public bool RIGHT\n+    {\n+        get { return true; }\n+        set\n+        {\n+            // ChangeMe\n+        }\n+    }\n+}\ndiff --git a/t/t4018/csharp-property-braces-same-line b/t/t4018/csharp-property-braces-same-line\nnew file mode 100644\nindex 00000000000..608131d3d31\n--- /dev/null\n+++ b/t/t4018/csharp-property-braces-same-line\n@@ -0,0 +1,10 @@\n+class Example\n+{\n+    public bool RIGHT {\n+        get { return true; }\n+        set\n+        {\n+            // ChangeMe\n+        }\n+    }\n+}\ndiff --git a/userdiff.c b/userdiff.c\nindex e399543823b..0d667a1f5a6 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -89,12 +89,48 @@ PATTERNS(\"cpp\",\n \t \"|\\\\.[0-9][0-9]*([Ee][-+]?[0-9]+)?[fFlL]?\"\n \t \"|[-+*/<>%&^|=!]=|--|\\\\+\\\\+|<<=?|>>=?|&&|\\\\|\\\\||::|->\\\\*?|\\\\.\\\\*|<=>\"),\n PATTERNS(\"csharp\",\n-\t /* Keywords */\n-\t \"!^[ \\t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\\n\"\n-\t /* Methods and constructors */\n-\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[<>@._[:alnum:]]+[ \\t]*\\\\(.*\\\\))[ \\t]*$\\n\"\n-\t /* Properties */\n-\t \"^[ \\t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \\t]+)*[][<>@.~_[:alnum:]]+[ \\t]+[@._[:alnum:]]+)[ \\t]*$\\n\"\n+\t /*\n+\t  * Jump over reserved keywords which are illegal method names, but which\n+\t  * can be followed by parentheses without special characters in between,\n+\t  * making them look like methods.\n+\t  */\n+\t \"!(^|[ \\t]+)\" /* Start of line or whitespace. */\n+\t\t\"(do|while|for|foreach|if|else|new|default|return|switch|case|throw\"\n+\t\t\"|catch|using|lock|fixed)\"\n+\t\t\"([ \\t(]+|$)\\n\" /* Whitespace, \"(\", or end of line. */\n+\t /*\n+\t  * Methods/constructors:\n+\t  * The strategy is to identify a minimum of two groups (any combination\n+\t  * of keywords/type/name) before the opening parenthesis, and without\n+\t  * final unexpected characters, normally only used in ordinary statements.\n+\t  */\n+\t \"^[ \\t]*\" /* Remove leading whitespace. */\n+\t\t\"(\" /* Start chunk header capture. */\n+\t\t\"(\" /* First group. */\n+\t\t\t\"[][[:alnum:]@_.]\" /* Name. */\n+\t\t\t\"(<[][[:alnum:]@_, \\t<>]+>)?\" /* Optional generic parameters. */\n+\t\t\")+\"\n+\t\t\"([ \\t]+\" /* Subsequent groups, prepended with space. */\n+\t\t\t\"([][[:alnum:]@_.](<[][[:alnum:]@_, \\t<>]+>)?)+\"\n+\t\t\")+\"\n+\t\t\"[ \\t]*\" /* Optional space before parameters start. */\n+\t\t\"\\\\(\" /* Start of method parameters. */\n+\t\t\"[^;]*\" /* Allow complex parameters, but exclude statements (;). */\n+\t\t\")$\\n\" /* Close chunk header capture. */\n+\t /*\n+\t  * Properties:\n+\t  * As with methods, expect a minimum of two groups. But, more trivial than\n+\t  * methods, the vast majority of properties long enough to be worth\n+\t  * showing a chunk header for don't include \"=:;,()\" on the line they are\n+\t  * defined, since they don't have a parameter list.\n+\t  */\n+\t \"^[ \\t]*(\"\n+\t\t\"([][[:alnum:]@_.](<[][[:alnum:]@_, \\t<>]+>)?)+\"\n+\t\t\"([ \\t]+\"\n+\t\t\t\"([][[:alnum:]@_.](<[][[:alnum:]@_, \\t<>]+>)?)+\"\n+\t\t\")+\" /* Up to here, same as methods regex. */\n+\t\t\"[^;=:,()]*\" /* Compared to methods, no parameter list allowed. */\n+\t\t\")$\\n\"\n \t /* Type definitions */\n \t \"^[ \\t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \\t]+)*(class|enum|interface|struct|record)[ \\t]+.*)$\\n\"\n \t /* Namespace */\n\nbase-commit: f41f85c9ec8d4d46de0fd5fded88db94d3ec8c11\n-- \ngitgitgadget\n"},{"id":"492327","messageId":"c2154457-3f2f-496e-9b8b-c8ea7257027b@kdbg.org","threadId":"60993","inReplyTo":"pull.1682.v5.git.git.1712180564927.gitgitgadget@gmail.com","subject":"Re: [PATCH v5] userdiff: better method/property matching for C#","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2024-04-05T22:02:47Z","receivedAt":"2024-04-05T22:03:04Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 03.04.24 um 23:42 schrieb Steven Jeuris via GitGitGadget:\n> From: Steven Jeuris <steven.jeuris@3shape.com>\n> \n> - Support multi-line methods by not requiring closing parenthesis.\n> - Support multiple generics (comma was missing before).\n> - Add missing `foreach`, `lock` and  `fixed` keywords to skip over.\n> - Remove `instanceof` keyword, which isn't C#.\n> - Also detect non-method keywords not positioned at the start of a line.\n> - Added tests; none existed before.\n> \n> The overall strategy is to focus more on what isn't expected for\n> method/property definitions, instead of what is, but is fully optional.\n> \n> Signed-off-by: Steven Jeuris <steven.jeuris@gmail.com>\n\n>     Change since v4:\n>     \n>      * Better matching of at least two \"words\".\n>      * Better handling of generics by restricting commas within < ... >.\n>      * Allow any spaces around commas in generics.\n>      * Because of stricter use of comma, Johannes' identified failing cases\n>        now pass.\n>      * Updated tests to cover all of the above.\n\nThe proposed patterns look reasonable and are an improvement over the\nexisting patterns, so I think we can move this patch forward.\n\nThank you,\n-- Hannes\n\n"},{"id":"492328","messageId":"xmqqa5m7tq9u.fsf@gitster.g","threadId":"60993","inReplyTo":"c2154457-3f2f-496e-9b8b-c8ea7257027b@kdbg.org","subject":"Re: [PATCH v5] userdiff: better method/property matching for C#","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-05T22:10:05Z","receivedAt":"2024-04-05T22:10:10Z","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> Am 03.04.24 um 23:42 schrieb Steven Jeuris via GitGitGadget:\n>> From: Steven Jeuris <steven.jeuris@3shape.com>\n>>  ...\n> The proposed patterns look reasonable and are an improvement over the\n> existing patterns, so I think we can move this patch forward.\n\nThanks, both.  Let me mark the topic for 'next'.\n"}]}