Skip to content

Commit 3bb80e7

Browse files
committed
Add polyfills for ForAttributeWithMetadataName
1 parent b3b60f4 commit 3bb80e7

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#if !ROSLYN_4_3
6+
7+
using System.Collections.Immutable;
8+
9+
namespace Microsoft.CodeAnalysis;
10+
11+
/// <summary>
12+
/// A type containing information for a match from <see cref="SyntaxValueProviderExtensions.ForAttributeWithMetadataName"/>.
13+
/// </summary>
14+
internal readonly struct GeneratorAttributeSyntaxContext
15+
{
16+
/// <summary>
17+
/// Creates a new <see cref="GeneratorAttributeSyntaxContext"/> instance with the specified parameters.
18+
/// </summary>
19+
/// <param name="targetNode">The syntax node the attribute is attached to.</param>
20+
/// <param name="targetSymbol">The symbol that the attribute is attached to.</param>
21+
/// <param name="semanticModel">Semantic model for the file that <see cref="TargetNode"/> is contained within.</param>
22+
/// <param name="attributes">The collection of matching attributes.</param>
23+
internal GeneratorAttributeSyntaxContext(
24+
SyntaxNode targetNode,
25+
ISymbol targetSymbol,
26+
SemanticModel semanticModel,
27+
ImmutableArray<AttributeData> attributes)
28+
{
29+
TargetNode = targetNode;
30+
TargetSymbol = targetSymbol;
31+
SemanticModel = semanticModel;
32+
Attributes = attributes;
33+
}
34+
35+
/// <summary>
36+
/// The syntax node the attribute is attached to. For example, with <c>[CLSCompliant] class C { }</c> this would the class declaration node.
37+
/// </summary>
38+
public SyntaxNode TargetNode { get; }
39+
40+
/// <summary>
41+
/// 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>.
42+
/// </summary>
43+
public ISymbol TargetSymbol { get; }
44+
45+
/// <summary>
46+
/// Semantic model for the file that <see cref="TargetNode"/> is contained within.
47+
/// </summary>
48+
public SemanticModel SemanticModel { get; }
49+
50+
/// <summary>
51+
/// <see cref="AttributeData"/>s for any matching attributes on <see cref="TargetSymbol"/>. Always non-empty. All
52+
/// these attributes will have an <see cref="AttributeData.AttributeClass"/> whose fully qualified name metadata
53+
/// name matches the name requested in <see cref="SyntaxValueProviderExtensions.ForAttributeWithMetadataName"/>.
54+
/// <para>
55+
/// To get the entire list of attributes, use <see cref="ISymbol.GetAttributes"/> on <see cref="TargetSymbol"/>.
56+
/// </para>
57+
/// </summary>
58+
public ImmutableArray<AttributeData> Attributes { get; }
59+
}
60+
61+
#endif
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#if !ROSLYN_4_3
6+
7+
using System.Threading;
8+
using System;
9+
using System.Collections.Immutable;
10+
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
11+
12+
namespace Microsoft.CodeAnalysis;
13+
14+
/// <summary>
15+
/// Extension methods for the <see cref="SyntaxValueProvider"/> type.
16+
/// </summary>
17+
internal static class SyntaxValueProviderExtensions
18+
{
19+
/// <summary>
20+
/// Creates an <see cref="IncrementalValuesProvider{T}"/> that can provide a transform over all <see
21+
/// cref="SyntaxNode"/>s if that node has an attribute on it that binds to a <see cref="INamedTypeSymbol"/> with the
22+
/// same fully-qualified metadata as the provided <paramref name="fullyQualifiedMetadataName"/>. <paramref
23+
/// name="fullyQualifiedMetadataName"/> should be the fully-qualified, metadata name of the attribute, including the
24+
/// <c>Attribute</c> suffix. For example <c>"System.CLSCompliantAttribute</c> for <see cref="CLSCompliantAttribute"/>.
25+
/// </summary>
26+
/// <param name="syntaxValueProvider">The source <see cref="SyntaxValueProvider"/> instance to use.</param>
27+
/// <param name="fullyQualifiedMetadataName">The fully qualified metadata name of the attribute to look for.</param>
28+
/// <param name="predicate">A function that determines if the given <see cref="SyntaxNode"/> attribute target (<see
29+
/// cref="GeneratorAttributeSyntaxContext.TargetNode"/>) should be transformed. Nodes that do not pass this
30+
/// predicate will not have their attributes looked at at all.</param>
31+
/// <param name="transform">A function that performs the transform. This will only be passed nodes that return <see
32+
/// langword="true"/> for <paramref name="predicate"/> and which have a matching <see cref="AttributeData"/> whose
33+
/// <see cref="AttributeData.AttributeClass"/> has the same fully qualified, metadata name as <paramref
34+
/// name="fullyQualifiedMetadataName"/>.</param>
35+
public static IncrementalValuesProvider<T> ForAttributeWithMetadataName<T>(
36+
this SyntaxValueProvider syntaxValueProvider,
37+
string fullyQualifiedMetadataName,
38+
Func<SyntaxNode, CancellationToken, bool> predicate,
39+
Func<GeneratorAttributeSyntaxContext, CancellationToken, T> transform)
40+
{
41+
string fullyQualifiedMetadataNameWithGlobalPrefix = $"global::{fullyQualifiedMetadataName}";
42+
43+
return
44+
syntaxValueProvider
45+
.CreateSyntaxProvider(
46+
predicate,
47+
(context, token) =>
48+
{
49+
ISymbol? symbol = context.SemanticModel.GetDeclaredSymbol(context.Node, token);
50+
51+
// If the syntax node doesn't have a declared symbol, just skip this node. This would be
52+
// the case for eg. lambda attributes, but those are not supported by the MVVM Toolkit.
53+
if (symbol is null)
54+
{
55+
return null;
56+
}
57+
58+
// Skip symbols without the target attribute
59+
if (!symbol.TryGetAttributeWithFullyQualifiedName(fullyQualifiedMetadataNameWithGlobalPrefix, out AttributeData? attributeData))
60+
{
61+
return null;
62+
}
63+
64+
// Create the GeneratorAttributeSyntaxContext value to pass to the input transform. The attributes array
65+
// will only ever have a single value, but that's fine with the attributes the various generators look for.
66+
GeneratorAttributeSyntaxContext syntaxContext = new(
67+
targetNode: context.Node,
68+
targetSymbol: symbol,
69+
semanticModel: context.SemanticModel,
70+
attributes: ImmutableArray.Create(attributeData));
71+
72+
return new Option<T>(transform(syntaxContext, token));
73+
})
74+
.Where(static item => item is not null)
75+
.Select(static (item, _) => item!.Value)!;
76+
}
77+
78+
/// <summary>
79+
/// A simple record to wrap a value that might be missing.
80+
/// </summary>
81+
/// <typeparam name="T">The type of values to wrap</typeparam>
82+
/// <param name="Value">The wrapped value, if it exists.</param>
83+
private sealed record Option<T>(T? Value);
84+
}
85+
86+
#endif

0 commit comments

Comments
 (0)