From 50d8ff5b8e36251105b1922bf16356284bf83b36 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 12 Nov 2024 19:35:51 -0800 Subject: [PATCH 1/2] Generate new diagnostics on target symbols --- ...RoslynVersionForPartialPropertyAnalyzer.cs | 5 +- ...rvablePropertyOnPartialPropertyAnalyzer.cs | 5 +- ...leCustomPropertyWithBasesMemberAnalyzer.cs | 7 ++- ...pertyOnFieldsIsNotAotCompatibleAnalyzer.cs | 5 +- ...indableCustomPropertyCompatibleAnalyzer.cs | 5 +- ...RoslynVersionForPartialPropertyAnalyzer.cs | 4 +- .../Test_SourceGeneratorsDiagnostics.cs | 32 +++++------ ...lPropertyForObservablePropertyCodeFixer.cs | 56 +++++++++---------- .../Test_SourceGeneratorsDiagnostics.cs | 4 +- 9 files changed, 64 insertions(+), 59 deletions(-) diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UnsupportedRoslynVersionForPartialPropertyAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UnsupportedRoslynVersionForPartialPropertyAnalyzer.cs index dc0512312..f33a7ad4e 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UnsupportedRoslynVersionForPartialPropertyAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UnsupportedRoslynVersionForPartialPropertyAnalyzer.cs @@ -5,6 +5,7 @@ #if !ROSLYN_4_11_0_OR_GREATER using System.Collections.Immutable; +using System.Linq; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -44,11 +45,11 @@ public override void Initialize(AnalysisContext context) } // If the property has [ObservableProperty], emit an error in all cases - if (propertySymbol.TryGetAttributeWithType(observablePropertySymbol, out AttributeData? observablePropertyAttribute)) + if (propertySymbol.HasAttributeWithType(observablePropertySymbol)) { context.ReportDiagnostic(Diagnostic.Create( UnsupportedRoslynVersionForObservablePartialPropertySupport, - observablePropertyAttribute.GetLocation(), + propertySymbol.Locations.FirstOrDefault(), propertySymbol.ContainingType, propertySymbol)); } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnPartialPropertyAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnPartialPropertyAnalyzer.cs index caeeaaeff..a4f067e5b 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnPartialPropertyAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnPartialPropertyAnalyzer.cs @@ -5,6 +5,7 @@ #if ROSLYN_4_11_0_OR_GREATER using System.Collections.Immutable; +using System.Linq; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -57,7 +58,7 @@ public override void Initialize(AnalysisContext context) } // Check that we are in fact using [ObservableProperty] - if (!fieldSymbol.TryGetAttributeWithType(observablePropertySymbol, out AttributeData? observablePropertyAttribute)) + if (!fieldSymbol.HasAttributeWithType(observablePropertySymbol)) { return; } @@ -74,7 +75,7 @@ public override void Initialize(AnalysisContext context) // Emit the diagnostic for this field to suggest changing to a partial property instead context.ReportDiagnostic(Diagnostic.Create( UseObservablePropertyOnPartialProperty, - observablePropertyAttribute.GetLocation(), + fieldSymbol.Locations.FirstOrDefault(), ImmutableDictionary.Create() .Add(FieldReferenceForObservablePropertyFieldAnalyzer.FieldNameKey, fieldSymbol.Name) .Add(FieldReferenceForObservablePropertyFieldAnalyzer.PropertyNameKey, ObservablePropertyGenerator.Execute.GetGeneratedPropertyName(fieldSymbol)), diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs index 2f3c8bab2..891e84553 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -55,7 +56,7 @@ public override void Initialize(AnalysisContext context) } // We only care about it if it's using [GeneratedBindableCustomProperty] - if (!typeSymbol.TryGetAttributeWithType(generatedBindableCustomPropertySymbol, out AttributeData? generatedBindableCustomPropertyAttribute)) + if (!typeSymbol.HasAttributeWithType(generatedBindableCustomPropertySymbol)) { return; } @@ -65,7 +66,7 @@ public override void Initialize(AnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( WinRTGeneratedBindableCustomPropertyWithBaseObservablePropertyOnField, - generatedBindableCustomPropertyAttribute.GetLocation(), + typeSymbol.Locations.FirstOrDefault(), typeSymbol, fieldSymbol.ContainingType, fieldSymbol.Name)); @@ -76,7 +77,7 @@ public override void Initialize(AnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( WinRTGeneratedBindableCustomPropertyWithBaseRelayCommand, - generatedBindableCustomPropertyAttribute.GetLocation(), + typeSymbol.Locations.FirstOrDefault(), typeSymbol, methodSymbol)); } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer.cs index 7c619c120..f5d04d1bf 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer.cs @@ -5,6 +5,7 @@ #if ROSLYN_4_11_0_OR_GREATER using System.Collections.Immutable; +using System.Linq; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -50,11 +51,11 @@ public override void Initialize(AnalysisContext context) } // Emit a diagnostic if the field is using the [ObservableProperty] attribute - if (fieldSymbol.TryGetAttributeWithType(observablePropertySymbol, out AttributeData? observablePropertyAttribute)) + if (fieldSymbol.HasAttributeWithType(observablePropertySymbol)) { context.ReportDiagnostic(Diagnostic.Create( WinRTObservablePropertyOnFieldsIsNotAotCompatible, - observablePropertyAttribute.GetLocation(), + fieldSymbol.Locations.FirstOrDefault(), ImmutableDictionary.Create() .Add(FieldReferenceForObservablePropertyFieldAnalyzer.FieldNameKey, fieldSymbol.Name) .Add(FieldReferenceForObservablePropertyFieldAnalyzer.PropertyNameKey, ObservablePropertyGenerator.Execute.GetGeneratedPropertyName(fieldSymbol)), diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs index a7fe9f5b9..3c92eb9e3 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs @@ -3,6 +3,7 @@ // 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; @@ -49,7 +50,7 @@ public override void Initialize(AnalysisContext context) } // If the method is not using [RelayCommand], we can skip it - if (!methodSymbol.TryGetAttributeWithType(relayCommandSymbol, out AttributeData? relayCommandAttribute)) + if (!methodSymbol.HasAttributeWithType(relayCommandSymbol)) { return; } @@ -59,7 +60,7 @@ public override void Initialize(AnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatible, - relayCommandAttribute.GetLocation(), + methodSymbol.Locations.FirstOrDefault(), methodSymbol)); } }, SymbolKind.Method); diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_UnsupportedRoslynVersionForPartialPropertyAnalyzer.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_UnsupportedRoslynVersionForPartialPropertyAnalyzer.cs index b5cadd896..9b7ef7e40 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_UnsupportedRoslynVersionForPartialPropertyAnalyzer.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_UnsupportedRoslynVersionForPartialPropertyAnalyzer.cs @@ -21,8 +21,8 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0044:ObservableProperty|}] - public string Bar { get; set; } + [ObservableProperty] + public string {|MVVMTK0044:Bar|} { get; set; } } } """; diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_SourceGeneratorsDiagnostics.cs index ec33b277c..2fc686df1 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_SourceGeneratorsDiagnostics.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_SourceGeneratorsDiagnostics.cs @@ -78,8 +78,8 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0042:ObservableProperty|}] - private string name; + [ObservableProperty] + private string {|MVVMTK0042:name|}; } } """; @@ -371,8 +371,8 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0045:ObservableProperty|}] - private string name; + [ObservableProperty] + private string {|MVVMTK0045:name|}; } } """; @@ -415,8 +415,8 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0045:ObservableProperty|}] - private string name; + [ObservableProperty] + private string {|MVVMTK0045:name|}; } } """; @@ -437,8 +437,8 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0045:ObservableProperty|}] - private string name; + [ObservableProperty] + private string {|MVVMTK0045:name|}; } } """; @@ -459,8 +459,8 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0045:ObservableProperty|}] - private string name; + [ObservableProperty] + private string {|MVVMTK0045:name|}; } } @@ -486,8 +486,8 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0045:ObservableProperty|}] - private string name; + [ObservableProperty] + private string {|MVVMTK0045:name|}; } } @@ -585,8 +585,8 @@ public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_Ta namespace MyApp { - [{|MVVMTK0047:GeneratedBindableCustomProperty|}] - public partial class SampleViewModel : BaseViewModel + [GeneratedBindableCustomProperty] + public partial class {|MVVMTK0047:SampleViewModel|} : BaseViewModel { } @@ -620,8 +620,8 @@ public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_Ta namespace MyApp { - [{|MVVMTK0048:GeneratedBindableCustomProperty|}] - public partial class SampleViewModel : BaseViewModel + [GeneratedBindableCustomProperty] + public partial class {|MVVMTK0048:SampleViewModel|} : BaseViewModel { } diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs index 1236d5971..0ccc7032f 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs @@ -54,8 +54,8 @@ partial class C : ObservableObject test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(5,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "i"), + // /0/Test0.cs(6,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 17, 6, 18).WithArguments("C", "i"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -102,8 +102,8 @@ partial class C : ObservableObject test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(5,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "i"), + // /0/Test0.cs(7,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 17, 7, 18).WithArguments("C", "i"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -152,8 +152,8 @@ partial class C : ObservableObject test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(5,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "i"), + // /0/Test0.cs(8,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(8, 17, 8, 18).WithArguments("C", "i"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -204,8 +204,8 @@ partial class C : ObservableObject test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(6,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 6, 6, 24).WithArguments("C", "i"), + // /0/Test0.cs(9,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(9, 17, 9, 18).WithArguments("C", "i"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -275,8 +275,8 @@ public class TestAttribute(string text) : Attribute; test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(7,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 6, 7, 24).WithArguments("C", "i"), + // /0/Test0.cs(15,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(15, 17, 15, 18).WithArguments("C", "i"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -323,8 +323,8 @@ partial class C : ObservableObject test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(6,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 6, 6, 24).WithArguments("C", "i"), + // /0/Test0.cs(7,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 17, 7, 18).WithArguments("C", "i"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -373,8 +373,8 @@ partial class C : ObservableObject test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(7,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 6, 7, 24).WithArguments("C", "i"), + // /0/Test0.cs(8,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(8, 17, 8, 18).WithArguments("C", "i"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -427,8 +427,8 @@ partial class C : ObservableObject test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(9,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(9, 6, 9, 24).WithArguments("C", "i"), + // /0/Test0.cs(10,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(10, 17, 10, 18).WithArguments("C", "i"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -487,8 +487,8 @@ public void M() test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(5,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "i"), + // /0/Test0.cs(6,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 17, 6, 18).WithArguments("C", "i"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -533,8 +533,8 @@ partial class C : ObservableObject test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(5,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "i"), + // /0/Test0.cs(6,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 17, 6, 18).WithArguments("C", "i"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -584,8 +584,8 @@ partial class C : ObservableObject test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(6,6): info MVVMTK0042: The field C.items using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 6, 6, 24).WithArguments("C", "items"), + // /0/Test0.cs(7,33): info MVVMTK0042: The field C.items using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 33, 7, 38).WithArguments("C", "items"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -635,8 +635,8 @@ partial class C : ObservableObject test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(6,6): info MVVMTK0042: The field C.items using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 6, 6, 24).WithArguments("C", "items"), + // /0/Test0.cs(7,33): info MVVMTK0042: The field C.items using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 33, 7, 38).WithArguments("C", "items"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -685,8 +685,8 @@ partial class C : ObservableObject test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(5,6): info MVVMTK0042: The field C.foo using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "foo"), + // /0/Test0.cs(6,30): info MVVMTK0042: The field C.foo using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 30, 6, 33).WithArguments("C", "foo"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] @@ -755,8 +755,8 @@ public void M() test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); test.ExpectedDiagnostics.AddRange(new[] { - // /0/Test0.cs(5,6): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) - CSharpCodeFixVerifier.Diagnostic().WithSpan(5, 6, 5, 24).WithArguments("C", "i"), + // /0/Test0.cs(6,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well) + CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 17, 6, 18).WithArguments("C", "i"), }); test.FixedState.ExpectedDiagnostics.AddRange(new[] diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs index d9169c00c..8030231aa 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs @@ -2063,8 +2063,8 @@ namespace MyApp [GeneratedBindableCustomProperty] public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0046:RelayCommand|}] - private void DoStuff() + [RelayCommand] + private void {|MVVMTK0046:DoStuff|}() { } } From 4e6cf2bc217c1f12ece99c96d005e8a8ccbc6237 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 12 Nov 2024 22:51:37 -0800 Subject: [PATCH 2/2] Improve logic to find target symbol locations --- ...nsupportedCSharpLanguageVersionAnalyzer.cs | 3 +-- ...NotifyPropertyChangedAttributesAnalyzer.cs | 3 +-- ...leCustomPropertyWithBasesMemberAnalyzer.cs | 7 +++-- ...indableCustomPropertyCompatibleAnalyzer.cs | 5 ++-- .../Extensions/ISymbolExtensions.cs | 27 +++++++++++++++++++ 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs index 42312904b..cbfa6ecd1 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -76,7 +75,7 @@ public override void Initialize(AnalysisContext context) typeSymbols.TryGetValue(attributeName, out INamedTypeSymbol? attributeSymbol) && SymbolEqualityComparer.Default.Equals(attributeClass, attributeSymbol)) { - context.ReportDiagnostic(Diagnostic.Create(UnsupportedCSharpLanguageVersionError, context.Symbol.Locations.FirstOrDefault())); + context.ReportDiagnostic(Diagnostic.Create(UnsupportedCSharpLanguageVersionError, context.Symbol.GetLocationFromAttributeDataOrDefault(attribute))); // If we created a diagnostic for this symbol, we can stop. Even if there's multiple attributes, no need for repeated errors return; diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer.cs index b96c58443..2b0621dcb 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -79,7 +78,7 @@ public override void Initialize(AnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( GeneratorAttributeNamesToDiagnosticsMap[attributeClass.Name], - context.Symbol.Locations.FirstOrDefault(), + context.Symbol.GetLocationFromAttributeDataOrDefault(attribute), context.Symbol)); } } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs index 891e84553..f76cb4bc7 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -56,7 +55,7 @@ public override void Initialize(AnalysisContext context) } // We only care about it if it's using [GeneratedBindableCustomProperty] - if (!typeSymbol.HasAttributeWithType(generatedBindableCustomPropertySymbol)) + if (!typeSymbol.TryGetAttributeWithType(generatedBindableCustomPropertySymbol, out AttributeData? generatedBindableCustomPropertyAttribute)) { return; } @@ -66,7 +65,7 @@ public override void Initialize(AnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( WinRTGeneratedBindableCustomPropertyWithBaseObservablePropertyOnField, - typeSymbol.Locations.FirstOrDefault(), + typeSymbol.GetLocationFromAttributeDataOrDefault(generatedBindableCustomPropertyAttribute), typeSymbol, fieldSymbol.ContainingType, fieldSymbol.Name)); @@ -77,7 +76,7 @@ public override void Initialize(AnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( WinRTGeneratedBindableCustomPropertyWithBaseRelayCommand, - typeSymbol.Locations.FirstOrDefault(), + typeSymbol.GetLocationFromAttributeDataOrDefault(generatedBindableCustomPropertyAttribute), typeSymbol, methodSymbol)); } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs index 3c92eb9e3..7cbcf136a 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs @@ -3,7 +3,6 @@ // 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; @@ -50,7 +49,7 @@ public override void Initialize(AnalysisContext context) } // If the method is not using [RelayCommand], we can skip it - if (!methodSymbol.HasAttributeWithType(relayCommandSymbol)) + if (!methodSymbol.TryGetAttributeWithType(relayCommandSymbol, out AttributeData? relayCommandAttribute)) { return; } @@ -60,7 +59,7 @@ public override void Initialize(AnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatible, - methodSymbol.Locations.FirstOrDefault(), + methodSymbol.GetLocationFromAttributeDataOrDefault(relayCommandAttribute), methodSymbol)); } }, SymbolKind.Method); diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs index a825454fb..964e4dafb 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs @@ -175,4 +175,31 @@ public static bool CanBeAccessedFrom(this ISymbol symbol, IAssemblySymbol assemb accessibility == Accessibility.Public || accessibility == Accessibility.Internal && symbol.ContainingAssembly.GivesAccessTo(assembly); } + + /// + /// Gets the location of a given symbol that is in the same syntax tree of a specified attribute, or the first one. + /// + /// The input instance to check. + /// The target instance. + /// The best match. + public static Location? GetLocationFromAttributeDataOrDefault(this ISymbol symbol, AttributeData attributeData) + { + Location? firstLocation = null; + + // Get the syntax tree where the attribute application is located. We use + // it to try to find the symbol location that belongs to the same file. + SyntaxTree? attributeTree = attributeData.ApplicationSyntaxReference?.SyntaxTree; + + foreach (Location location in symbol.Locations) + { + if (location.SourceTree == attributeTree) + { + return location; + } + + firstLocation ??= location; + } + + return firstLocation; + } }