Skip to content

Commit 5fcd7de

Browse files
committed
ASP003 check parameter types. Close #11.
1 parent a680b53 commit 5fcd7de

12 files changed

Lines changed: 465 additions & 23 deletions

File tree

AspNetCoreAnalyzers.Tests/ASP001ParameterNameTests/CodeFix.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public class CodeFix
99
{
1010
private static readonly DiagnosticAnalyzer Analyzer = new AttributeAnalyzer();
1111
private static readonly ExpectedDiagnostic ExpectedDiagnostic = Gu.Roslyn.Asserts.ExpectedDiagnostic.Create(ASP001ParameterName.Descriptor);
12-
private static readonly CodeFixProvider RenameParameterFix = new RenameParameterFix();
12+
private static readonly CodeFixProvider Fix = new ParameterNameFix();
1313

1414
[Test]
1515
public void ImplicitSingleParameter()
@@ -94,7 +94,7 @@ public async Task<IActionResult> GetOrder(int id)
9494
}
9595
}
9696
}";
97-
AnalyzerAssert.CodeFix(Analyzer, RenameParameterFix, ExpectedDiagnostic, new[] { order, db, before }, after);
97+
AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, new[] { order, db, before }, after);
9898
}
9999

100100
[Test]
@@ -206,7 +206,7 @@ public async Task<IActionResult> GetOrder(int orderId, int itemId)
206206
}
207207
}
208208
}";
209-
AnalyzerAssert.CodeFix(Analyzer, RenameParameterFix, ExpectedDiagnostic, new[] { orderItem, order, db, before }, after);
209+
AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, new[] { orderItem, order, db, before }, after);
210210
}
211211

212212
[Test]
@@ -318,7 +318,7 @@ public async Task<IActionResult> GetOrder(int orderId, int itemId)
318318
}
319319
}
320320
}";
321-
AnalyzerAssert.CodeFix(Analyzer, RenameParameterFix, ExpectedDiagnostic, new[] { orderItem, order, db, before }, after);
321+
AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, new[] { orderItem, order, db, before }, after);
322322
}
323323

324324
[Test]
@@ -404,7 +404,7 @@ public async Task<IActionResult> GetOrder([FromRoute]int id)
404404
}
405405
}
406406
}";
407-
AnalyzerAssert.CodeFix(Analyzer, RenameParameterFix, ExpectedDiagnostic, new[] { order, db, before }, after);
407+
AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, new[] { order, db, before }, after);
408408
}
409409

410410
[Test]
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
namespace AspNetCoreAnalyzers.Tests.ASP003ParameterTypeTests
2+
{
3+
using Gu.Roslyn.Asserts;
4+
using Microsoft.CodeAnalysis.CodeFixes;
5+
using Microsoft.CodeAnalysis.Diagnostics;
6+
using NUnit.Framework;
7+
8+
public class CodeFix
9+
{
10+
private static readonly DiagnosticAnalyzer Analyzer = new AttributeAnalyzer();
11+
private static readonly ExpectedDiagnostic ExpectedDiagnostic = Gu.Roslyn.Asserts.ExpectedDiagnostic.Create(ASP003ParameterType.Descriptor);
12+
private static readonly CodeFixProvider Fix = new ParameterTypeFix();
13+
14+
[TestCase("api/orders/{id:int}", "int id")]
15+
[TestCase("api/orders/{id:bool}", "bool id")]
16+
[TestCase("api/orders/{id:decimal}", "decimal id")]
17+
[TestCase("api/orders/{id:double}", "double id")]
18+
[TestCase("api/orders/{id:float}", "float id")]
19+
20+
public void ExplicitType(string template, string parameter)
21+
{
22+
var order = @"
23+
namespace ValidCode
24+
{
25+
public class Order
26+
{
27+
public int Id { get; set; }
28+
}
29+
}";
30+
31+
var db = @"
32+
namespace ValidCode
33+
{
34+
using Microsoft.EntityFrameworkCore;
35+
36+
public class Db : DbContext
37+
{
38+
public DbSet<Order> Orders { get; set; }
39+
}
40+
}";
41+
var code = @"
42+
namespace ValidCode
43+
{
44+
using System.Threading.Tasks;
45+
using Microsoft.AspNetCore.Mvc;
46+
using Microsoft.EntityFrameworkCore;
47+
48+
[ApiController]
49+
public class OrdersController : Controller
50+
{
51+
private readonly Db db;
52+
53+
public OrdersController(Db db)
54+
{
55+
this.db = db;
56+
}
57+
58+
[HttpGet(""api/orders/{id:int}"")]
59+
public async Task<IActionResult> GetOrder(↓byte id)
60+
{
61+
var match = await this.db.Orders.FirstOrDefaultAsync(x => x.Id == id);
62+
if (match == null)
63+
{
64+
return this.NotFound();
65+
}
66+
67+
return this.Ok(match);
68+
}
69+
}
70+
}".AssertReplace("api/orders/{id:int}", template);
71+
72+
var fixedCode = @"
73+
namespace ValidCode
74+
{
75+
using System.Threading.Tasks;
76+
using Microsoft.AspNetCore.Mvc;
77+
using Microsoft.EntityFrameworkCore;
78+
79+
[ApiController]
80+
public class OrdersController : Controller
81+
{
82+
private readonly Db db;
83+
84+
public OrdersController(Db db)
85+
{
86+
this.db = db;
87+
}
88+
89+
[HttpGet(""api/orders/{id:int}"")]
90+
public async Task<IActionResult> GetOrder(byte id)
91+
{
92+
var match = await this.db.Orders.FirstOrDefaultAsync(x => x.Id == id);
93+
if (match == null)
94+
{
95+
return this.NotFound();
96+
}
97+
98+
return this.Ok(match);
99+
}
100+
}
101+
}".AssertReplace("api/orders/{id:int}", template)
102+
.AssertReplace("byte id", parameter);
103+
AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, new[] { order, db, code }, fixedCode);
104+
}
105+
}
106+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
namespace AspNetCoreAnalyzers.Tests.ASP003ParameterTypeTests
2+
{
3+
using Gu.Roslyn.Asserts;
4+
using Microsoft.CodeAnalysis.Diagnostics;
5+
using NUnit.Framework;
6+
7+
public class ValidCode
8+
{
9+
private static readonly DiagnosticAnalyzer Analyzer = new AttributeAnalyzer();
10+
11+
[Test]
12+
public void ImplicitType()
13+
{
14+
var order = @"
15+
namespace ValidCode
16+
{
17+
public class Order
18+
{
19+
public int Id { get; set; }
20+
}
21+
}";
22+
23+
var db = @"
24+
namespace ValidCode
25+
{
26+
using Microsoft.EntityFrameworkCore;
27+
28+
public class Db : DbContext
29+
{
30+
public DbSet<Order> Orders { get; set; }
31+
}
32+
}";
33+
var code = @"
34+
namespace ValidCode
35+
{
36+
using System.Threading.Tasks;
37+
using Microsoft.AspNetCore.Mvc;
38+
using Microsoft.EntityFrameworkCore;
39+
40+
[ApiController]
41+
public class OrdersController : Controller
42+
{
43+
private readonly Db db;
44+
45+
public OrdersController(Db db)
46+
{
47+
this.db = db;
48+
}
49+
50+
[HttpGet(""api/orders/{id}"")]
51+
public async Task<IActionResult> GetOrder(int id)
52+
{
53+
var match = await this.db.Orders.FirstOrDefaultAsync(x => x.Id == id);
54+
if (match == null)
55+
{
56+
return this.NotFound();
57+
}
58+
59+
return this.Ok(match);
60+
}
61+
}
62+
}";
63+
AnalyzerAssert.Valid(Analyzer, order, db, code);
64+
}
65+
66+
[TestCase("api/orders/{id:int}")]
67+
[TestCase("api/orders/{id:min(1)}")]
68+
[TestCase("api/orders/{id:max(1)}")]
69+
[TestCase("api/orders/{id:range(1,10)}")]
70+
[TestCase("api/orders/{id:required}")]
71+
public void ExplicitInteger(string template)
72+
{
73+
var order = @"
74+
namespace ValidCode
75+
{
76+
public class Order
77+
{
78+
public int Id { get; set; }
79+
}
80+
}";
81+
82+
var db = @"
83+
namespace ValidCode
84+
{
85+
using Microsoft.EntityFrameworkCore;
86+
87+
public class Db : DbContext
88+
{
89+
public DbSet<Order> Orders { get; set; }
90+
}
91+
}";
92+
var code = @"
93+
namespace ValidCode
94+
{
95+
using System.Threading.Tasks;
96+
using Microsoft.AspNetCore.Mvc;
97+
using Microsoft.EntityFrameworkCore;
98+
99+
[ApiController]
100+
public class OrdersController : Controller
101+
{
102+
private readonly Db db;
103+
104+
public OrdersController(Db db)
105+
{
106+
this.db = db;
107+
}
108+
109+
[HttpGet(""api/orders/{id:int}"")]
110+
public async Task<IActionResult> GetOrder(int id)
111+
{
112+
var match = await this.db.Orders.FirstOrDefaultAsync(x => x.Id == id);
113+
if (match == null)
114+
{
115+
return this.NotFound();
116+
}
117+
118+
return this.Ok(match);
119+
}
120+
}
121+
}".AssertReplace("api/orders/{id:int}", template);
122+
AnalyzerAssert.Valid(Analyzer, order, db, code);
123+
}
124+
125+
[TestCase("api/orders/{id:minlength(1)}")]
126+
[TestCase("api/orders/{id:maxlength(1)}")]
127+
[TestCase("api/orders/{id:length(1)}")]
128+
[TestCase("api/orders/{id:length(1,3)}")]
129+
[TestCase("api/orders/{id:alpha}")]
130+
[TestCase("api/orders/{id:required}")]
131+
public void ExplicitString(string template)
132+
{
133+
var order = @"
134+
namespace ValidCode
135+
{
136+
public class Order
137+
{
138+
public string Id { get; set; }
139+
}
140+
}";
141+
142+
var db = @"
143+
namespace ValidCode
144+
{
145+
using Microsoft.EntityFrameworkCore;
146+
147+
public class Db : DbContext
148+
{
149+
public DbSet<Order> Orders { get; set; }
150+
}
151+
}";
152+
var code = @"
153+
namespace ValidCode
154+
{
155+
using System.Threading.Tasks;
156+
using Microsoft.AspNetCore.Mvc;
157+
using Microsoft.EntityFrameworkCore;
158+
159+
[ApiController]
160+
public class OrdersController : Controller
161+
{
162+
private readonly Db db;
163+
164+
public OrdersController(Db db)
165+
{
166+
this.db = db;
167+
}
168+
169+
[HttpGet(""api/orders/{id:int}"")]
170+
public async Task<IActionResult> GetOrder(string id)
171+
{
172+
var match = await this.db.Orders.FirstOrDefaultAsync(x => x.Id == id);
173+
if (match == null)
174+
{
175+
return this.NotFound();
176+
}
177+
178+
return this.Ok(match);
179+
}
180+
}
181+
}".AssertReplace("api/orders/{id:int}", template);
182+
AnalyzerAssert.Valid(Analyzer, order, db, code);
183+
}
184+
}
185+
}

AspNetCoreAnalyzers.Tests/Documentation/Tests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#pragma warning disable CA1055 // Uri return values should not be strings
2+
#pragma warning disable CA1056 // Uri properties should not be strings
3+
#pragma warning disable CA1721 // Property names should not match get methods
14
namespace AspNetCoreAnalyzers.Tests.Documentation
25
{
36
using System;

AspNetCoreAnalyzers.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".docs", ".docs", "{1C271AF2
3131
ProjectSection(SolutionItems) = preProject
3232
documentation\ASP001.md = documentation\ASP001.md
3333
documentation\ASP002.md = documentation\ASP002.md
34+
documentation\ASP003.md = documentation\ASP003.md
3435
README.md = README.md
3536
RELEASE_NOTES.md = RELEASE_NOTES.md
3637
EndProjectSection

AspNetCoreAnalyzers/Analyzers/AttributeAnalyzer.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ public class AttributeAnalyzer : DiagnosticAnalyzer
1313
{
1414
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
1515
ASP001ParameterName.Descriptor,
16-
ASP002MissingParameter.Descriptor);
16+
ASP002MissingParameter.Descriptor,
17+
ASP003ParameterType.Descriptor);
1718

1819
public override void Initialize(AnalysisContext context)
1920
{
@@ -59,6 +60,32 @@ context.ContainingSymbol is IMethodSymbol method &&
5960
ASP002MissingParameter.Descriptor,
6061
methodDeclaration.ParameterList.GetLocation()));
6162
}
63+
64+
foreach (var pair in pairs)
65+
{
66+
if (pair.Template?.Type is TextAndLocation templateType &&
67+
pair.Method is IParameterSymbol parameter)
68+
{
69+
switch (templateType.Text)
70+
{
71+
// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-2.2#route-constraint-reference
72+
case "bool" when parameter.Type != KnownSymbol.Boolean && methodDeclaration.TryFindParameter(parameter.Name, out parameterSyntax):
73+
case "decimal" when parameter.Type != KnownSymbol.Decimal && methodDeclaration.TryFindParameter(parameter.Name, out parameterSyntax):
74+
case "double" when parameter.Type != KnownSymbol.Float && methodDeclaration.TryFindParameter(parameter.Name, out parameterSyntax):
75+
case "float" when parameter.Type != KnownSymbol.Double && methodDeclaration.TryFindParameter(parameter.Name, out parameterSyntax):
76+
case "int" when parameter.Type != KnownSymbol.Int32 && methodDeclaration.TryFindParameter(parameter.Name, out parameterSyntax):
77+
case "long" when parameter.Type != KnownSymbol.Int64 && methodDeclaration.TryFindParameter(parameter.Name, out parameterSyntax):
78+
context.ReportDiagnostic(
79+
Diagnostic.Create(
80+
ASP003ParameterType.Descriptor,
81+
parameterSyntax.Type.GetLocation(),
82+
ImmutableDictionary<string, string>.Empty.Add(
83+
nameof(TypeSyntax),
84+
templateType.Text)));
85+
break;
86+
}
87+
}
88+
}
6289
}
6390
}
6491
}

0 commit comments

Comments
 (0)