Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add 'InvalidPartialPropertyLevelObservablePropertyAttributeAnalyzer'
  • Loading branch information
Sergio0694 committed Nov 29, 2024
commit da6527e481051580b7c22dc4fff346054bd759ac
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,7 @@ MVVMTK0047 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator
MVVMTK0048 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0048
MVVMTK0049 | CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0049
MVVMTK0050 | CommunityToolkit.Mvvm.SourceGenerators.ObservableObjectGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0050
MVVMTK0051 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Info | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0050
MVVMTK0051 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Info | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0051
MVVMTK0052 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0052
MVVMTK0053 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0053
MVVMTK0054 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0054
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\TransitiveMembersGenerator.Execute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\AsyncVoidReturningRelayCommandMethodAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\InvalidGeneratedPropertyObservablePropertyAttributeAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\InvalidPartialPropertyLevelObservablePropertyAttributeAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#if ROSLYN_4_12_0_OR_GREATER

using System.Collections.Immutable;
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;

namespace CommunityToolkit.Mvvm.SourceGenerators;

/// <summary>
/// A diagnostic analyzer that generates an error whenever <c>[ObservableProperty]</c> is used on an invalid partial property declaration.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class InvalidPartialPropertyLevelObservablePropertyAttributeAnalyzer : DiagnosticAnalyzer
{
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
InvalidObservablePropertyDeclarationIsNotIncompletePartialDefinition,
InvalidObservablePropertyDeclarationReturnsByRef,
InvalidObservablePropertyDeclarationReturnsRefLikeType);

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterCompilationStartAction(static context =>
{
// Get the [ObservableProperty] and [GeneratedCode] symbols
if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is not INamedTypeSymbol observablePropertySymbol ||
context.Compilation.GetTypeByMetadataName("System.CodeDom.Compiler.GeneratedCodeAttribute") is not { } generatedCodeAttributeSymbol)
{
return;
}

context.RegisterSymbolAction(context =>
{
// Ensure that we have some target property to analyze (also skip implementation parts)
if (context.Symbol is not IPropertySymbol { PartialDefinitionPart: null } propertySymbol)
{
return;
}

// If the property is not using [ObservableProperty], there's nothing to do
if (!context.Symbol.TryGetAttributeWithType(observablePropertySymbol, out AttributeData? observablePropertyAttribute))
{
return;
}

// Emit an error if the property is not a partial definition with no implementation...
if (propertySymbol is not { IsPartialDefinition: true, PartialImplementationPart: null })
{
// ...But only if it wasn't actually generated by the [ObservableProperty] generator.
bool isImplementationAllowed =
propertySymbol is { IsPartialDefinition: true, PartialImplementationPart: IPropertySymbol implementationPartSymbol } &&
implementationPartSymbol.TryGetAttributeWithType(generatedCodeAttributeSymbol, out AttributeData? generatedCodeAttributeData) &&
generatedCodeAttributeData.TryGetConstructorArgument(0, out string? toolName) &&
toolName == typeof(ObservablePropertyGenerator).FullName;

// Emit the diagnostic only for cases that were not valid generator outputs
if (!isImplementationAllowed)
{
context.ReportDiagnostic(Diagnostic.Create(
InvalidObservablePropertyDeclarationIsNotIncompletePartialDefinition,
observablePropertyAttribute.GetLocation(),
context.Symbol));
}
}

// Emit an error if the property returns a value by ref
if (propertySymbol.ReturnsByRef || propertySymbol.ReturnsByRefReadonly)
{
context.ReportDiagnostic(Diagnostic.Create(
InvalidObservablePropertyDeclarationReturnsByRef,
observablePropertyAttribute.GetLocation(),
context.Symbol));
}

// Emit an error if the property type is a ref struct
if (propertySymbol.Type.IsRefLikeType)
{
context.ReportDiagnostic(Diagnostic.Create(
InvalidObservablePropertyDeclarationReturnsRefLikeType,
observablePropertyAttribute.GetLocation(),
context.Symbol));
}
}, SymbolKind.Property);
});
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -718,17 +718,17 @@ internal static class DiagnosticDescriptors
/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when <c>[ObservableProperty]</c> is applied to a property with an invalid declaration.
/// <para>
/// 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>.
/// 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>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor InvalidPropertyDeclarationForObservableProperty = new DiagnosticDescriptor(
id: "MVVMTK0043",
title: "Invalid property declaration for [ObservableProperty]",
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)",
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)",
category: typeof(ObservablePropertyGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "Properties annotated with [ObservableProperty] must be partial properties with a getter and a setter that is not init-only.",
description: "Properties annotated with [ObservableProperty] must be instance (non static) partial properties with a getter and a setter that is not init-only.",
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0043");

/// <summary>
Expand Down Expand Up @@ -859,4 +859,52 @@ internal static class DiagnosticDescriptors
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).",
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0051",
customTags: WellKnownDiagnosticTags.CompilationEnd);

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for when <c>[ObservableProperty]</c> is used on a property that is not an incomplete partial definition.
/// <para>
/// 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>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor InvalidObservablePropertyDeclarationIsNotIncompletePartialDefinition = new(
id: "MVVMTK0052",
title: "Using [ObservableProperty] on an invalid property declaration (not incomplete partial definition)",
messageFormat: """The property {0}.{1} is not an incomplete partial definition ([ObservableProperty] must be used on partial property definitions with no implementation part)""",
category: typeof(ObservablePropertyGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "A property using [ObservableProperty] is not a partial implementation part ([ObservableProperty] must be used on partial property definitions with no implementation part).",
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0052");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for when <c>[ObservableProperty]</c> is used on a property that returns a ref value.
/// <para>
/// Format: <c>"The property {0}.{1} returns a ref value ([ObservableProperty] must be used on properties returning a type by value)"</c>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor InvalidObservablePropertyDeclarationReturnsByRef = new(
id: "MVVMTK0053",
title: "Using [ObservableProperty] on a property that returns byref",
messageFormat: """The property {0}.{1} returns a ref value ([ObservableProperty] must be used on properties returning a type by value)""",
category: typeof(ObservablePropertyGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "A property using [ObservableProperty] returns a value by reference ([ObservableProperty] must be used on properties returning a type by value).",
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0053");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for when <c>[ObservableProperty]</c> is used on a property that returns a byref-like value.
/// <para>
/// Format: <c>"The property {0}.{1} returns a byref-like value ([ObservableProperty] must be used on properties of a non byref-like type)"</c>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor InvalidObservablePropertyDeclarationReturnsRefLikeType = new(
id: "MVVMTK0054",
title: "Using [ObservableProperty] on a property that returns byref-like",
messageFormat: """The property {0}.{1} returns a byref-like value ([ObservableProperty] must be used on properties of a non byref-like type)""",
category: typeof(ObservablePropertyGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "A property using [ObservableProperty] returns a byref-like value ([ObservableProperty] must be used on properties of a non byref-like type).",
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0054");
}