Skip to content

Commit

Permalink
ASP010 Unexpected character in url. #19
Browse files Browse the repository at this point in the history
  • Loading branch information
JohanLarsson committed Dec 31, 2018
1 parent 2de0850 commit 9b6fccd
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 7 deletions.
57 changes: 57 additions & 0 deletions AspNetCoreAnalyzers.Tests/ASP010UrlSyntaxTests/Diagnostics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
namespace AspNetCoreAnalyzers.Tests.ASP010UrlSyntaxTests
{
using Gu.Roslyn.Asserts;
using Microsoft.CodeAnalysis.Diagnostics;
using NUnit.Framework;

public class Diagnostics
{
private static readonly DiagnosticAnalyzer Analyzer = new AttributeAnalyzer();
private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(ASP010UrlSyntax.Descriptor);

[TestCase("\"api/a↓?b/{id}\"")]
public void WhenMethodAttribute(string before)
{
var code = @"
namespace AspBox
{
using Microsoft.AspNetCore.Mvc;
[ApiController]
public class OrdersController : Controller
{
[HttpGet(""api/a↓?b/{id}"")]
public IActionResult GetId(string id)
{
return this.Ok(id);
}
}
}".AssertReplace("\"api/a↓?b/{id}\"", before);

AnalyzerAssert.Diagnostics(Analyzer, ExpectedDiagnostic, code);
}

[TestCase("\"api/a↓?b\"")]
public void WhenRouteAttribute(string before)
{
var code = @"
namespace AspBox
{
using Microsoft.AspNetCore.Mvc;
[Route(""api/a↓?b"")]
[ApiController]
public class OrdersController : Controller
{
[HttpGet(""{id}"")]
public IActionResult GetId(string id)
{
return this.Ok(id);
}
}
}".AssertReplace("\"api/a↓?b\"", before);

AnalyzerAssert.Diagnostics(Analyzer, ExpectedDiagnostic, code);
}
}
}
36 changes: 36 additions & 0 deletions AspNetCoreAnalyzers.Tests/ASP010UrlSyntaxTests/ValidCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace AspNetCoreAnalyzers.Tests.ASP010UrlSyntaxTests
{
using Gu.Roslyn.Asserts;
using Microsoft.CodeAnalysis.Diagnostics;
using NUnit.Framework;

public class ValidCode
{
private static readonly DiagnosticAnalyzer Analyzer = new AttributeAnalyzer();

[TestCase("\"{value}\"")]
[TestCase("\"api/orders/{value}\"")]
[TestCase("\"api/two-words/{value}\"")]
public void WithParameter(string parameter)
{
var code = @"
namespace AspBox
{
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
[ApiController]
public class OrdersController : Controller
{
[HttpGet(""api/{value}"")]
public IActionResult GetValue(string value)
{
return this.Ok(value);
}
}
}".AssertReplace("\"api/{value}\"", parameter);
AnalyzerAssert.Valid(Analyzer, code);
}
}
}
1 change: 1 addition & 0 deletions AspNetCoreAnalyzers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".docs", ".docs", "{1C271AF2
documentation\ASP006.md = documentation\ASP006.md
documentation\ASP008.md = documentation\ASP008.md
documentation\ASP009.md = documentation\ASP009.md
documentation\ASP010.md = documentation\ASP010.md
README.md = README.md
RELEASE_NOTES.md = RELEASE_NOTES.md
EndProjectSection
Expand Down
19 changes: 19 additions & 0 deletions AspNetCoreAnalyzers/ASP010UrlSyntax.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace AspNetCoreAnalyzers
{
using Microsoft.CodeAnalysis;

internal static class ASP010UrlSyntax
{
public const string DiagnosticId = "ASP010";

internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
id: DiagnosticId,
title: "Unexpected character in url.",
messageFormat: "Literal sections cannot contain the '{0}' character",
category: AnalyzerCategory.Routing,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "Unexpected character in url.",
helpLinkUri: HelpLink.ForId(DiagnosticId));
}
}
40 changes: 39 additions & 1 deletion AspNetCoreAnalyzers/Analyzers/AttributeAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public class AttributeAnalyzer : DiagnosticAnalyzer
ASP006ParameterRegex.Descriptor,
ASP007MissingParameter.Descriptor,
ASP008ValidRouteParameterName.Descriptor,
ASP009KebabCaseUrl.Descriptor);
ASP009KebabCaseUrl.Descriptor,
ASP010UrlSyntax.Descriptor);

public override void Initialize(AnalysisContext context)
{
Expand Down Expand Up @@ -146,6 +147,15 @@ context.Node is AttributeSyntax attribute &&
segment.Span.GetLocation(),
ImmutableDictionary<string, string>.Empty.Add(nameof(Text), kebabCase)));
}

if (ContainsReservedCharacter(segment, out location))
{
context.ReportDiagnostic(
Diagnostic.Create(
ASP010UrlSyntax.Descriptor,
location,
segment.Span.ToString(location)));
}
}
}
}
Expand Down Expand Up @@ -587,5 +597,33 @@ bool IsHumpOrSnakeCased(Span span)
return false;
}
}

/// <summary>
/// https://tools.ietf.org/html/rfc3986#section-2.2.
/// </summary>
private static bool ContainsReservedCharacter(PathSegment segment, out Location location)
{
if (segment.Parameter == null)
{
for (var i = 0; i < segment.Span.Length; i++)
{
switch (segment.Span[i])
{
//case ':':
//case '/':
case '?':
//case '#':
//case '[':
//case ']':
//case '@':
location = segment.Span.GetLocation(i, 1);
return true;
}
}
}

location = null;
return false;
}
}
}
2 changes: 2 additions & 0 deletions AspNetCoreAnalyzers/Helpers/Span.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public override string ToString() => this.TextSpan.Length == 0
? string.Empty
: this.Literal.ValueText.Substring(this.TextSpan.Start, this.TextSpan.Length);

public string ToString(Location location) => this.Literal.ToString(location);

public Location GetLocation() => this.Literal.GetLocation(this.TextSpan);

public Location GetLocation(int start, int length) => this.Literal.GetLocation(new TextSpan(this.TextSpan.Start + start, length));
Expand Down
12 changes: 6 additions & 6 deletions AspNetCoreAnalyzers/Helpers/StringLiteral.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,17 @@ public bool Equals(StringLiteral other)

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}

return obj is StringLiteral other && this.Equals(other);
return obj is StringLiteral other &&
this.Equals(other);
}

public override int GetHashCode()
{
return this.literalExpression.GetHashCode();
}

public string ToString(Location location) => location.SourceSpan.Length == 0
? string.Empty
: this.Text.Substring(location.SourceSpan.Start - this.literalExpression.SpanStart, location.SourceSpan.Length);
}
}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ Roslyn analyzers for ASP.NET.Core.
<td><a href="https://github.com/DotNetAnalyzers/AspNetCoreAnalyzers/tree/master/documentation/ASP009.md">ASP009</a></td>
<td>Use kebab-cased urls.</td>
</tr>
<tr>
<td><a href="https://github.com/DotNetAnalyzers/AspNetCoreAnalyzers/tree/master/documentation/ASP010.md">ASP010</a></td>
<td>Unexpected character in url.</td>
</tr>
<table>
<!-- end generated table -->

Expand Down
75 changes: 75 additions & 0 deletions documentation/ASP010.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# ASP010
## Unexpected character in url.

<!-- start generated table -->
<table>
<tr>
<td>CheckId</td>
<td>ASP010</td>
</tr>
<tr>
<td>Severity</td>
<td>Warning</td>
</tr>
<tr>
<td>Enabled</td>
<td>True</td>
</tr>
<tr>
<td>Category</td>
<td>AspNetCoreAnalyzers.Routing</td>
</tr>
<tr>
<td>Code</td>
<td><a href="https://github.com/DotNetAnalyzers/AspNetCoreAnalyzers/blob/master/AspNetCoreAnalyzers/Analyzers/AttributeAnalyzer.cs">AttributeAnalyzer</a></td>
</tr>
</table>
<!-- end generated table -->

## Description

Unexpected character in url.

## Motivation

```cs
[HttpGet(""api/a?b"")]
public IActionResult GetId(string id)
{
...
}
```

Throws an exception at runtime.

## How to fix violations

Fix the url template.

<!-- start generated config severity -->
## Configure severity

### Via ruleset file.

Configure the severity per project, for more info see [MSDN](https://msdn.microsoft.com/en-us/library/dd264949.aspx).

### Via #pragma directive.
```C#
#pragma warning disable ASP010 // Unexpected character in url.
Code violating the rule here
#pragma warning restore ASP010 // Unexpected character in url.
```

Or put this at the top of the file to disable all instances.
```C#
#pragma warning disable ASP010 // Unexpected character in url.
```

### Via attribute `[SuppressMessage]`.

```C#
[System.Diagnostics.CodeAnalysis.SuppressMessage("AspNetCoreAnalyzers.Routing",
"ASP010:Unexpected character in url.",
Justification = "Reason...")]
```
<!-- end generated config severity -->

0 comments on commit 9b6fccd

Please sign in to comment.