Skip to content

Commit 5532430

Browse files
authored
Merge pull request #34 from DotNetAnalyzers/homegrown_span
Homegrown span
2 parents 730a1a9 + 47c9883 commit 5532430

12 files changed

Lines changed: 321 additions & 164 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
namespace AspNetCoreAnalyzers.Tests.Helpers
2+
{
3+
using System;
4+
using Gu.Roslyn.Asserts;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
using NUnit.Framework;
7+
8+
public class StringLiteralSpanTests
9+
{
10+
[TestCase("\"abc\"", "abc", true)]
11+
[TestCase("\"abc\"", "ab", false)]
12+
[TestCase("\"abc\"", "bc", false)]
13+
public void Equals(string text, string value, bool expected)
14+
{
15+
var syntaxTree = CSharpSyntaxTree.ParseText(@"
16+
namespace AspBox.Controllers
17+
{
18+
using Microsoft.AspNetCore.Mvc;
19+
20+
[Route(""abc"")]
21+
class C
22+
{
23+
24+
}
25+
}".AssertReplace("\"abc\"", text));
26+
var literal = syntaxTree.FindLiteralExpression(text);
27+
var span = new Span(new StringLiteral(literal), 0, 3);
28+
Assert.AreEqual(expected, span.Equals(value, StringComparison.Ordinal));
29+
}
30+
31+
[TestCase("\"abc\"", "abc", true)]
32+
[TestCase("\"abc\"", "ab", true)]
33+
[TestCase("\"abc\"", "a", true)]
34+
[TestCase("\"abc\"", "bc", false)]
35+
public void StartsWith(string text, string value, bool expected)
36+
{
37+
var syntaxTree = CSharpSyntaxTree.ParseText(@"
38+
namespace AspBox.Controllers
39+
{
40+
using Microsoft.AspNetCore.Mvc;
41+
42+
[Route(""abc"")]
43+
class C
44+
{
45+
46+
}
47+
}".AssertReplace("\"abc\"", text));
48+
var literal = syntaxTree.FindLiteralExpression(text);
49+
var span = new Span(new StringLiteral(literal), 0, 3);
50+
Assert.AreEqual(expected, span.StartsWith(value, StringComparison.Ordinal));
51+
}
52+
53+
[TestCase("\"abc\"", "abc", true)]
54+
[TestCase("\"abc\"", "bc", true)]
55+
[TestCase("\"abc\"", "c", true)]
56+
[TestCase("\"abc\"", "ab", false)]
57+
public void EndsWith(string text, string value, bool expected)
58+
{
59+
var syntaxTree = CSharpSyntaxTree.ParseText(@"
60+
namespace AspBox.Controllers
61+
{
62+
using Microsoft.AspNetCore.Mvc;
63+
64+
[Route(""abc"")]
65+
class C
66+
{
67+
68+
}
69+
}".AssertReplace("\"abc\"", text));
70+
var literal = syntaxTree.FindLiteralExpression(text);
71+
var span = new Span(new StringLiteral(literal), 0, 3);
72+
Assert.AreEqual(expected, span.EndsWith(value, StringComparison.Ordinal));
73+
}
74+
}
75+
}

AspNetCoreAnalyzers.Tests/Helpers/UrlTemplateTests.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace AspNetCoreAnalyzers.Tests.Helpers
22
{
3+
using System;
34
using System.Linq;
45
using Gu.Roslyn.Asserts;
56
using Microsoft.CodeAnalysis.CSharp;
@@ -60,9 +61,10 @@ public async Task<IActionResult> GetOrder([FromRoute]int id)
6061
CollectionAssert.AreEqual(expected, template.Path.Select(x => x.Span.ToString()));
6162

6263
// ReSharper disable once PossibleInvalidOperationException
63-
var parameter = template.Path.Single(x => x.Parameter.HasValue)
64-
.Parameter.Value;
65-
Assert.AreEqual("id", parameter.Name.ToString());
64+
var segment = template.Path.Single(x => x.Parameter.HasValue);
65+
66+
Assert.AreEqual(expected.Single(x => x.StartsWith("{", StringComparison.Ordinal)), segment.Span.ToString());
67+
Assert.AreEqual("id", segment.Parameter.Value.Name.ToString());
6668
}
6769

6870
[TestCase("\"orders/{id}\"", new[] { "orders", "{id}" }, new string[0])]

AspNetCoreAnalyzers/Analyzers/AttributeAnalyzer.cs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,38 +36,38 @@ context.ContainingSymbol is IMethodSymbol method &&
3636
{
3737
using (var pairs = GetPairs(template, method))
3838
{
39-
if (pairs.TrySingle(x => x.Template == null, out var withMethodParameter) &&
40-
methodDeclaration.TryFindParameter(withMethodParameter.Method.Name, out var parameterSyntax) &&
41-
pairs.TrySingle(x => x.Method == null, out var withTemplateParameter) &&
42-
withTemplateParameter.Template is TemplateParameter templateParameter)
39+
if (pairs.TrySingle(x => x.FromTemplate == null, out var withMethodParameter) &&
40+
methodDeclaration.TryFindParameter(withMethodParameter.FromMethodSymbol.Name, out var parameterSyntax) &&
41+
pairs.TrySingle(x => x.FromMethodSymbol == null, out var withTemplateParameter) &&
42+
withTemplateParameter.FromTemplate is TemplateParameter templateParameter)
4343
{
4444
context.ReportDiagnostic(
4545
Diagnostic.Create(
4646
ASP001ParameterName.Descriptor,
4747
parameterSyntax.Identifier.GetLocation(),
4848
ImmutableDictionary<string, string>.Empty.Add(
4949
nameof(NameSyntax),
50-
templateParameter.Name.Text)));
50+
templateParameter.Name.ToString())));
5151

5252
context.ReportDiagnostic(
5353
Diagnostic.Create(
5454
ASP002MissingParameter.Descriptor,
5555
templateParameter.Name.GetLocation(),
5656
ImmutableDictionary<string, string>.Empty.Add(
5757
nameof(Text),
58-
withMethodParameter.Method.Name)));
58+
withMethodParameter.FromMethodSymbol.Name)));
5959
}
60-
else if (pairs.Count(x => x.Template == null) > 1 &&
61-
pairs.Count(x => x.Method == null) > 1)
60+
else if (pairs.Count(x => x.FromTemplate == null) > 1 &&
61+
pairs.Count(x => x.FromMethodSymbol == null) > 1)
6262
{
6363
context.ReportDiagnostic(
6464
Diagnostic.Create(
6565
ASP001ParameterName.Descriptor,
6666
methodDeclaration.ParameterList.GetLocation()));
6767
}
6868

69-
if (pairs.TryFirst(x => x.Method == null, out _) &&
70-
!pairs.TryFirst(x => x.Template == null, out _))
69+
if (pairs.TryFirst(x => x.FromMethodSymbol == null, out _) &&
70+
!pairs.TryFirst(x => x.FromTemplate == null, out _))
7171
{
7272
context.ReportDiagnostic(
7373
Diagnostic.Create(
@@ -78,7 +78,7 @@ context.ContainingSymbol is IMethodSymbol method &&
7878
foreach (var pair in pairs)
7979
{
8080
if (HasWrongType(pair, out var typeName) &&
81-
methodDeclaration.TryFindParameter(pair.Method?.Name, out parameterSyntax))
81+
methodDeclaration.TryFindParameter(pair.FromMethodSymbol?.Name, out parameterSyntax))
8282
{
8383
context.ReportDiagnostic(
8484
Diagnostic.Create(
@@ -164,7 +164,7 @@ private static PooledList<ParameterPair> GetPairs(UrlTemplate template, IMethodS
164164
{
165165
if (IsFromRoute(parameter))
166166
{
167-
list.Add(template.Path.TrySingle(x => x.Parameter?.Name.Text == parameter.Name, out var templateParameter)
167+
list.Add(template.Path.TrySingle(x => x.Parameter?.Name.Equals(parameter.Name, StringComparison.Ordinal) == true, out var templateParameter)
168168
? new ParameterPair(templateParameter.Parameter, parameter)
169169
: new ParameterPair(null, parameter));
170170
}
@@ -173,7 +173,7 @@ private static PooledList<ParameterPair> GetPairs(UrlTemplate template, IMethodS
173173
foreach (var component in template.Path)
174174
{
175175
if (component.Parameter is TemplateParameter templateParameter &&
176-
list.All(x => x.Template != templateParameter))
176+
list.All(x => x.FromTemplate != templateParameter))
177177
{
178178
list.Add(new ParameterPair(templateParameter, null));
179179
}
@@ -184,13 +184,13 @@ private static PooledList<ParameterPair> GetPairs(UrlTemplate template, IMethodS
184184

185185
private static bool HasWrongType(ParameterPair pair, out string correctType)
186186
{
187-
if (pair.Template?.Constraints is ImmutableArray<RouteConstraint> constraints &&
188-
pair.Method is IParameterSymbol parameter)
187+
if (pair.FromTemplate?.Constraints is ImmutableArray<RouteConstraint> constraints &&
188+
pair.FromMethodSymbol is IParameterSymbol parameter)
189189
{
190190
foreach (var constraint in constraints)
191191
{
192192
// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-2.2#route-constraint-reference
193-
if (TryGetType(constraint.Span.Text, out var type))
193+
if (TryGetType(constraint.Span, out var type))
194194
{
195195
correctType = parameter.Type == type ? null : type.Alias ?? type.FullName;
196196
return correctType != null;
@@ -201,7 +201,7 @@ private static bool HasWrongType(ParameterPair pair, out string correctType)
201201
correctType = null;
202202
return false;
203203

204-
bool TryGetType(string constraint, out QualifiedType type)
204+
bool TryGetType(Span constraint, out QualifiedType type)
205205
{
206206
if (constraint.Equals("bool", StringComparison.Ordinal))
207207
{
@@ -275,7 +275,7 @@ private static bool HasWrongSyntax(PathSegment segment, out Location location, o
275275
{
276276
foreach (var constraint in parameter.Constraints)
277277
{
278-
var text = constraint.Span.Text;
278+
var text = constraint.Span;
279279
if (text.StartsWith("min(", StringComparison.OrdinalIgnoreCase) ||
280280
text.StartsWith("max(", StringComparison.OrdinalIgnoreCase) ||
281281
text.StartsWith("minlength(", StringComparison.OrdinalIgnoreCase) ||
@@ -328,7 +328,7 @@ private static bool HasWrongSyntax(PathSegment segment, out Location location, o
328328
}
329329
else
330330
{
331-
var text = segment.Span.Text;
331+
var text = segment.Span;
332332
if (text.StartsWith("{", StringComparison.Ordinal) &&
333333
!text.EndsWith("}", StringComparison.Ordinal))
334334
{
@@ -352,7 +352,7 @@ private static bool HasWrongSyntax(PathSegment segment, out Location location, o
352352

353353
bool HasWrongIntArgumentSyntax(RouteConstraint constraint, string methodName, out Location result)
354354
{
355-
var text = constraint.Span.Text;
355+
var text = constraint.Span;
356356
if (text.Length > methodName.Length + 2 &&
357357
text.StartsWith(methodName, StringComparison.OrdinalIgnoreCase) &&
358358
text[methodName.Length] == '(' &&
@@ -379,7 +379,7 @@ private static bool HasWrongRegexSyntax(PathSegment segment, out Location locati
379379
{
380380
foreach (var constraint in parameter.Constraints)
381381
{
382-
var text = constraint.Span.Text;
382+
var text = constraint.Span;
383383
if (text.StartsWith("regex(", StringComparison.OrdinalIgnoreCase))
384384
{
385385
for (var i = 6; i < text.Length - 1; i++)
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
namespace AspNetCoreAnalyzers
22
{
3+
using System.Diagnostics;
34
using Microsoft.CodeAnalysis;
45

6+
[DebuggerDisplay("{this.FromTemplate?.Name ?? this.FromMethodSymbol.Name}")]
57
public struct ParameterPair
68
{
7-
public ParameterPair(TemplateParameter? template, IParameterSymbol method)
9+
public ParameterPair(TemplateParameter? fromTemplate, IParameterSymbol fromMethodSymbol)
810
{
9-
this.Template = template;
10-
this.Method = method;
11+
this.FromTemplate = fromTemplate;
12+
this.FromMethodSymbol = fromMethodSymbol;
1113
}
1214

13-
public TemplateParameter? Template { get; }
15+
public TemplateParameter? FromTemplate { get; }
1416

15-
public IParameterSymbol Method { get; }
17+
public IParameterSymbol FromMethodSymbol { get; }
1618
}
1719
}

AspNetCoreAnalyzers/Helpers/PathSegment.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,25 @@ namespace AspNetCoreAnalyzers
22
{
33
using System.Diagnostics;
44

5-
[DebuggerDisplay("{this.Span.Text}")]
5+
[DebuggerDisplay("{this.Span.ToString()}")]
66
public struct PathSegment
77
{
88
public PathSegment(StringLiteral literal, int start, int end)
99
{
10-
this.Span = new StringLiteralSpan(literal, start, end);
10+
this.Span = new Span(literal, start, end);
1111
this.Parameter = TemplateParameter.TryParse(this.Span, out var parameter)
1212
? parameter
1313
: (TemplateParameter?)null;
1414
}
1515

16-
public StringLiteralSpan Span { get; }
16+
public Span Span { get; }
1717

1818
public TemplateParameter? Parameter { get; }
1919

2020
public static bool TryRead(StringLiteral literal, int start, out PathSegment segment)
2121
{
2222
// https://tools.ietf.org/html/rfc3986
23-
var text = literal.LiteralExpression.Token.ValueText;
23+
var text = literal.ValueText;
2424
var pos = start;
2525
if (pos < text.Length - 1)
2626
{

AspNetCoreAnalyzers/Helpers/RouteConstraint.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ namespace AspNetCoreAnalyzers
33
using System;
44
using System.Diagnostics;
55

6-
[DebuggerDisplay("{this.Span.Text}")]
6+
[DebuggerDisplay("{this.Span.ToString()}")]
77
public struct RouteConstraint : IEquatable<RouteConstraint>
88
{
9-
public RouteConstraint(StringLiteralSpan span)
9+
public RouteConstraint(Span span)
1010
{
1111
this.Span = span;
1212
}
1313

14-
public StringLiteralSpan Span { get; }
14+
public Span Span { get; }
1515

1616
public static bool operator ==(RouteConstraint left, RouteConstraint right)
1717
{
@@ -23,10 +23,10 @@ public RouteConstraint(StringLiteralSpan span)
2323
return !left.Equals(right);
2424
}
2525

26-
public static bool TryRead(StringLiteralSpan span, int pos, out RouteConstraint constraint)
26+
public static bool TryRead(Span span, int pos, out RouteConstraint constraint)
2727
{
2828
if (pos >= span.TextSpan.End ||
29-
span.Text[pos] != ':')
29+
span[pos] != ':')
3030
{
3131
constraint = default(RouteConstraint);
3232
return false;
@@ -35,10 +35,10 @@ public static bool TryRead(StringLiteralSpan span, int pos, out RouteConstraint
3535
pos++;
3636
for (var i = pos; i < span.TextSpan.Length; i++)
3737
{
38-
switch (span.Text[i])
38+
switch (span[i])
3939
{
40-
case '(' when Text.TrySkipPast(span.Text, ref i, "):") ||
41-
Text.TrySkipPast(span.Text, ref i, ")}"):
40+
case '(' when Text.TrySkipPast(span, ref i, "):") ||
41+
Text.TrySkipPast(span, ref i, ")}"):
4242
constraint = new RouteConstraint(span.Slice(pos, i - 1));
4343
return true;
4444
case '}':

0 commit comments

Comments
 (0)