Skip to content

Commit 74fc2aa

Browse files
committed
Add draft UnsupportedCSharpLanguageVersionAnalyzer
1 parent 5be269e commit 74fc2aa

File tree

1 file changed

+77
-0
lines changed

1 file changed

+77
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
using System.Collections.Generic;
6+
using System.Collections.Immutable;
7+
using System.Linq;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.Diagnostics;
10+
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
11+
12+
namespace CommunityToolkit.Mvvm.SourceGenerators;
13+
14+
/// <summary>
15+
/// A diagnostic analyzer that generates an error whenever a source-generator attribute is used with not high enough C# version enabled.
16+
/// </summary>
17+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
18+
public sealed class UnsupportedCSharpLanguageVersionAnalyzer : DiagnosticAnalyzer
19+
{
20+
/// <summary>
21+
/// The mapping of target attributes that will trigger the analyzer.
22+
/// </summary>
23+
private static readonly ImmutableDictionary<string, string> GeneratorAttributeNamesToFullyQualifiedNamesMap = ImmutableDictionary.CreateRange(new[]
24+
{
25+
new KeyValuePair<string, string>("INotifyPropertyChangedAttribute", "CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute"),
26+
new KeyValuePair<string, string>("NotifyCanExecuteChangedForAttribute", "CommunityToolkit.Mvvm.ComponentModel.NotifyCanExecuteChangedForAttribute"),
27+
new KeyValuePair<string, string>("NotifyDataErrorInfoAttribute", "CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute"),
28+
new KeyValuePair<string, string>("NotifyPropertyChangedForAttribute", "CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedForAttribute"),
29+
new KeyValuePair<string, string>("NotifyPropertyChangedRecipientsAttribute", "CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute"),
30+
new KeyValuePair<string, string>("ObservableObjectAttribute", "CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute"),
31+
new KeyValuePair<string, string>("ObservablePropertyAttribute", "CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute"),
32+
new KeyValuePair<string, string>("ObservableRecipientAttribute", "CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute"),
33+
new KeyValuePair<string, string>("RelayCommandAttribute", "CommunityToolkit.Mvvm.Input.RelayCommandAttribute"),
34+
});
35+
36+
/// <inheritdoc/>
37+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(UnsupportedCSharpLanguageVersionError);
38+
39+
/// <inheritdoc/>
40+
public override void Initialize(AnalysisContext context)
41+
{
42+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
43+
context.EnableConcurrentExecution();
44+
45+
context.RegisterSymbolAction(static context =>
46+
{
47+
// The possible attribute targets are only fields, classes and methods
48+
if (context.Symbol is not IFieldSymbol or INamedTypeSymbol { TypeKind: TypeKind.Class, IsImplicitlyDeclared: false } or IMethodSymbol)
49+
{
50+
return;
51+
}
52+
53+
ImmutableArray<AttributeData> attributes = context.Symbol.GetAttributes();
54+
55+
// If the symbol has no attributes, there's nothing left to do
56+
if (attributes.IsEmpty)
57+
{
58+
return;
59+
}
60+
61+
foreach (AttributeData attribute in attributes)
62+
{
63+
// Go over each attribute on the target symbol, and check if the attribute type name is a candidate.
64+
// If it is, double check by actually resolving the symbol from the compilation and comparing against it.
65+
// This minimizes the calls to CompilationGetTypeByMetadataName(string) to only cases where it's almost
66+
// guaranteed we'll actually get a match. If we do have one, then we can emit the diagnostic for the symbol.
67+
if (attribute.AttributeClass is { Name: string attributeName } attributeClass &&
68+
GeneratorAttributeNamesToFullyQualifiedNamesMap.TryGetValue(attributeName, out string? fullyQualifiedAttributeName) &&
69+
context.Compilation.GetTypeByMetadataName(fullyQualifiedAttributeName) is INamedTypeSymbol attributeSymbol &&
70+
SymbolEqualityComparer.Default.Equals(attributeClass, attributeSymbol))
71+
{
72+
context.ReportDiagnostic(Diagnostic.Create(UnsupportedCSharpLanguageVersionError, context.Symbol.Locations.FirstOrDefault()));
73+
}
74+
}
75+
}, SymbolKind.Field, SymbolKind.NamedType, SymbolKind.Method);
76+
}
77+
}

0 commit comments

Comments
 (0)