Skip to content

Commit 76d995d

Browse files
authored
Feature Add OAPH From Observable method (#31)
* Feature Add OAPH From Observable method * Update to include using and diagnostic
1 parent d9d6309 commit 76d995d

9 files changed

+492
-33
lines changed

src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public partial class TestViewModel : ReactiveObject
3434
public TestViewModel()
3535
{
3636
InitializeCommands();
37+
InitializeOAPH();
3738

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

101+
/// <summary>
102+
/// Gets observables as property test.
103+
/// </summary>
104+
/// <returns>
105+
/// Observable of double.
106+
/// </returns>
107+
[ObservableAsProperty(PropertyName = "MyReadOnlyProperty")]
108+
public IObservable<double> ObservableAsPropertyTest() => Observable.Return(10.0);
109+
100110
/// <summary>
101111
/// Test1s this instance.
102112
/// </summary>

src/ReactiveUI.SourceGenerators/AnalyzerReleases.Shipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ RXUISG0013 | ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator | Error |
2323
RXUISG0014 | ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator | Error | See https://www.reactiveui.net/errors/RXUISG0014
2424
RXUISG0015 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https://www.reactiveui.net/errors/RXUISG0015
2525
RXUISG0016 | ReactiveUI.SourceGenerators.PropertyToReactiveFieldCodeFixProvider | Info | See https://www.reactiveui.net/errors/RXUISG0016
26+
RXUISG0017 | ReactiveUI.SourceGenerators.ObservableAsPropertyFromObservableGenerator | Error | See https://www.reactiveui.net/errors/RXUISG0017

src/ReactiveUI.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,4 +264,17 @@ internal static class DiagnosticDescriptors
264264
isEnabledByDefault: true,
265265
description: "Used to create a Read Write INPC Reactive Property for ReactiveUI, annotated with [Reactive].",
266266
helpLinkUri: "https://www.reactiveui.net/errors/RXUISG0016");
267+
268+
/// <summary>
269+
/// The observable as property method has parameters error.
270+
/// </summary>
271+
public static readonly DiagnosticDescriptor ObservableAsPropertyMethodHasParametersError = new DiagnosticDescriptor(
272+
id: "RXUISG0017",
273+
title: "Invalid generated property declaration",
274+
messageFormat: "The method {0} cannot be used to generate an observable As property, as it has parameters",
275+
category: typeof(ObservableAsPropertyFromObservableGenerator).FullName,
276+
defaultSeverity: DiagnosticSeverity.Error,
277+
isEnabledByDefault: true,
278+
description: "The method annotated with [ObservableAsProperty] cannot currently initialize methods with parameters.",
279+
helpLinkUri: "https://www.reactiveui.net/errors/RXUISG0017");
267280
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using System.Collections.Immutable;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
using ReactiveUI.SourceGenerators.Extensions;
12+
using static ReactiveUI.SourceGenerators.Diagnostics.SuppressionDescriptors;
13+
14+
namespace ReactiveUI.SourceGenerators.Diagnostics.Suppressions;
15+
16+
/// <summary>
17+
/// ReactiveCommand Attribute With Field Or Property Target Diagnostic Suppressor.
18+
/// </summary>
19+
/// <seealso cref="DiagnosticSuppressor" />
20+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
21+
public sealed class OAPHMethodDoesNotNeedToBeStaticDiagnosticSuppressor : DiagnosticSuppressor
22+
{
23+
/// <inheritdoc/>
24+
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(ReactiveCommandDoesNotAccessInstanceData);
25+
26+
/// <inheritdoc/>
27+
public override void ReportSuppressions(SuppressionAnalysisContext context)
28+
{
29+
foreach (var diagnostic in context.ReportedDiagnostics)
30+
{
31+
var syntaxNode = diagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(diagnostic.Location.SourceSpan);
32+
33+
// Check that the target is a method declaration, which is the case we're looking for
34+
if (syntaxNode is MethodDeclarationSyntax methodDeclaration)
35+
{
36+
var semanticModel = context.GetSemanticModel(syntaxNode.SyntaxTree);
37+
38+
// Get the method symbol from the first variable declaration
39+
ISymbol? declaredSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration, context.CancellationToken);
40+
41+
// Check if the method is using [ObservableAsProperty], in which case we should suppress the warning
42+
if (declaredSymbol is IMethodSymbol methodSymbol &&
43+
semanticModel.Compilation.GetTypeByMetadataName("ReactiveUI.SourceGenerators.ObservableAsPropertyAttribute") is INamedTypeSymbol oaphSymbol &&
44+
methodSymbol.HasAttributeWithType(oaphSymbol))
45+
{
46+
context.ReportSuppression(Suppression.Create(ReactiveCommandDoesNotAccessInstanceData, diagnostic));
47+
}
48+
}
49+
}
50+
}
51+
}

src/ReactiveUI.SourceGenerators/Diagnostics/Suppressions/ReactiveCommandMethodDoesNotNeedToBeStaticDiagnosticSuppressor.cs

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,39 @@
1111
using ReactiveUI.SourceGenerators.Extensions;
1212
using static ReactiveUI.SourceGenerators.Diagnostics.SuppressionDescriptors;
1313

14-
namespace ReactiveUI.SourceGenerators.Diagnostics.Suppressions
14+
namespace ReactiveUI.SourceGenerators.Diagnostics.Suppressions;
15+
16+
/// <summary>
17+
/// ReactiveCommand Attribute With Field Or Property Target Diagnostic Suppressor.
18+
/// </summary>
19+
/// <seealso cref="DiagnosticSuppressor" />
20+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
21+
public sealed class ReactiveCommandMethodDoesNotNeedToBeStaticDiagnosticSuppressor : DiagnosticSuppressor
1522
{
16-
/// <summary>
17-
/// ReactiveCommand Attribute With Field Or Property Target Diagnostic Suppressor.
18-
/// </summary>
19-
/// <seealso cref="DiagnosticSuppressor" />
20-
[DiagnosticAnalyzer(LanguageNames.CSharp)]
21-
public sealed class ReactiveCommandMethodDoesNotNeedToBeStaticDiagnosticSuppressor : DiagnosticSuppressor
22-
{
23-
/// <inheritdoc/>
24-
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(ReactiveCommandDoesNotAccessInstanceData);
23+
/// <inheritdoc/>
24+
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(ReactiveCommandDoesNotAccessInstanceData);
2525

26-
/// <inheritdoc/>
27-
public override void ReportSuppressions(SuppressionAnalysisContext context)
26+
/// <inheritdoc/>
27+
public override void ReportSuppressions(SuppressionAnalysisContext context)
28+
{
29+
foreach (var diagnostic in context.ReportedDiagnostics)
2830
{
29-
foreach (var diagnostic in context.ReportedDiagnostics)
30-
{
31-
var syntaxNode = diagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(diagnostic.Location.SourceSpan);
31+
var syntaxNode = diagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(diagnostic.Location.SourceSpan);
3232

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

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

41-
// Check if the method is using [ReactiveCommand], in which case we should suppress the warning
42-
if (declaredSymbol is IMethodSymbol methodSymbol &&
43-
semanticModel.Compilation.GetTypeByMetadataName("ReactiveUI.SourceGenerators.ReactiveCommandAttribute") is INamedTypeSymbol reactiveCommandSymbol &&
44-
methodSymbol.HasAttributeWithType(reactiveCommandSymbol))
45-
{
46-
context.ReportSuppression(Suppression.Create(ReactiveCommandDoesNotAccessInstanceData, diagnostic));
47-
}
41+
// Check if the method is using [ReactiveCommand], in which case we should suppress the warning
42+
if (declaredSymbol is IMethodSymbol methodSymbol &&
43+
semanticModel.Compilation.GetTypeByMetadataName("ReactiveUI.SourceGenerators.ReactiveCommandAttribute") is INamedTypeSymbol reactiveCommandSymbol &&
44+
methodSymbol.HasAttributeWithType(reactiveCommandSymbol))
45+
{
46+
context.ReportSuppression(Suppression.Create(ReactiveCommandDoesNotAccessInstanceData, diagnostic));
4847
}
4948
}
5049
}

src/ReactiveUI.SourceGenerators/Helpers/AttributeDefinitions.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// The .NET Foundation licenses this file to you under the MIT license.
44
// See the LICENSE file in the project root for full license information.
55

6+
using System;
7+
68
namespace ReactiveUI.SourceGenerators.Helpers;
79

810
internal static class AttributeDefinitions
@@ -48,7 +50,7 @@ namespace ReactiveUI.SourceGenerators;
4850
/// ReativeCommandAttribute.
4951
/// </summary>
5052
/// <seealso cref="Attribute" />
51-
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "1.1.0.0")]
53+
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveCommandGenerator", "1.1.0.0")]
5254
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
5355
public sealed class ReactiveCommandAttribute : Attribute
5456
{
@@ -81,7 +83,7 @@ namespace ReactiveUI.SourceGenerators;
8183
/// ReactiveAttribute.
8284
/// </summary>
8385
/// <seealso cref="Attribute" />
84-
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "1.1.0.0")]
86+
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveGenerator", "1.1.0.0")]
8587
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
8688
public sealed class ReactiveAttribute : Attribute;
8789
#nullable restore
@@ -105,9 +107,18 @@ namespace ReactiveUI.SourceGenerators;
105107
/// ReactivePropertyAttribute.
106108
/// </summary>
107109
/// <seealso cref="Attribute" />
108-
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "1.1.0.0")]
109-
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
110-
public sealed class ObservableAsPropertyAttribute : Attribute;
110+
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator", "1.1.0.0")]
111+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
112+
public sealed class ObservableAsPropertyAttribute : Attribute
113+
{
114+
/// <summary>
115+
/// Gets the name of the property.
116+
/// </summary>
117+
/// <value>
118+
/// The name of the property.
119+
/// </value>
120+
public string? PropertyName { get; init; }
121+
}
111122
#nullable restore
112123
#pragma warning restore
113124
""";
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using Microsoft.CodeAnalysis;
7+
using ReactiveUI.SourceGenerators.Helpers;
8+
9+
namespace ReactiveUI.SourceGenerators.ObservableAsProperty.Models
10+
{
11+
internal record ObservableMethodInfo(
12+
string MethodName,
13+
ITypeSymbol MethodReturnType,
14+
ITypeSymbol? ArgumentType,
15+
string PropertyName,
16+
EquatableArray<AttributeInfo> ForwardedPropertyAttributes)
17+
{
18+
public string GetObservableTypeText() => MethodReturnType is not INamedTypeSymbol typeSymbol
19+
? string.Empty
20+
: typeSymbol.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
21+
}
22+
}

0 commit comments

Comments
 (0)