|
| 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