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(); + } +}