Skip to content

Commit 97eb898

Browse files
committed
Add FieldReferenceForObservablePropertyFieldAnalyzer
1 parent aa4ff30 commit 97eb898

File tree

5 files changed

+70
-3
lines changed

5 files changed

+70
-3
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ MVVMTK0030 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator
4040
MVVMTK0031 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0031
4141
MVVMTK0032 | CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0032
4242
MVVMTK0033 | CommunityToolkit.Mvvm.SourceGenerators.ObservableObjectGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0033
43+
MVVMTK0034 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0034

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@
4848
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\ObservableValidatorValidateAllPropertiesGenerator.Execute.cs" />
4949
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\TransitiveMembersGenerator.cs" />
5050
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\TransitiveMembersGenerator.Execute.cs" />
51-
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\FieldWithOrphanedDependentObservablePropertyAttributesAnalyzer.cs" />
5251
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\ClassUsingAttributeInsteadOfInheritanceAnalyzer.cs" />
52+
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\FieldWithOrphanedDependentObservablePropertyAttributesAnalyzer.cs" />
53+
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\FieldReferenceForObservablePropertyFieldAnalyzer.cs" />
5354
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\UnsupportedCSharpLanguageVersionAnalyzer.cs" />
5455
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Suppressors\ObservablePropertyAttributeWithPropertyTargetDiagnosticSuppressor.cs" />
5556
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\DiagnosticDescriptors.cs" />

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ public override void Initialize(AnalysisContext context)
4646
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
4747
context.EnableConcurrentExecution();
4848

49-
// Defer the callback registration to when the compilation starts, so we can execute more
50-
// preliminary checks and skip registering any kind of symbol analysis at all if not needed.
5149
context.RegisterSymbolAction(static context =>
5250
{
5351
// We're looking for class declarations
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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.Immutable;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.Diagnostics;
8+
using Microsoft.CodeAnalysis.Operations;
9+
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
10+
11+
namespace CommunityToolkit.Mvvm.SourceGenerators;
12+
13+
/// <summary>
14+
/// A diagnostic analyzer that generates a warning when accessing a field instead of a generated observable property.
15+
/// </summary>
16+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
17+
public sealed class FieldReferenceForObservablePropertyFieldAnalyzer : DiagnosticAnalyzer
18+
{
19+
/// <inheritdoc/>
20+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(FieldReferenceForObservablePropertyFieldWarning);
21+
22+
/// <inheritdoc/>
23+
public override void Initialize(AnalysisContext context)
24+
{
25+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
26+
context.EnableConcurrentExecution();
27+
28+
context.RegisterOperationAction(static context =>
29+
{
30+
// We're only looking for references to fields that could potentially be observable properties
31+
if (context.Operation is not IFieldReferenceOperation { Field: IFieldSymbol { IsStatic: false, IsConst: false, IsImplicitlyDeclared: false, ContainingType: INamedTypeSymbol } fieldSymbol })
32+
{
33+
return;
34+
}
35+
36+
foreach (AttributeData attribute in fieldSymbol.GetAttributes())
37+
{
38+
// Look for the [ObservableProperty] attribute (there can only ever be one per field)
39+
if (attribute.AttributeClass is { Name: "ObservablePropertyAttribute" } attributeClass &&
40+
context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is INamedTypeSymbol attributeSymbol &&
41+
SymbolEqualityComparer.Default.Equals(attributeClass, attributeSymbol))
42+
{
43+
// Emit a warning to redirect users to access the generated property instead
44+
context.ReportDiagnostic(Diagnostic.Create(FieldReferenceForObservablePropertyFieldWarning, context.Operation.Syntax.GetLocation(), fieldSymbol.ContainingType, fieldSymbol));
45+
46+
return;
47+
}
48+
}
49+
}, OperationKind.FieldReference);
50+
}
51+
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,4 +543,20 @@ internal static class DiagnosticDescriptors
543543
"Classes with no base types should prefer inheriting from ObservableObject instead of using attributes to generate INotifyPropertyChanged code, as that will " +
544544
"reduce the binary size of the application (the attributes are only meant to support cases where the annotated types are already inheriting from a different type).",
545545
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0032");
546+
547+
/// <summary>
548+
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when a field with <c>[ObservableProperty]</c> is being directly referenced.
549+
/// <para>
550+
/// Format: <c>"The field {0}.{1} is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)"</c>.
551+
/// </para>
552+
/// </summary>
553+
public static readonly DiagnosticDescriptor FieldReferenceForObservablePropertyFieldWarning = new DiagnosticDescriptor(
554+
id: "MVVMTK0034",
555+
title: "Invalid task scheduler exception flow option usage",
556+
messageFormat: "The field {0}.{1} is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)",
557+
category: typeof(ObservablePropertyGenerator).FullName,
558+
defaultSeverity: DiagnosticSeverity.Warning,
559+
isEnabledByDefault: true,
560+
description: "Fields with [ObservableProperty] should not be directly referenced, and the generated properties should be used instead.",
561+
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0033");
546562
}

0 commit comments

Comments
 (0)