userdiff: better method/property matching for C#
- 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> Acked-by: Johannes Sixt <j6t@kdbg.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Steven Jeuris committed
Apr 3, 2024 at 21:42 UTC
ec0e3075d245afa3f3a848bd7bdc6376dea85fe0
21 files changed
+352
-6
t/t4018/csharp-exclude-assignments
new
+20
@@ -0,0 +1,20 @@
1
+class Example
2
+{
3
+ string Method(int RIGHT)
4
+ {
5
+ var constantAssignment = "test";
6
+ var methodAssignment = MethodCall();
7
+ var multiLineMethodAssignment = MethodCall(
8
+ );
9
+ var multiLine = "first"
10
+ + MethodCall()
11
+ +
12
+ ( MethodCall()
13
+ )
14
+ + MethodCall();
15
+
16
+ return "ChangeMe";
17
+ }
18
+
19
+ string MethodCall(int a = 0, int b = 0) => "test";
20
+}
t/t4018/csharp-exclude-control-statements
new
+34
@@ -0,0 +1,34 @@
1
+class Example
2
+{
3
+ string Method(int RIGHT)
4
+ {
5
+ if (false)
6
+ {
7
+ return "out";
8
+ }
9
+ else { }
10
+ if (true) MethodCall(
11
+ );
12
+ else MethodCall(
13
+ );
14
+ switch ("test")
15
+ {
16
+ case "one":
17
+ return MethodCall(
18
+ );
19
+ case "two":
20
+ break;
21
+ }
22
+ (int, int) tuple = (1, 4);
23
+ switch (tuple)
24
+ {
25
+ case (1, 4):
26
+ MethodCall();
27
+ break;
28
+ }
29
+
30
+ return "ChangeMe";
31
+ }
32
+
33
+ string MethodCall(int a = 0, int b = 0) => "test";
34
+}
t/t4018/csharp-exclude-exceptions
new
+29
@@ -0,0 +1,29 @@
1
+using System;
2
+
3
+class Example
4
+{
5
+ string Method(int RIGHT)
6
+ {
7
+ try
8
+ {
9
+ throw new Exception("fail");
10
+ }
11
+ catch (Exception)
12
+ {
13
+ }
14
+ finally
15
+ {
16
+ }
17
+ try { } catch (Exception) {}
18
+ try
19
+ {
20
+ throw GetException(
21
+ );
22
+ }
23
+ catch (Exception) { }
24
+
25
+ return "ChangeMe";
26
+ }
27
+
28
+ Exception GetException() => new Exception("fail");
29
+}
t/t4018/csharp-exclude-generic-method-calls
new
+12
@@ -0,0 +1,12 @@
1
+class Example
2
+{
3
+ string Method(int RIGHT)
4
+ {
5
+ GenericMethodCall<int, int>(
6
+ );
7
+
8
+ return "ChangeMe";
9
+ }
10
+
11
+ string GenericMethodCall<T, T2>() => "test";
12
+}
t/t4018/csharp-exclude-init-dispose
new
+22
@@ -0,0 +1,22 @@
1
+using System;
2
+
3
+class Example : IDisposable
4
+{
5
+ string Method(int RIGHT)
6
+ {
7
+ new Example();
8
+ new Example(
9
+ );
10
+ new Example { };
11
+ using (this)
12
+ {
13
+ }
14
+ var def =
15
+ this is default(
16
+ Example);
17
+
18
+ return "ChangeMe";
19
+ }
20
+
21
+ public void Dispose() {}
22
+}
t/t4018/csharp-exclude-iterations
new
+26
@@ -0,0 +1,26 @@
1
+using System.Linq;
2
+
3
+class Example
4
+{
5
+ string Method(int RIGHT)
6
+ {
7
+ do { } while (true);
8
+ do MethodCall(
9
+ ); while (true);
10
+ while (true);
11
+ while (true) {
12
+ break;
13
+ }
14
+ for (int i = 0; i < 10; ++i)
15
+ {
16
+ }
17
+ foreach (int i in Enumerable.Range(0, 10))
18
+ {
19
+ }
20
+ int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
21
+
22
+ return "ChangeMe";
23
+ }
24
+
25
+ string MethodCall(int a = 0, int b = 0) => "test";
26
+}
t/t4018/csharp-exclude-method-calls
new
+20
@@ -0,0 +1,20 @@
1
+class Example
2
+{
3
+ string Method(int RIGHT)
4
+ {
5
+ MethodCall();
6
+ MethodCall(1, 2);
7
+ MethodCall(
8
+ 1, 2);
9
+ MethodCall(
10
+ 1, 2,
11
+ 3);
12
+ MethodCall(
13
+ 1, MethodCall(),
14
+ 2);
15
+
16
+ return "ChangeMe";
17
+ }
18
+
19
+ int MethodCall(int a = 0, int b = 0, int c = 0) => 42;
20
+}
t/t4018/csharp-exclude-other
new
+18
@@ -0,0 +1,18 @@
1
+class Example
2
+{
3
+ string Method(int RIGHT)
4
+ {
5
+ lock (this)
6
+ {
7
+ }
8
+ unsafe
9
+ {
10
+ byte[] bytes = [1, 2, 3];
11
+ fixed (byte* pointerToFirst = bytes)
12
+ {
13
+ }
14
+ }
15
+
16
+ return "ChangeMe";
17
+ }
18
+}
t/t4018/csharp-method
new
+10
@@ -0,0 +1,10 @@
1
+class Example
2
+{
3
+ string Method(int RIGHT)
4
+ {
5
+ // Filler
6
+ // Filler
7
+
8
+ return "ChangeMe";
9
+ }
10
+}
t/t4018/csharp-method-array
new
+10
@@ -0,0 +1,10 @@
1
+class Example
2
+{
3
+ string[] Method(int RIGHT)
4
+ {
5
+ // Filler
6
+ // Filler
7
+
8
+ return ["ChangeMe"];
9
+ }
10
+}
t/t4018/csharp-method-explicit
new
+12
@@ -0,0 +1,12 @@
1
+using System;
2
+
3
+class Example : IDisposable
4
+{
5
+ void IDisposable.Dispose() // RIGHT
6
+ {
7
+ // Filler
8
+ // Filler
9
+
10
+ // ChangeMe
11
+ }
12
+}
t/t4018/csharp-method-generics
new
+11
@@ -0,0 +1,11 @@
1
+class Example<T1, T2>
2
+{
3
+ Example<int, string> Method<TA, TB>(TA RIGHT, TB b)
4
+ {
5
+ // Filler
6
+ // Filler
7
+
8
+ // ChangeMe
9
+ return null;
10
+ }
11
+}
t/t4018/csharp-method-generics-alternate-spaces
new
+11
@@ -0,0 +1,11 @@
1
+class Example<T1, T2>
2
+{
3
+ Example<int,string> Method<TA ,TB>(TA RIGHT, TB b)
4
+ {
5
+ // Filler
6
+ // Filler
7
+
8
+ // ChangeMe
9
+ return null;
10
+ }
11
+}
t/t4018/csharp-method-modifiers
new
+13
@@ -0,0 +1,13 @@
1
+using System.Threading.Tasks;
2
+
3
+class Example
4
+{
5
+ static internal async Task Method(int RIGHT)
6
+ {
7
+ // Filler
8
+ // Filler
9
+
10
+ // ChangeMe
11
+ await Task.Delay(1);
12
+ }
13
+}
t/t4018/csharp-method-multiline
new
+10
@@ -0,0 +1,10 @@
1
+class Example
2
+{
3
+ string Method_RIGHT(
4
+ int a,
5
+ int b,
6
+ int c)
7
+ {
8
+ return "ChangeMe";
9
+ }
10
+}
t/t4018/csharp-method-params
new
+10
@@ -0,0 +1,10 @@
1
+class Example
2
+{
3
+ string Method(int RIGHT, int b, int c = 42)
4
+ {
5
+ // Filler
6
+ // Filler
7
+
8
+ return "ChangeMe";
9
+ }
10
+}
t/t4018/csharp-method-special-chars
new
+11
@@ -0,0 +1,11 @@
1
+class @Some_Type
2
+{
3
+ @Some_Type @Method_With_Underscore(int RIGHT)
4
+ {
5
+ // Filler
6
+ // Filler
7
+
8
+ // ChangeMe
9
+ return new @Some_Type();
10
+ }
11
+}
t/t4018/csharp-method-with-spacing
new
+10
@@ -0,0 +1,10 @@
1
+class Example
2
+{
3
+ string Method ( int RIGHT )
4
+ {
5
+ // Filler
6
+ // Filler
7
+
8
+ return "ChangeMe";
9
+ }
10
+}
t/t4018/csharp-property
new
+11
@@ -0,0 +1,11 @@
1
+class Example
2
+{
3
+ public bool RIGHT
4
+ {
5
+ get { return true; }
6
+ set
7
+ {
8
+ // ChangeMe
9
+ }
10
+ }
11
+}
t/t4018/csharp-property-braces-same-line
new
+10
@@ -0,0 +1,10 @@
1
+class Example
2
+{
3
+ public bool RIGHT {
4
+ get { return true; }
5
+ set
6
+ {
7
+ // ChangeMe
8
+ }
9
+ }
10
+}
userdiff.c
+42
-6
@@ -90,12 +90,48 @@ PATTERNS("cpp",
90
"|\\.[0-9][0-9]*([Ee][-+]?[0-9]+)?[fFlL]?"
91
"|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*|<=>"),
92
PATTERNS("csharp",
93
- /* Keywords */
94
- "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n"
95
- /* Methods and constructors */
96
- "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n"
97
- /* Properties */
98
- "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n"
93
+ /*
94
+ * Jump over reserved keywords which are illegal method names, but which
95
+ * can be followed by parentheses without special characters in between,
96
+ * making them look like methods.
97
+ */
98
+ "!(^|[ \t]+)" /* Start of line or whitespace. */
99
+ "(do|while|for|foreach|if|else|new|default|return|switch|case|throw"
100
+ "|catch|using|lock|fixed)"
101
+ "([ \t(]+|$)\n" /* Whitespace, "(", or end of line. */
102
+ /*
103
+ * Methods/constructors:
104
+ * The strategy is to identify a minimum of two groups (any combination
105
+ * of keywords/type/name) before the opening parenthesis, and without
106
+ * final unexpected characters, normally only used in ordinary statements.
107
+ */
108
+ "^[ \t]*" /* Remove leading whitespace. */
109
+ "(" /* Start chunk header capture. */
110
+ "(" /* First group. */
111
+ "[][[:alnum:]@_.]" /* Name. */
112
+ "(<[][[:alnum:]@_, \t<>]+>)?" /* Optional generic parameters. */
113
+ ")+"
114
+ "([ \t]+" /* Subsequent groups, prepended with space. */
115
+ "([][[:alnum:]@_.](<[][[:alnum:]@_, \t<>]+>)?)+"
116
+ ")+"
117
+ "[ \t]*" /* Optional space before parameters start. */
118
+ "\\(" /* Start of method parameters. */
119
+ "[^;]*" /* Allow complex parameters, but exclude statements (;). */
120
+ ")$\n" /* Close chunk header capture. */
121
+ /*
122
+ * Properties:
123
+ * As with methods, expect a minimum of two groups. But, more trivial than
124
+ * methods, the vast majority of properties long enough to be worth
125
+ * showing a chunk header for don't include "=:;,()" on the line they are
126
+ * defined, since they don't have a parameter list.
127
+ */
128
+ "^[ \t]*("
129
+ "([][[:alnum:]@_.](<[][[:alnum:]@_, \t<>]+>)?)+"
130
+ "([ \t]+"
131
+ "([][[:alnum:]@_.](<[][[:alnum:]@_, \t<>]+>)?)+"
132
+ ")+" /* Up to here, same as methods regex. */
133
+ "[^;=:,()]*" /* Compared to methods, no parameter list allowed. */
134
+ ")$\n"
135
/* Type definitions */
136
"^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct|record)[ \t]+.*)$\n"
137
/* Namespace */