3
3
4
4
using System ;
5
5
using System . Diagnostics . CodeAnalysis ;
6
+ using System . Text ;
6
7
using Microsoft . AspNetCore . Analyzers . Infrastructure ;
7
8
using Microsoft . AspNetCore . Analyzers . RouteEmbeddedLanguage . Infrastructure ;
8
9
using Microsoft . AspNetCore . App . Analyzers . Infrastructure ;
@@ -16,7 +17,8 @@ internal class EndpointParameter
16
17
public EndpointParameter ( Endpoint endpoint , IParameterSymbol parameter , WellKnownTypes wellKnownTypes )
17
18
{
18
19
Type = parameter . Type ;
19
- Name = parameter . Name ;
20
+ SymbolName = parameter . Name ;
21
+ LookupName = parameter . Name ; // Default lookup name is same as parameter name (which is a valid C# identifier).
20
22
Ordinal = parameter . Ordinal ;
21
23
Source = EndpointParameterSource . Unknown ;
22
24
IsOptional = parameter . IsOptional ( ) ;
@@ -26,21 +28,21 @@ public EndpointParameter(Endpoint endpoint, IParameterSymbol parameter, WellKnow
26
28
if ( parameter . HasAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata ) , out var fromRouteAttribute ) )
27
29
{
28
30
Source = EndpointParameterSource . Route ;
29
- Name = GetParameterName ( fromRouteAttribute , parameter . Name ) ;
31
+ LookupName = GetEscapedParameterName ( fromRouteAttribute , parameter . Name ) ;
30
32
IsParsable = TryGetParsability ( parameter , wellKnownTypes , out var parsingBlockEmitter ) ;
31
33
ParsingBlockEmitter = parsingBlockEmitter ;
32
34
}
33
35
else if ( parameter . HasAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromQueryMetadata ) , out var fromQueryAttribute ) )
34
36
{
35
37
Source = EndpointParameterSource . Query ;
36
- Name = GetParameterName ( fromQueryAttribute , parameter . Name ) ;
38
+ LookupName = GetEscapedParameterName ( fromQueryAttribute , parameter . Name ) ;
37
39
IsParsable = TryGetParsability ( parameter , wellKnownTypes , out var parsingBlockEmitter ) ;
38
40
ParsingBlockEmitter = parsingBlockEmitter ;
39
41
}
40
42
else if ( parameter . HasAttributeImplementingInterface ( wellKnownTypes . Get ( WellKnownType . Microsoft_AspNetCore_Http_Metadata_IFromHeaderMetadata ) , out var fromHeaderAttribute ) )
41
43
{
42
44
Source = EndpointParameterSource . Header ;
43
- Name = GetParameterName ( fromHeaderAttribute , parameter . Name ) ;
45
+ LookupName = GetEscapedParameterName ( fromHeaderAttribute , parameter . Name ) ;
44
46
IsParsable = TryGetParsability ( parameter , wellKnownTypes , out var parsingBlockEmitter ) ;
45
47
ParsingBlockEmitter = parsingBlockEmitter ;
46
48
}
@@ -130,7 +132,8 @@ private static bool ShouldDisableInferredBodyParameters(string httpMethod)
130
132
public ITypeSymbol Type { get ; }
131
133
public ITypeSymbol ElementType { get ; }
132
134
133
- public string Name { get ; }
135
+ public string SymbolName { get ; }
136
+ public string LookupName { get ; }
134
137
public int Ordinal { get ; }
135
138
public bool IsOptional { get ; }
136
139
public bool IsArray { get ; set ; }
@@ -327,15 +330,79 @@ private static bool TryGetExplicitFromJsonBody(IParameterSymbol parameter,
327
330
return true ;
328
331
}
329
332
330
- private static string GetParameterName ( AttributeData attribute , string parameterName ) =>
331
- attribute . TryGetNamedArgumentValue < string > ( "Name" , out var fromSourceName )
332
- ? ( fromSourceName ?? parameterName )
333
- : parameterName ;
333
+ private static string GetEscapedParameterName ( AttributeData attribute , string parameterName )
334
+ {
335
+ if ( attribute . TryGetNamedArgumentValue < string > ( "Name" , out var fromSourceName ) && fromSourceName is not null )
336
+ {
337
+ return ConvertEndOfLineAndQuotationCharactersToEscapeForm ( fromSourceName ) ;
338
+ }
339
+ else
340
+ {
341
+ return parameterName ;
342
+ }
343
+ }
344
+
345
+ // Lifted from:
346
+ // https://github.com/dotnet/runtime/blob/dc5a6c8be1644915c14c4a464447b0d54e223a46/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs#L562
347
+ private static string ConvertEndOfLineAndQuotationCharactersToEscapeForm ( string s )
348
+ {
349
+ var index = 0 ;
350
+ while ( index < s . Length )
351
+ {
352
+ if ( s [ index ] is '\n ' or '\r ' or '"' or '\\ ' )
353
+ {
354
+ break ;
355
+ }
356
+ index ++ ;
357
+ }
358
+
359
+ if ( index >= s . Length )
360
+ {
361
+ return s ;
362
+ }
363
+
364
+ var sb = new StringBuilder ( s . Length ) ;
365
+ sb . Append ( s , 0 , index ) ;
366
+
367
+ while ( index < s . Length )
368
+ {
369
+ switch ( s [ index ] )
370
+ {
371
+ case '\n ' :
372
+ sb . Append ( '\\ ' ) ;
373
+ sb . Append ( 'n' ) ;
374
+ break ;
375
+
376
+ case '\r ' :
377
+ sb . Append ( '\\ ' ) ;
378
+ sb . Append ( 'r' ) ;
379
+ break ;
380
+
381
+ case '"' :
382
+ sb . Append ( '\\ ' ) ;
383
+ sb . Append ( '"' ) ;
384
+ break ;
385
+
386
+ case '\\ ' :
387
+ sb . Append ( '\\ ' ) ;
388
+ sb . Append ( '\\ ' ) ;
389
+ break ;
390
+
391
+ default :
392
+ sb . Append ( s [ index ] ) ;
393
+ break ;
394
+ }
395
+
396
+ index ++ ;
397
+ }
398
+
399
+ return sb . ToString ( ) ;
400
+ }
334
401
335
402
public override bool Equals ( object obj ) =>
336
403
obj is EndpointParameter other &&
337
404
other . Source == Source &&
338
- other . Name == Name &&
405
+ other . SymbolName == SymbolName &&
339
406
other . Ordinal == Ordinal &&
340
407
other . IsOptional == IsOptional &&
341
408
SymbolEqualityComparer . Default . Equals ( other . Type , Type ) ;
@@ -347,7 +414,7 @@ obj is EndpointParameter other &&
347
414
public override int GetHashCode ( )
348
415
{
349
416
var hashCode = new HashCode ( ) ;
350
- hashCode . Add ( Name ) ;
417
+ hashCode . Add ( SymbolName ) ;
351
418
hashCode . Add ( Type , SymbolEqualityComparer . Default ) ;
352
419
return hashCode . ToHashCode ( ) ;
353
420
}
0 commit comments