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 'PropertyNameCollisionObservablePropertyAttributeAnalyzer'
  • Loading branch information
Sergio0694 committed Oct 28, 2024
commit ead5b1163bc0421516c58a5aeff910c9d919a29c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\TransitiveMembersGenerator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\TransitiveMembersGenerator.Execute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\AsyncVoidReturningRelayCommandMethodAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\PropertyNameCollisionObservablePropertyAttributeAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\InvalidTargetObservablePropertyAttributeAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\AutoPropertyWithFieldTargetedObservablePropertyAttributeAnalyzer.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,6 @@ public static bool TryGetInfo(
// Check for name collisions (only for fields)
if (fieldName == propertyName && memberSyntax.IsKind(SyntaxKind.FieldDeclaration))
{
builder.Add(
ObservablePropertyNameCollisionError,
memberSymbol,
memberSymbol.ContainingType,
memberSymbol.Name);

propertyInfo = null;
diagnostics = builder.ToImmutable();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public override void Initialize(AnalysisContext context)
}

// Ensure we do have the [ObservableProperty] attribute
if (!context.Symbol.TryGetAttributeWithType(observablePropertySymbol, out AttributeData? attributeDataobservablePropertyAttribute))
if (!context.Symbol.HasAttributeWithType(observablePropertySymbol))
{
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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.

using System.Collections.Immutable;
using System.Linq;
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 when a generated property from <c>[ObservableProperty]</c> would collide with the field name.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class PropertyNameCollisionObservablePropertyAttributeAnalyzer : DiagnosticAnalyzer
{
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(ObservablePropertyNameCollisionError);

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

context.RegisterCompilationStartAction(static context =>
{
// Get the symbol for [ObservableProperty]
if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is not INamedTypeSymbol observablePropertySymbol)
{
return;
}

context.RegisterSymbolAction(context =>
{
// Ensure we do have a valid field
if (context.Symbol is not IFieldSymbol fieldSymbol)
{
return;
}

// We only care if the field has [ObservableProperty]
if (!fieldSymbol.HasAttributeWithType(observablePropertySymbol))
{
return;
}

// Emit the diagnostic if there is a name collision
if (fieldSymbol.Name == ObservablePropertyGenerator.Execute.GetGeneratedPropertyName(fieldSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(
ObservablePropertyNameCollisionError,
fieldSymbol.Locations.FirstOrDefault(),
fieldSymbol.ContainingType,
fieldSymbol.Name));
}
}, SymbolKind.Field);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ private async Task GreetUserAsync(User user)
}

[TestMethod]
public void NameCollisionForGeneratedObservableProperty()
public async Task NameCollisionForGeneratedObservableProperty_PascalCaseField_Warns()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
Expand All @@ -674,12 +674,51 @@ namespace MyApp
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
private string Name;
private string {|MVVMTK0014:Name|};
}
}
""";

VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0014");
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<PropertyNameCollisionObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp8);
}

[TestMethod]
public async Task NameCollisionForGeneratedObservableProperty_CamelCaseField_DoesNotWarn()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;

namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
private string name;
}
}
""";

// Using C# 9 here because the generated code will emit [MemberNotNull] on the property setter, which requires C# 9
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<PropertyNameCollisionObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp9);
}

[TestMethod]
public async Task NameCollisionForGeneratedObservableProperty_PascalCaseProperty_DoesNotWarn()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;

namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
private string Name { get; set; }
}
}
""";

await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<PropertyNameCollisionObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp8);
}

[TestMethod]
Expand Down