Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Mockolate.SourceGenerators/Entities/Attribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ internal record Attribute
{
public Attribute(AttributeData attributeData)
{
Name = ToName(attributeData.AttributeClass?.ToDisplayString(Helpers.TypeDisplayFormat) ?? "");
Name = ToName(attributeData.AttributeClass?.ToQualifiedDisplayString(Helpers.TypeDisplayFormat) ?? "");
if (attributeData.ConstructorArguments.Length > 0)
{
Parameters = new EquatableArray<AttributeParameter>(attributeData.ConstructorArguments
Expand Down
30 changes: 14 additions & 16 deletions Source/Mockolate.SourceGenerators/Entities/AttributeParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,24 @@ internal record AttributeParameter
{
public AttributeParameter(TypedConstant value)
{
if (value.Kind == TypedConstantKind.Array)
{
Value = $"new {value.Type}{{{string.Join(", ", value.Values.Select(SerializeConstantValue))}}}";
}
else if (value.Kind == TypedConstantKind.Type)
{
Value = $"typeof({value.Value})";
}
else if (value.Kind == TypedConstantKind.Enum)
{
Value = $"({value.Type?.ToDisplayString(Helpers.TypeDisplayFormat)}){value.Value}";
}
else
{
Value = SerializeConstantValue(value);
}
Value = Serialize(value);
}

public string? Value { get; }

private static string Serialize(TypedConstant value)
=> value.Kind switch
{
TypedConstantKind.Array when value.IsNull => "null",
TypedConstantKind.Array =>
$"new {value.Type?.ToQualifiedDisplayString(Helpers.TypeDisplayFormat)}{{{string.Join(", ", value.Values.Select(Serialize))}}}",
TypedConstantKind.Type when value.Value is ITypeSymbol type =>
$"typeof({type.ToQualifiedDisplayString(Helpers.TypeDisplayFormat)})",
TypedConstantKind.Enum =>
$"({value.Type?.ToQualifiedDisplayString(Helpers.TypeDisplayFormat)}){value.Value}",
_ => SerializeConstantValue(value),
};

private static string SerializeConstantValue(TypedConstant value)
{
if (value.Value is null)
Expand Down
2 changes: 1 addition & 1 deletion Source/Mockolate.SourceGenerators/Entities/Class.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public Class(ITypeSymbol type,
#pragma warning restore S107
{
_sourceAssembly = sourceAssembly;
ClassFullName = type.ToDisplayString(Helpers.TypeDisplayFormat);
ClassFullName = type.ToQualifiedDisplayString(Helpers.TypeDisplayFormat);
ClassName = GetTypeName(type);
DisplayString = GetTypeFullName(type);

Expand Down
2 changes: 1 addition & 1 deletion Source/Mockolate.SourceGenerators/Entities/Event.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public Event(IEventSymbol eventSymbol, IMethodSymbol delegateInvokeMethod, List<
IsAbstract = eventSymbol.IsAbstract;
Name = Helpers.EscapeIfKeyword(eventSymbol.ExplicitInterfaceImplementations.Length > 0 ? eventSymbol.ExplicitInterfaceImplementations[0].Name : eventSymbol.Name);
Type = Type.From(eventSymbol.Type);
ContainingType = eventSymbol.ContainingType.ToDisplayString(Helpers.TypeDisplayFormat);
ContainingType = eventSymbol.ContainingType.ToQualifiedDisplayString(Helpers.TypeDisplayFormat);
DeclaredContainingType = ContainingType;
Delegate = new Method(delegateInvokeMethod, null, sourceAssembly);
Attributes = eventSymbol.GetAttributes().ToAttributeArray(sourceAssembly);
Expand Down
2 changes: 1 addition & 1 deletion Source/Mockolate.SourceGenerators/Entities/Method.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public Method(IMethodSymbol methodSymbol, List<Method>? alreadyDefinedMethods, I
IsDelegateInvoke = methodSymbol.MethodKind == MethodKind.DelegateInvoke;
ReturnType = methodSymbol.ReturnsVoid ? Type.Void : Type.From(methodSymbol.ReturnType);
Name = Helpers.EscapeIfKeyword(methodSymbol.ExplicitInterfaceImplementations.Length > 0 ? methodSymbol.ExplicitInterfaceImplementations[0].Name : methodSymbol.Name);
ContainingType = methodSymbol.ContainingType.ToDisplayString(Helpers.TypeDisplayFormat);
ContainingType = methodSymbol.ContainingType.ToQualifiedDisplayString(Helpers.TypeDisplayFormat);
DeclaredContainingType = ContainingType;
Parameters = new EquatableArray<MethodParameter>(
methodSymbol.Parameters.Select(MethodParameter.From).ToArray());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public MethodParameter(IParameterSymbol parameterSymbol)
}
else if (parameterSymbol.Type.TypeKind == TypeKind.Enum)
{
string enumTypeName = parameterSymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
string enumTypeName = parameterSymbol.Type.ToQualifiedDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
ExplicitDefaultValue = "(" + enumTypeName + ")" + explicitDefaultValue;
}
else
Expand Down
2 changes: 1 addition & 1 deletion Source/Mockolate.SourceGenerators/Entities/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public Property(IPropertySymbol propertySymbol, List<Property>? alreadyDefinedPr
string rawName = propertySymbol.ExplicitInterfaceImplementations.Length > 0 ? propertySymbol.ExplicitInterfaceImplementations[0].Name : propertySymbol.Name;
Name = propertySymbol.IsIndexer ? rawName : Helpers.EscapeIfKeyword(rawName);
Type = Type.From(propertySymbol.Type);
ContainingType = propertySymbol.ContainingType.ToDisplayString(Helpers.TypeDisplayFormat);
ContainingType = propertySymbol.ContainingType.ToQualifiedDisplayString(Helpers.TypeDisplayFormat);
DeclaredContainingType = ContainingType;
IsIndexer = propertySymbol.IsIndexer;
IsAbstract = propertySymbol.IsAbstract;
Expand Down
2 changes: 1 addition & 1 deletion Source/Mockolate.SourceGenerators/Entities/Type.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ private Type(string fullname)
internal Type(ITypeSymbol typeSymbol)
{
// Removes '*' from multi-dimensional array types
Fullname = typeSymbol.ToDisplayString(Helpers.TypeDisplayFormat).Replace("*", "");
Fullname = typeSymbol.ToQualifiedDisplayString(Helpers.TypeDisplayFormat).Replace("*", "");
DisplayName = typeSymbol.ToDisplayString(Helpers.TypeDisplayShortFormat).Replace("*", "");
Namespace = typeSymbol.ContainingNamespace?.ToString();
if (typeSymbol is INamedTypeSymbol namedTypeSymbol)
Expand Down
31 changes: 31 additions & 0 deletions Source/Mockolate.SourceGenerators/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Mockolate.SourceGenerators.Entities;
using Mockolate.SourceGenerators.Internals;
using Attribute = Mockolate.SourceGenerators.Entities.Attribute;
using Type = Mockolate.SourceGenerators.Entities.Type;

Expand All @@ -26,6 +27,36 @@ internal static class Helpers
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters);

/// <summary>
/// Formats <paramref name="symbol" /> like <see cref="ISymbol.ToDisplayString" />, but qualifies types from a
/// reference with only extern aliases by that alias instead of <c>global::</c>, under which they do not resolve.
/// </summary>
public static string ToQualifiedDisplayString(this ISymbol symbol, SymbolDisplayFormat format)
{
EntityCache? cache = EntityCache.Current;
if (cache is null || !cache.HasExternAliases)
{
return symbol.ToDisplayString(format);
}

ImmutableArray<SymbolDisplayPart> parts = symbol.ToDisplayParts(format);
StringBuilder sb = new();
for (int i = 0; i < parts.Length; i++)
{
if (parts[i].Symbol is INamespaceSymbol { IsGlobalNamespace: true, ContainingAssembly: { } assembly, } &&
cache.GetExternAlias(assembly) is { } alias)
{
sb.Append(alias);
}
else
{
sb.Append(parts[i]);
}
}

return sb.ToString();
}

public static string EscapeIfKeyword(string name)
=> SyntaxFacts.GetKeywordKind(name) != SyntaxKind.None ? "@" + name : name;

Expand Down
48 changes: 47 additions & 1 deletion Source/Mockolate.SourceGenerators/Internals/EntityCache.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;
using Mockolate.SourceGenerators.Entities;
Expand Down Expand Up @@ -30,10 +31,17 @@ internal sealed class EntityCache
// `IsNullableAnnotated` flags and produce different generated code.
private readonly ConcurrentDictionary<ITypeSymbol, Type> _types = new(SymbolEqualityComparer.IncludeNullability);

private readonly Dictionary<IAssemblySymbol, string> _externAliases;

private EntityCache(Compilation compilation)
{
_externAliases = GetExternAliases(compilation);
}

[field: ThreadStatic] public static EntityCache? Current { get; private set; }

public static EntityCache GetOrCreate(Compilation compilation)
=> _caches.GetValue(compilation, static _ => new EntityCache());
=> _caches.GetValue(compilation, static c => new EntityCache(c));

public static Scope EnterScope(EntityCache cache)
{
Expand All @@ -48,6 +56,44 @@ public Type GetOrAddType(ITypeSymbol symbol, Func<ITypeSymbol, Type> factory)
public MethodParameter GetOrAddParameter(IParameterSymbol symbol, Func<IParameterSymbol, MethodParameter> factory)
=> _parameters.GetOrAdd(symbol, factory);

public bool HasExternAliases => _externAliases.Count > 0;

/// <summary>
/// Returns the extern alias through which types of <paramref name="assembly" /> must be qualified, or
/// <see langword="null" /> when they are reachable through <c>global::</c>.
/// </summary>
public string? GetExternAlias(IAssemblySymbol assembly)
=> _externAliases.TryGetValue(assembly, out string? alias) ? alias : null;

private static Dictionary<IAssemblySymbol, string> GetExternAliases(Compilation compilation)
{
Dictionary<IAssemblySymbol, string> externAliases = new(SymbolEqualityComparer.Default);
foreach (MetadataReference reference in compilation.References)
{
ImmutableArray<string> aliases = reference.Properties.Aliases;
if (!aliases.IsEmpty && !aliases.Contains("global") &&
compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
{
externAliases[assembly] = aliases[0];
}
}

if (externAliases.Count > 0)
{
foreach (MetadataReference reference in compilation.References)
{
ImmutableArray<string> aliases = reference.Properties.Aliases;
if ((aliases.IsEmpty || aliases.Contains("global")) &&
compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
{
externAliases.Remove(assembly);
}
}
}

return externAliases;
}

public readonly struct Scope : IDisposable
{
private static void ExitScope(EntityCache? previous) => Current = previous;
Expand Down
Loading
Loading