Skip to content

Commit da6527e

Browse files
committed
Add 'InvalidPartialPropertyLevelObservablePropertyAttributeAnalyzer'
1 parent 1b51b6c commit da6527e

File tree

4 files changed

+154
-4
lines changed

4 files changed

+154
-4
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,7 @@ MVVMTK0047 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator
9292
MVVMTK0048 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0048
9393
MVVMTK0049 | CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0049
9494
MVVMTK0050 | CommunityToolkit.Mvvm.SourceGenerators.ObservableObjectGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0050
95-
MVVMTK0051 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Info | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0050
95+
MVVMTK0051 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Info | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0051
96+
MVVMTK0052 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0052
97+
MVVMTK0053 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0053
98+
MVVMTK0054 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0054

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\InvalidPartialPropertyLevelObservablePropertyAttributeAnalyzer.cs" />
4445
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer.cs" />
4546
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs" />
4647
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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_12_0_OR_GREATER
6+
7+
using System.Collections.Immutable;
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 an error whenever <c>[ObservableProperty]</c> is used on an invalid partial property declaration.
17+
/// </summary>
18+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
19+
public sealed class InvalidPartialPropertyLevelObservablePropertyAttributeAnalyzer : DiagnosticAnalyzer
20+
{
21+
/// <inheritdoc/>
22+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
23+
InvalidObservablePropertyDeclarationIsNotIncompletePartialDefinition,
24+
InvalidObservablePropertyDeclarationReturnsByRef,
25+
InvalidObservablePropertyDeclarationReturnsRefLikeType);
26+
27+
/// <inheritdoc/>
28+
public override void Initialize(AnalysisContext context)
29+
{
30+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
31+
context.EnableConcurrentExecution();
32+
33+
context.RegisterCompilationStartAction(static context =>
34+
{
35+
// Get the [ObservableProperty] and [GeneratedCode] symbols
36+
if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is not INamedTypeSymbol observablePropertySymbol ||
37+
context.Compilation.GetTypeByMetadataName("System.CodeDom.Compiler.GeneratedCodeAttribute") is not { } generatedCodeAttributeSymbol)
38+
{
39+
return;
40+
}
41+
42+
context.RegisterSymbolAction(context =>
43+
{
44+
// Ensure that we have some target property to analyze (also skip implementation parts)
45+
if (context.Symbol is not IPropertySymbol { PartialDefinitionPart: null } propertySymbol)
46+
{
47+
return;
48+
}
49+
50+
// If the property is not using [ObservableProperty], there's nothing to do
51+
if (!context.Symbol.TryGetAttributeWithType(observablePropertySymbol, out AttributeData? observablePropertyAttribute))
52+
{
53+
return;
54+
}
55+
56+
// Emit an error if the property is not a partial definition with no implementation...
57+
if (propertySymbol is not { IsPartialDefinition: true, PartialImplementationPart: null })
58+
{
59+
// ...But only if it wasn't actually generated by the [ObservableProperty] generator.
60+
bool isImplementationAllowed =
61+
propertySymbol is { IsPartialDefinition: true, PartialImplementationPart: IPropertySymbol implementationPartSymbol } &&
62+
implementationPartSymbol.TryGetAttributeWithType(generatedCodeAttributeSymbol, out AttributeData? generatedCodeAttributeData) &&
63+
generatedCodeAttributeData.TryGetConstructorArgument(0, out string? toolName) &&
64+
toolName == typeof(ObservablePropertyGenerator).FullName;
65+
66+
// Emit the diagnostic only for cases that were not valid generator outputs
67+
if (!isImplementationAllowed)
68+
{
69+
context.ReportDiagnostic(Diagnostic.Create(
70+
InvalidObservablePropertyDeclarationIsNotIncompletePartialDefinition,
71+
observablePropertyAttribute.GetLocation(),
72+
context.Symbol));
73+
}
74+
}
75+
76+
// Emit an error if the property returns a value by ref
77+
if (propertySymbol.ReturnsByRef || propertySymbol.ReturnsByRefReadonly)
78+
{
79+
context.ReportDiagnostic(Diagnostic.Create(
80+
InvalidObservablePropertyDeclarationReturnsByRef,
81+
observablePropertyAttribute.GetLocation(),
82+
context.Symbol));
83+
}
84+
85+
// Emit an error if the property type is a ref struct
86+
if (propertySymbol.Type.IsRefLikeType)
87+
{
88+
context.ReportDiagnostic(Diagnostic.Create(
89+
InvalidObservablePropertyDeclarationReturnsRefLikeType,
90+
observablePropertyAttribute.GetLocation(),
91+
context.Symbol));
92+
}
93+
}, SymbolKind.Property);
94+
});
95+
}
96+
}
97+
98+
#endif

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

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -718,17 +718,17 @@ internal static class DiagnosticDescriptors
718718
/// <summary>
719719
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when <c>[ObservableProperty]</c> is applied to a property with an invalid declaration.
720720
/// <para>
721-
/// Format: <c>"The property {0}.{1} cannot be used to generate an observable property, as its declaration is not valid (it must be a partial property with a getter and a setter that is not init-only)"</c>.
721+
/// Format: <c>"The property {0}.{1} cannot be used to generate an observable property, as its declaration is not valid (it must be an instance (non static) partial property with a getter and a setter that is not init-only)"</c>.
722722
/// </para>
723723
/// </summary>
724724
public static readonly DiagnosticDescriptor InvalidPropertyDeclarationForObservableProperty = new DiagnosticDescriptor(
725725
id: "MVVMTK0043",
726726
title: "Invalid property declaration for [ObservableProperty]",
727-
messageFormat: "The property {0}.{1} cannot be used to generate an observable property, as its declaration is not valid (it must be a partial property with a getter and a setter that is not init-only)",
727+
messageFormat: "The property {0}.{1} cannot be used to generate an observable property, as its declaration is not valid (it must be an instance (non static) partial property with a getter and a setter that is not init-only)",
728728
category: typeof(ObservablePropertyGenerator).FullName,
729729
defaultSeverity: DiagnosticSeverity.Error,
730730
isEnabledByDefault: true,
731-
description: "Properties annotated with [ObservableProperty] must be partial properties with a getter and a setter that is not init-only.",
731+
description: "Properties annotated with [ObservableProperty] must be instance (non static) partial properties with a getter and a setter that is not init-only.",
732732
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0043");
733733

734734
/// <summary>
@@ -859,4 +859,52 @@ internal static class DiagnosticDescriptors
859859
description: "This project producing one or more 'MVVMTK0045' warnings due to [ObservableProperty] being used on fields, which is not AOT compatible in WinRT scenarios, should set 'LangVersion' to 'preview' to enable partial properties and the associated code fixer (setting 'LangVersion=preview' is required to use [ObservableProperty] on partial properties and address these warnings).",
860860
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0051",
861861
customTags: WellKnownDiagnosticTags.CompilationEnd);
862+
863+
/// <summary>
864+
/// Gets a <see cref="DiagnosticDescriptor"/> for when <c>[ObservableProperty]</c> is used on a property that is not an incomplete partial definition.
865+
/// <para>
866+
/// Format: <c>"The property {0}.{1} is not an incomplete partial definition ([ObservableProperty] must be used on partial property definitions with no implementation part)"</c>.
867+
/// </para>
868+
/// </summary>
869+
public static readonly DiagnosticDescriptor InvalidObservablePropertyDeclarationIsNotIncompletePartialDefinition = new(
870+
id: "MVVMTK0052",
871+
title: "Using [ObservableProperty] on an invalid property declaration (not incomplete partial definition)",
872+
messageFormat: """The property {0}.{1} is not an incomplete partial definition ([ObservableProperty] must be used on partial property definitions with no implementation part)""",
873+
category: typeof(ObservablePropertyGenerator).FullName,
874+
defaultSeverity: DiagnosticSeverity.Error,
875+
isEnabledByDefault: true,
876+
description: "A property using [ObservableProperty] is not a partial implementation part ([ObservableProperty] must be used on partial property definitions with no implementation part).",
877+
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0052");
878+
879+
/// <summary>
880+
/// Gets a <see cref="DiagnosticDescriptor"/> for when <c>[ObservableProperty]</c> is used on a property that returns a ref value.
881+
/// <para>
882+
/// Format: <c>"The property {0}.{1} returns a ref value ([ObservableProperty] must be used on properties returning a type by value)"</c>.
883+
/// </para>
884+
/// </summary>
885+
public static readonly DiagnosticDescriptor InvalidObservablePropertyDeclarationReturnsByRef = new(
886+
id: "MVVMTK0053",
887+
title: "Using [ObservableProperty] on a property that returns byref",
888+
messageFormat: """The property {0}.{1} returns a ref value ([ObservableProperty] must be used on properties returning a type by value)""",
889+
category: typeof(ObservablePropertyGenerator).FullName,
890+
defaultSeverity: DiagnosticSeverity.Error,
891+
isEnabledByDefault: true,
892+
description: "A property using [ObservableProperty] returns a value by reference ([ObservableProperty] must be used on properties returning a type by value).",
893+
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0053");
894+
895+
/// <summary>
896+
/// Gets a <see cref="DiagnosticDescriptor"/> for when <c>[ObservableProperty]</c> is used on a property that returns a byref-like value.
897+
/// <para>
898+
/// Format: <c>"The property {0}.{1} returns a byref-like value ([ObservableProperty] must be used on properties of a non byref-like type)"</c>.
899+
/// </para>
900+
/// </summary>
901+
public static readonly DiagnosticDescriptor InvalidObservablePropertyDeclarationReturnsRefLikeType = new(
902+
id: "MVVMTK0054",
903+
title: "Using [ObservableProperty] on a property that returns byref-like",
904+
messageFormat: """The property {0}.{1} returns a byref-like value ([ObservableProperty] must be used on properties of a non byref-like type)""",
905+
category: typeof(ObservablePropertyGenerator).FullName,
906+
defaultSeverity: DiagnosticSeverity.Error,
907+
isEnabledByDefault: true,
908+
description: "A property using [ObservableProperty] returns a byref-like value ([ObservableProperty] must be used on properties of a non byref-like type).",
909+
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0054");
862910
}

0 commit comments

Comments
 (0)