Skip to content

Commit d8829b8

Browse files
committed
Use kebab-case urls. Close #14
1 parent bafae5f commit d8829b8

File tree

7 files changed

+86
-32
lines changed

7 files changed

+86
-32
lines changed

AspNetCoreAnalyzers.Tests/ASP009LowercaseUrlsTests/CodeFix.cs renamed to AspNetCoreAnalyzers.Tests/ASP009KebabCaseUrlTests/CodeFix.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace AspNetCoreAnalyzers.Tests.ASP009LowercaseUrlsTests
1+
namespace AspNetCoreAnalyzers.Tests.ASP009KebabCaseUrlTests
22
{
33
using Gu.Roslyn.Asserts;
44
using Microsoft.CodeAnalysis.CodeFixes;
@@ -8,10 +8,13 @@ namespace AspNetCoreAnalyzers.Tests.ASP009LowercaseUrlsTests
88
public class CodeFix
99
{
1010
private static readonly DiagnosticAnalyzer Analyzer = new AttributeAnalyzer();
11-
private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(ASP009LowercaseUrl.Descriptor);
11+
private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(ASP009KebabCaseUrl.Descriptor);
1212
private static readonly CodeFixProvider Fix = new TemplateTextFix();
1313

14-
[TestCase("\"api/↓Orders/{id}\"", "\"api/orders/{id}\"")]
14+
[TestCase("\"api/↓Orders/{id}\"", "\"api/orders/{id}\"")]
15+
[TestCase("\"api/↓TwoWords/{id}\"", "\"api/two-words/{id}\"")]
16+
[TestCase("\"api/↓twoWords/{id}\"", "\"api/two-words/{id}\"")]
17+
[TestCase("\"api/↓two_words/{id}\"", "\"api/two-words/{id}\"")]
1518
public void WhenMethodAttribute(string before, string after)
1619
{
1720
var code = @"
@@ -48,15 +51,18 @@ public IActionResult GetId(string id)
4851
AnalyzerAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, code, fixedCode);
4952
}
5053

51-
[TestCase("\"api/↓Orders\"", "\"api/orders\"")]
54+
[TestCase("\"api/↓Orders\"", "\"api/orders\"")]
55+
[TestCase("\"api/↓TwoWords\"", "\"api/two-words\"")]
56+
[TestCase("\"api/↓twoWords\"", "\"api/two-words\"")]
57+
[TestCase("\"api/↓two_words\"", "\"api/two-words\"")]
5258
public void WhenRouteAttribute(string before, string after)
5359
{
5460
var code = @"
5561
namespace ValidCode
5662
{
5763
using Microsoft.AspNetCore.Mvc;
5864
59-
[Route(""api/Orders"")]
65+
[Route(""api/↓TwoWords"")]
6066
[ApiController]
6167
public class OrdersController : Controller
6268
{
@@ -66,7 +72,7 @@ public IActionResult GetId(string id)
6672
return this.Ok(id);
6773
}
6874
}
69-
}".AssertReplace("\"api/Orders\"", before);
75+
}".AssertReplace("\"api/↓TwoWords\"", before);
7076

7177
var fixedCode = @"
7278
namespace ValidCode

AspNetCoreAnalyzers.Tests/ASP009LowercaseUrlsTests/ValidCode.cs renamed to AspNetCoreAnalyzers.Tests/ASP009KebabCaseUrlTests/ValidCode.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace AspNetCoreAnalyzers.Tests.ASP009LowercaseUrlsTests
1+
namespace AspNetCoreAnalyzers.Tests.ASP009KebabCaseUrlTests
22
{
33
using Gu.Roslyn.Asserts;
44
using Microsoft.CodeAnalysis.Diagnostics;
@@ -10,7 +10,7 @@ public class ValidCode
1010

1111
[TestCase("\"{value}\"")]
1212
[TestCase("\"api/orders/{value}\"")]
13-
[TestCase("\"api/TwoWords/{value}\"")]
13+
[TestCase("\"api/two-words/{value}\"")]
1414
public void WithParameter(string parameter)
1515
{
1616
var code = @"

AspNetCoreAnalyzers/ASP009LowercaseUrl.cs renamed to AspNetCoreAnalyzers/ASP009KebabCaseUrl.cs

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

5-
internal static class ASP009LowercaseUrl
5+
internal static class ASP009KebabCaseUrl
66
{
77
public const string DiagnosticId = "ASP009";
88

99
internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
1010
id: DiagnosticId,
11-
title: "Use lowercase urls.",
12-
messageFormat: "Use lowercase urls.",
11+
title: "Use kebab-cased urls.",
12+
messageFormat: "Use kebab-cased urls.",
1313
category: AnalyzerCategory.Routing,
1414
defaultSeverity: DiagnosticSeverity.Warning,
1515
isEnabledByDefault: true,
16-
description: "Use lowercase urls.",
16+
description: "Use kebab-cased urls.",
1717
helpLinkUri: HelpLink.ForId(DiagnosticId));
1818
}
1919
}

AspNetCoreAnalyzers/Analyzers/AttributeAnalyzer.cs

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class AttributeAnalyzer : DiagnosticAnalyzer
2323
ASP006ParameterRegex.Descriptor,
2424
ASP007MissingParameter.Descriptor,
2525
ASP008ValidRouteParameterName.Descriptor,
26-
ASP009LowercaseUrl.Descriptor);
26+
ASP009KebabCaseUrl.Descriptor);
2727

2828
public override void Initialize(AnalysisContext context)
2929
{
@@ -134,19 +134,17 @@ context.Node is AttributeSyntax attribute &&
134134
ASP008ValidRouteParameterName.Descriptor,
135135
location,
136136
name == null
137-
? ImmutableDictionary<string, string>.Empty
138-
: ImmutableDictionary<string, string>.Empty.Add(nameof(Text), name)));
137+
? ImmutableDictionary<string, string>.Empty
138+
: ImmutableDictionary<string, string>.Empty.Add(nameof(Text), name)));
139139
}
140140

141-
if (IsUpperCase(segment, out var lowercase))
141+
if (ShouldKebabCase(segment, out var kebabCase))
142142
{
143143
context.ReportDiagnostic(
144144
Diagnostic.Create(
145-
ASP009LowercaseUrl.Descriptor,
145+
ASP009KebabCaseUrl.Descriptor,
146146
segment.Span.GetLocation(),
147-
lowercase == null
148-
? ImmutableDictionary<string, string>.Empty
149-
: ImmutableDictionary<string, string>.Empty.Add(nameof(Text), lowercase)));
147+
ImmutableDictionary<string, string>.Empty.Add(nameof(Text), kebabCase)));
150148
}
151149
}
152150
}
@@ -518,8 +516,7 @@ private static bool HasInvalidName(PathSegment segment, out Location location, o
518516
parameter.Name.EndsWith(" ", StringComparison.OrdinalIgnoreCase))
519517
{
520518
location = parameter.Name.GetLocation();
521-
correctName = parameter.Name.ToString()
522-
.Trim();
519+
correctName = parameter.Name.ToString().Trim();
523520
return true;
524521
}
525522

@@ -540,7 +537,7 @@ private static bool HasInvalidName(PathSegment segment, out Location location, o
540537
return false;
541538
}
542539

543-
private static bool IsUpperCase(PathSegment segment, out string lowercase)
540+
private static bool IsUppercase(PathSegment segment, out string lowercase)
544541
{
545542
if (segment.Parameter == null &&
546543
segment.Span.Length > 0 &&
@@ -562,5 +559,56 @@ private static bool IsUpperCase(PathSegment segment, out string lowercase)
562559
lowercase = null;
563560
return false;
564561
}
562+
563+
private static bool ShouldKebabCase(PathSegment segment, out string kebabCase)
564+
{
565+
if (segment.Parameter == null &&
566+
IsHumpOrSnakeCased(segment.Span))
567+
{
568+
var builder = StringBuilderPool.Borrow();
569+
for (var i = 0; i < segment.Span.Length; i++)
570+
{
571+
var c = segment.Span[i];
572+
if (char.IsUpper(c))
573+
{
574+
if (i > 0)
575+
{
576+
_ = builder.Append("-");
577+
}
578+
579+
_ = builder.Append(char.ToLower(c));
580+
}
581+
else if (c == '_')
582+
{
583+
_ = builder.Append("-");
584+
}
585+
else
586+
{
587+
_ = builder.Append(c);
588+
}
589+
}
590+
591+
kebabCase = builder.Return();
592+
return true;
593+
}
594+
595+
kebabCase = null;
596+
return false;
597+
598+
bool IsHumpOrSnakeCased(Span span)
599+
{
600+
for (var i = 0; i < segment.Span.Length; i++)
601+
{
602+
var c = segment.Span[i];
603+
if (char.IsUpper(c) ||
604+
c == '_')
605+
{
606+
return true;
607+
}
608+
}
609+
610+
return false;
611+
}
612+
}
565613
}
566614
}

AspNetCoreAnalyzers/CodeFixes/TemplateTextFix.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class TemplateTextFix : CodeFixProvider
2121
ASP005ParameterSyntax.DiagnosticId,
2222
ASP006ParameterRegex.DiagnosticId,
2323
ASP008ValidRouteParameterName.DiagnosticId,
24-
ASP009LowercaseUrl.DiagnosticId);
24+
ASP009KebabCaseUrl.DiagnosticId);
2525

2626
public override FixAllProvider GetFixAllProvider() => null;
2727

@@ -66,7 +66,7 @@ private static string GetTitle(Diagnostic diagnostic)
6666
return "Escape regex.";
6767
case ASP008ValidRouteParameterName.DiagnosticId:
6868
return "Fix name.";
69-
case ASP009LowercaseUrl.DiagnosticId:
69+
case ASP009KebabCaseUrl.DiagnosticId:
7070
return "To lowercase.";
7171
default:
7272
throw new InvalidOperationException("Should never get here.");

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Roslyn analyzers for ASP.NET.Core.
4545
</tr>
4646
<tr>
4747
<td><a href="https://github.com/DotNetAnalyzers/AspNetCoreAnalyzers/tree/master/documentation/ASP009.md">ASP009</a></td>
48-
<td>Use lowercase urls.</td>
48+
<td>Use kebab-cased urls.</td>
4949
</tr>
5050
<table>
5151
<!-- end generated table -->

documentation/ASP009.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# ASP009
2-
## Use lowercase urls.
2+
## Use kebab-cased urls.
33

44
<!-- start generated table -->
55
<table>
@@ -28,7 +28,7 @@
2828

2929
## Description
3030

31-
Use lowercase urls.
31+
Use kebab-cased urls.
3232

3333
## Motivation
3434

@@ -47,21 +47,21 @@ Configure the severity per project, for more info see [MSDN](https://msdn.micros
4747

4848
### Via #pragma directive.
4949
```C#
50-
#pragma warning disable ASP009 // Use lowercase urls.
50+
#pragma warning disable ASP009 // Use kebab-cased urls.
5151
Code violating the rule here
52-
#pragma warning restore ASP009 // Use lowercase urls.
52+
#pragma warning restore ASP009 // Use kebab-cased urls.
5353
```
5454

5555
Or put this at the top of the file to disable all instances.
5656
```C#
57-
#pragma warning disable ASP009 // Use lowercase urls.
57+
#pragma warning disable ASP009 // Use kebab-cased urls.
5858
```
5959

6060
### Via attribute `[SuppressMessage]`.
6161

6262
```C#
6363
[System.Diagnostics.CodeAnalysis.SuppressMessage("AspNetCoreAnalyzers.Routing",
64-
"ASP009:Use lowercase urls.",
64+
"ASP009:Use kebab-cased urls.",
6565
Justification = "Reason...")]
6666
```
6767
<!-- end generated config severity -->

0 commit comments

Comments
 (0)