Skip to content

Commit

Permalink
Parser refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
daviddotcs committed Apr 3, 2022
1 parent c24433f commit 4464698
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 115 deletions.
2 changes: 1 addition & 1 deletion src/SafeRouting.Generator/CSharpSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ public static string GetExcludeFromCodeCoverageAttribute()
public static LiteralExpressionSyntax ToStringLiteralExpression(string value)
=> SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(value));

private static AssemblyName AssemblyName { get; } = Assembly.GetAssembly(typeof(GeneratorSupport)).GetName();
private static readonly AssemblyName AssemblyName = Assembly.GetAssembly(typeof(GeneratorSupport)).GetName();
}
}
12 changes: 6 additions & 6 deletions src/SafeRouting.Generator/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,47 +22,47 @@ public static Diagnostic CreateConflictingPageClassDiagnostic(string pageClassNa
public static Diagnostic CreateUnsupportedLanguageVersionDiagnostic()
=> Diagnostic.Create(UnsupportedLanguageVersionDescriptor, location: null);

private static DiagnosticDescriptor ConflictingMethodsDescriptor { get; } = new(
private static readonly DiagnosticDescriptor ConflictingMethodsDescriptor = new(
id: "CSR0001",
title: "Conflicting methods",
messageFormat: "The class '{0}' contains multiple methods which map to the route method '{1}'.",
category: typeof(RouteGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

private static DiagnosticDescriptor InvalidOptionsDescriptor { get; } = new(
private static readonly DiagnosticDescriptor InvalidOptionsDescriptor = new(
id: "CSR0002",
title: "Invalid options",
messageFormat: "Value for the option '{0}' is invalid. {1}",
category: typeof(RouteGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

private static DiagnosticDescriptor InvalidIdentifierDescriptor { get; } = new(
private static readonly DiagnosticDescriptor InvalidIdentifierDescriptor = new(
id: "CSR0003",
title: "Invalid identifier",
messageFormat: "The text '{0}' is not a valid C# identifier.",
category: typeof(RouteGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

private static DiagnosticDescriptor ConflictingControllerDescriptor { get; } = new(
private static readonly DiagnosticDescriptor ConflictingControllerDescriptor = new(
id: "CSR0004",
title: "Conflicting Controller",
messageFormat: "The controller '{0}' conflicts with another controller of the same name.",
category: typeof(RouteGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

private static DiagnosticDescriptor ConflictingPageClassDescriptor { get; } = new(
private static readonly DiagnosticDescriptor ConflictingPageClassDescriptor = new(
id: "CSR0005",
title: "Conflicting PageModel Class",
messageFormat: "The page class '{0}' conflicts with another page class with the same resulting name.",
category: typeof(RouteGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

private static DiagnosticDescriptor UnsupportedLanguageVersionDescriptor { get; } = new(
private static readonly DiagnosticDescriptor UnsupportedLanguageVersionDescriptor = new(
id: "CSR0006",
title: "Unsupported Language Version",
messageFormat: "C# 8 or later is required for route generation.",
Expand Down
4 changes: 2 additions & 2 deletions src/SafeRouting.Generator/Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -463,8 +463,8 @@ private MemberType(string titleCaseNoun, string titleCasePluralNoun)
public string TitleCaseNoun { get; }
public string TitleCasePluralNoun { get; }

public static MemberType Parameter { get; } = new MemberType("Parameter", "Parameters");
public static MemberType Property { get; } = new MemberType("Property", "Properties");
public static MemberType Parameter { get; } = new("Parameter", "Parameters");
public static MemberType Property { get; } = new("Property", "Properties");
}
}
}
177 changes: 71 additions & 106 deletions src/SafeRouting.Generator/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,65 +247,25 @@ private static void GetControllerMembers(CandidateClassInfo classInfo, SourcePro

foreach (var member in symbol.GetMembers())
{
if (member.IsAbstract || member.IsImplicitlyDeclared || member.IsStatic || member.DeclaredAccessibility != Accessibility.Public)
{
continue;
}

var displayName = member.ToDisplayString(UniqueClassMemberSymbolDisplayFormat);

if (!accessedMembers.Add(displayName))
if (member.IsAbstract || member.IsImplicitlyDeclared || member.IsStatic || member.DeclaredAccessibility != Accessibility.Public
|| !accessedMembers.Add(member.ToDisplayString(UniqueClassMemberSymbolDisplayFormat)))
{
continue;
}

if (member is IPropertySymbol propertySymbol)
{
if (propertySymbol.GetMethod?.DeclaredAccessibility != Accessibility.Public || propertySymbol.SetMethod?.DeclaredAccessibility != Accessibility.Public || GetMvcPropertyInfo(context, propertySymbol, defaultBindingSource, classInfo.SemanticModel) is not MvcPropertyInfo propertyInfo)
if (GetMvcPropertyInfo(context, propertySymbol, defaultBindingSource, classInfo.SemanticModel) is MvcPropertyInfo propertyInfo)
{
continue;
properties.Add(propertyInfo);
}

properties.Add(propertyInfo);
}
else if (member is IMethodSymbol methodSymbol)
{
if (methodSymbol.MethodKind != MethodKind.Ordinary || methodSymbol.IsGenericMethod || GetControllerMethodInfo(context, methodSymbol, classInfo.SemanticModel) is not ControllerMethodInfo method)
{
continue;
}

var identifier = $"{method.Name}({string.Join(", ", method.Parameters.Select(x => x.Type.FullyQualifiedName))})";

// Collapse multiple methods with the same parameters
if (methodIdentifierDictionary.ContainsKey(identifier))
{
continue;
}

var urlAffectedIdentifier = $"{method.Name}({string.Join(", ", method.Parameters.Where(x => x.AffectsUrl()).Select(x => x.Type.FullyQualifiedName))})";

if (!urlAffectedIdentifiers.Add(urlAffectedIdentifier))
{
context.ReportDiagnostic(Diagnostics.CreateConflictingMethodsDiagnostic(classInfo.TypeSymbol.Name, urlAffectedIdentifier, methodSymbol.DeclaringSyntaxReferences[0].GetSyntax(context.CancellationToken).GetLocation()));
continue;
}

// Methods with the same name but different parameters need to be given unique identifiers for the code generated classes
if (!methodNames.Add(method.Name))
if (GetControllerMethodInfo(context, methodSymbol, classInfo.SemanticModel) is ControllerMethodInfo method)
{
var suffix = 2;
string uniqueName;

do
{
uniqueName = FormattableString.Invariant($"{method.Name}{suffix++:D}");
} while (!methodNames.Add(uniqueName));

method = method with { UniqueName = uniqueName };
AddUniqueControllerMethodInfo(classInfo, method, methodSymbol, methodIdentifierDictionary, urlAffectedIdentifiers, methodNames, context);
}

methodIdentifierDictionary[identifier] = method;
}
}

Expand Down Expand Up @@ -364,6 +324,11 @@ private static bool TryGetControllerMethodAttributes(SourceProductionContext con
}
private static ControllerMethodInfo? GetControllerMethodInfo(SourceProductionContext context, IMethodSymbol methodSymbol, SemanticModel semanticModel)
{
if (methodSymbol.MethodKind != MethodKind.Ordinary || methodSymbol.IsGenericMethod)
{
return null;
}

var name = methodSymbol.Name.EndsWith("Async", StringComparison.Ordinal)
? methodSymbol.Name.Substring(0, methodSymbol.Name.Length - "Async".Length)
: methodSymbol.Name;
Expand Down Expand Up @@ -397,6 +362,40 @@ private static bool TryGetControllerMethodAttributes(SourceProductionContext con

return new ControllerMethodInfo(name, escapedName, name, actionName, areaName, fullyQualifiedMethodDeclaration, parameters);
}
private static void AddUniqueControllerMethodInfo(CandidateClassInfo classInfo, ControllerMethodInfo method, IMethodSymbol methodSymbol, IDictionary<string, ControllerMethodInfo> methodIdentifierDictionary, ISet<string> urlAffectedIdentifiers, ISet<string> methodNames, SourceProductionContext context)
{
var identifier = $"{method.Name}({string.Join(", ", method.Parameters.Select(x => x.Type.FullyQualifiedName))})";

// Collapse multiple methods with the same parameters
if (methodIdentifierDictionary.ContainsKey(identifier))
{
return;
}

var urlAffectedIdentifier = $"{method.Name}({string.Join(", ", method.Parameters.Where(x => x.AffectsUrl()).Select(x => x.Type.FullyQualifiedName))})";

if (!urlAffectedIdentifiers.Add(urlAffectedIdentifier))
{
context.ReportDiagnostic(Diagnostics.CreateConflictingMethodsDiagnostic(classInfo.TypeSymbol.Name, urlAffectedIdentifier, methodSymbol.DeclaringSyntaxReferences[0].GetSyntax(context.CancellationToken).GetLocation()));
return;
}

// Methods with the same name but different parameters need to be given unique identifiers for the code generated classes
if (!methodNames.Add(method.Name))
{
var suffix = 2;
string uniqueName;

do
{
uniqueName = FormattableString.Invariant($"{method.Name}{suffix++:D}");
} while (!methodNames.Add(uniqueName));

method = method with { UniqueName = uniqueName };
}

methodIdentifierDictionary[identifier] = method;
}
private static bool TryGetMvcMethodParameterAttributes(SourceProductionContext context, IParameterSymbol parameterSymbol, ref string generatorName, out MvcBindingSourceInfo? bindingSource)
{
bindingSource = null;
Expand All @@ -406,38 +405,23 @@ private static bool TryGetMvcMethodParameterAttributes(SourceProductionContext c
switch (attribute.AttributeClass?.ToDisplayString())
{
case AspNetClassNames.FromBodyAttribute:
if (bindingSource is null)
{
bindingSource = attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Body);
}
bindingSource ??= attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Body);
break;

case AspNetClassNames.FromFormAttribute:
if (bindingSource is null)
{
bindingSource = attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Form);
}
bindingSource ??= attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Form);
break;

case AspNetClassNames.FromHeaderAttribute:
if (bindingSource is null)
{
bindingSource = attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Header);
}
bindingSource ??= attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Header);
break;

case AspNetClassNames.FromQueryAttribute:
if (bindingSource is null)
{
bindingSource = attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Query);
}
bindingSource ??= attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Query);
break;

case AspNetClassNames.FromRouteAttribute:
if (bindingSource is null)
{
bindingSource = attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Route);
}
bindingSource ??= attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Route);
break;

case AspNetClassNames.FromServicesAttribute:
Expand Down Expand Up @@ -501,45 +485,27 @@ private static bool TryGetMvcPropertyAttributes(SourceProductionContext context,
switch (attribute.AttributeClass?.ToDisplayString())
{
case AspNetClassNames.BindPropertyAttribute:
if (bindingSource is null)
{
bindingSource = attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Custom);
}
bindingSource ??= attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Custom);
break;

case AspNetClassNames.FromBodyAttribute:
if (bindingSource is null)
{
bindingSource = attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Body);
}
bindingSource ??= attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Body);
break;

case AspNetClassNames.FromFormAttribute:
if (bindingSource is null)
{
bindingSource = attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Form);
}
bindingSource ??= attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Form);
break;

case AspNetClassNames.FromHeaderAttribute:
if (bindingSource is null)
{
bindingSource = attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Header);
}
bindingSource ??= attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Header);
break;

case AspNetClassNames.FromQueryAttribute:
if (bindingSource is null)
{
bindingSource = attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Query);
}
bindingSource ??= attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Query);
break;

case AspNetClassNames.FromRouteAttribute:
if (bindingSource is null)
{
bindingSource = attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Route);
}
bindingSource ??= attribute.ParseBindingSourceAttribute(MvcBindingSourceType.Route);
break;

case GeneratorClassNames.ExcludeFromRouteGeneratorAttribute:
Expand All @@ -565,6 +531,11 @@ private static bool TryGetMvcPropertyAttributes(SourceProductionContext context,
}
private static MvcPropertyInfo? GetMvcPropertyInfo(SourceProductionContext context, IPropertySymbol propertySymbol, MvcBindingSourceInfo? defaultBindingSource, SemanticModel semanticModel)
{
if (propertySymbol.GetMethod?.DeclaredAccessibility != Accessibility.Public || propertySymbol.SetMethod?.DeclaredAccessibility != Accessibility.Public)
{
return null;
}

var generatorName = propertySymbol.Name;

if (!TryGetMvcPropertyAttributes(context, propertySymbol, ref generatorName, out var bindingSource))
Expand Down Expand Up @@ -596,12 +567,7 @@ private static void GetPageAttributes(SourceProductionContext context, INamedTyp
switch (attribute.AttributeClass?.ToDisplayString())
{
case AspNetClassNames.BindPropertiesAttribute:
if (defaultBindingSource is not null)
{
break;
}

defaultBindingSource = new MvcBindingSourceInfo(MvcBindingSourceType.Custom);
defaultBindingSource ??= new MvcBindingSourceInfo(MvcBindingSourceType.Custom);
break;

case GeneratorClassNames.RouteGeneratorNameAttribute:
Expand Down Expand Up @@ -653,16 +619,14 @@ private static void GetPageMembers(CandidateClassInfo classInfo, SourceProductio

if (member is IPropertySymbol propertySymbol)
{
if (propertySymbol.GetMethod?.DeclaredAccessibility != Accessibility.Public || propertySymbol.SetMethod?.DeclaredAccessibility != Accessibility.Public || GetMvcPropertyInfo(context, propertySymbol, defaultBindingSource, classInfo.SemanticModel) is not MvcPropertyInfo propertyInfo)
if (GetMvcPropertyInfo(context, propertySymbol, defaultBindingSource, classInfo.SemanticModel) is MvcPropertyInfo propertyInfo)
{
continue;
properties.Add(propertyInfo);
}

properties.Add(propertyInfo);
}
else if (member is IMethodSymbol methodSymbol)
{
if (methodSymbol.MethodKind != MethodKind.Ordinary || methodSymbol.IsGenericMethod || GetPageMethodInfo(context, methodSymbol, classInfo.SemanticModel) is not PageMethodInfo method)
if (GetPageMethodInfo(context, methodSymbol, classInfo.SemanticModel) is not PageMethodInfo method)
{
continue;
}
Expand All @@ -683,7 +647,8 @@ private static void GetPageMembers(CandidateClassInfo classInfo, SourceProductio
}
private static PageMethodInfo? GetPageMethodInfo(SourceProductionContext context, IMethodSymbol methodSymbol, SemanticModel semanticModel)
{
if (!ParseRazorPageMethodName(methodSymbol.Name, out var name, out var handlerName))
if (methodSymbol.MethodKind != MethodKind.Ordinary || methodSymbol.IsGenericMethod
|| !ParseRazorPageMethodName(methodSymbol.Name, out var name, out var handlerName))
{
return null;
}
Expand Down Expand Up @@ -837,7 +802,7 @@ private static bool ParseRazorPageMethodName(string methodName, [MaybeNullWhen(f
return true;
}

private static SymbolDisplayFormat UniqueClassMemberSymbolDisplayFormat { get; } = new SymbolDisplayFormat(
private static readonly SymbolDisplayFormat UniqueClassMemberSymbolDisplayFormat = new(
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
Expand All @@ -850,12 +815,12 @@ private static bool ParseRazorPageMethodName(string methodName, [MaybeNullWhen(f
kindOptions: SymbolDisplayKindOptions.None,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

private static SymbolDisplayFormat UniqueClassMemberWithNullableAnnotationsSymbolDisplayFormat { get; } = UniqueClassMemberSymbolDisplayFormat
private static readonly SymbolDisplayFormat UniqueClassMemberWithNullableAnnotationsSymbolDisplayFormat = UniqueClassMemberSymbolDisplayFormat
.AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);

private static SymbolDisplayFormat FullyQualifiedWithAnnotationsFormat { get; } = SymbolDisplayFormat.FullyQualifiedFormat
private static readonly SymbolDisplayFormat FullyQualifiedWithAnnotationsFormat = SymbolDisplayFormat.FullyQualifiedFormat
.AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);

private static Regex RazorPageMethodNameRegex { get; } = new Regex(@"^On(?<name>(?<verb>Delete|Get|Head|Options|Patch|Post|Put)(?<handler>.*?))(Async)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture, TimeSpan.FromSeconds(5));
private static readonly Regex RazorPageMethodNameRegex = new(@"^On(?<name>(?<verb>Delete|Get|Head|Options|Patch|Post|Put)(?<handler>.*?))(Async)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture, TimeSpan.FromSeconds(5));
}
}

0 comments on commit 4464698

Please sign in to comment.