Git fork

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>

authored by

Steven Jeuris and committed by
Junio C Hamano
ec0e3075 43072b4c

+352 -6
+20
t/t4018/csharp-exclude-assignments
··· 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 + }
+34
t/t4018/csharp-exclude-control-statements
··· 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 + }
+29
t/t4018/csharp-exclude-exceptions
··· 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 + }
+12
t/t4018/csharp-exclude-generic-method-calls
··· 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 + }
+22
t/t4018/csharp-exclude-init-dispose
··· 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 + }
+26
t/t4018/csharp-exclude-iterations
··· 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 + }
+20
t/t4018/csharp-exclude-method-calls
··· 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 + }
+18
t/t4018/csharp-exclude-other
··· 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 + }
+10
t/t4018/csharp-method
··· 1 + class Example 2 + { 3 + string Method(int RIGHT) 4 + { 5 + // Filler 6 + // Filler 7 + 8 + return "ChangeMe"; 9 + } 10 + }
+10
t/t4018/csharp-method-array
··· 1 + class Example 2 + { 3 + string[] Method(int RIGHT) 4 + { 5 + // Filler 6 + // Filler 7 + 8 + return ["ChangeMe"]; 9 + } 10 + }
+12
t/t4018/csharp-method-explicit
··· 1 + using System; 2 + 3 + class Example : IDisposable 4 + { 5 + void IDisposable.Dispose() // RIGHT 6 + { 7 + // Filler 8 + // Filler 9 + 10 + // ChangeMe 11 + } 12 + }
+11
t/t4018/csharp-method-generics
··· 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 + }
+11
t/t4018/csharp-method-generics-alternate-spaces
··· 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 + }
+13
t/t4018/csharp-method-modifiers
··· 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 + }
+10
t/t4018/csharp-method-multiline
··· 1 + class Example 2 + { 3 + string Method_RIGHT( 4 + int a, 5 + int b, 6 + int c) 7 + { 8 + return "ChangeMe"; 9 + } 10 + }
+10
t/t4018/csharp-method-params
··· 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 + }
+11
t/t4018/csharp-method-special-chars
··· 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 + }
+10
t/t4018/csharp-method-with-spacing
··· 1 + class Example 2 + { 3 + string Method ( int RIGHT ) 4 + { 5 + // Filler 6 + // Filler 7 + 8 + return "ChangeMe"; 9 + } 10 + }
+11
t/t4018/csharp-property
··· 1 + class Example 2 + { 3 + public bool RIGHT 4 + { 5 + get { return true; } 6 + set 7 + { 8 + // ChangeMe 9 + } 10 + } 11 + }
+10
t/t4018/csharp-property-braces-same-line
··· 1 + class Example 2 + { 3 + public bool RIGHT { 4 + get { return true; } 5 + set 6 + { 7 + // ChangeMe 8 + } 9 + } 10 + }
+42 -6
userdiff.c
··· 90 90 "|\\.[0-9][0-9]*([Ee][-+]?[0-9]+)?[fFlL]?" 91 91 "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*|<=>"), 92 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" 99 135 /* Type definitions */ 100 136 "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct|record)[ \t]+.*)$\n" 101 137 /* Namespace */