|
// 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.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions;
internal readonly struct GeneratorAttributeSyntaxContext
{
/// <summary>
/// The syntax node the attribute is attached to. For example, with <c>[CLSCompliant] class C { }</c> this would
/// the class declaration node.
/// </summary>
public SyntaxNode TargetNode { get; }
/// <summary>
/// The symbol that the attribute is attached to. For example, with <c>[CLSCompliant] class C { }</c> this would be
/// the <see cref="INamedTypeSymbol"/> for <c>"C"</c>.
/// </summary>
public ISymbol TargetSymbol { get; }
/// <summary>
/// Semantic model for the file that <see cref="TargetNode"/> is contained within.
/// </summary>
public SemanticModel SemanticModel { get; }
/// <summary>
/// <see cref="AttributeData"/>s for any matching attributes on <see cref="TargetSymbol"/>. Always non-empty. All
/// these attributes will have an <see cref="AttributeData.AttributeClass"/> whose fully qualified name metadata
/// name matches the name requested in <see cref="SyntaxValueProvider.ForAttributeWithMetadataName{T}"/>.
/// <para>
/// To get the entire list of attributes, use <see cref="ISymbol.GetAttributes"/> on <see cref="TargetSymbol"/>.
/// </para>
/// </summary>
public ImmutableArray<AttributeData> Attributes { get; }
internal GeneratorAttributeSyntaxContext(
SyntaxNode targetNode,
ISymbol targetSymbol,
SemanticModel semanticModel,
ImmutableArray<AttributeData> attributes)
{
TargetNode = targetNode;
TargetSymbol = targetSymbol;
SemanticModel = semanticModel;
Attributes = attributes;
}
}
internal static partial class SyntaxValueProviderExtensions
{
#if false
// Deviation from roslyn. We do not support attributes that are nested or generic. That's ok as that's not a
// scenario that ever arises in our generators.
private static readonly char[] s_nestedTypeNameSeparators = new char[] { '+' };
private static readonly SymbolDisplayFormat s_metadataDisplayFormat =
SymbolDisplayFormat.QualifiedNameArityFormat.AddCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UsePlusForNestedTypes);
#endif
/// <summary>
/// Creates an <see cref="IncrementalValuesProvider{T}"/> that can provide a transform over all <see
/// cref="SyntaxNode"/>s if that node has an attribute on it that binds to a <see cref="INamedTypeSymbol"/> with the
/// same fully-qualified metadata as the provided <paramref name="fullyQualifiedMetadataName"/>. <paramref
/// name="fullyQualifiedMetadataName"/> should be the fully-qualified, metadata name of the attribute, including the
/// <c>Attribute</c> suffix. For example <c>"System.CLSCompliantAttribute</c> for <see
/// cref="System.CLSCompliantAttribute"/>.
/// </summary>
/// <param name="predicate">A function that determines if the given <see cref="SyntaxNode"/> attribute target (<see
/// cref="GeneratorAttributeSyntaxContext.TargetNode"/>) should be transformed. Nodes that do not pass this
/// predicate will not have their attributes looked at at all.</param>
/// <param name="transform">A function that performs the transform. This will only be passed nodes that return <see
/// langword="true"/> for <paramref name="predicate"/> and which have a matching <see cref="AttributeData"/> whose
/// <see cref="AttributeData.AttributeClass"/> has the same fully qualified, metadata name as <paramref
/// name="fullyQualifiedMetadataName"/>.</param>
public static IncrementalValuesProvider<T> ForAttributeWithMetadataName<T>(
this SyntaxValueProvider @this,
IncrementalGeneratorInitializationContext context,
string fullyQualifiedMetadataName,
Func<SyntaxNode, CancellationToken, bool> predicate,
Func<GeneratorAttributeSyntaxContext, CancellationToken, T> transform)
{
#if false
// Deviation from roslyn. We do not support attributes that are nested or generic. That's ok as that's not a
// scenario that ever arises in our generators.
var metadataName = fullyQualifiedMetadataName.Contains('+')
? MetadataTypeName.FromFullName(fullyQualifiedMetadataName.Split(s_nestedTypeNameSeparators).Last())
: MetadataTypeName.FromFullName(fullyQualifiedMetadataName);
var nodesWithAttributesMatchingSimpleName = @this.ForAttributeWithSimpleName(context, metadataName.UnmangledTypeName, predicate);
#else
var lastDotIndex = fullyQualifiedMetadataName.LastIndexOf('.');
Debug.Assert(lastDotIndex > 0);
var unmangledTypeName = fullyQualifiedMetadataName.Substring(lastDotIndex + 1);
var nodesWithAttributesMatchingSimpleName = @this.ForAttributeWithSimpleName(context, unmangledTypeName, predicate);
#endif
var compilationAndGroupedNodesProvider = nodesWithAttributesMatchingSimpleName
.Combine(context.CompilationProvider)
/*.WithTrackingName("compilationAndGroupedNodes_ForAttributeWithMetadataName")*/;
var syntaxHelper = CSharpSyntaxHelper.Instance;
var finalProvider = compilationAndGroupedNodesProvider.SelectMany((tuple, cancellationToken) =>
{
var ((syntaxTree, syntaxNodes), compilation) = tuple;
Debug.Assert(syntaxNodes.All(n => n.SyntaxTree == syntaxTree));
using var result = new ValueListBuilder<T>(Span<T>.Empty);
if (!syntaxNodes.IsEmpty)
{
var semanticModel = compilation.GetSemanticModel(syntaxTree);
foreach (var targetNode in syntaxNodes)
{
cancellationToken.ThrowIfCancellationRequested();
var targetSymbol =
targetNode is ICompilationUnitSyntax compilationUnit ? semanticModel.Compilation.Assembly :
syntaxHelper.IsLambdaExpression(targetNode) ? semanticModel.GetSymbolInfo(targetNode, cancellationToken).Symbol :
semanticModel.GetDeclaredSymbol(targetNode, cancellationToken);
if (targetSymbol is null)
continue;
var attributes = getMatchingAttributes(targetNode, targetSymbol, fullyQualifiedMetadataName);
if (attributes.Length > 0)
{
result.Append(transform(
new GeneratorAttributeSyntaxContext(targetNode, targetSymbol, semanticModel, attributes),
cancellationToken));
}
}
}
return result.AsSpan().ToImmutableArray();
})/*.WithTrackingName("result_ForAttributeWithMetadataName")*/;
return finalProvider;
static ImmutableArray<AttributeData> getMatchingAttributes(
SyntaxNode attributeTarget,
ISymbol symbol,
string fullyQualifiedMetadataName)
{
var targetSyntaxTree = attributeTarget.SyntaxTree;
var result = new ValueListBuilder<AttributeData>(Span<AttributeData>.Empty);
try
{
addMatchingAttributes(ref result, symbol.GetAttributes());
addMatchingAttributes(ref result, (symbol as IMethodSymbol)?.GetReturnTypeAttributes());
if (symbol is IAssemblySymbol assemblySymbol)
{
foreach (var module in assemblySymbol.Modules)
addMatchingAttributes(ref result, module.GetAttributes());
}
return result.AsSpan().ToImmutableArray();
}
finally
{
result.Dispose();
}
void addMatchingAttributes(
ref ValueListBuilder<AttributeData> result,
ImmutableArray<AttributeData>? attributes)
{
if (!attributes.HasValue)
return;
foreach (var attribute in attributes.Value)
{
if (attribute.ApplicationSyntaxReference?.SyntaxTree == targetSyntaxTree &&
attribute.AttributeClass?.ToDisplayString(/*s_metadataDisplayFormat*/) == fullyQualifiedMetadataName)
{
result.Append(attribute);
}
}
}
}
}
}
|