Skip to content

Commit 4ef73af

Browse files
committed
ASP002: Check that method has corresponding parameter. Close #7
1 parent 1d8ffe5 commit 4ef73af

8 files changed

Lines changed: 384 additions & 2 deletions

File tree

AspNetCoreAnalyzers.Tests/ASP001ParameterNameTests/ValidCode.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
namespace AspNetCoreAnalyzers.Tests.ASP001ParameterNameTests
22
{
33
using Gu.Roslyn.Asserts;
4+
using Microsoft.CodeAnalysis;
45
using Microsoft.CodeAnalysis.Diagnostics;
56
using NUnit.Framework;
67

78
public class ValidCode
89
{
910
private static readonly DiagnosticAnalyzer Analyzer = new AttributeAnalyzer();
11+
private static readonly DiagnosticDescriptor Descriptor = ASP001ParameterName.Descriptor;
1012

1113
[Test]
1214
public void ImplicitFromRoute()
@@ -171,7 +173,7 @@ public async Task<IActionResult> GetOrder([FromHeader]int headerValue)
171173
}
172174
}
173175
}".AssertReplace("[FromHeader]", attribute);
174-
AnalyzerAssert.Valid(Analyzer, order, db, code);
176+
AnalyzerAssert.Valid(Analyzer, Descriptor, order, db, code);
175177
}
176178

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

AspNetCoreAnalyzers.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ EndProject
3030
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".docs", ".docs", "{1C271AF2-C36F-4734-BBC6-89B969FEDDA2}"
3131
ProjectSection(SolutionItems) = preProject
3232
documentation\ASP001.md = documentation\ASP001.md
33+
documentation\ASP002.md = documentation\ASP002.md
3334
README.md = README.md
3435
RELEASE_NOTES.md = RELEASE_NOTES.md
3536
EndProjectSection
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace AspNetCoreAnalyzers
2+
{
3+
using Microsoft.CodeAnalysis;
4+
5+
internal static class ASP002MissingParameter
6+
{
7+
public const string DiagnosticId = "ASP002";
8+
9+
internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
10+
id: DiagnosticId,
11+
title: "The method has no corresponding parameter.",
12+
messageFormat: "The method has no corresponding parameter.",
13+
category: AnalyzerCategory.Correctness,
14+
defaultSeverity: DiagnosticSeverity.Hidden,
15+
isEnabledByDefault: true,
16+
description: "The method has no corresponding parameter.",
17+
helpLinkUri: HelpLink.ForId(DiagnosticId));
18+
}
19+
}

AspNetCoreAnalyzers/Analyzers/AttributeAnalyzer.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ namespace AspNetCoreAnalyzers
1212
public class AttributeAnalyzer : DiagnosticAnalyzer
1313
{
1414
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
15-
ASP001ParameterName.Descriptor);
15+
ASP001ParameterName.Descriptor,
16+
ASP002MissingParameter.Descriptor);
1617

1718
public override void Initialize(AnalysisContext context)
1819
{
@@ -24,6 +25,7 @@ private static void Handle(SyntaxNodeAnalysisContext context)
2425
if (!context.IsExcludedFromAnalysis() &&
2526
context.Node is AttributeSyntax attribute &&
2627
context.ContainingSymbol is IMethodSymbol method &&
28+
attribute.TryFirstAncestor<MethodDeclarationSyntax>(out var methodDeclaration) &&
2729
TryGetTemplate(attribute, context, out var template))
2830
{
2931
foreach (var component in template.Path)
@@ -39,6 +41,14 @@ context.ContainingSymbol is IMethodSymbol method &&
3941
single.Locations.Single(),
4042
ImmutableDictionary<string, string>.Empty.Add(nameof(NameSyntax), name)));
4143
}
44+
45+
if (!method.Parameters.TryFirst(x => IsFromRoute(x), out _))
46+
{
47+
context.ReportDiagnostic(
48+
Diagnostic.Create(
49+
ASP002MissingParameter.Descriptor,
50+
methodDeclaration.ParameterList.GetLocation()));
51+
}
4252
}
4353
}
4454
}

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ Roslyn analyzers for ASP.NET.Core.
1313
<td><a href="https://github.com/DotNetAnalyzers/AspNetCoreAnalyzers/tree/master/documentation/ASP001.md">ASP001</a></td>
1414
<td>The parameter name does not match the url parameter.</td>
1515
</tr>
16+
<tr>
17+
<td><a href="https://github.com/DotNetAnalyzers/AspNetCoreAnalyzers/tree/master/documentation/ASP002.md">ASP002</a></td>
18+
<td>The method has no corresponding parameter.</td>
19+
</tr>
1620
<table>
1721
<!-- end generated table -->
1822

0 commit comments

Comments
 (0)