[PATCH v3] userdiff: better method/property matching for C#
- From
- Steven Jeuris via GitGitGadget <gitgitgadget@gmail.com>
- Date
- Mar 28, 2024, 08:07 UTC
- Message-ID
- <pull.1682.v3.git.git.1711613220277.gitgitgadget@gmail.com>
- In-Reply-To
- <pull.1682.v2.git.git.1709756493673.gitgitgadget@gmail.com>
From: Steven Jeuris <steven.jeuris@3shape.com>
- Support multi-line methods by not requiring closing parenthesis. - Support multiple generics (comma was missing before). - Add missing `foreach`, `lock` and `fixed` keywords to skip over. - Remove `instanceof` keyword, which isn't C#. - Also detect non-method keywords not positioned at the start of a line. - Added tests; none existed before.
The overall strategy is to focus more on what isn't expected for method/property definitions, instead of what is, but is fully optional.
Signed-off-by: Steven Jeuris <steven.jeuris@gmail.com>
---
userdiff: better method/property matching for C#
Change since v1: I removed "from" from the list of keywords to skip.
First, I considered adding "await", but I discovered both "await" and
"from" are "contextual keywords", which unlike the other keywords
currently listed, aren't reserved, and can thus cause false negatives.
I.e., it is valid to have a method named "await" or "from". In edge
cases, this may lead to false positives, but a different exclusion rule
will need to be added to handle these.
Change since v2:
* Corrected comment formatting.
* Added csharp-property-skip-body test.
* Added comments in test code to explain sections not part of the test.
* Elaborated regex comments.
* Excluded math operators (+-*/%) in method pattern to not catch
multiline operations, and tested for this in the -skip-body tests.
Catching "-" only worked when it was defined at the end of the
exclusion block for some reason. The regex matcher seems quite
bugged.Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1682%2FWhathecode%2Fmaster-v3 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1682/Whathecode/master-v3 Pull-Request: https://github.com/git/git/pull/1682
Range-diff vs v2:
1: 00315519014 ! 1: 9b6c76f5342 userdiff: better method/property matching for C#
@@ t/t4018/csharp-method (new)
@@
+class Example
+{
-+ string Method(int RIGHT)
-+ {
-+ // Filler
-+ // Filler
-+
-+ return "ChangeMe";
-+ }
++ string Method(int RIGHT)
++ {
++ // Filler
++ // Filler
++
++ return "ChangeMe";
++ }
+}
## t/t4018/csharp-method-explicit (new) ##
@@ t/t4018/csharp-method-explicit (new)
+
+class Example : IDisposable
+{
-+ void IDisposable.Dispose() // RIGHT
-+ {
-+ // Filler
-+ // Filler
-+
-+ // ChangeMe
-+ }
++ void IDisposable.Dispose() // RIGHT
++ {
++ // Filler
++ // Filler
++
++ // ChangeMe
++ }
+}
## t/t4018/csharp-method-generics (new) ##
@@
+class Example<T1, T2>
+{
-+ Example<int, string> Method<TA, TB>(TA RIGHT, TB b)
-+ {
-+ // Filler
-+ // Filler
-+
-+ // ChangeMe
-+ return null;
-+ }
++ Example<int, string> Method<TA, TB>(TA RIGHT, TB b)
++ {
++ // Filler
++ // Filler
++
++ // ChangeMe
++ return null;
++ }
+}
## t/t4018/csharp-method-modifiers (new) ##
@@ t/t4018/csharp-method-modifiers (new)
+
+class Example
+{
-+ static internal async Task Method(int RIGHT)
-+ {
-+ // Filler
-+ // Filler
-+
-+ // ChangeMe
-+ await Task.Delay(1);
-+ }
++ static internal async Task Method(int RIGHT)
++ {
++ // Filler
++ // Filler
++
++ // ChangeMe
++ await Task.Delay(1);
++ }
+}
## t/t4018/csharp-method-multiline (new) ##
@@
+class Example
+{
-+ string Method_RIGHT(
-+ int a,
-+ int b,
-+ int c)
-+ {
-+ return "ChangeMe";
-+ }
++ string Method_RIGHT(
++ int a,
++ int b,
++ int c)
++ {
++ return "ChangeMe";
++ }
+}
## t/t4018/csharp-method-params (new) ##
@@
+class Example
+{
-+ string Method(int RIGHT, int b, int c = 42)
-+ {
-+ // Filler
-+ // Filler
-+
-+ return "ChangeMe";
-+ }
++ string Method(int RIGHT, int b, int c = 42)
++ {
++ // Filler
++ // Filler
++
++ return "ChangeMe";
++ }
+}
## t/t4018/csharp-method-skip-body (new) ##
@@ t/t4018/csharp-method-skip-body (new)
+
+class Example : IDisposable
+{
-+ string Method(int RIGHT)
-+ {
-+ // Method calls
-+ MethodCall();
-+ MethodCall(1, 2);
-+ MethodCall(
-+ 1, 2);
-+
-+ // Assignments
-+ var constantAssignment = "test";
-+ var methodAssignment = MethodCall();
-+ var multiLineMethodAssignment = MethodCall(
-+ );
-+
-+ // Initializations/disposal
-+ new Example();
-+ new Example(
-+ );
-+ new Example { };
-+ using (this)
-+ {
-+ }
-+ var def =
-+ this is default(
-+ Example);
-+
-+ // Iteration statements
-+ do { } while (true);
-+ do MethodCall(
-+ ); while (true);
-+ while (true);
-+ while (true) {
-+ break;
-+ }
-+ for (int i = 0; i < 10; ++i)
-+ {
-+ }
-+ foreach (int i in Enumerable.Range(0, 10))
-+ {
-+ }
-+ int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
-+
-+ // Control
-+ if (false)
-+ {
-+ return "out";
-+ }
-+ else { }
-+ if (true) MethodCall(
-+ );
-+ else MethodCall(
-+ );
-+ switch ("test")
-+ {
-+ case "one":
-+ return MethodCall(
-+ );
-+ case "two":
-+ break;
-+ }
-+ (int, int) tuple = (1, 4);
-+ switch (tuple)
-+ {
-+ case (1, 4):
-+ MethodCall();
-+ }
-+
-+ // Exceptions
-+ try
-+ {
-+ throw new Exception("fail");
-+ }
-+ catch (Exception)
-+ {
-+ }
-+ finally
-+ {
-+ }
-+ try { } catch (Exception) {}
-+ try
-+ {
-+ throw GetException(
-+ );
-+ }
-+ catch (Exception) { }
-+
-+ // Others
-+ lock (this)
-+ {
-+ }
-+ unsafe
-+ {
-+ byte[] bytes = [1, 2, 3];
-+ fixed (byte* pointerToFirst = bytes)
-+ {
-+ }
-+ }
-+
-+ return "ChangeMe";
-+ }
-+
-+ public void Dispose() {}
-+
-+ string MethodCall(int a = 0, int b = 0) => "test";
-+ Exception GetException() => new Exception("fail");
-+ int[] Numbers() => [0, 1];
++ string Method(int RIGHT)
++ {
++ // Method calls
++ MethodCall();
++ MethodCall(1, 2);
++ MethodCall(
++ 1, 2);
++
++ // Assignments
++ var constantAssignment = "test";
++ var methodAssignment = MethodCall();
++ var multiLineMethodAssignment = MethodCall(
++ );
++ var multiLine = "first"
++ + MethodCall()
++ - MethodCall()
++ + MethodCall();
++
++ // Initializations/disposal
++ new Example();
++ new Example(
++ );
++ new Example { };
++ using (this)
++ {
++ }
++ var def =
++ this is default(
++ Example);
++
++ // Iteration statements
++ do { } while (true);
++ do MethodCall(
++ ); while (true);
++ while (true);
++ while (true) {
++ break;
++ }
++ for (int i = 0; i < 10; ++i)
++ {
++ }
++ foreach (int i in Enumerable.Range(0, 10))
++ {
++ }
++ int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
++
++ // Control
++ if (false)
++ {
++ return "out";
++ }
++ else { }
++ if (true) MethodCall(
++ );
++ else MethodCall(
++ );
++ switch ("test")
++ {
++ case "one":
++ return MethodCall(
++ );
++ case "two":
++ break;
++ }
++ (int, int) tuple = (1, 4);
++ switch (tuple)
++ {
++ case (1, 4):
++ MethodCall();
++ }
++
++ // Exceptions
++ try
++ {
++ throw new Exception("fail");
++ }
++ catch (Exception)
++ {
++ }
++ finally
++ {
++ }
++ try { } catch (Exception) {}
++ try
++ {
++ throw GetException(
++ );
++ }
++ catch (Exception) { }
++
++ // Others
++ lock (this)
++ {
++ }
++ unsafe
++ {
++ byte[] bytes = [1, 2, 3];
++ fixed (byte* pointerToFirst = bytes)
++ {
++ }
++ }
++
++ return "ChangeMe";
++ }
++
++ // Supporting methods to make the tested method above valid C#.
++ public void Dispose() {}
++ string MethodCall(int a = 0, int b = 0) => "test";
++ Exception GetException() => new Exception("fail");
++ int[] Numbers() => [0, 1];
+}
## t/t4018/csharp-method-special-chars (new) ##
@@
+class @Some_Type
+{
-+ @Some_Type @Method_With_Underscore(int RIGHT)
-+ {
-+ // Filler
-+ // Filler
-+
-+ // ChangeMe
-+ return new @Some_Type();
-+ }
++ @Some_Type @Method_With_Underscore(int RIGHT)
++ {
++ // Filler
++ // Filler
++
++ // ChangeMe
++ return new @Some_Type();
++ }
+}
## t/t4018/csharp-method-with-spacing (new) ##
@@ t/t4018/csharp-method-with-spacing (new)
+{
+ string Method ( int RIGHT )
+ {
-+ // Filler
-+ // Filler
++ // Filler
++ // Filler
+
-+ return "ChangeMe";
-+ }
++ return "ChangeMe";
++ }
+}
## t/t4018/csharp-property (new) ##
@@
+class Example
+{
-+ public bool RIGHT
++ public bool RIGHT
+ {
+ get { return true; }
+ set
@@ t/t4018/csharp-property (new)
+ // ChangeMe
+ }
+ }
++}
+
+ ## t/t4018/csharp-property-skip-body (new) ##
+@@
++using System.Linq;
++using System;
++
++class Example : IDisposable
++{
++ public string RIGHT
++ {
++ get
++ {
++ // Method calls
++ MethodCall();
++ MethodCall(1, 2);
++ MethodCall(
++ 1, 2);
++
++ // Assignments
++ var constantAssignment = "test";
++ var methodAssignment = MethodCall();
++ var multiLineMethodAssignment = MethodCall(
++ );
++ var multiLine = "first"
++ + MethodCall()
++ - MethodCall()
++ + MethodCall();
++
++ // Initializations/disposal
++ new Example();
++ new Example(
++ );
++ new Example { };
++ using (this)
++ {
++ }
++ var def =
++ this is default(
++ Example);
++
++ // Iteration statements
++ do { } while (true);
++ do MethodCall(
++ ); while (true);
++ while (true);
++ while (true) {
++ break;
++ }
++ for (int i = 0; i < 10; ++i)
++ {
++ }
++ foreach (int i in Enumerable.Range(0, 10))
++ {
++ }
++ int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
++
++ // Control
++ if (false)
++ {
++ return "out";
++ }
++ else { }
++ if (true) MethodCall(
++ );
++ else MethodCall(
++ );
++ switch ("test")
++ {
++ case "one":
++ return MethodCall(
++ );
++ case "two":
++ break;
++ }
++ (int, int) tuple = (1, 4);
++ switch (tuple)
++ {
++ case (1, 4):
++ MethodCall();
++ }
++
++ // Exceptions
++ try
++ {
++ throw new Exception("fail");
++ }
++ catch (Exception)
++ {
++ }
++ finally
++ {
++ }
++ try { } catch (Exception) {}
++ try
++ {
++ throw GetException(
++ );
++ }
++ catch (Exception) { }
++
++ // Others
++ lock (this)
++ {
++ }
++ unsafe
++ {
++ byte[] bytes = [1, 2, 3];
++ fixed (byte* pointerToFirst = bytes)
++ {
++ }
++ }
++
++ return "ChangeMe";
++ }
++ set { }
++ }
++
++ // Supporting methods to make the tested property above valid C#.
++ public void Dispose() {}
++ string MethodCall(int a = 0, int b = 0) => "test";
++ Exception GetException() => new Exception("fail");
++ int[] Numbers() => [0, 1];
+}
## userdiff.c ##
@@ userdiff.c: PATTERNS("cpp",
- "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n"
- /* Methods and constructors */
- "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n"
+- /* Properties */
+- "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n"
+ /*
-+ * Jump over keywords not used by methods which can be followed by parentheses without special characters in between,
++ * Jump over reserved keywords which are illegal method names, but which
++ * can be followed by parentheses without special characters in between,
+ * making them look like methods.
+ */
+ "!(^|[ \t]+)(do|while|for|foreach|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \t(]+|$)\n"
-+ /* Methods/constructors:
-+ * the strategy is to identify a minimum of two groups (any combination of keywords/type/name),
-+ * without intermediate or final characters which can't be part of method definitions before the opening parenthesis.
++ /*
++ * Methods/constructors:
++ * The strategy is to identify a minimum of two groups (any combination
++ * of keywords/type/name), without intermediate characters which can't be
++ * part of method definitions before the opening parenthesis, and without
++ * final unexpected characters, normally only used in ordinary statements.
++ * "=" is excluded to ignore assignments, but as a result rules out
++ * methods with expression bodies. However, since those fit on 1-2 lines,
++ * a chunk header isn't useful either way.
+ */
-+ "^[ \t]*(([][[:alnum:]@_<>.,]*[^=:{ \t][ \t]+[][[:alnum:]@_<>.,]*)+\\([^;]*)$\n"
- /* Properties */
-- "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n"
-+ "^[ \t]*((([][[:alnum:]@_<>.,]+)[ \t]+[][[:alnum:]@_]*)+[^=:;,()]*)$\n"
++ "^[ \t]*(([][[:alnum:]@_<>.,]*[^=:{ \t+*%\\/\\-][ \t]+[][[:alnum:]@_<>.,]*)+\\([^;]*)$\n"
++ /*
++ * Properties:
++ * As with methods, expect a minimum of two groups. And, the vast majority
++ * of properties long enough to be worth showing a chunk header for don't
++ * include "=:;,()" on the line they are defined.
++ */
++ "^[ \t]*([][[:alnum:]@_<>.,]+[ \t]+[][[:alnum:]@_.]+[^=:;,()]*)$\n"
/* Type definitions */
"^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct|record)[ \t]+.*)$\n"
/* Namespace */t/t4018/csharp-method | 10 +++ t/t4018/csharp-method-explicit | 12 +++ t/t4018/csharp-method-generics | 11 +++ t/t4018/csharp-method-modifiers | 13 +++ t/t4018/csharp-method-multiline | 10 +++ t/t4018/csharp-method-params | 10 +++ t/t4018/csharp-method-skip-body | 116 +++++++++++++++++++++++++++ t/t4018/csharp-method-special-chars | 11 +++ t/t4018/csharp-method-with-spacing | 10 +++ t/t4018/csharp-property | 11 +++ t/t4018/csharp-property-skip-body | 120 ++++++++++++++++++++++++++++ userdiff.c | 30 +++++-- 12 files changed, 358 insertions(+), 6 deletions(-) create mode 100644 t/t4018/csharp-method create mode 100644 t/t4018/csharp-method-explicit create mode 100644 t/t4018/csharp-method-generics create mode 100644 t/t4018/csharp-method-modifiers create mode 100644 t/t4018/csharp-method-multiline create mode 100644 t/t4018/csharp-method-params create mode 100644 t/t4018/csharp-method-skip-body create mode 100644 t/t4018/csharp-method-special-chars create mode 100644 t/t4018/csharp-method-with-spacing create mode 100644 t/t4018/csharp-property create mode 100644 t/t4018/csharp-property-skip-body
diff --git a/t/t4018/csharp-method b/t/t4018/csharp-method new file mode 100644 index 00000000000..16b367aca2b --- /dev/null +++ b/t/t4018/csharp-method @@ -0,0 +1,10 @@ +class Example +{ + string Method(int RIGHT) + { + // Filler + // Filler + + return "ChangeMe"; + } +} diff --git a/t/t4018/csharp-method-explicit b/t/t4018/csharp-method-explicit new file mode 100644 index 00000000000..5a710116cc4 --- /dev/null +++ b/t/t4018/csharp-method-explicit @@ -0,0 +1,12 @@ +using System; + +class Example : IDisposable +{ + void IDisposable.Dispose() // RIGHT + { + // Filler + // Filler + + // ChangeMe + } +} diff --git a/t/t4018/csharp-method-generics b/t/t4018/csharp-method-generics new file mode 100644 index 00000000000..b3216bfb2a7 --- /dev/null +++ b/t/t4018/csharp-method-generics @@ -0,0 +1,11 @@ +class Example<T1, T2> +{ + Example<int, string> Method<TA, TB>(TA RIGHT, TB b) + { + // Filler + // Filler + + // ChangeMe + return null; + } +} diff --git a/t/t4018/csharp-method-modifiers b/t/t4018/csharp-method-modifiers new file mode 100644 index 00000000000..caefa8ee99c --- /dev/null +++ b/t/t4018/csharp-method-modifiers @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +class Example +{ + static internal async Task Method(int RIGHT) + { + // Filler + // Filler + + // ChangeMe + await Task.Delay(1); + } +} diff --git a/t/t4018/csharp-method-multiline b/t/t4018/csharp-method-multiline new file mode 100644 index 00000000000..3983ff42f51 --- /dev/null +++ b/t/t4018/csharp-method-multiline @@ -0,0 +1,10 @@ +class Example +{ + string Method_RIGHT( + int a, + int b, + int c) + { + return "ChangeMe"; + } +} diff --git a/t/t4018/csharp-method-params b/t/t4018/csharp-method-params new file mode 100644 index 00000000000..3f00410ba1f --- /dev/null +++ b/t/t4018/csharp-method-params @@ -0,0 +1,10 @@ +class Example +{ + string Method(int RIGHT, int b, int c = 42) + { + // Filler + // Filler + + return "ChangeMe"; + } +} diff --git a/t/t4018/csharp-method-skip-body b/t/t4018/csharp-method-skip-body new file mode 100644 index 00000000000..b3c7194eb74 --- /dev/null +++ b/t/t4018/csharp-method-skip-body @@ -0,0 +1,116 @@ +using System.Linq; +using System; + +class Example : IDisposable +{ + string Method(int RIGHT) + { + // Method calls + MethodCall(); + MethodCall(1, 2); + MethodCall( + 1, 2); + + // Assignments + var constantAssignment = "test"; + var methodAssignment = MethodCall(); + var multiLineMethodAssignment = MethodCall( + ); + var multiLine = "first" + + MethodCall() + - MethodCall() + + MethodCall(); + + // Initializations/disposal + new Example(); + new Example( + ); + new Example { }; + using (this) + { + } + var def = + this is default( + Example); + + // Iteration statements + do { } while (true); + do MethodCall( + ); while (true); + while (true); + while (true) { + break; + } + for (int i = 0; i < 10; ++i) + { + } + foreach (int i in Enumerable.Range(0, 10)) + { + } + int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0]; + + // Control + if (false) + { + return "out"; + } + else { } + if (true) MethodCall( + ); + else MethodCall( + ); + switch ("test") + { + case "one": + return MethodCall( + ); + case "two": + break; + } + (int, int) tuple = (1, 4); + switch (tuple) + { + case (1, 4): + MethodCall(); + } + + // Exceptions + try + { + throw new Exception("fail"); + } + catch (Exception) + { + } + finally + { + } + try { } catch (Exception) {} + try + { + throw GetException( + ); + } + catch (Exception) { } + + // Others + lock (this) + { + } + unsafe + { + byte[] bytes = [1, 2, 3]; + fixed (byte* pointerToFirst = bytes) + { + } + } + + return "ChangeMe"; + } + + // Supporting methods to make the tested method above valid C#. + public void Dispose() {} + string MethodCall(int a = 0, int b = 0) => "test"; + Exception GetException() => new Exception("fail"); + int[] Numbers() => [0, 1]; +} diff --git a/t/t4018/csharp-method-special-chars b/t/t4018/csharp-method-special-chars new file mode 100644 index 00000000000..e6c7bc01a18 --- /dev/null +++ b/t/t4018/csharp-method-special-chars @@ -0,0 +1,11 @@ +class @Some_Type +{ + @Some_Type @Method_With_Underscore(int RIGHT) + { + // Filler + // Filler + + // ChangeMe + return new @Some_Type(); + } +} diff --git a/t/t4018/csharp-method-with-spacing b/t/t4018/csharp-method-with-spacing new file mode 100644 index 00000000000..da0c9b7e60c --- /dev/null +++ b/t/t4018/csharp-method-with-spacing @@ -0,0 +1,10 @@ +class Example +{ + string Method ( int RIGHT ) + { + // Filler + // Filler + + return "ChangeMe"; + } +} diff --git a/t/t4018/csharp-property b/t/t4018/csharp-property new file mode 100644 index 00000000000..e56dfce34c1 --- /dev/null +++ b/t/t4018/csharp-property @@ -0,0 +1,11 @@ +class Example +{ + public bool RIGHT + { + get { return true; } + set + { + // ChangeMe + } + } +} diff --git a/t/t4018/csharp-property-skip-body b/t/t4018/csharp-property-skip-body new file mode 100644 index 00000000000..ad9d96007a8 --- /dev/null +++ b/t/t4018/csharp-property-skip-body @@ -0,0 +1,120 @@ +using System.Linq; +using System; + +class Example : IDisposable +{ + public string RIGHT + { + get + { + // Method calls + MethodCall(); + MethodCall(1, 2); + MethodCall( + 1, 2); + + // Assignments + var constantAssignment = "test"; + var methodAssignment = MethodCall(); + var multiLineMethodAssignment = MethodCall( + ); + var multiLine = "first" + + MethodCall() + - MethodCall() + + MethodCall(); + + // Initializations/disposal + new Example(); + new Example( + ); + new Example { }; + using (this) + { + } + var def = + this is default( + Example); + + // Iteration statements + do { } while (true); + do MethodCall( + ); while (true); + while (true); + while (true) { + break; + } + for (int i = 0; i < 10; ++i) + { + } + foreach (int i in Enumerable.Range(0, 10)) + { + } + int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0]; + + // Control + if (false) + { + return "out"; + } + else { } + if (true) MethodCall( + ); + else MethodCall( + ); + switch ("test") + { + case "one": + return MethodCall( + ); + case "two": + break; + } + (int, int) tuple = (1, 4); + switch (tuple) + { + case (1, 4): + MethodCall(); + } + + // Exceptions + try + { + throw new Exception("fail"); + } + catch (Exception) + { + } + finally + { + } + try { } catch (Exception) {} + try + { + throw GetException( + ); + } + catch (Exception) { } + + // Others + lock (this) + { + } + unsafe + { + byte[] bytes = [1, 2, 3]; + fixed (byte* pointerToFirst = bytes) + { + } + } + + return "ChangeMe"; + } + set { } + } + + // Supporting methods to make the tested property above valid C#. + public void Dispose() {} + string MethodCall(int a = 0, int b = 0) => "test"; + Exception GetException() => new Exception("fail"); + int[] Numbers() => [0, 1]; +} diff --git a/userdiff.c b/userdiff.c index e399543823b..5440ccf2de5 100644 --- a/userdiff.c +++ b/userdiff.c @@ -89,12 +89,30 @@ PATTERNS("cpp", "|\\.[0-9][0-9]*([Ee][-+]?[0-9]+)?[fFlL]?" "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*|<=>"), PATTERNS("csharp", - /* Keywords */ - "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n" - /* Methods and constructors */ - "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n" - /* Properties */ - "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n" + /* + * Jump over reserved keywords which are illegal method names, but which + * can be followed by parentheses without special characters in between, + * making them look like methods. + */ + "!(^|[ \t]+)(do|while|for|foreach|if|else|new|default|return|switch|case|throw|catch|using|lock|fixed)([ \t(]+|$)\n" + /* + * Methods/constructors: + * The strategy is to identify a minimum of two groups (any combination + * of keywords/type/name), without intermediate characters which can't be + * part of method definitions before the opening parenthesis, and without + * final unexpected characters, normally only used in ordinary statements. + * "=" is excluded to ignore assignments, but as a result rules out + * methods with expression bodies. However, since those fit on 1-2 lines, + * a chunk header isn't useful either way. + */ + "^[ \t]*(([][[:alnum:]@_<>.,]*[^=:{ \t+*%\\/\\-][ \t]+[][[:alnum:]@_<>.,]*)+\\([^;]*)$\n" + /* + * Properties: + * As with methods, expect a minimum of two groups. And, the vast majority + * of properties long enough to be worth showing a chunk header for don't + * include "=:;,()" on the line they are defined. + */ + "^[ \t]*([][[:alnum:]@_<>.,]+[ \t]+[][[:alnum:]@_.]+[^=:;,()]*)$\n" /* Type definitions */ "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct|record)[ \t]+.*)$\n" /* Namespace */ base-commit: f41f85c9ec8d4d46de0fd5fded88db94d3ec8c11
-- gitgitgadget