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>
This commit is contained in:
Steven Jeuris 2024-04-03 21:42:44 +00:00 committed by Junio C Hamano
parent 43072b4ca1
commit ec0e3075d2
21 changed files with 352 additions and 6 deletions

View File

@ -0,0 +1,20 @@
class Example
{
string Method(int RIGHT)
{
var constantAssignment = "test";
var methodAssignment = MethodCall();
var multiLineMethodAssignment = MethodCall(
);
var multiLine = "first"
+ MethodCall()
+
( MethodCall()
)
+ MethodCall();
return "ChangeMe";
}
string MethodCall(int a = 0, int b = 0) => "test";
}

View File

@ -0,0 +1,34 @@
class Example
{
string Method(int RIGHT)
{
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();
break;
}
return "ChangeMe";
}
string MethodCall(int a = 0, int b = 0) => "test";
}

View File

@ -0,0 +1,29 @@
using System;
class Example
{
string Method(int RIGHT)
{
try
{
throw new Exception("fail");
}
catch (Exception)
{
}
finally
{
}
try { } catch (Exception) {}
try
{
throw GetException(
);
}
catch (Exception) { }
return "ChangeMe";
}
Exception GetException() => new Exception("fail");
}

View File

@ -0,0 +1,12 @@
class Example
{
string Method(int RIGHT)
{
GenericMethodCall<int, int>(
);
return "ChangeMe";
}
string GenericMethodCall<T, T2>() => "test";
}

View File

@ -0,0 +1,22 @@
using System;
class Example : IDisposable
{
string Method(int RIGHT)
{
new Example();
new Example(
);
new Example { };
using (this)
{
}
var def =
this is default(
Example);
return "ChangeMe";
}
public void Dispose() {}
}

View File

@ -0,0 +1,26 @@
using System.Linq;
class Example
{
string Method(int RIGHT)
{
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];
return "ChangeMe";
}
string MethodCall(int a = 0, int b = 0) => "test";
}

View File

@ -0,0 +1,20 @@
class Example
{
string Method(int RIGHT)
{
MethodCall();
MethodCall(1, 2);
MethodCall(
1, 2);
MethodCall(
1, 2,
3);
MethodCall(
1, MethodCall(),
2);
return "ChangeMe";
}
int MethodCall(int a = 0, int b = 0, int c = 0) => 42;
}

View File

@ -0,0 +1,18 @@
class Example
{
string Method(int RIGHT)
{
lock (this)
{
}
unsafe
{
byte[] bytes = [1, 2, 3];
fixed (byte* pointerToFirst = bytes)
{
}
}
return "ChangeMe";
}
}

10
t/t4018/csharp-method Normal file
View File

@ -0,0 +1,10 @@
class Example
{
string Method(int RIGHT)
{
// Filler
// Filler
return "ChangeMe";
}
}

View File

@ -0,0 +1,10 @@
class Example
{
string[] Method(int RIGHT)
{
// Filler
// Filler
return ["ChangeMe"];
}
}

View File

@ -0,0 +1,12 @@
using System;
class Example : IDisposable
{
void IDisposable.Dispose() // RIGHT
{
// Filler
// Filler
// ChangeMe
}
}

View File

@ -0,0 +1,11 @@
class Example<T1, T2>
{
Example<int, string> Method<TA, TB>(TA RIGHT, TB b)
{
// Filler
// Filler
// ChangeMe
return null;
}
}

View File

@ -0,0 +1,11 @@
class Example<T1, T2>
{
Example<int,string> Method<TA ,TB>(TA RIGHT, TB b)
{
// Filler
// Filler
// ChangeMe
return null;
}
}

View File

@ -0,0 +1,13 @@
using System.Threading.Tasks;
class Example
{
static internal async Task Method(int RIGHT)
{
// Filler
// Filler
// ChangeMe
await Task.Delay(1);
}
}

View File

@ -0,0 +1,10 @@
class Example
{
string Method_RIGHT(
int a,
int b,
int c)
{
return "ChangeMe";
}
}

View File

@ -0,0 +1,10 @@
class Example
{
string Method(int RIGHT, int b, int c = 42)
{
// Filler
// Filler
return "ChangeMe";
}
}

View File

@ -0,0 +1,11 @@
class @Some_Type
{
@Some_Type @Method_With_Underscore(int RIGHT)
{
// Filler
// Filler
// ChangeMe
return new @Some_Type();
}
}

View File

@ -0,0 +1,10 @@
class Example
{
string Method ( int RIGHT )
{
// Filler
// Filler
return "ChangeMe";
}
}

11
t/t4018/csharp-property Normal file
View File

@ -0,0 +1,11 @@
class Example
{
public bool RIGHT
{
get { return true; }
set
{
// ChangeMe
}
}
}

View File

@ -0,0 +1,10 @@
class Example
{
public bool RIGHT {
get { return true; }
set
{
// ChangeMe
}
}
}

View File

@ -90,12 +90,48 @@ 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]+)" /* Start of line or whitespace. */
"(do|while|for|foreach|if|else|new|default|return|switch|case|throw"
"|catch|using|lock|fixed)"
"([ \t(]+|$)\n" /* Whitespace, "(", or end of line. */
/*
* Methods/constructors:
* The strategy is to identify a minimum of two groups (any combination
* of keywords/type/name) before the opening parenthesis, and without
* final unexpected characters, normally only used in ordinary statements.
*/
"^[ \t]*" /* Remove leading whitespace. */
"(" /* Start chunk header capture. */
"(" /* First group. */
"[][[:alnum:]@_.]" /* Name. */
"(<[][[:alnum:]@_, \t<>]+>)?" /* Optional generic parameters. */
")+"
"([ \t]+" /* Subsequent groups, prepended with space. */
"([][[:alnum:]@_.](<[][[:alnum:]@_, \t<>]+>)?)+"
")+"
"[ \t]*" /* Optional space before parameters start. */
"\\(" /* Start of method parameters. */
"[^;]*" /* Allow complex parameters, but exclude statements (;). */
")$\n" /* Close chunk header capture. */
/*
* Properties:
* As with methods, expect a minimum of two groups. But, more trivial than
* methods, the vast majority of properties long enough to be worth
* showing a chunk header for don't include "=:;,()" on the line they are
* defined, since they don't have a parameter list.
*/
"^[ \t]*("
"([][[:alnum:]@_.](<[][[:alnum:]@_, \t<>]+>)?)+"
"([ \t]+"
"([][[:alnum:]@_.](<[][[:alnum:]@_, \t<>]+>)?)+"
")+" /* Up to here, same as methods regex. */
"[^;=:,()]*" /* Compared to methods, no parameter list allowed. */
")$\n"
/* Type definitions */
"^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct|record)[ \t]+.*)$\n"
/* Namespace */