Skip to content

Commit 3393d30

Browse files
authored
Merge pull request #981 from CommunityToolkit/dev/winrt-class-generator-analyzers
Add more WinRT analyzers for class-level attributes
2 parents 1f0a2d3 + ae66c28 commit 3393d30

File tree

9 files changed

+354
-86
lines changed

9 files changed

+354
-86
lines changed

src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,5 @@ MVVMTK0045 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator
9090
MVVMTK0046 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0046
9191
MVVMTK0047 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0047
9292
MVVMTK0048 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0048
93+
MVVMTK0049 | CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0049
94+
MVVMTK0050 | CommunityToolkit.Mvvm.SourceGenerators.ObservableObjectGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0050

src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\TransitiveMembersGenerator.Execute.cs" />
4242
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\AsyncVoidReturningRelayCommandMethodAnalyzer.cs" />
4343
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\InvalidGeneratedPropertyObservablePropertyAttributeAnalyzer.cs" />
44+
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer.cs" />
4445
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs" />
4546
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs" />
4647
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer.cs" />

src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
namespace CommunityToolkit.Mvvm.SourceGenerators;
1313

1414
/// <summary>
15-
/// A source generator for message registration without relying on compiled LINQ expressions.
15+
/// A source generator for property validation without relying on compiled LINQ expressions.
1616
/// </summary>
1717
[Generator(LanguageNames.CSharp)]
1818
public sealed partial class ObservableValidatorValidateAllPropertiesGenerator : IIncrementalGenerator
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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 CommunityToolkit.Mvvm.SourceGenerators.Extensions;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
12+
13+
namespace CommunityToolkit.Mvvm.SourceGenerators;
14+
15+
/// <summary>
16+
/// A diagnostic analyzer that generates a warning when <c>[ObservableObject]</c> and <c>[INotifyPropertyChanged]</c> are used on a class in WinRT scenarios.
17+
/// </summary>
18+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
19+
public sealed class WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer : DiagnosticAnalyzer
20+
{
21+
/// <summary>
22+
/// The mapping of target attributes that will trigger the analyzer.
23+
/// </summary>
24+
private static readonly ImmutableDictionary<string, string> GeneratorAttributeNamesToFullyQualifiedNamesMap = ImmutableDictionary.CreateRange(new[]
25+
{
26+
new KeyValuePair<string, string>("ObservableObjectAttribute", "CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute"),
27+
new KeyValuePair<string, string>("INotifyPropertyChangedAttribute", "CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute"),
28+
});
29+
30+
/// <summary>
31+
/// The mapping of diagnostics for each target attribute.
32+
/// </summary>
33+
private static readonly ImmutableDictionary<string, DiagnosticDescriptor> GeneratorAttributeNamesToDiagnosticsMap = ImmutableDictionary.CreateRange(new[]
34+
{
35+
new KeyValuePair<string, DiagnosticDescriptor>("ObservableObjectAttribute", WinRTUsingObservableObjectAttribute),
36+
new KeyValuePair<string, DiagnosticDescriptor>("INotifyPropertyChangedAttribute", WinRTUsingINotifyPropertyChangedAttribute),
37+
});
38+
39+
/// <inheritdoc/>
40+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
41+
WinRTUsingObservableObjectAttribute,
42+
WinRTUsingINotifyPropertyChangedAttribute);
43+
44+
/// <inheritdoc/>
45+
public override void Initialize(AnalysisContext context)
46+
{
47+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
48+
context.EnableConcurrentExecution();
49+
50+
context.RegisterCompilationStartAction(static context =>
51+
{
52+
// This analyzer is only enabled when CsWinRT is in AOT mode
53+
if (!context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.IsCsWinRTAotOptimizerEnabled(context.Compilation))
54+
{
55+
return;
56+
}
57+
58+
// Try to get all necessary type symbols
59+
if (!context.Compilation.TryBuildNamedTypeSymbolMap(GeneratorAttributeNamesToFullyQualifiedNamesMap, out ImmutableDictionary<string, INamedTypeSymbol>? typeSymbols))
60+
{
61+
return;
62+
}
63+
64+
context.RegisterSymbolAction(context =>
65+
{
66+
// We're looking for class declarations that don't have any base type (same as the other analyzer for non-WinRT scenarios), but inverted for base types.
67+
// That is, we only want to warn in cases where the other analyzer would not warn. Otherwise, warnings from that one are already more than sufficient.
68+
if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class, IsRecord: false, IsStatic: false, IsImplicitlyDeclared: false, BaseType.SpecialType: not SpecialType.System_Object } classSymbol)
69+
{
70+
return;
71+
}
72+
73+
foreach (AttributeData attribute in context.Symbol.GetAttributes())
74+
{
75+
// Warn if either attribute is used, as it's not compatible with AOT
76+
if (attribute.AttributeClass is { Name: string attributeName } attributeClass &&
77+
typeSymbols.TryGetValue(attributeName, out INamedTypeSymbol? attributeSymbol) &&
78+
SymbolEqualityComparer.Default.Equals(attributeClass, attributeSymbol))
79+
{
80+
context.ReportDiagnostic(Diagnostic.Create(
81+
GeneratorAttributeNamesToDiagnosticsMap[attributeClass.Name],
82+
context.Symbol.Locations.FirstOrDefault(),
83+
context.Symbol));
84+
}
85+
}
86+
}, SymbolKind.NamedType);
87+
});
88+
}
89+
}

src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public override void Initialize(AnalysisContext context)
3838
return;
3939
}
4040

41-
// Get the symbol for [ObservableProperty], [RelayCommand] and [GeneratedBindableCustomProperty]
41+
// Get the symbols for [ObservableProperty], [RelayCommand] and [GeneratedBindableCustomProperty]
4242
if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is not INamedTypeSymbol observablePropertySymbol ||
4343
context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.Input.RelayCommandAttribute") is not INamedTypeSymbol relayCommandSymbol ||
4444
context.Compilation.GetTypeByMetadataName("WinRT.GeneratedBindableCustomPropertyAttribute") is not INamedTypeSymbol generatedBindableCustomPropertySymbol)

src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public override void Initialize(AnalysisContext context)
3333
return;
3434
}
3535

36-
// Get the symbol for [RelayCommand] and [GeneratedBindableCustomProperty]
36+
// Get the symbols for [RelayCommand] and [GeneratedBindableCustomProperty]
3737
if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.Input.RelayCommandAttribute") is not INamedTypeSymbol relayCommandSymbol ||
3838
context.Compilation.GetTypeByMetadataName("WinRT.GeneratedBindableCustomPropertyAttribute") is not INamedTypeSymbol generatedBindableCustomPropertySymbol)
3939
{

src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,4 +812,36 @@ internal static class DiagnosticDescriptors
812812
isEnabledByDefault: true,
813813
description: "Using [GeneratedBindableCustomProperty] on types that also use [RelayCommand] on any inherited methods is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator).",
814814
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0048");
815+
816+
/// <summary>
817+
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when <c>[INotifyPropertyChanged]</c> is used on a type in WinRT scenarios.
818+
/// <para>
819+
/// Format: <c>"The type {0} is using the [INotifyPropertyChanged] attribute, with is not AOT compatible in WinRT scenarios (such as UWP XAML and WinUI 3 apps), and it should derive from ObservableObject or manually implement INotifyPropertyChanged instead (as it allows the CsWinRT generators to correctly produce the necessary WinRT marshalling code)"</c>.
820+
/// </para>
821+
/// </summary>
822+
public static readonly DiagnosticDescriptor WinRTUsingINotifyPropertyChangedAttribute = new DiagnosticDescriptor(
823+
id: "MVVMTK0049",
824+
title: "Using [INotifyPropertyChanged] is not AOT compatible for WinRT",
825+
messageFormat: "The type {0} is using the [INotifyPropertyChanged] attribute, with is not AOT compatible in WinRT scenarios (such as UWP XAML and WinUI 3 apps), and it should derive from ObservableObject or manually implement INotifyPropertyChanged instead (as it allows the CsWinRT generators to correctly produce the necessary WinRT marshalling code)",
826+
category: typeof(INotifyPropertyChangedGenerator).FullName,
827+
defaultSeverity: DiagnosticSeverity.Warning,
828+
isEnabledByDefault: true,
829+
description: "Using the [INotifyPropertyChanged] attribute on types is not AOT compatible in WinRT scenarios (such as UWP XAML and WinUI 3 apps), and they should derive from ObservableObject or manually implement INotifyPropertyChanged instead (as it allows the CsWinRT generators to correctly produce the necessary WinRT marshalling code).",
830+
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0049");
831+
832+
/// <summary>
833+
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when <c>[ObservableObject]</c> is used on a type in WinRT scenarios.
834+
/// <para>
835+
/// Format: <c>"The type {0} is using the [ObservableObject] attribute, with is not AOT compatible in WinRT scenarios (such as UWP XAML and WinUI 3 apps), and it should derive from ObservableObject instead (as it allows the CsWinRT generators to correctly produce the necessary WinRT marshalling code)"</c>.
836+
/// </para>
837+
/// </summary>
838+
public static readonly DiagnosticDescriptor WinRTUsingObservableObjectAttribute = new DiagnosticDescriptor(
839+
id: "MVVMTK0050",
840+
title: "Using [ObservableObject] is not AOT compatible for WinRT",
841+
messageFormat: "The type {0} is using the [ObservableObject] attribute, with is not AOT compatible in WinRT scenarios (such as UWP XAML and WinUI 3 apps), and it should derive from ObservableObject instead (as it allows the CsWinRT generators to correctly produce the necessary WinRT marshalling code)",
842+
category: typeof(ObservableObjectGenerator).FullName,
843+
defaultSeverity: DiagnosticSeverity.Warning,
844+
isEnabledByDefault: true,
845+
description: "Using the [ObservableObject] attribute on types is not AOT compatible in WinRT scenarios (such as UWP XAML and WinUI 3 apps), and they should derive from ObservableObject instead (as it allows the CsWinRT generators to correctly produce the necessary WinRT marshalling code).",
846+
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0050");
815847
}

tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_SourceGeneratorsDiagnostics.cs

Lines changed: 0 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -503,89 +503,6 @@ await CSharpAnalyzerWithLanguageVersionTest<WinRTObservablePropertyOnFieldsIsNot
503503
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "true"), ("CsWinRTAotWarningLevel", 1)]);
504504
}
505505

506-
[TestMethod]
507-
public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_NotTargetingWindows_DoesNotWarn()
508-
{
509-
const string source = """
510-
using CommunityToolkit.Mvvm.ComponentModel;
511-
using CommunityToolkit.Mvvm.Input;
512-
513-
namespace MyApp
514-
{
515-
public partial class SampleViewModel : ObservableObject
516-
{
517-
[RelayCommand]
518-
private void DoStuff()
519-
{
520-
}
521-
}
522-
}
523-
""";
524-
525-
await CSharpAnalyzerWithLanguageVersionTest<WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer>.VerifyAnalyzerAsync(
526-
source,
527-
LanguageVersion.CSharp12,
528-
editorconfig: []);
529-
}
530-
531-
[TestMethod]
532-
public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_DoesNotWarn()
533-
{
534-
const string source = """
535-
using CommunityToolkit.Mvvm.ComponentModel;
536-
using CommunityToolkit.Mvvm.Input;
537-
538-
namespace MyApp
539-
{
540-
public partial class SampleViewModel : ObservableObject
541-
{
542-
[RelayCommand]
543-
private void DoStuff()
544-
{
545-
}
546-
}
547-
}
548-
""";
549-
550-
await CSharpAnalyzerWithLanguageVersionTest<WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer>.VerifyAnalyzerAsync(
551-
source,
552-
LanguageVersion.CSharp12,
553-
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
554-
}
555-
556-
[TestMethod]
557-
public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_Bindable_Warns()
558-
{
559-
const string source = """
560-
using System;
561-
using CommunityToolkit.Mvvm.ComponentModel;
562-
using CommunityToolkit.Mvvm.Input;
563-
using WinRT;
564-
565-
namespace MyApp
566-
{
567-
[GeneratedBindableCustomProperty]
568-
public partial class SampleViewModel : ObservableObject
569-
{
570-
[{|MVVMTK0046:RelayCommand|}]
571-
private void DoStuff()
572-
{
573-
}
574-
}
575-
}
576-
577-
namespace WinRT
578-
{
579-
public class GeneratedBindableCustomPropertyAttribute : Attribute;
580-
}
581-
""";
582-
583-
await CSharpAnalyzerWithLanguageVersionTest<WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer>.VerifyAnalyzerAsync(
584-
source,
585-
LanguageVersion.CSharp12,
586-
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
587-
}
588-
589506
[TestMethod]
590507
public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_NotTargetingWindows_DoesNotWarn()
591508
{

0 commit comments

Comments
 (0)