Skip to content

Commit a78a301

Browse files
committed
Escape regex constraints, stub. #24
1 parent 99b4059 commit a78a301

12 files changed

Lines changed: 207 additions & 33 deletions

File tree

AspNetCoreAnalyzers.Tests/ASP004ParameterSyntaxTests/ValidCode.cs

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,34 @@ public class ValidCode
88
{
99
private static readonly DiagnosticAnalyzer Analyzer = new AttributeAnalyzer();
1010

11-
[TestCase("{value}", "string")]
12-
[TestCase("{value?}", "string")]
13-
[TestCase("{value:bool}", "bool")]
14-
[TestCase("{value:datetime}", "System.DateTime")]
15-
[TestCase("{value:decimal}", "decimal")]
16-
[TestCase("{value:double}", "double")]
17-
[TestCase("{value:float}", "float")]
18-
[TestCase("{value:int}", "int")]
19-
[TestCase("api/orders/{value:int:min(1)}", "int")]
20-
[TestCase("api/orders/{value:int:max(1)}", "int")]
21-
[TestCase("api/orders/{value:int:range(1,10)}", "int")]
22-
[TestCase("api/orders/{value:int:required}", "int")]
23-
[TestCase("{value:long}", "long")]
24-
[TestCase("api/orders/{value:min(1)}", "long")]
25-
[TestCase("api/orders/{value:max(1)}", "long")]
26-
[TestCase("api/orders/{value:range(1,10)}", "long")]
27-
[TestCase("api/orders/{value:required}", "long")]
28-
[TestCase("{value:guid}", "System.Guid")]
29-
[TestCase("api/orders/{value:minlength(1)}", "string")]
30-
[TestCase("api/orders/{value:maxlength(1)}", "string")]
31-
[TestCase("api/orders/{value:length(1)}", "string")]
32-
[TestCase("api/orders/{value:length(1,3)}", "string")]
33-
[TestCase("api/orders/{value:alpha}", "string")]
34-
[TestCase("api/orders/{value:regex(a-(0|1))}", "string")]
35-
[TestCase("api/orders/{value:regex(^\\\\d{{3}}-\\\\d{{2}}-\\\\d{{4}}$)}", "string")]
36-
[TestCase("api/orders/{value:required}", "string")]
11+
[TestCase("{value}", "string")]
12+
[TestCase("{value?}", "string")]
13+
[TestCase("{value:bool}", "bool")]
14+
[TestCase("{value:datetime}", "System.DateTime")]
15+
[TestCase("{value:decimal}", "decimal")]
16+
[TestCase("{value:double}", "double")]
17+
[TestCase("{value:float}", "float")]
18+
[TestCase("{value:int}", "int")]
19+
[TestCase("api/orders/{value:int:min(1)}", "int")]
20+
[TestCase("api/orders/{value:int:max(1)}", "int")]
21+
[TestCase("api/orders/{value:int:range(1,10)}", "int")]
22+
[TestCase("api/orders/{value:int:required}", "int")]
23+
[TestCase("{value:long}", "long")]
24+
[TestCase("api/orders/{value:min(1)}", "long")]
25+
[TestCase("api/orders/{value:max(1)}", "long")]
26+
[TestCase("api/orders/{value:range(1,10)}", "long")]
27+
[TestCase("api/orders/{value:required}", "long")]
28+
[TestCase("{value:guid}", "System.Guid")]
29+
[TestCase("api/orders/{value:minlength(1)}", "string")]
30+
[TestCase("api/orders/{value:maxlength(1)}", "string")]
31+
[TestCase("api/orders/{value:length(1)}", "string")]
32+
[TestCase("api/orders/{value:length(1,3)}", "string")]
33+
[TestCase("api/orders/{value:alpha}", "string")]
34+
[TestCase("api/orders/{value:regex(a-(0|1))}", "string")]
35+
[TestCase("api/orders/{value:regex(^\\\\d{{3}}-\\\\d{{2}}-\\\\d{{4}}$)}", "string")]
36+
[TestCase("api/orders/{value:regex(a{{0,1}})}", "string")]
37+
[TestCase("api/orders/{value:minlength(1):maxlength(2):required:alpha:regex(a{{0,1}})}", "string")]
38+
[TestCase("api/orders/{value:required}", "string")]
3739
public void WithParameter(string parameter, string type)
3840
{
3941
var code = @"
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
namespace AspNetCoreAnalyzers.Tests.ASP005ParameterRegexTests
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+
[TestCase("{value}", "string")]
12+
[TestCase("{value?}", "string")]
13+
[TestCase("{value:bool}", "bool")]
14+
[TestCase("{value:datetime}", "System.DateTime")]
15+
[TestCase("{value:decimal}", "decimal")]
16+
[TestCase("{value:double}", "double")]
17+
[TestCase("{value:float}", "float")]
18+
[TestCase("{value:int}", "int")]
19+
[TestCase("api/orders/{value:int:min(1)}", "int")]
20+
[TestCase("api/orders/{value:int:max(1)}", "int")]
21+
[TestCase("api/orders/{value:int:range(1,10)}", "int")]
22+
[TestCase("api/orders/{value:int:required}", "int")]
23+
[TestCase("{value:long}", "long")]
24+
[TestCase("api/orders/{value:min(1)}", "long")]
25+
[TestCase("api/orders/{value:max(1)}", "long")]
26+
[TestCase("api/orders/{value:range(1,10)}", "long")]
27+
[TestCase("api/orders/{value:required}", "long")]
28+
[TestCase("{value:guid}", "System.Guid")]
29+
[TestCase("api/orders/{value:minlength(1)}", "string")]
30+
[TestCase("api/orders/{value:maxlength(1)}", "string")]
31+
[TestCase("api/orders/{value:length(1)}", "string")]
32+
[TestCase("api/orders/{value:length(1,3)}", "string")]
33+
[TestCase("api/orders/{value:alpha}", "string")]
34+
[TestCase("api/orders/{value:regex(a-(0|1))}", "string")]
35+
[TestCase("api/orders/{value:regex(^\\\\d{{3}}-\\\\d{{2}}-\\\\d{{4}}$)}", "string")]
36+
[TestCase("api/orders/{value:required}", "string")]
37+
public void WithParameter(string parameter, string type)
38+
{
39+
var code = @"
40+
namespace ValidCode
41+
{
42+
using System.Threading.Tasks;
43+
using Microsoft.AspNetCore.Mvc;
44+
using Microsoft.EntityFrameworkCore;
45+
46+
[ApiController]
47+
public class OrdersController : Controller
48+
{
49+
[HttpGet(""api/{value}"")]
50+
public IActionResult GetValue(string value)
51+
{
52+
return this.Ok(value);
53+
}
54+
}
55+
}".AssertReplace("{value}", parameter)
56+
.AssertReplace("string", type);
57+
AnalyzerAssert.Valid(Analyzer, code);
58+
}
59+
}
60+
}

AspNetCoreAnalyzers.Tests/Helpers/UrlTemplateTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ public async Task<IActionResult> GetOrder([FromRoute]int id)
110110
[TestCase("orders/{id:length(1,2)}", new[] { "orders", "{id:length(1,2)}" }, new[] { "length(1,2)" })]
111111
[TestCase("orders/{id:regex(^\\\\d{{3}}-\\\\d{{2}}-\\\\d{4}$)}", new[] { "orders", "{id:regex(^\\d{{3}}-\\d{{2}}-\\d{4}$)}" }, new[] { "regex(^\\d{{3}}-\\d{{2}}-\\d{4}$)" })]
112112
[TestCase("orders/{id:regex(a/b)}", new[] { "orders", "{id:regex(a/b)}" }, new[] { "regex(a/b)" })]
113-
////[TestCase("orders/{id:regex(a[)}/]b)}", new[] { "orders", "{id:regex(a[)}/]b)}" })]
113+
[TestCase("orders/{id:regex(a{{0,1}})}", new[] { "orders", "{id:regex(a{{0,1}})}" }, new[] { "regex(a{{0,1}})" })]
114+
[TestCase("orders/{id:minlength(1):regex(a{{0,1}})}", new[] { "orders", "{id:minlength(1):regex(a{{0,1}})}" }, new[] { "minlength(1)", "regex(a{{0,1}})" })]
114115
public void TryParseWhenStringParameter(string text, string[] segments, string[] constraints)
115116
{
116117
var syntaxTree = CSharpSyntaxTree.ParseText(@"

AspNetCoreAnalyzers.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".docs", ".docs", "{1C271AF2
3333
documentation\ASP002.md = documentation\ASP002.md
3434
documentation\ASP003.md = documentation\ASP003.md
3535
documentation\ASP004.md = documentation\ASP004.md
36+
documentation\ASP005.md = documentation\ASP005.md
3637
README.md = README.md
3738
RELEASE_NOTES.md = RELEASE_NOTES.md
3839
EndProjectSection

AspNetCoreAnalyzers/ASP004ParameterSyntax.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ internal static class ASP004ParameterSyntax
1616
description: "Syntax error in parameter.",
1717
helpLinkUri: HelpLink.ForId(DiagnosticId));
1818
}
19-
}
19+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace AspNetCoreAnalyzers
2+
{
3+
using Microsoft.CodeAnalysis;
4+
5+
/// <summary>
6+
/// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-2.2#regular-expressions
7+
/// </summary>
8+
internal static class ASP005ParameterRegex
9+
{
10+
public const string DiagnosticId = "ASP005";
11+
12+
internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
13+
id: DiagnosticId,
14+
title: "Escape constraint regex.",
15+
messageFormat: "Escape constraint regex.",
16+
category: AnalyzerCategory.Routing,
17+
defaultSeverity: DiagnosticSeverity.Warning,
18+
isEnabledByDefault: true,
19+
description: "Escape constraint regex.",
20+
helpLinkUri: HelpLink.ForId(DiagnosticId));
21+
}
22+
}

AspNetCoreAnalyzers/Analyzers/AttributeAnalyzer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ public class AttributeAnalyzer : DiagnosticAnalyzer
1717
ASP001ParameterName.Descriptor,
1818
ASP002MissingParameter.Descriptor,
1919
ASP003ParameterType.Descriptor,
20-
ASP004ParameterSyntax.Descriptor);
20+
ASP004ParameterSyntax.Descriptor,
21+
ASP005ParameterRegex.Descriptor);
2122

2223
public override void Initialize(AnalysisContext context)
2324
{

AspNetCoreAnalyzers/CodeFixes/ParameterSyntaxFix.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ namespace AspNetCoreAnalyzers
1212
public class ParameterSyntaxFix : CodeFixProvider
1313
{
1414
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(
15-
ASP004ParameterSyntax.DiagnosticId);
15+
ASP004ParameterSyntax.DiagnosticId,
16+
ASP005ParameterRegex.DiagnosticId);
1617

1718
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
1819
{
@@ -26,7 +27,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
2627
{
2728
context.RegisterCodeFix(
2829
CodeAction.Create(
29-
"Fix syntax error.",
30+
diagnostic.Id == ASP004ParameterSyntax.DiagnosticId ? "Fix syntax error." : "Escape regex",
3031
_ => Fix(_)),
3132
diagnostic);
3233

AspNetCoreAnalyzers/Helpers/Text.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public static bool TrySkipDigits(string text, ref int pos)
2626
public static bool TrySkipPast(string text, ref int pos, string substring)
2727
{
2828
var before = pos;
29-
while (pos + substring.Length < text.Length)
29+
while (pos + substring.Length <= text.Length)
3030
{
3131
if (IsAt(text, pos, substring))
3232
{

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ Roslyn analyzers for ASP.NET.Core.
2626
<td><a href="https://github.com/DotNetAnalyzers/AspNetCoreAnalyzers/tree/master/documentation/ASP004.md">ASP004</a></td>
2727
<td>Syntax error in parameter.</td>
2828
</tr>
29+
<tr>
30+
<td><a href="https://github.com/DotNetAnalyzers/AspNetCoreAnalyzers/tree/master/documentation/ASP005.md">ASP005</a></td>
31+
<td>Escape constraint regex.</td>
32+
</tr>
2933
<table>
3034
<!-- end generated table -->
3135

0 commit comments

Comments
 (0)