Skip to content

Commit 3eb2946

Browse files
committed
Refactor UrlTemplate.TryParse
1 parent 42070a9 commit 3eb2946

6 files changed

Lines changed: 90 additions & 31 deletions

File tree

AspNetCoreAnalyzers.Tests/ASP003ParameterTypeTests/CodeFix.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace AspNetCoreAnalyzers.Tests.ASP003ParameterTypeTests
88
public class CodeFix
99
{
1010
private static readonly DiagnosticAnalyzer Analyzer = new AttributeAnalyzer();
11-
private static readonly ExpectedDiagnostic ExpectedDiagnostic = Gu.Roslyn.Asserts.ExpectedDiagnostic.Create(ASP003ParameterType.Descriptor);
11+
private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(ASP003ParameterType.Descriptor);
1212
private static readonly CodeFixProvider Fix = new ParameterTypeFix();
1313

1414
[TestCase("api/orders/{id:int}", "int id")]

AspNetCoreAnalyzers.Tests/ASP003ParameterTypeTests/ValidCode.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ public async Task<IActionResult> GetOrder(int id)
128128
[TestCase("api/orders/{id:length(1,3)}")]
129129
[TestCase("api/orders/{id:alpha}")]
130130
[TestCase("api/orders/{id:regex(a-(0|1))}")]
131+
[TestCase("api/orders/{id:regex(^\\\\d{{3}}-\\\\d{{2}}-\\\\d{{4}}$)}")]
131132
[TestCase("api/orders/{id:required}")]
132133
public void ExplicitString(string template)
133134
{
@@ -167,7 +168,7 @@ public OrdersController(Db db)
167168
this.db = db;
168169
}
169170
170-
[HttpGet(""api/orders/{id:int}"")]
171+
[HttpGet(""api/orders/{id}"")]
171172
public async Task<IActionResult> GetOrder(string id)
172173
{
173174
var match = await this.db.Orders.FirstOrDefaultAsync(x => x.Id == id);
@@ -179,7 +180,7 @@ public async Task<IActionResult> GetOrder(string id)
179180
return this.Ok(match);
180181
}
181182
}
182-
}".AssertReplace("api/orders/{id:int}", template);
183+
}".AssertReplace("api/orders/{id}", template);
183184
AnalyzerAssert.Valid(Analyzer, order, db, code);
184185
}
185186
}

AspNetCoreAnalyzers.Tests/Helpers/UrlTemplateTests.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ namespace AspNetCoreAnalyzers.Tests.Helpers
77

88
public class UrlTemplateTests
99
{
10-
[TestCase("foo", new[] { "foo" })]
11-
[TestCase("foo/bar", new[] { "foo", "bar" })]
10+
[TestCase("foo", new[] { "foo" })]
11+
[TestCase("foo/bar", new[] { "foo", "bar" })]
1212
public void TryParse(string text, string[] expected)
1313
{
1414
var syntaxTree = CSharpSyntaxTree.ParseText(@"
@@ -65,10 +65,11 @@ public async Task<IActionResult> GetOrder([FromRoute]int id)
6565
Assert.AreEqual("id", parameter.Name.Text);
6666
}
6767

68-
[TestCase("orders/{id:min(1)}", new[] { "orders", "{id:min(1)}" })]
69-
[TestCase("orders/{id:max(2)}", new[] { "orders", "{id:max(2)}" })]
70-
[TestCase("orders/{id:int:max(2)}", new[] { "orders", "{id:int:max(2)}" })]
71-
[TestCase("orders/{id:range(1,23)}", new[] { "orders", "{id:range(1,23)}" })]
68+
[TestCase("orders/{id:min(1)}", new[] { "orders", "{id:min(1)}" })]
69+
[TestCase("orders/{id:int:min(1):max(2)}", new[] { "orders", "{id:int:min(1):max(2)}" })]
70+
[TestCase("orders/{id:max(2)}", new[] { "orders", "{id:max(2)}" })]
71+
[TestCase("orders/{id:int:max(2)}", new[] { "orders", "{id:int:max(2)}" })]
72+
[TestCase("orders/{id:range(1,23)}", new[] { "orders", "{id:range(1,23)}" })]
7273
public void TryParseWhenIntParameter(string text, string[] expected)
7374
{
7475
var syntaxTree = CSharpSyntaxTree.ParseText(@"
@@ -102,6 +103,9 @@ public async Task<IActionResult> GetOrder([FromRoute]int id)
102103
[TestCase("orders/{id:maxlength(1)}", new[] { "orders", "{id:maxlength(1)}" })]
103104
[TestCase("orders/{id:length(1)}", new[] { "orders", "{id:length(1)}" })]
104105
[TestCase("orders/{id:length(1,2)}", new[] { "orders", "{id:length(1,2)}" })]
106+
[TestCase("orders/{id:regex(^\\\\d{{3}}-\\\\d{{2}}-\\\\d{{4}}$)}", new[] { "orders", "{id:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}" })]
107+
[TestCase("orders/{id:regex(a/b)}", new[] { "orders", "{id:regex(a/b)}" })]
108+
////[TestCase("orders/{id:regex(a[)}/]b)}", new[] { "orders", "{id:regex(a[)}/]b)}" })]
105109
public void TryParseWhenStringParameter(string text, string[] expected)
106110
{
107111
var syntaxTree = CSharpSyntaxTree.ParseText(@"

AspNetCoreAnalyzers/Helpers/TemplateParameter.cs

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static bool TryParse(TextAndLocation textAndLocation, out TemplateParamet
4040
}
4141

4242
start++;
43-
SkipWhiteSpace(text, ref start);
43+
Text.SkipWhiteSpace(text, ref start);
4444

4545
var end = text.IndexOf('}', start);
4646
if (end < 0)
@@ -49,7 +49,7 @@ public static bool TryParse(TextAndLocation textAndLocation, out TemplateParamet
4949
return false;
5050
}
5151

52-
BackWhiteSpace(text, ref end);
52+
Text.BackWhiteSpace(text, ref end);
5353

5454
for (var i = start; i < end; i++)
5555
{
@@ -74,14 +74,14 @@ public static bool TryParse(TextAndLocation textAndLocation, out TemplateParamet
7474
}
7575

7676
typeStart++;
77-
SkipWhiteSpace(text, ref typeStart);
77+
Text.SkipWhiteSpace(text, ref typeStart);
7878
var typeEnd = text.IndexOf('}', typeStart);
7979
if (typeEnd < 0)
8080
{
8181
return null;
8282
}
8383

84-
BackWhiteSpace(text, ref typeEnd);
84+
Text.BackWhiteSpace(text, ref typeEnd);
8585
return textAndLocation.Substring(typeStart, typeEnd - typeStart);
8686
}
8787
}
@@ -100,23 +100,5 @@ public override int GetHashCode()
100100
{
101101
return this.Name.GetHashCode();
102102
}
103-
104-
private static void SkipWhiteSpace(string text, ref int pos)
105-
{
106-
while (pos < text.Length &&
107-
text[pos] == ' ')
108-
{
109-
pos++;
110-
}
111-
}
112-
113-
private static void BackWhiteSpace(string text, ref int pos)
114-
{
115-
while (pos >= 0 &&
116-
text[pos] == ' ')
117-
{
118-
pos--;
119-
}
120-
}
121103
}
122104
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
namespace AspNetCoreAnalyzers
2+
{
3+
public static class Text
4+
{
5+
public static void SkipWhiteSpace(string text, ref int pos)
6+
{
7+
while (pos < text.Length &&
8+
text[pos] == ' ')
9+
{
10+
pos++;
11+
}
12+
}
13+
14+
public static bool TrySkipPast(string text, ref int pos, string substring)
15+
{
16+
var before = pos;
17+
while (pos + substring.Length < text.Length)
18+
{
19+
if (IsAt(text, pos, substring))
20+
{
21+
pos += substring.Length;
22+
return true;
23+
}
24+
25+
pos++;
26+
}
27+
28+
pos = before;
29+
return false;
30+
}
31+
32+
public static void BackWhiteSpace(string text, ref int pos)
33+
{
34+
while (pos >= 0 &&
35+
text[pos] == ' ')
36+
{
37+
pos--;
38+
}
39+
}
40+
41+
private static bool IsAt(string text, int pos, string substring)
42+
{
43+
for (var i = 0; i < substring.Length; i++)
44+
{
45+
if (text[pos + i] != substring[i])
46+
{
47+
return false;
48+
}
49+
}
50+
51+
return true;
52+
}
53+
}
54+
}

AspNetCoreAnalyzers/Helpers/UrlTemplate.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,24 @@ private static bool TryParse(LiteralExpressionSyntax literal, string text, int s
100100
return true;
101101
}
102102

103+
if (text[pos] == '(')
104+
{
105+
pos++;
106+
while (Text.TrySkipPast(text, ref pos, ")"))
107+
{
108+
Text.SkipWhiteSpace(text, ref pos);
109+
switch (text[pos])
110+
{
111+
case ':':
112+
break;
113+
case '}':
114+
pos++;
115+
segment = new PathSegment(literal, start, pos);
116+
return true;
117+
}
118+
}
119+
}
120+
103121
pos++;
104122
}
105123

0 commit comments

Comments
 (0)