Skip to content

Feature Add OAPH From Observable method #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 10 additions & 0 deletions src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public partial class TestViewModel : ReactiveObject
public TestViewModel()
{
InitializeCommands();
InitializeOAPH();

Console.Out.WriteLine(Test1Command);
Console.Out.WriteLine(Test2Command);
Expand Down Expand Up @@ -97,6 +98,15 @@ public TestViewModel()
public IObservable<bool> CanExecuteTest1 => Observable.Return(true);
#pragma warning restore CA1822 // Mark members as static

/// <summary>
/// Gets observables as property test.
/// </summary>
/// <returns>
/// Observable of double.
/// </returns>
[ObservableAsProperty(PropertyName = "MyReadOnlyProperty")]
public IObservable<double> ObservableAsPropertyTest() => Observable.Return(10.0);

/// <summary>
/// Test1s this instance.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ RXUISG0013 | ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator | Error |
RXUISG0014 | ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator | Error | See https://www.reactiveui.net/errors/RXUISG0014
RXUISG0015 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https://www.reactiveui.net/errors/RXUISG0015
RXUISG0016 | ReactiveUI.SourceGenerators.PropertyToReactiveFieldCodeFixProvider | Info | See https://www.reactiveui.net/errors/RXUISG0016
RXUISG0017 | ReactiveUI.SourceGenerators.ObservableAsPropertyFromObservableGenerator | Error | See https://www.reactiveui.net/errors/RXUISG0017
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,17 @@ internal static class DiagnosticDescriptors
isEnabledByDefault: true,
description: "Used to create a Read Write INPC Reactive Property for ReactiveUI, annotated with [Reactive].",
helpLinkUri: "https://www.reactiveui.net/errors/RXUISG0016");

/// <summary>
/// The observable as property method has parameters error.
/// </summary>
public static readonly DiagnosticDescriptor ObservableAsPropertyMethodHasParametersError = new DiagnosticDescriptor(
id: "RXUISG0017",
title: "Invalid generated property declaration",
messageFormat: "The method {0} cannot be used to generate an observable As property, as it has parameters",
category: typeof(ObservableAsPropertyFromObservableGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "The method annotated with [ObservableAsProperty] cannot currently initialize methods with parameters.",
helpLinkUri: "https://www.reactiveui.net/errors/RXUISG0017");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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 static ReactiveUI.SourceGenerators.Diagnostics.SuppressionDescriptors;

namespace ReactiveUI.SourceGenerators.Diagnostics.Suppressions;

/// <summary>
/// ReactiveCommand Attribute With Field Or Property Target Diagnostic Suppressor.
/// </summary>
/// <seealso cref="DiagnosticSuppressor" />
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class OAPHMethodDoesNotNeedToBeStaticDiagnosticSuppressor : DiagnosticSuppressor
{
/// <inheritdoc/>
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(ReactiveCommandDoesNotAccessInstanceData);

/// <inheritdoc/>
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 a method declaration, which is the case we're looking for
if (syntaxNode is MethodDeclarationSyntax methodDeclaration)
{
var semanticModel = context.GetSemanticModel(syntaxNode.SyntaxTree);

// Get the method symbol from the first variable declaration
ISymbol? declaredSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration, context.CancellationToken);

// Check if the method is using [ObservableAsProperty], in which case we should suppress the warning
if (declaredSymbol is IMethodSymbol methodSymbol &&
semanticModel.Compilation.GetTypeByMetadataName("ReactiveUI.SourceGenerators.ObservableAsPropertyAttribute") is INamedTypeSymbol oaphSymbol &&
methodSymbol.HasAttributeWithType(oaphSymbol))
{
context.ReportSuppression(Suppression.Create(ReactiveCommandDoesNotAccessInstanceData, diagnostic));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,39 @@
using ReactiveUI.SourceGenerators.Extensions;
using static ReactiveUI.SourceGenerators.Diagnostics.SuppressionDescriptors;

namespace ReactiveUI.SourceGenerators.Diagnostics.Suppressions
namespace ReactiveUI.SourceGenerators.Diagnostics.Suppressions;

/// <summary>
/// ReactiveCommand Attribute With Field Or Property Target Diagnostic Suppressor.
/// </summary>
/// <seealso cref="DiagnosticSuppressor" />
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ReactiveCommandMethodDoesNotNeedToBeStaticDiagnosticSuppressor : DiagnosticSuppressor
{
/// <summary>
/// ReactiveCommand Attribute With Field Or Property Target Diagnostic Suppressor.
/// </summary>
/// <seealso cref="DiagnosticSuppressor" />
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ReactiveCommandMethodDoesNotNeedToBeStaticDiagnosticSuppressor : DiagnosticSuppressor
{
/// <inheritdoc/>
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(ReactiveCommandDoesNotAccessInstanceData);
/// <inheritdoc/>
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(ReactiveCommandDoesNotAccessInstanceData);

/// <inheritdoc/>
public override void ReportSuppressions(SuppressionAnalysisContext context)
/// <inheritdoc/>
public override void ReportSuppressions(SuppressionAnalysisContext context)
{
foreach (var diagnostic in context.ReportedDiagnostics)
{
foreach (var diagnostic in context.ReportedDiagnostics)
{
var syntaxNode = diagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(diagnostic.Location.SourceSpan);
var syntaxNode = diagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(diagnostic.Location.SourceSpan);

// Check that the target is a method declaration, which is the case we're looking for
if (syntaxNode is MethodDeclarationSyntax methodDeclaration)
{
var semanticModel = context.GetSemanticModel(syntaxNode.SyntaxTree);
// Check that the target is a method declaration, which is the case we're looking for
if (syntaxNode is MethodDeclarationSyntax methodDeclaration)
{
var semanticModel = context.GetSemanticModel(syntaxNode.SyntaxTree);

// Get the method symbol from the first variable declaration
ISymbol? declaredSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration, context.CancellationToken);
// Get the method symbol from the first variable declaration
ISymbol? declaredSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration, context.CancellationToken);

// Check if the method is using [ReactiveCommand], in which case we should suppress the warning
if (declaredSymbol is IMethodSymbol methodSymbol &&
semanticModel.Compilation.GetTypeByMetadataName("ReactiveUI.SourceGenerators.ReactiveCommandAttribute") is INamedTypeSymbol reactiveCommandSymbol &&
methodSymbol.HasAttributeWithType(reactiveCommandSymbol))
{
context.ReportSuppression(Suppression.Create(ReactiveCommandDoesNotAccessInstanceData, diagnostic));
}
// Check if the method is using [ReactiveCommand], in which case we should suppress the warning
if (declaredSymbol is IMethodSymbol methodSymbol &&
semanticModel.Compilation.GetTypeByMetadataName("ReactiveUI.SourceGenerators.ReactiveCommandAttribute") is INamedTypeSymbol reactiveCommandSymbol &&
methodSymbol.HasAttributeWithType(reactiveCommandSymbol))
{
context.ReportSuppression(Suppression.Create(ReactiveCommandDoesNotAccessInstanceData, diagnostic));
}
}
}
Expand Down
21 changes: 16 additions & 5 deletions src/ReactiveUI.SourceGenerators/Helpers/AttributeDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// 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;

namespace ReactiveUI.SourceGenerators.Helpers;

internal static class AttributeDefinitions
Expand Down Expand Up @@ -48,7 +50,7 @@ namespace ReactiveUI.SourceGenerators;
/// ReativeCommandAttribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "1.1.0.0")]
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveCommandGenerator", "1.1.0.0")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class ReactiveCommandAttribute : Attribute
{
Expand Down Expand Up @@ -81,7 +83,7 @@ namespace ReactiveUI.SourceGenerators;
/// ReactiveAttribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "1.1.0.0")]
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveGenerator", "1.1.0.0")]
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public sealed class ReactiveAttribute : Attribute;
#nullable restore
Expand All @@ -105,9 +107,18 @@ namespace ReactiveUI.SourceGenerators;
/// ReactivePropertyAttribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "1.1.0.0")]
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public sealed class ObservableAsPropertyAttribute : Attribute;
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator", "1.1.0.0")]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class ObservableAsPropertyAttribute : Attribute
{
/// <summary>
/// Gets the name of the property.
/// </summary>
/// <value>
/// The name of the property.
/// </value>
public string? PropertyName { get; init; }
}
#nullable restore
#pragma warning restore
""";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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 Microsoft.CodeAnalysis;
using ReactiveUI.SourceGenerators.Helpers;

namespace ReactiveUI.SourceGenerators.ObservableAsProperty.Models
{
internal record ObservableMethodInfo(
string MethodName,
ITypeSymbol MethodReturnType,
ITypeSymbol? ArgumentType,
string PropertyName,
EquatableArray<AttributeInfo> ForwardedPropertyAttributes)
{
public string GetObservableTypeText() => MethodReturnType is not INamedTypeSymbol typeSymbol
? string.Empty
: typeSymbol.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}
}
Loading
Loading