Skip to content

Commit

Permalink
Fully qualify type references; remove usings
Browse files Browse the repository at this point in the history
This updates the interface generation to always fully qualify type references, and no longer generate "using" statements in generated interfaces.

This makes the interface generation resilient to namespace collisions and other namespace edge-cases (like resolving global usings in parent assemblies). It also means the generator is now able to correctly resolve types with duplicate names
  • Loading branch information
simonmckenzie committed Aug 21, 2024
1 parent d8d8fc4 commit f2df1bc
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 436 deletions.
53 changes: 19 additions & 34 deletions AutomaticInterface/AutomaticInterface/Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ public static class Builder
| SymbolDisplayParameterOptions.IncludeParamsRefOut
);

private static readonly SymbolDisplayFormat TypeDisplayFormat =
new(
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes
| SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier
);

public static string BuildInterfaceFor(ITypeSymbol typeSymbol)
{
if (
Expand All @@ -34,7 +43,6 @@ is not ClassDeclarationSyntax classSyntax

var interfaceGenerator = new InterfaceBuilder(namespaceName, interfaceName);

interfaceGenerator.AddUsings(GetUsings(typeSymbol));
interfaceGenerator.AddClassDocumentation(GetDocumentationForClass(classSyntax));
interfaceGenerator.AddGeneric(GetGeneric(classSyntax));
AddPropertiesToInterface(typeSymbol, interfaceGenerator);
Expand Down Expand Up @@ -102,16 +110,13 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth

var typedArgs = method
.TypeParameters.Select(arg =>
(
arg.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
arg.GetWhereStatement()
)
(arg.ToDisplayString(TypeDisplayFormat), arg.GetWhereStatement(TypeDisplayFormat))
)
.ToList();

codeGenerator.AddMethodToInterface(
name,
returnType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
returnType.ToDisplayString(TypeDisplayFormat),
InheritDoc,
paramResult,
typedArgs
Expand Down Expand Up @@ -189,7 +194,11 @@ InterfaceBuilder codeGenerator

ActivateNullableIfNeeded(codeGenerator, type);

codeGenerator.AddEventToInterface(name, type.ToDisplayString(), InheritDoc);
codeGenerator.AddEventToInterface(
name,
type.ToDisplayString(TypeDisplayFormat),
InheritDoc
);
});
}

Expand All @@ -199,7 +208,7 @@ private static string GetMethodSignature(IParameterSymbol x)
var refKindText = GetRefKind(x);
var optionalValue = GetMethodOptionalValue(x);

return $"{refKindText}{x.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)} {name}{optionalValue}";
return $"{refKindText}{x.Type.ToDisplayString(TypeDisplayFormat)} {name}{optionalValue}";
}

private static string GetMethodOptionalValue(IParameterSymbol x)
Expand All @@ -215,7 +224,7 @@ private static string GetMethodOptionalValue(IParameterSymbol x)
bool value => $" = {(value ? "true" : "false")}",
// struct
null when x.Type.IsValueType
=> $" = default({x.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)})",
=> $" = default({x.Type.ToDisplayString(TypeDisplayFormat)})",
null => " = null",
_ => $" = {x.ExplicitDefaultValue}"
};
Expand Down Expand Up @@ -272,7 +281,7 @@ InterfaceBuilder interfaceGenerator

interfaceGenerator.AddPropertyToInterface(
name,
type.ToDisplayString(),
type.ToDisplayString(TypeDisplayFormat),
hasGet,
hasSet,
isRef,
Expand Down Expand Up @@ -330,30 +339,6 @@ private static string GetDocumentationForClass(CSharpSyntaxNode classSyntax)
return trivia.ToFullString().Trim();
}

private static IEnumerable<string> GetUsings(ISymbol classSymbol)
{
var allUsings = SyntaxFactory.List<UsingDirectiveSyntax>();
foreach (var syntaxRef in classSymbol.DeclaringSyntaxReferences)
{
allUsings = syntaxRef
.GetSyntax()
.Ancestors(false)
.Aggregate(
allUsings,
(current, parent) =>
parent switch
{
NamespaceDeclarationSyntax ndSyntax
=> current.AddRange(ndSyntax.Usings),
CompilationUnitSyntax cuSyntax => current.AddRange(cuSyntax.Usings),
_ => current
}
);
}

return [.. allUsings.Select(x => x.ToString())];
}

private static string GetGeneric(TypeDeclarationSyntax classSyntax)
{
if (classSyntax.TypeParameterList?.Parameters.Count == 0)
Expand Down
20 changes: 3 additions & 17 deletions AutomaticInterface/AutomaticInterface/InterfaceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public class InterfaceBuilder(string nameSpaceName, string interfaceName)
""";

private readonly HashSet<string> usings = ["using System.CodeDom.Compiler;"];
private readonly List<PropertyInfo> propertyInfos = [];
private readonly List<MethodInfo> methodInfos = [];
private readonly List<EventInfo> events = [];
Expand Down Expand Up @@ -69,14 +68,6 @@ public void AddClassDocumentation(string documentation)
classDocumentation = documentation;
}

public void AddUsings(IEnumerable<string> additionalUsings)
{
foreach (var usg in additionalUsings)
{
usings.Add(usg);
}
}

public void AddMethodToInterface(
string name,
string returnType,
Expand All @@ -103,21 +94,16 @@ public string Build()
cb.AppendLine("#nullable enable");
}

foreach (var usg in usings)
{
cb.AppendLine(usg);
}

cb.AppendLine("");

cb.AppendLine($"namespace {nameSpaceName}");
cb.AppendLine("{");

cb.Indent();

cb.AppendAndNormalizeMultipleLines(classDocumentation);

cb.AppendLine($"[GeneratedCode(\"AutomaticInterface\", \"\")]");
cb.AppendLine(
"[global::System.CodeDom.Compiler.GeneratedCode(\"AutomaticInterface\", \"\")]"
);
cb.AppendLine($"public partial interface {interfaceName}{genericType}");
cb.AppendLine("{");

Expand Down
26 changes: 17 additions & 9 deletions AutomaticInterface/AutomaticInterface/RoslynExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ public static string GetClassName(this ClassDeclarationSyntax proxy)
/// <summary>
/// Thanks to https://www.codeproject.com/Articles/871704/Roslyn-Code-Analysis-in-Easy-Samples-Part-2
/// </summary>
/// <param name="typeParameterSymbol"></param>
/// <returns></returns>
public static string GetWhereStatement(this ITypeParameterSymbol typeParameterSymbol)
public static string GetWhereStatement(
this ITypeParameterSymbol typeParameterSymbol,
SymbolDisplayFormat typeDisplayFormat
)
{
var result = $"where {typeParameterSymbol.Name} : ";

Expand Down Expand Up @@ -69,7 +70,7 @@ public static string GetWhereStatement(this ITypeParameterSymbol typeParameterSy
isFirstConstraint = false;
}

constraints += constraintType.GetFullTypeString();
constraints += constraintType.GetFullTypeString(typeDisplayFormat);
}

if (string.IsNullOrEmpty(constraints))
Expand All @@ -82,14 +83,21 @@ public static string GetWhereStatement(this ITypeParameterSymbol typeParameterSy
return result;
}

private static string GetFullTypeString(this ISymbol type)
private static string GetFullTypeString(
this ISymbol type,
SymbolDisplayFormat typeDisplayFormat
)
{
return type.Name
+ type.GetTypeArgsStr(symbol => ((INamedTypeSymbol)symbol).TypeArguments);
return type.ToDisplayString(typeDisplayFormat)
+ type.GetTypeArgsStr(
typeDisplayFormat,
symbol => ((INamedTypeSymbol)symbol).TypeArguments
);
}

private static string GetTypeArgsStr(
this ISymbol symbol,
SymbolDisplayFormat typeDisplayFormat,
Func<ISymbol, ImmutableArray<ITypeSymbol>> typeArgGetter
)
{
Expand All @@ -106,13 +114,13 @@ Func<ISymbol, ImmutableArray<ITypeSymbol>> typeArgGetter
if (arg is ITypeParameterSymbol typeParameterSymbol)
{
// this is a generic argument
strToAdd = typeParameterSymbol.Name;
strToAdd = typeParameterSymbol.ToDisplayString(typeDisplayFormat);
}
else
{
// this is a generic argument value.
var namedTypeSymbol = arg as INamedTypeSymbol;
strToAdd = namedTypeSymbol!.GetFullTypeString();
strToAdd = namedTypeSymbol!.GetFullTypeString(typeDisplayFormat);
}

stringsToAdd.Add(strToAdd);
Expand Down
63 changes: 12 additions & 51 deletions AutomaticInterface/Tests/GeneratorTests.Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,9 @@ class DemoClass
// </auto-generated>
//--------------------------------------------------------------------------------------------------
using System.CodeDom.Compiler;
using AutomaticInterface;
namespace AutomaticInterfaceExample
{
[GeneratedCode("AutomaticInterface", "")]
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
Expand Down Expand Up @@ -83,12 +80,9 @@ class DemoClass
// </auto-generated>
//--------------------------------------------------------------------------------------------------
using System.CodeDom.Compiler;
using AutomaticInterface;
namespace AutomaticInterfaceExample
{
[GeneratedCode("AutomaticInterface", "")]
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
Expand Down Expand Up @@ -137,16 +131,12 @@ class DemoClass
//--------------------------------------------------------------------------------------------------
#nullable enable
using System.CodeDom.Compiler;
using AutomaticInterface;
using System;
namespace AutomaticInterfaceExample
{
/// <summary>
/// Bla bla
/// </summary>
[GeneratedCode("AutomaticInterface", "")]
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
Expand Down Expand Up @@ -197,21 +187,16 @@ class DemoClass
//--------------------------------------------------------------------------------------------------
#nullable enable
using System.CodeDom.Compiler;
using AutomaticInterface;
using System;
using System.Threading.Tasks;
namespace AutomaticInterfaceExample
{
/// <summary>
/// Bla bla
/// </summary>
[GeneratedCode("AutomaticInterface", "")]
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
System.Threading.Tasks.Task<string?> NullableProperty { get; set; }
global::System.Threading.Tasks.Task<string?> NullableProperty { get; set; }
}
}
Expand Down Expand Up @@ -253,13 +238,9 @@ public partial class SecondClass : FirstClass
// </auto-generated>
//--------------------------------------------------------------------------------------------------
using System.CodeDom.Compiler;
using AutomaticInterface;
using System.Threading.Tasks;
namespace AutomaticInterfaceExample
{
[GeneratedCode("AutomaticInterface", "")]
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface ISecondClass
{
/// <inheritdoc />
Expand Down Expand Up @@ -302,13 +283,9 @@ public class DemoClass : BaseClass
// </auto-generated>
//--------------------------------------------------------------------------------------------------
using System.CodeDom.Compiler;
using AutomaticInterface;
using System;
namespace AutomaticInterfaceExample
{
[GeneratedCode("AutomaticInterface", "")]
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
Expand Down Expand Up @@ -352,13 +329,9 @@ public class DemoClass : BaseClass
// </auto-generated>
//--------------------------------------------------------------------------------------------------
using System.CodeDom.Compiler;
using AutomaticInterface;
using System;
namespace AutomaticInterfaceExample
{
[GeneratedCode("AutomaticInterface", "")]
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
Expand Down Expand Up @@ -399,12 +372,9 @@ class DemoClass
// </auto-generated>
//--------------------------------------------------------------------------------------------------
using System.CodeDom.Compiler;
using AutomaticInterface;
namespace AutomaticInterfaceExample
{
[GeneratedCode("AutomaticInterface", "")]
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
Expand Down Expand Up @@ -446,12 +416,9 @@ class DemoClass
// </auto-generated>
//--------------------------------------------------------------------------------------------------
using System.CodeDom.Compiler;
using AutomaticInterface;
namespace AutomaticInterfaceExample
{
[GeneratedCode("AutomaticInterface", "")]
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
Expand Down Expand Up @@ -493,12 +460,9 @@ class DemoClass
// </auto-generated>
//--------------------------------------------------------------------------------------------------
using System.CodeDom.Compiler;
using AutomaticInterface;
namespace AutomaticInterfaceExample
{
[GeneratedCode("AutomaticInterface", "")]
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
Expand Down Expand Up @@ -539,12 +503,9 @@ class DemoClass
// </auto-generated>
//--------------------------------------------------------------------------------------------------
using System.CodeDom.Compiler;
using AutomaticInterface;
namespace AutomaticInterfaceExample
{
[GeneratedCode("AutomaticInterface", "")]
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
Expand Down
Loading

0 comments on commit f2df1bc

Please sign in to comment.