@@ -23,7 +23,8 @@ public class AttributeAnalyzer : DiagnosticAnalyzer
2323 ASP006ParameterRegex . Descriptor ,
2424 ASP007MissingParameter . Descriptor ,
2525 ASP008ValidRouteParameterName . Descriptor ,
26- ASP009KebabCaseUrl . Descriptor ) ;
26+ ASP009KebabCaseUrl . Descriptor ,
27+ ASP010UrlSyntax . Descriptor ) ;
2728
2829 public override void Initialize ( AnalysisContext context )
2930 {
@@ -146,6 +147,15 @@ context.Node is AttributeSyntax attribute &&
146147 segment . Span . GetLocation ( ) ,
147148 ImmutableDictionary < string , string > . Empty . Add ( nameof ( Text ) , kebabCase ) ) ) ;
148149 }
150+
151+ if ( ContainsReservedCharacter ( segment , out location ) )
152+ {
153+ context . ReportDiagnostic (
154+ Diagnostic . Create (
155+ ASP010UrlSyntax . Descriptor ,
156+ location ,
157+ segment . Span . ToString ( location ) ) ) ;
158+ }
149159 }
150160 }
151161 }
@@ -587,5 +597,33 @@ bool IsHumpOrSnakeCased(Span span)
587597 return false ;
588598 }
589599 }
600+
601+ /// <summary>
602+ /// https://tools.ietf.org/html/rfc3986#section-2.2.
603+ /// </summary>
604+ private static bool ContainsReservedCharacter ( PathSegment segment , out Location location )
605+ {
606+ if ( segment . Parameter == null )
607+ {
608+ for ( var i = 0 ; i < segment . Span . Length ; i ++ )
609+ {
610+ switch ( segment . Span [ i ] )
611+ {
612+ //case ':':
613+ //case '/':
614+ case '?' :
615+ //case '#':
616+ //case '[':
617+ //case ']':
618+ //case '@':
619+ location = segment . Span . GetLocation ( i , 1 ) ;
620+ return true ;
621+ }
622+ }
623+ }
624+
625+ location = null ;
626+ return false ;
627+ }
590628 }
591629}
0 commit comments