diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs index 00f4841..8a4af22 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs @@ -236,6 +236,7 @@ public TestViewModel() /// The partial property test. /// [Reactive] + [field: JsonInclude] public partial string? PartialPropertyTest { get; set; } /// diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/Core/Extensions/ContextExtensions.cs b/src/ReactiveUI.SourceGenerators.Roslyn/Core/Extensions/ContextExtensions.cs index e8858dd..f3d2fd6 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/Core/Extensions/ContextExtensions.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/Core/Extensions/ContextExtensions.cs @@ -65,7 +65,7 @@ internal static void GetForwardedAttributes( // Only look for attribute lists explicitly targeting the (generated) property. Roslyn will normally emit a // CS0657 warning (invalid target), but that is automatically suppressed by a dedicated diagnostic suppressor // that recognizes uses of this target specifically to support [ObservableAsProperty]. - if (attributeList.Target?.Identifier is not SyntaxToken(SyntaxKind.PropertyKeyword)) + if (attributeList.Target?.Identifier is not SyntaxToken(SyntaxKind.PropertyKeyword) && attributeList.Target?.Identifier is not SyntaxToken(SyntaxKind.FieldKeyword)) { continue; } diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/Diagnostics/Suppressions/ReactiveAttributeWithFieldTargetDiagnosticSuppressor.cs b/src/ReactiveUI.SourceGenerators.Roslyn/Diagnostics/Suppressions/ReactiveAttributeWithFieldTargetDiagnosticSuppressor.cs new file mode 100644 index 0000000..6a956a7 --- /dev/null +++ b/src/ReactiveUI.SourceGenerators.Roslyn/Diagnostics/Suppressions/ReactiveAttributeWithFieldTargetDiagnosticSuppressor.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved. +// 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 full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using ReactiveUI.SourceGenerators.Extensions; +using ReactiveUI.SourceGenerators.Helpers; +using static ReactiveUI.SourceGenerators.Diagnostics.SuppressionDescriptors; + +namespace ReactiveUI.SourceGenerators.Diagnostics.Suppressions +{ + /// + /// ReactiveCommand Attribute With Field Or Property Target Diagnostic Suppressor. + /// + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class ReactiveAttributeWithFieldTargetDiagnosticSuppressor : DiagnosticSuppressor + { + /// + public override ImmutableArray SupportedSuppressions => ImmutableArray.Create(FieldOrPropertyAttributeListForReactiveProperty); + + /// + public override void ReportSuppressions(SuppressionAnalysisContext context) + { + foreach (var diagnostic in context.ReportedDiagnostics) + { + var syntaxNode = diagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(diagnostic.Location.SourceSpan); + + // Check that the target is effectively [field:] or [property:] over a method declaration, which is the case we're looking for + if (syntaxNode is AttributeTargetSpecifierSyntax { Parent.Parent: PropertyDeclarationSyntax propertyDeclaration, Identifier: SyntaxToken(SyntaxKind.FieldKeyword) }) + { + var semanticModel = context.GetSemanticModel(syntaxNode.SyntaxTree); + + // Get the method symbol from the first variable declaration + ISymbol? declaredSymbol = semanticModel.GetDeclaredSymbol(propertyDeclaration, context.CancellationToken); + + // Check if the method is using [Reactive], in which case we should suppress the warning + if (declaredSymbol is IPropertySymbol propertySymbol && + semanticModel.Compilation.GetTypeByMetadataName(AttributeDefinitions.ReactiveAttributeType) is INamedTypeSymbol reactiveSymbol && + propertySymbol.HasAttributeWithType(reactiveSymbol)) + { + context.ReportSuppression(Suppression.Create(FieldOrPropertyAttributeListForReactiveProperty, diagnostic)); + } + } + } + } + } +} diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs index 7180adf..4159344 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs @@ -86,7 +86,15 @@ public sealed partial class ReactiveGenerator out var isReferenceTypeOrUnconstraindTypeParameter, out var includeMemberNotNullOnSetAccessor); - ImmutableArray forwardedAttributesString = []; + var propertyDeclaration = (PropertyDeclarationSyntax)context.TargetNode; + + context.GetForwardedAttributes( + builder, + propertySymbol, + propertyDeclaration.AttributeLists, + token, + out var forwardedAttributesString); + token.ThrowIfCancellationRequested(); // Get the containing type info diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveUI.SourceGenerators.Roslyn.projitems b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveUI.SourceGenerators.Roslyn.projitems index 715e996..0b6ebe4 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveUI.SourceGenerators.Roslyn.projitems +++ b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveUI.SourceGenerators.Roslyn.projitems @@ -40,6 +40,7 @@ +