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
Next Next commit
Add fixers project and implement a fixer for FieldReferenceForObserva…
…blePropertyFieldAnalyzer

This adds a fixer for MVVMTK0034, that simply swaps the field reference for what will be generated for the property. It leaves the qualification intact, basically just doing a text swap. Because it's very a very simple fixer, the built-in batch fixall provider works just fine and does not need special handling.

While I implemented the fixer and added some tests, I did not actually set up all the msbuild goop for properly packaging this as part of the existing packages (as, frankly, the multi-targeting and msbuild setup in this repo is very complicated and I didn't feel like trying to grok exactly how to add it given the setup). The fixer assembly needs to be put next to the analyzer assemblies. This PR will be editable by maintainers, so feel free to implement this in-place in the PR itself.

Fixes #577.
  • Loading branch information
333fred committed Jan 22, 2023
commit 441abbbdbc8b8a2718a15f52141226c3a8d8fa51
22 changes: 22 additions & 0 deletions dotnet Community Toolkit.sln
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Exter
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431", "tests\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431.csproj", "{4FCD501C-1BB5-465C-AD19-356DAB6600C6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Mvvm.Fixers", "src\CommunityToolkit.Mvvm.Fixers\CommunityToolkit.Mvvm.Fixers.csproj", "{E79DCA2A-4C59-499F-85BD-F45215ED6B72}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -435,6 +437,26 @@ Global
{4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|x64.Build.0 = Release|Any CPU
{4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|x86.ActiveCfg = Release|Any CPU
{4FCD501C-1BB5-465C-AD19-356DAB6600C6}.Release|x86.Build.0 = Release|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Debug|ARM.ActiveCfg = Debug|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Debug|ARM.Build.0 = Debug|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Debug|ARM64.Build.0 = Debug|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Debug|x64.ActiveCfg = Debug|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Debug|x64.Build.0 = Debug|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Debug|x86.ActiveCfg = Debug|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Debug|x86.Build.0 = Debug|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|Any CPU.Build.0 = Release|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|ARM.ActiveCfg = Release|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|ARM.Build.0 = Release|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|ARM64.ActiveCfg = Release|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|ARM64.Build.0 = Release|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|x64.ActiveCfg = Release|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|x64.Build.0 = Release|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|x86.ActiveCfg = Release|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CommunityToolkit.Mvvm.SourceGenerators.Roslyn401\CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.SourceGenerators;
using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace CommunityToolkit.Mvvm.Fixers;

#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
[ExportCodeFixProvider(LanguageNames.CSharp)]
public class FieldReferenceForObservablePropertyFieldFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticDescriptors.FieldReferenceForObservablePropertyFieldId);

public override FixAllProvider? GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
Diagnostic diagnostic = context.Diagnostics[0];
Microsoft.CodeAnalysis.Text.TextSpan diagnosticSpan = diagnostic.Location.SourceSpan;

string? propertyName = diagnostic.Properties[FieldReferenceForObservablePropertyFieldAnalyzer.PropertyNameKey];
string? fieldName = diagnostic.Properties[FieldReferenceForObservablePropertyFieldAnalyzer.FieldNameKey];

if (propertyName == null || fieldName == null)
{
return;
}

SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
Debug.Assert(root != null);

IdentifierNameSyntax fieldReference = root!.FindNode(diagnosticSpan).DescendantNodesAndSelf().OfType<IdentifierNameSyntax>().FirstOrDefault(i => i.ToString() == fieldName);

if (fieldReference == null)
{
return;
}

context.RegisterCodeFix(
CodeAction.Create(
title: "Reference property",
createChangedDocument: c => UpdateReference(context.Document, fieldReference, propertyName, c),
equivalenceKey: "Reference property"),
diagnostic);

}

private async Task<Document> UpdateReference(Document document, IdentifierNameSyntax fieldReference, string propertyName, CancellationToken cancellationToken)
{
IdentifierNameSyntax propertyReference = SyntaxFactory.IdentifierName(propertyName);
SyntaxNode originalRoot = await fieldReference.SyntaxTree.GetRootAsync(cancellationToken);
SyntaxTree updatedTree = originalRoot.ReplaceNode(fieldReference, propertyReference).SyntaxTree;
return document.WithSyntaxRoot(await updatedTree.GetRootAsync(cancellationToken));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Input\Models\CommandInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Input\RelayCommandGenerator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Input\RelayCommandGenerator.Execute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)InternalsVisibleTo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messaging\IMessengerRegisterAllGenerator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messaging\IMessengerRegisterAllGenerator.Execute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messaging\Models\RecipientInfo.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ namespace CommunityToolkit.Mvvm.SourceGenerators;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class FieldReferenceForObservablePropertyFieldAnalyzer : DiagnosticAnalyzer
{
internal const string PropertyNameKey = "PropertyName";
internal const string FieldNameKey = "FieldName";

/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(FieldReferenceForObservablePropertyFieldWarning);

Expand Down Expand Up @@ -59,7 +62,13 @@ public override void Initialize(AnalysisContext context)
SymbolEqualityComparer.Default.Equals(attributeClass, attributeSymbol))
{
// Emit a warning to redirect users to access the generated property instead
context.ReportDiagnostic(Diagnostic.Create(FieldReferenceForObservablePropertyFieldWarning, context.Operation.Syntax.GetLocation(), fieldSymbol));
context.ReportDiagnostic(Diagnostic.Create(
FieldReferenceForObservablePropertyFieldWarning,
context.Operation.Syntax.GetLocation(),
ImmutableDictionary.Create<string, string?>()
.Add(PropertyNameKey, ObservablePropertyGenerator.Execute.GetGeneratedPropertyName(fieldSymbol))
.Add(FieldNameKey, fieldSymbol.Name),
fieldSymbol));

return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,14 +543,16 @@ internal static class DiagnosticDescriptors
"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).",
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0033");

public const string FieldReferenceForObservablePropertyFieldId = "MVVMTK0034";

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when a field with <c>[ObservableProperty]</c> is being directly referenced.
/// <para>
/// Format: <c>"The field {0} is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)"</c>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor FieldReferenceForObservablePropertyFieldWarning = new DiagnosticDescriptor(
id: "MVVMTK0034",
id: FieldReferenceForObservablePropertyFieldId,
title: "Direct field reference to [ObservableProperty] backing field",
messageFormat: "The field {0} is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)",
category: typeof(ObservablePropertyGenerator).FullName,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// 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.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("CommunityToolkit.Mvvm.Fixers, PublicKey=002400000480000094000000060200000024000052534131000400000100010041753af735ae6140c9508567666c51c6ab929806adb0d210694b30ab142a060237bc741f9682e7d8d4310364b4bba4ee89cc9d3d5ce7e5583587e8ea44dca09977996582875e71fb54fa7b170798d853d5d8010b07219633bdb761d01ac924da44576d6180cdceae537973982bb461c541541d58417a3794e34f45e6f2d129e2")]
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest" Version="1.1.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="1.1.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest" Version="1.1.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\CommunityToolkit.Mvvm.Fixers\CommunityToolkit.Mvvm.Fixers.csproj" />
<ProjectReference Include="..\..\src\CommunityToolkit.Mvvm\CommunityToolkit.Mvvm.csproj" />
<ProjectReference Include="..\..\src\CommunityToolkit.Mvvm.SourceGenerators.Roslyn401\CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.csproj" />
</ItemGroup>
Expand Down
Loading