|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
#pragma warning disable CA1716
namespace Microsoft.Gen.Shared;
#pragma warning restore CA1716
#if !SHARED_PROJECT
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
#endif
internal static class RoslynExtensions
{
/// <summary>
/// Gets a type by its metadata name to use for code analysis within a <see cref="Compilation"/>. This method
/// attempts to find the "best" symbol to use for code analysis, which is the symbol matching the first of the
/// following rules.
///
/// <list type="number">
/// <item><description>
/// If only one type with the given name is found within the compilation and its referenced assemblies, that
/// type is returned regardless of accessibility.
/// </description></item>
/// <item><description>
/// If the current <paramref name="compilation"/> defines the symbol, that symbol is returned.
/// </description></item>
/// <item><description>
/// If exactly one referenced assembly defines the symbol in a manner that makes it visible to the current
/// <paramref name="compilation"/>, that symbol is returned.
/// </description></item>
/// <item><description>
/// Otherwise, this method returns <see langword="null"/>.
/// </description></item>
/// </list>
/// </summary>
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
/// <param name="fullyQualifiedMetadataName">The fully qualified metadata type name to find.</param>
/// <returns>The symbol to use for code analysis; otherwise, <see langword="null"/>.</returns>
// Copied from: https://github.com/dotnet/roslyn/blob/af7b0ebe2b0ed5c335a928626c25620566372dd1/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs
public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName)
{
// Try to get the unique type with this name, ignoring accessibility
var type = compilation.GetTypeByMetadataName(fullyQualifiedMetadataName);
// Otherwise, try to get the unique type with this name originally defined in 'compilation'
type ??= compilation.Assembly?.GetTypeByMetadataName(fullyQualifiedMetadataName);
// Otherwise, try to get the unique accessible type with this name from a reference
if (type is null)
{
if (compilation.Assembly is null)
{
// nothing to do
return null;
}
foreach (var module in compilation.Assembly.Modules)
{
foreach (var referencedAssembly in module.ReferencedAssemblySymbols)
{
var currentType = referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName);
if (currentType is null)
{
continue;
}
switch (currentType.GetResultantVisibility())
{
case SymbolVisibility.Public:
case SymbolVisibility.Internal when referencedAssembly.GivesAccessTo(compilation.Assembly):
break;
default:
continue;
}
if (type is object)
{
// Multiple visible types with the same metadata name are present
return null;
}
type = currentType;
}
}
}
return type;
}
/// <summary>
/// A thin wrapper over <see cref="GetBestTypeByMetadataName(Compilation, string)"/>,
/// but taking the type itself rather than the fully qualified metadata type name.
/// </summary>
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
/// <param name="type">The type to find.</param>
public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, Type type) =>
type.IsArray || type.FullName is null
? throw new ArgumentException("The input type must correspond to a named type symbol.")
: GetBestTypeByMetadataName(compilation, type.FullName);
public static ImmutableArray<T> ToImmutableArray<T>(this ReadOnlySpan<T> span)
{
#pragma warning disable S109 // Magic numbers should not be used
switch (span.Length)
{
case 0:
return ImmutableArray<T>.Empty;
case 1:
return ImmutableArray.Create(span[0]);
case 2:
return ImmutableArray.Create(span[0], span[1]);
case 3:
return ImmutableArray.Create(span[0], span[1], span[2]);
case 4:
return ImmutableArray.Create(span[0], span[1], span[2], span[3]);
default:
var builder = ImmutableArray.CreateBuilder<T>(span.Length);
foreach (var item in span)
{
builder.Add(item);
}
return builder.MoveToImmutable();
}
#pragma warning restore S109 // Magic numbers should not be used
}
public static SimpleNameSyntax GetUnqualifiedName(this NameSyntax name)
=> name switch
{
AliasQualifiedNameSyntax alias => alias.Name,
QualifiedNameSyntax qualified => qualified.Right,
SimpleNameSyntax simple => simple,
_ => throw new InvalidOperationException("Unreachable"),
};
// Copied from: https://github.com/dotnet/roslyn/blob/af7b0ebe2b0ed5c335a928626c25620566372dd1/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs
private static SymbolVisibility GetResultantVisibility(this ISymbol symbol)
{
// Start by assuming it's visible.
SymbolVisibility visibility = SymbolVisibility.Public;
switch (symbol.Kind)
{
case SymbolKind.Alias:
// Aliases are uber private. They're only visible in the same file that they
// were declared in.
return SymbolVisibility.Private;
case SymbolKind.Parameter:
// Parameters are only as visible as their containing symbol
return GetResultantVisibility(symbol.ContainingSymbol);
case SymbolKind.TypeParameter:
// Type Parameters are private.
return SymbolVisibility.Private;
}
while (symbol is not null && symbol.Kind != SymbolKind.Namespace)
{
switch (symbol.DeclaredAccessibility)
{
// If we see anything private, then the symbol is private.
case Accessibility.NotApplicable:
case Accessibility.Private:
return SymbolVisibility.Private;
// If we see anything internal, then knock it down from public to
// internal.
case Accessibility.Internal:
case Accessibility.ProtectedAndInternal:
visibility = SymbolVisibility.Internal;
break;
// For anything else (Public, Protected, ProtectedOrInternal), the
// symbol stays at the level we've gotten so far.
}
symbol = symbol.ContainingSymbol;
}
return visibility;
}
// Copied from: https://github.com/dotnet/roslyn/blob/af7b0ebe2b0ed5c335a928626c25620566372dd1/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolVisibility.cs
#pragma warning disable CA1027 // Mark enums with FlagsAttribute
private enum SymbolVisibility
#pragma warning restore CA1027 // Mark enums with FlagsAttribute
{
Public = 0,
Internal = 1,
Private = 2,
Friend = Internal,
}
internal static bool HasAttributeSuffix(this string name, bool isCaseSensitive)
{
const string AttributeSuffix = "Attribute";
var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
return name.Length > AttributeSuffix.Length && name.EndsWith(AttributeSuffix, comparison);
}
}
|