diff --git a/dotnet Community Toolkit.sln b/dotnet Community Toolkit.sln
index 8e1b61745..e8d004357 100644
--- a/dotnet Community Toolkit.sln
+++ b/dotnet Community Toolkit.sln
@@ -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.CodeFixers", "src\CommunityToolkit.Mvvm.CodeFixers\CommunityToolkit.Mvvm.CodeFixers.csproj", "{E79DCA2A-4C59-499F-85BD-F45215ED6B72}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -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
diff --git a/src/CommunityToolkit.HighPerformance/CommunityToolkit.HighPerformance.csproj b/src/CommunityToolkit.HighPerformance/CommunityToolkit.HighPerformance.csproj
index 1639b330d..bfc0e6bbe 100644
--- a/src/CommunityToolkit.HighPerformance/CommunityToolkit.HighPerformance.csproj
+++ b/src/CommunityToolkit.HighPerformance/CommunityToolkit.HighPerformance.csproj
@@ -36,7 +36,7 @@
-
+
diff --git a/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.csproj b/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.csproj
new file mode 100644
index 000000000..a3b8e5376
--- /dev/null
+++ b/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netstandard2.0
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CommunityToolkit.Mvvm.CodeFixers/FieldReferenceForObservablePropertyFieldFixer.cs b/src/CommunityToolkit.Mvvm.CodeFixers/FieldReferenceForObservablePropertyFieldFixer.cs
new file mode 100644
index 000000000..5a3a186db
--- /dev/null
+++ b/src/CommunityToolkit.Mvvm.CodeFixers/FieldReferenceForObservablePropertyFieldFixer.cs
@@ -0,0 +1,86 @@
+// 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.Composition;
+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;
+using Microsoft.CodeAnalysis.Text;
+
+namespace CommunityToolkit.Mvvm.CodeFixers;
+
+///
+/// A code fixer that automatically updates references to fields with [ObservableProperty] to reference the generated property instead.
+///
+[ExportCodeFixProvider(LanguageNames.CSharp)]
+[Shared]
+public sealed class FieldReferenceForObservablePropertyFieldCodeFixer : CodeFixProvider
+{
+ ///
+ public override ImmutableArray 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];
+ TextSpan diagnosticSpan = diagnostic.Location.SourceSpan;
+
+ // Retrieve the properties passed by the analyzer
+ if (diagnostic.Properties[FieldReferenceForObservablePropertyFieldAnalyzer.FieldNameKey] is not string fieldName ||
+ diagnostic.Properties[FieldReferenceForObservablePropertyFieldAnalyzer.PropertyNameKey] is not string propertyName)
+ {
+ return;
+ }
+
+ SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+
+ foreach (SyntaxNode syntaxNode in root!.FindNode(diagnosticSpan).DescendantNodesAndSelf())
+ {
+ // Find the first descendant node from the source of the diagnostic that is an identifier with the target name
+ if (syntaxNode is IdentifierNameSyntax { Identifier.Text: string identifierName } identifierNameSyntax &&
+ identifierName == fieldName)
+ {
+ // Register the code fix to update the field reference to use the generated property instead
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title: "Reference property",
+ createChangedDocument: token => UpdateReference(context.Document, identifierNameSyntax, propertyName, token),
+ equivalenceKey: "Reference property"),
+ diagnostic);
+
+ return;
+ }
+ }
+ }
+
+ ///
+ /// Applies the code fix to a target identifier and returns an updated document.
+ ///
+ /// The original document being fixed.
+ /// The corresponding to the field reference to update.
+ /// The name of the generated property.
+ /// The cancellation token for the operation.
+ /// An updated document with the applied code fix, and being replaced with a property reference.
+ private static async Task UpdateReference(Document document, IdentifierNameSyntax fieldReference, string propertyName, CancellationToken cancellationToken)
+ {
+ IdentifierNameSyntax propertyReference = SyntaxFactory.IdentifierName(propertyName);
+ SyntaxNode originalRoot = await fieldReference.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
+ SyntaxTree updatedTree = originalRoot.ReplaceNode(fieldReference, propertyReference).SyntaxTree;
+
+ return document.WithSyntaxRoot(await updatedTree.GetRootAsync(cancellationToken).ConfigureAwait(false));
+ }
+}
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.props b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.props
index c3130e015..ee2074322 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.props
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.props
@@ -35,4 +35,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/FieldReferenceForObservablePropertyFieldAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/FieldReferenceForObservablePropertyFieldAnalyzer.cs
index cda520e6f..dd7885ed2 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/FieldReferenceForObservablePropertyFieldAnalyzer.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/FieldReferenceForObservablePropertyFieldAnalyzer.cs
@@ -16,6 +16,16 @@ namespace CommunityToolkit.Mvvm.SourceGenerators;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class FieldReferenceForObservablePropertyFieldAnalyzer : DiagnosticAnalyzer
{
+ ///
+ /// The key for the name of the target field to update.
+ ///
+ internal const string FieldNameKey = "FieldName";
+
+ ///
+ /// The key for the name of the generated property to update a field reference to.
+ ///
+ internal const string PropertyNameKey = "PropertyName";
+
///
public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(FieldReferenceForObservablePropertyFieldWarning);
@@ -59,7 +69,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()
+ .Add(FieldNameKey, fieldSymbol.Name)
+ .Add(PropertyNameKey, ObservablePropertyGenerator.Execute.GetGeneratedPropertyName(fieldSymbol)),
+ fieldSymbol));
return;
}
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs
index d3c4fd4d1..05efb3e48 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs
@@ -14,6 +14,11 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Diagnostics;
///
internal static class DiagnosticDescriptors
{
+ ///
+ /// The diagnostic id for .
+ ///
+ public const string FieldReferenceForObservablePropertyFieldId = "MVVMTK0034";
+
///
/// Gets a indicating when a duplicate declaration of would happen.
///
@@ -550,7 +555,7 @@ internal static class DiagnosticDescriptors
///
///
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,
diff --git a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj
index 43adfb763..272f56c7f 100644
--- a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj
+++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj
@@ -34,10 +34,11 @@
-
+
+
@@ -73,9 +74,17 @@
+
+
\ No newline at end of file
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj
index 8bfbf73a2..947810b2e 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj
@@ -6,6 +6,8 @@
+
+
@@ -14,6 +16,7 @@
+
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/Test_FieldReferenceForObservablePropertyFieldFixer.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/Test_FieldReferenceForObservablePropertyFieldFixer.cs
new file mode 100644
index 000000000..dec18e08a
--- /dev/null
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/Test_FieldReferenceForObservablePropertyFieldFixer.cs
@@ -0,0 +1,286 @@
+// 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.Threading.Tasks;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using CSharpCodeFixTest = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest<
+ CommunityToolkit.Mvvm.SourceGenerators.FieldReferenceForObservablePropertyFieldAnalyzer,
+ CommunityToolkit.Mvvm.CodeFixers.FieldReferenceForObservablePropertyFieldCodeFixer,
+ Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier>;
+using CSharpCodeFixVerifier = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<
+ CommunityToolkit.Mvvm.SourceGenerators.FieldReferenceForObservablePropertyFieldAnalyzer,
+ CommunityToolkit.Mvvm.CodeFixers.FieldReferenceForObservablePropertyFieldCodeFixer,
+ Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier>;
+
+namespace CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests;
+
+[TestClass]
+public class Test_FieldReferenceForObservablePropertyFieldCodeFixer
+{
+ [TestMethod]
+ public async Task SimpleMemberAccess()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ class C : ObservableObject
+ {
+ [ObservableProperty]
+ private int i;
+
+ void M()
+ {
+ _ = i;
+ i = 1;
+ }
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ class C : ObservableObject
+ {
+ [ObservableProperty]
+ private int i;
+
+ void M()
+ {
+ _ = I;
+ I = 1;
+ }
+ }
+ """;
+
+ CSharpCodeFixTest test = new()
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net60
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(10,13): warning MVVMTK0034: The field C.i is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(10, 13, 10, 14).WithArguments("C.i"),
+
+ // /0/Test0.cs(11,9): warning MVVMTK0034: The field C.i is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(11, 9, 11, 10).WithArguments("C.i")
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(10,13): error CS0103: The name 'I' does not exist in the current context
+ DiagnosticResult.CompilerError("CS0103").WithSpan(10, 13, 10, 14).WithArguments("I"),
+
+ // /0/Test0.cs(11,9): error CS0103: The name 'I' does not exist in the current context
+ DiagnosticResult.CompilerError("CS0103").WithSpan(11, 9, 11, 10).WithArguments("I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task QualifiedMemberAccess()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ class C : ObservableObject
+ {
+ [ObservableProperty]
+ private int i;
+
+ void M()
+ {
+ _ = this.i;
+ this.i = 1;
+ }
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ class C : ObservableObject
+ {
+ [ObservableProperty]
+ private int i;
+
+ void M()
+ {
+ _ = this.I;
+ this.I = 1;
+ }
+ }
+ """;
+
+ CSharpCodeFixTest test = new()
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net60
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(10,13): warning MVVMTK0034: The field C.i is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(10, 13, 10, 19).WithArguments("C.i"),
+
+ // /0/Test0.cs(11,9): warning MVVMTK0034: The field C.i is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(11, 9, 11, 15).WithArguments("C.i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(10,18): error CS1061: 'C' does not contain a definition for 'I' and no accessible extension method 'I' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?)
+ DiagnosticResult.CompilerError("CS1061").WithSpan(10, 18, 10, 19).WithArguments("C", "I"),
+
+ // /0/Test0.cs(11,14): error CS1061: 'C' does not contain a definition for 'I' and no accessible extension method 'I' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?)
+ DiagnosticResult.CompilerError("CS1061").WithSpan(11, 14, 11, 15).WithArguments("C", "I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task ExternalQualifiedMemberAccess()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ class C : ObservableObject
+ {
+ [ObservableProperty]
+ public int i;
+ }
+
+ class D
+ {
+ void M()
+ {
+ var c = new C();
+ c.i = 1;
+ _ = c.i;
+ }
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ class C : ObservableObject
+ {
+ [ObservableProperty]
+ public int i;
+ }
+
+ class D
+ {
+ void M()
+ {
+ var c = new C();
+ c.I = 1;
+ _ = c.I;
+ }
+ }
+ """;
+
+ CSharpCodeFixTest test = new()
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net60
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(14,9): warning MVVMTK0034: The field C.i is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(14, 9, 14, 12).WithArguments("C.i"),
+
+ // /0/Test0.cs(15,13): warning MVVMTK0034: The field C.i is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(15, 13, 15, 16).WithArguments("C.i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(14,11): error CS1061: 'C' does not contain a definition for 'I' and no accessible extension method 'I' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?)
+ DiagnosticResult.CompilerError("CS1061").WithSpan(14, 11, 14, 12).WithArguments("C", "I"),
+
+ // /0/Test0.cs(15,15): error CS1061: 'C' does not contain a definition for 'I' and no accessible extension method 'I' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?)
+ DiagnosticResult.CompilerError("CS1061").WithSpan(15, 15, 15, 16).WithArguments("C", "I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task ExternalConditionalMemberAccess()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ class C : ObservableObject
+ {
+ [ObservableProperty]
+ public int i;
+ }
+
+ class D
+ {
+ void M()
+ {
+ var c = new C();
+ _ = c?.i;
+ }
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ class C : ObservableObject
+ {
+ [ObservableProperty]
+ public int i;
+ }
+
+ class D
+ {
+ void M()
+ {
+ var c = new C();
+ _ = c?.I;
+ }
+ }
+ """;
+
+ CSharpCodeFixTest test = new()
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net60
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(14,15): warning MVVMTK0034: The field C.i is annotated with [ObservableProperty] and should not be directly referenced (use the generated property instead)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(14, 15, 14, 17).WithArguments("C.i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(14,15): error CS1061: 'C' does not contain a definition for 'I' and no accessible extension method 'I' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?)
+ DiagnosticResult.CompilerError("CS1061").WithSpan(14, 15, 14, 17).WithArguments("C", "I"),
+ });
+
+ await test.RunAsync();
+ }
+}