diff --git a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.FromField#TestNs.TestVM.ObservableAsProperties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.FromField#TestVM.ObservableAsProperties.g.verified.cs
similarity index 72%
rename from src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.FromField#TestNs.TestVM.ObservableAsProperties.g.verified.cs
rename to src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.FromField#TestVM.ObservableAsProperties.g.verified.cs
index 6692b2a..b748b5e 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.FromField#TestNs.TestVM.ObservableAsProperties.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.FromField#TestVM.ObservableAsProperties.g.verified.cs
@@ -1,18 +1,20 @@
-//HintName: TestNs.TestVM.ObservableAsProperties.g.cs
+//HintName: TestVM.ObservableAsProperties.g.cs
//
#pragma warning disable
#nullable enable
namespace TestNs
{
///
- partial class TestVM
+ public partial class TestVM
{
- ///
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator", "1.1.0.0")]
+ ///
private readonly ReactiveUI.ObservableAsPropertyHelper _test1Helper;
+
///
- [global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator", "1.1.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public int Test1 { get => _test1 = _test1Helper?.Value ?? _test1; }
}
-}
\ No newline at end of file
+}
+#nullable restore
+#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.NamedFromField#TestNs.TestVM.ObservableAsProperties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.NamedFromField#TestVM.ObservableAsProperties.g.verified.cs
similarity index 72%
rename from src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.NamedFromField#TestNs.TestVM.ObservableAsProperties.g.verified.cs
rename to src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.NamedFromField#TestVM.ObservableAsProperties.g.verified.cs
index 5795a76..6d8ed38 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.NamedFromField#TestNs.TestVM.ObservableAsProperties.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.NamedFromField#TestVM.ObservableAsProperties.g.verified.cs
@@ -1,18 +1,20 @@
-//HintName: TestNs.TestVM.ObservableAsProperties.g.cs
+//HintName: TestVM.ObservableAsProperties.g.cs
//
#pragma warning disable
#nullable enable
namespace TestNs
{
///
- partial class TestVM
+ public partial class TestVM
{
- ///
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator", "1.1.0.0")]
+ ///
private readonly ReactiveUI.ObservableAsPropertyHelper _test2Helper;
+
///
- [global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator", "1.1.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public int Test2 { get => _test2 = _test2Helper?.Value ?? _test2; }
}
-}
\ No newline at end of file
+}
+#nullable restore
+#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.NonReadOnlyFromField#TestNs.TestVM.ObservableAsProperties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.NonReadOnlyFromField#TestVM.ObservableAsProperties.g.verified.cs
similarity index 72%
rename from src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.NonReadOnlyFromField#TestNs.TestVM.ObservableAsProperties.g.verified.cs
rename to src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.NonReadOnlyFromField#TestVM.ObservableAsProperties.g.verified.cs
index 5795a76..6d8ed38 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.NonReadOnlyFromField#TestNs.TestVM.ObservableAsProperties.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPGeneratorTests.NonReadOnlyFromField#TestVM.ObservableAsProperties.g.verified.cs
@@ -1,18 +1,20 @@
-//HintName: TestNs.TestVM.ObservableAsProperties.g.cs
+//HintName: TestVM.ObservableAsProperties.g.cs
//
#pragma warning disable
#nullable enable
namespace TestNs
{
///
- partial class TestVM
+ public partial class TestVM
{
- ///
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator", "1.1.0.0")]
+ ///
private readonly ReactiveUI.ObservableAsPropertyHelper _test2Helper;
+
///
- [global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator", "1.1.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public int Test2 { get => _test2 = _test2Helper?.Value ?? _test2; }
}
-}
\ No newline at end of file
+}
+#nullable restore
+#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
index 72e7230..7eb9857 100644
--- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
+++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
@@ -35,6 +35,9 @@ public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDis
[ObservableAsProperty(ReadOnly = false)]
private double? _test11Property = 11.1d;
+ [ObservableAsProperty(ReadOnly = false)]
+ private double _test13Property = 11.1d;
+
[property: Test(AParameter = "Test Input")]
[Reactive]
private double? _test12Property = 12.1d;
@@ -138,6 +141,10 @@ public TestViewModel()
Console.Out.WriteLine(_myReadOnlyNonNullProperty);
_testNonNullSubject.OnNext(default);
+ Console.Out.WriteLine(_test13Property);
+ Console.Out.WriteLine(Test13Property);
+ Console.Out.WriteLine(_test13PropertyHelper);
+
// expected value 0 as the _testNonNullSubject has been updated.
Console.Out.WriteLine(MyReadOnlyNonNullProperty);
Console.Out.WriteLine(_myReadOnlyNonNullProperty);
diff --git a/src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs b/src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs
index 9919884..77a069c 100644
--- a/src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs
+++ b/src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs
@@ -12,10 +12,11 @@ namespace ReactiveUI.SourceGenerators.Helpers;
internal static class AttributeDefinitions
{
public const string GeneratedCode = "global::System.CodeDom.Compiler.GeneratedCode";
- public const string ExcludeFromCodeCoverage = "global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage";
public const string Obsolete = "global::System.Obsolete";
public const string AccessModifierType = "ReactiveUI.SourceGenerators.AccessModifier";
+ public static string[] ExcludeFromCodeCoverage = ["[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"];
+ public static string ExcludeFromCodeCoverageString = "global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage";
public static string GetAccessModifierEnum() => $$"""
// Copyright (c) {{DateTime.Now.Year}} .NET Foundation and Contributors. All rights reserved.
diff --git a/src/ReactiveUI.SourceGenerators/IViewFor/IViewForGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/IViewFor/IViewForGenerator.Execute.cs
index ab1a640..03c8e1a 100644
--- a/src/ReactiveUI.SourceGenerators/IViewFor/IViewForGenerator.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators/IViewFor/IViewForGenerator.Execute.cs
@@ -189,7 +189,7 @@ namespace {{containingNamespace}}
/// Partial class for the {{containingTypeName}} which contains ReactiveUI IViewFor initialization.
///
{{forwardedAttributesString}}
- partial class {{containingTypeName}} : IViewFor<{{iviewForInfo.ViewModelTypeName}}>
+ {{containingClassVisibility}} partial {{containingType}} {{containingTypeName}} : IViewFor<{{iviewForInfo.ViewModelTypeName}}>
{
///
[Category("ReactiveUI")]
@@ -223,7 +223,7 @@ namespace {{containingNamespace}}
/// Partial class for the {{containingTypeName}} which contains ReactiveUI IViewFor initialization.
///
{{forwardedAttributesString}}
- public partial class {{containingTypeName}} : IViewFor<{{iviewForInfo.ViewModelTypeName}}>
+ {{containingClassVisibility}} partial {{containingType}} {{containingTypeName}} : IViewFor<{{iviewForInfo.ViewModelTypeName}}>
{
///
/// The view model dependency property.
@@ -282,7 +282,7 @@ namespace {{containingNamespace}}
/// Partial class for the {{containingTypeName}} which contains ReactiveUI IViewFor initialization.
///
{{forwardedAttributesString}}
- public partial class {{containingTypeName}} : IViewFor<{{iviewForInfo.ViewModelTypeName}}>
+ {{containingClassVisibility}} partial {{containingType}} {{containingTypeName}} : IViewFor<{{iviewForInfo.ViewModelTypeName}}>
{
public static readonly BindableProperty ViewModelProperty = BindableProperty.Create(nameof(ViewModel), typeof({{iviewForInfo.ViewModelTypeName}}), typeof(IViewFor<{{iviewForInfo.ViewModelTypeName}}>), default({{iviewForInfo.ViewModelTypeName}}), BindingMode.OneWay, propertyChanged: OnViewModelChanged);
diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.Generator.Execute.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.Generator.Execute.cs
deleted file mode 100644
index 88acd08..0000000
--- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.Generator.Execute.cs
+++ /dev/null
@@ -1,346 +0,0 @@
-// 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.Generic;
-using System.Collections.Immutable;
-using System.Diagnostics.CodeAnalysis;
-using System.Globalization;
-using System.Linq;
-using System.Threading;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using ReactiveUI.SourceGenerators.Extensions;
-using ReactiveUI.SourceGenerators.Helpers;
-using ReactiveUI.SourceGenerators.Models;
-using ReactiveUI.SourceGenerators.Reactive.Models;
-using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
-using static ReactiveUI.SourceGenerators.Diagnostics.DiagnosticDescriptors;
-
-namespace ReactiveUI.SourceGenerators;
-
-///
-/// ReactiveGenerator.
-///
-///
-public sealed partial class ObservableAsPropertyGenerator
-{
- ///
- /// A container for all the logic for .
- ///
- internal static partial class Execute
- {
- ///
- /// Gets the instance for the input field.
- ///
- /// The input instance to process.
- /// The generated instance for .
- internal static ImmutableArray GetPropertySyntax(PropertyInfo propertyInfo)
- {
- // Get the property type syntax
- TypeSyntax propertyType = IdentifierName(propertyInfo.TypeNameWithNullabilityAnnotations);
-
- string getterFieldIdentifierName;
-
- // In case the backing field is exactly named "value", we need to add the "this." prefix to ensure that comparisons and assignments
- // with it in the generated setter body are executed correctly and without conflicts with the implicit value parameter.
- if (propertyInfo.FieldName == "value")
- {
- // We only need to add "this." when referencing the field in the setter (getter and XML docs are not ambiguous)
- getterFieldIdentifierName = "value";
- }
- else if (SyntaxFacts.GetKeywordKind(propertyInfo.FieldName) != SyntaxKind.None ||
- SyntaxFacts.GetContextualKeywordKind(propertyInfo.FieldName) != SyntaxKind.None)
- {
- // If the identifier for the field could potentially be a keyword, we must escape it.
- // This usually happens if the annotated field was escaped as well (eg. "@event").
- // In this case, we must always escape the identifier, in all cases.
- getterFieldIdentifierName = $"@{propertyInfo.FieldName}";
- }
- else
- {
- getterFieldIdentifierName = propertyInfo.FieldName;
- }
-
- ArrowExpressionClauseSyntax getterArrowExpression;
-
- if (propertyInfo.TypeNameWithNullabilityAnnotations.EndsWith("?"))
- {
- getterArrowExpression = ArrowExpressionClause(ParseExpression($"{getterFieldIdentifierName} = ({getterFieldIdentifierName}Helper == null ? {getterFieldIdentifierName} : {getterFieldIdentifierName}Helper.Value)"));
- }
- else
- {
- getterArrowExpression = ArrowExpressionClause(ParseExpression($"{getterFieldIdentifierName} = {getterFieldIdentifierName}Helper?.Value ?? {getterFieldIdentifierName}"));
- }
-
- // Prepare the forwarded attributes, if any
- var forwardedAttributes =
- propertyInfo.ForwardedAttributes
- .Select(static a => AttributeList(SingletonSeparatedList(Attribute(ParseName(a.Substring(1, a.Length - 2))))))
- .ToImmutableArray();
-
- var modifiers = new List { Token(SyntaxKind.PrivateKeyword) };
- var helperTypeName = $"ReactiveUI.ObservableAsPropertyHelper<{propertyType}>?";
- if (propertyInfo.AccessModifier == "readonly")
- {
- helperTypeName = $"ReactiveUI.ObservableAsPropertyHelper<{propertyType}>";
- modifiers.Add(Token(SyntaxKind.ReadOnlyKeyword));
- }
-
- // Construct the generated property as follows:
- //
- // ///
- // [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
- // [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
- //
- // public
- // {
- // get => ;
- // }
- return
- ImmutableArray.Create(
- FieldDeclaration(VariableDeclaration(ParseTypeName(helperTypeName)))
- .AddDeclarationVariables(VariableDeclarator(getterFieldIdentifierName + "Helper"))
- .AddAttributeLists(
- AttributeList(SingletonSeparatedList(
- Attribute(IdentifierName(AttributeDefinitions.GeneratedCode))
- .AddArgumentListArguments(
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableAsPropertyGenerator).FullName))),
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableAsPropertyGenerator).Assembly.GetName().Version.ToString()))))))
- .WithOpenBracketToken(Token(TriviaList(Comment($"/// ")), SyntaxKind.OpenBracketToken, TriviaList())))
- .AddModifiers([.. modifiers]),
- PropertyDeclaration(propertyType, Identifier(propertyInfo.PropertyName))
- .AddAttributeLists(
- AttributeList(SingletonSeparatedList(
- Attribute(IdentifierName(AttributeDefinitions.GeneratedCode))
- .AddArgumentListArguments(
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableAsPropertyGenerator).FullName))),
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableAsPropertyGenerator).Assembly.GetName().Version.ToString()))))))
- .WithOpenBracketToken(Token(TriviaList(Comment($"/// ")), SyntaxKind.OpenBracketToken, TriviaList())),
- AttributeList(SingletonSeparatedList(Attribute(IdentifierName(AttributeDefinitions.ExcludeFromCodeCoverage)))))
- .AddAttributeLists([.. forwardedAttributes])
- .AddModifiers(Token(SyntaxKind.PublicKeyword))
- .AddAccessorListAccessors(
- AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
- .WithExpressionBody(getterArrowExpression)
- .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))));
- }
-
- internal static bool GetFieldInfoFromClass(
- FieldDeclarationSyntax fieldSyntax,
- IFieldSymbol fieldSymbol,
- SemanticModel semanticModel,
- bool? isReadonly,
- CancellationToken token,
- [NotNullWhen(true)] out PropertyInfo? propertyInfo,
- out ImmutableArray diagnostics)
- {
- using var builder = ImmutableArrayBuilder.Rent();
-
- // Validate the target type
- if (!IsTargetTypeValid(fieldSymbol))
- {
- builder.Add(
- InvalidObservableAsPropertyError,
- fieldSymbol,
- fieldSymbol.ContainingType,
- fieldSymbol.Name);
-
- propertyInfo = null;
- diagnostics = builder.ToImmutable();
-
- return false;
- }
-
- // Get the property type and name
- var typeNameWithNullabilityAnnotations = fieldSymbol.Type.GetFullyQualifiedNameWithNullabilityAnnotations();
- var fieldName = fieldSymbol.Name;
- var propertyName = GetGeneratedPropertyName(fieldSymbol);
- var initializer = fieldSyntax.Declaration.Variables.FirstOrDefault()?.Initializer?.ToFullString();
-
- // Check for name collisions
- if (fieldName == propertyName)
- {
- builder.Add(
- ReactivePropertyNameCollisionError,
- fieldSymbol,
- fieldSymbol.ContainingType,
- fieldSymbol.Name);
-
- propertyInfo = null;
- diagnostics = builder.ToImmutable();
-
- // If the generated property would collide, skip generating it entirely. This makes sure that
- // users only get the helpful diagnostic about the collision, and not the normal compiler error
- // about a definition for "Property" already existing on the target type, which might be confusing.
- return false;
- }
-
- token.ThrowIfCancellationRequested();
-
- using var forwardedAttributes = ImmutableArrayBuilder.Rent();
-
- // Gather attributes info
- foreach (var attributeData in fieldSymbol.GetAttributes())
- {
- token.ThrowIfCancellationRequested();
-
- // Track the current attribute for forwarding if it is a validation attribute
- if (attributeData.AttributeClass?.InheritsFromFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.ValidationAttribute") == true)
- {
- forwardedAttributes.Add(AttributeInfo.Create(attributeData));
- }
-
- // Track the current attribute for forwarding if it is a Json Serialization attribute
- if (attributeData.AttributeClass?.InheritsFromFullyQualifiedMetadataName("System.Text.Json.Serialization.JsonAttribute") == true)
- {
- forwardedAttributes.Add(AttributeInfo.Create(attributeData));
- }
-
- // Also track the current attribute for forwarding if it is of any of the following types:
- if (attributeData.AttributeClass?.HasOrInheritsFromFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.UIHintAttribute") == true ||
- attributeData.AttributeClass?.HasOrInheritsFromFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.ScaffoldColumnAttribute") == true ||
- attributeData.AttributeClass?.HasFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.DisplayAttribute") == true ||
- attributeData.AttributeClass?.HasFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.EditableAttribute") == true ||
- attributeData.AttributeClass?.HasFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.KeyAttribute") == true ||
- attributeData.AttributeClass?.HasFullyQualifiedMetadataName("System.Runtime.Serialization.DataMemberAttribute") == true ||
- attributeData.AttributeClass?.HasFullyQualifiedMetadataName("System.Runtime.Serialization.IgnoreDataMemberAttribute") == true)
- {
- forwardedAttributes.Add(AttributeInfo.Create(attributeData));
- }
- }
-
- token.ThrowIfCancellationRequested();
-
- // Gather explicit forwarded attributes info
- foreach (var attributeList in fieldSyntax.AttributeLists)
- {
- // 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))
- {
- continue;
- }
-
- token.ThrowIfCancellationRequested();
-
- foreach (var attribute in attributeList.Attributes)
- {
- // Roslyn ignores attributes in an attribute list with an invalid target, so we can't get the AttributeData as usual.
- // To reconstruct all necessary attribute info to generate the serialized model, we use the following steps:
- // - We try to get the attribute symbol from the semantic model, for the current attribute syntax. In case this is not
- // available (in theory it shouldn't, but it can be), we try to get it from the candidate symbols list for the node.
- // If there are no candidates or more than one, we just issue a diagnostic and stop processing the current attribute.
- // The returned symbols might be method symbols (constructor attribute) so in that case we can get the declaring type.
- // - We then go over each attribute argument expression and get the operation for it. This will still be available even
- // though the rest of the attribute is not validated nor bound at all. From the operation we can still retrieve all
- // constant values to build the AttributeInfo model. After all, attributes only support constant values, typeof(T)
- // expressions, or arrays of either these two types, or of other arrays with the same rules, recursively.
- // - From the syntax, we can also determine the identifier names for named attribute arguments, if any.
- // There is no need to validate anything here: the attribute will be forwarded as is, and then Roslyn will validate on the
- // generated property. Users will get the same validation they'd have had directly over the field. The only drawback is the
- // lack of IntelliSense when constructing attributes over the field, but this is the best we can do from this end anyway.
- if (!semanticModel.GetSymbolInfo(attribute, token).TryGetAttributeTypeSymbol(out var attributeTypeSymbol))
- {
- builder.Add(
- InvalidPropertyTargetedAttributeOnObservableAsPropertyField,
- attribute,
- fieldSymbol,
- attribute.Name);
-
- continue;
- }
-
- var attributeArguments = attribute.ArgumentList?.Arguments ?? Enumerable.Empty();
-
- // Try to extract the forwarded attribute
- if (!AttributeInfo.TryCreate(attributeTypeSymbol, semanticModel, attributeArguments, token, out var attributeInfo))
- {
- builder.Add(
- InvalidPropertyTargetedAttributeExpressionOnObservableAsPropertyField,
- attribute,
- fieldSymbol,
- attribute.Name);
-
- continue;
- }
-
- forwardedAttributes.Add(attributeInfo);
- }
- }
-
- token.ThrowIfCancellationRequested();
-
- // Get the nullability info for the property
- fieldSymbol.GetNullabilityInfo(
- semanticModel,
- out var isReferenceTypeOrUnconstraindTypeParameter,
- out var includeMemberNotNullOnSetAccessor);
-
- token.ThrowIfCancellationRequested();
- var attributes = forwardedAttributes.ToImmutable();
- var forwardedPropertyAttributes = attributes.Select(static a => a.ToString()).ToImmutableArray();
-
- // Get the containing type info
- var targetInfo = TargetInfo.From(fieldSymbol.ContainingType);
-
- propertyInfo = new PropertyInfo(
- targetInfo.FileHintName,
- targetInfo.TargetName,
- targetInfo.TargetNamespace,
- targetInfo.TargetNamespaceWithNamespace,
- targetInfo.TargetVisibility,
- targetInfo.TargetType,
- typeNameWithNullabilityAnnotations,
- fieldName,
- propertyName,
- initializer,
- isReferenceTypeOrUnconstraindTypeParameter,
- includeMemberNotNullOnSetAccessor,
- forwardedPropertyAttributes,
- isReadonly == false ? string.Empty : "readonly");
-
- diagnostics = builder.ToImmutable();
-
- return true;
- }
-
- ///
- /// Validates the containing type for a given field being annotated.
- ///
- /// The input instance to process.
- /// Whether or not the containing type for is valid.
- private static bool IsTargetTypeValid(IFieldSymbol fieldSymbol)
- {
- var isObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.ReactiveObject");
- var isIObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.IReactiveObject");
- var hasObservableObjectAttribute = fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("ReactiveUI.SourceGenerators.ReactiveObjectAttribute");
-
- return isIObservableObject || isObservableObject || hasObservableObjectAttribute;
- }
-
- ///
- /// Get the generated property name for an input field.
- ///
- /// The input instance to process.
- /// The generated property name for .
- private static string GetGeneratedPropertyName(IFieldSymbol fieldSymbol)
- {
- var propertyName = fieldSymbol.Name;
-
- if (propertyName.StartsWith("m_"))
- {
- propertyName = propertyName.Substring(2);
- }
- else if (propertyName.StartsWith("_"))
- {
- propertyName = propertyName.TrimStart('_');
- }
-
- return $"{char.ToUpper(propertyName[0], CultureInfo.InvariantCulture)}{propertyName.Substring(1)}";
- }
- }
-}
diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.Generator.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.Generator.cs
deleted file mode 100644
index bc7cfd4..0000000
--- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.Generator.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-// 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 System.Linq;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using ReactiveUI.SourceGenerators.Extensions;
-using ReactiveUI.SourceGenerators.Helpers;
-using ReactiveUI.SourceGenerators.Models;
-using ReactiveUI.SourceGenerators.Reactive.Models;
-
-namespace ReactiveUI.SourceGenerators;
-
-///
-/// A source generator for generating reative properties.
-///
-public sealed partial class ObservableAsPropertyGenerator
-{
- private static void RunObservableAsPropertyGenerator(in IncrementalGeneratorInitializationContext context)
- {
- // Gather info for all annotated command methods (starting from method declarations with at least one attribute)
- IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result Info)> propertyInfoWithErrors =
- context.SyntaxProvider
- .ForAttributeWithMetadataName(
- AttributeDefinitions.ObservableAsPropertyAttributeType,
- static (node, _) => node is VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax { Parent: ClassDeclarationSyntax or RecordDeclarationSyntax, AttributeLists.Count: > 0 } } },
- static (context, token) =>
- {
- var symbol = ModelExtensions.GetDeclaredSymbol(context.SemanticModel, context.TargetNode, token)!;
- token.ThrowIfCancellationRequested();
-
- // Skip symbols without the target attribute
- if (!symbol.TryGetAttributeWithFullyQualifiedMetadataName(AttributeDefinitions.ObservableAsPropertyAttributeType, out var attributeData))
- {
- return default;
- }
-
- // Get the can PropertyName member, if any
- attributeData.TryGetNamedArgument("ReadOnly", out bool? isReadonly);
-
- var fieldDeclaration = (FieldDeclarationSyntax)context.TargetNode.Parent!.Parent!;
- var fieldSymbol = (IFieldSymbol)context.TargetSymbol;
-
- // Get the hierarchy info for the target symbol, and try to gather the property info
- var hierarchy = HierarchyInfo.From(fieldSymbol.ContainingType);
-
- token.ThrowIfCancellationRequested();
-
- Execute.GetFieldInfoFromClass(fieldDeclaration, fieldSymbol, context.SemanticModel, isReadonly, token, out var propertyInfo, out var diagnostics);
-
- token.ThrowIfCancellationRequested();
- return (Hierarchy: hierarchy, new Result(propertyInfo, diagnostics));
- })
- .Where(static item => item.Hierarchy is not null)!;
-
- // Output the diagnostics
- context.ReportDiagnostics(propertyInfoWithErrors.Select(static (item, _) => item.Info.Errors));
-
- // Get the filtered sequence to enable caching
- var propertyInfo =
- propertyInfoWithErrors
- .Where(static item => item.Info.Value is not null)!;
-
- // Split and group by containing type
- var groupedPropertyInfo =
- propertyInfo
- .GroupBy(static item => item.Left, static item => item.Right.Value);
-
- // Generate the requested properties and methods
- context.RegisterSourceOutput(groupedPropertyInfo, static (context, item) =>
- {
- // Generate all member declarations for the current type
- var memberDeclarations =
- item.Right
- .Select(Execute.GetPropertySyntax)
- .SelectMany(static m => m)
- .ToImmutableArray();
-
- // Insert all members into the same partial type declaration
- var compilationUnit = item.Key.GetCompilationUnit(memberDeclarations);
- context.AddSource($"{item.Key.FilenameHint}.ObservableAsProperties.g.cs", compilationUnit);
- });
- }
-}
diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.cs
index 22cb754..5ef9461 100644
--- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.cs
+++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.cs
@@ -18,13 +18,16 @@ namespace ReactiveUI.SourceGenerators;
[Generator(LanguageNames.CSharp)]
public sealed partial class ObservableAsPropertyGenerator : IIncrementalGenerator
{
+ internal static readonly string GeneratorName = typeof(ObservableAsPropertyGenerator).FullName!;
+ internal static readonly string GeneratorVersion = typeof(ObservableAsPropertyGenerator).Assembly.GetName().Version.ToString();
+
///
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(ctx =>
ctx.AddSource($"{AttributeDefinitions.ObservableAsPropertyAttributeType}.g.cs", SourceText.From(AttributeDefinitions.ObservableAsPropertyAttribute, Encoding.UTF8)));
- RunObservablePropertyAsFromObservable(context);
- RunObservableAsPropertyGenerator(context);
+ RunObservableAsPropertyFromObservable(context);
+ RunObservableAsPropertyFromField(context);
}
}
diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.Execute.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.Execute.cs
new file mode 100644
index 0000000..a933143
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.Execute.cs
@@ -0,0 +1,267 @@
+// 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 System.Globalization;
+using System.Linq;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using ReactiveUI.SourceGenerators.Extensions;
+using ReactiveUI.SourceGenerators.Helpers;
+using ReactiveUI.SourceGenerators.Models;
+using ReactiveUI.SourceGenerators.Reactive.Models;
+
+namespace ReactiveUI.SourceGenerators;
+
+///
+/// ReactiveGenerator.
+///
+///
+public sealed partial class ObservableAsPropertyGenerator
+{
+ private static PropertyInfo? GetVariableInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token)
+ {
+ var symbol = context.TargetSymbol;
+ token.ThrowIfCancellationRequested();
+
+ // Skip symbols without the target attribute
+ if (!symbol.TryGetAttributeWithFullyQualifiedMetadataName(AttributeDefinitions.ObservableAsPropertyAttributeType, out var attributeData))
+ {
+ return default;
+ }
+
+ if (symbol is not IFieldSymbol fieldSymbol)
+ {
+ return default;
+ }
+
+ // Validate the target type
+ if (!IsTargetTypeValid(fieldSymbol))
+ {
+ return default;
+ }
+
+ // Get the can PropertyName member, if any
+ attributeData.TryGetNamedArgument("ReadOnly", out bool? isReadonly);
+
+ token.ThrowIfCancellationRequested();
+
+ // Get the property type and name
+ var typeNameWithNullabilityAnnotations = fieldSymbol.Type.GetFullyQualifiedNameWithNullabilityAnnotations();
+ var fieldName = fieldSymbol.Name;
+ var propertyName = GetGeneratedPropertyName(fieldSymbol);
+
+ var fieldDeclaration = (FieldDeclarationSyntax)context.TargetNode.Parent!.Parent!;
+ var initializer = fieldDeclaration.Declaration.Variables.FirstOrDefault()?.Initializer?.ToFullString();
+
+ // Check for name collisions
+ if (fieldName == propertyName)
+ {
+ return default;
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ using var forwardedAttributes = ImmutableArrayBuilder.Rent();
+
+ // Gather attributes info
+ foreach (var attribute in fieldSymbol.GetAttributes())
+ {
+ token.ThrowIfCancellationRequested();
+
+ // Track the current attribute for forwarding if it is a validation attribute
+ if (attribute.AttributeClass?.InheritsFromFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.ValidationAttribute") == true)
+ {
+ forwardedAttributes.Add(AttributeInfo.Create(attribute));
+ }
+
+ // Track the current attribute for forwarding if it is a Json Serialization attribute
+ if (attribute.AttributeClass?.InheritsFromFullyQualifiedMetadataName("System.Text.Json.Serialization.JsonAttribute") == true)
+ {
+ forwardedAttributes.Add(AttributeInfo.Create(attribute));
+ }
+
+ // Also track the current attribute for forwarding if it is of any of the following types:
+ if (attribute.AttributeClass?.HasOrInheritsFromFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.UIHintAttribute") == true ||
+ attribute.AttributeClass?.HasOrInheritsFromFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.ScaffoldColumnAttribute") == true ||
+ attribute.AttributeClass?.HasFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.DisplayAttribute") == true ||
+ attribute.AttributeClass?.HasFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.EditableAttribute") == true ||
+ attribute.AttributeClass?.HasFullyQualifiedMetadataName("System.ComponentModel.DataAnnotations.KeyAttribute") == true ||
+ attribute.AttributeClass?.HasFullyQualifiedMetadataName("System.Runtime.Serialization.DataMemberAttribute") == true ||
+ attribute.AttributeClass?.HasFullyQualifiedMetadataName("System.Runtime.Serialization.IgnoreDataMemberAttribute") == true)
+ {
+ forwardedAttributes.Add(AttributeInfo.Create(attribute));
+ }
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ // Gather explicit forwarded attributes info
+ foreach (var attributeList in fieldDeclaration.AttributeLists)
+ {
+ // 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))
+ {
+ continue;
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ foreach (var attribute in attributeList.Attributes)
+ {
+ // Roslyn ignores attributes in an attribute list with an invalid target, so we can't get the AttributeData as usual.
+ // To reconstruct all necessary attribute info to generate the serialized model, we use the following steps:
+ // - We try to get the attribute symbol from the semantic model, for the current attribute syntax. In case this is not
+ // available (in theory it shouldn't, but it can be), we try to get it from the candidate symbols list for the node.
+ // If there are no candidates or more than one, we just issue a diagnostic and stop processing the current attribute.
+ // The returned symbols might be method symbols (constructor attribute) so in that case we can get the declaring type.
+ // - We then go over each attribute argument expression and get the operation for it. This will still be available even
+ // though the rest of the attribute is not validated nor bound at all. From the operation we can still retrieve all
+ // constant values to build the AttributeInfo model. After all, attributes only support constant values, typeof(T)
+ // expressions, or arrays of either these two types, or of other arrays with the same rules, recursively.
+ // - From the syntax, we can also determine the identifier names for named attribute arguments, if any.
+ // There is no need to validate anything here: the attribute will be forwarded as is, and then Roslyn will validate on the
+ // generated property. Users will get the same validation they'd have had directly over the field. The only drawback is the
+ // lack of IntelliSense when constructing attributes over the field, but this is the best we can do from this end anyway.
+ if (!context.SemanticModel.GetSymbolInfo(attribute, token).TryGetAttributeTypeSymbol(out var attributeTypeSymbol))
+ {
+ continue;
+ }
+
+ var attributeArguments = attribute.ArgumentList?.Arguments ?? Enumerable.Empty();
+
+ // Try to extract the forwarded attribute
+ if (!AttributeInfo.TryCreate(attributeTypeSymbol, context.SemanticModel, attributeArguments, token, out var attributeInfo))
+ {
+ continue;
+ }
+
+ forwardedAttributes.Add(attributeInfo);
+ }
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ // Get the nullability info for the property
+ fieldSymbol.GetNullabilityInfo(
+ context.SemanticModel,
+ out var isReferenceTypeOrUnconstraindTypeParameter,
+ out var includeMemberNotNullOnSetAccessor);
+
+ token.ThrowIfCancellationRequested();
+ var attributes = forwardedAttributes.ToImmutable();
+ var forwardedPropertyAttributes = attributes.Select(static a => a.ToString()).ToImmutableArray();
+
+ // Get the containing type info
+ var targetInfo = TargetInfo.From(fieldSymbol.ContainingType);
+
+ return new PropertyInfo(
+ targetInfo.FileHintName,
+ targetInfo.TargetName,
+ targetInfo.TargetNamespace,
+ targetInfo.TargetNamespaceWithNamespace,
+ targetInfo.TargetVisibility,
+ targetInfo.TargetType,
+ typeNameWithNullabilityAnnotations,
+ fieldName,
+ propertyName,
+ initializer,
+ isReferenceTypeOrUnconstraindTypeParameter,
+ includeMemberNotNullOnSetAccessor,
+ forwardedPropertyAttributes,
+ isReadonly == false ? string.Empty : "readonly");
+ }
+
+ private static string GenerateSource(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, PropertyInfo[] properties)
+ {
+ var propertyDeclarations = string.Join("\n\r", properties.Select(GetPropertySyntax));
+
+ return $$"""
+//
+#pragma warning disable
+#nullable enable
+namespace {{containingNamespace}}
+{
+ ///
+ {{containingClassVisibility}} partial {{containingType}} {{containingTypeName}}
+ {
+ [global::System.CodeDom.Compiler.GeneratedCode("{{GeneratorName}}", "{{GeneratorVersion}}")]
+{{propertyDeclarations}}
+ }
+}
+#nullable restore
+#pragma warning restore
+""";
+ }
+
+ private static string GetPropertySyntax(PropertyInfo propertyInfo)
+ {
+ var propertyAttributes = string.Join("\n ", AttributeDefinitions.ExcludeFromCodeCoverage.Concat(propertyInfo.ForwardedAttributes));
+
+ var getter = $$"""{ get => {{propertyInfo.FieldName}} = {{propertyInfo.FieldName}}Helper?.Value ?? {{propertyInfo.FieldName}}; }""";
+
+ // If the property is nullable, we need to add a null check to the getter
+ if (propertyInfo.TypeNameWithNullabilityAnnotations.EndsWith("?"))
+ {
+ getter = $$"""{ get => {{propertyInfo.FieldName}} = ({{propertyInfo.FieldName}}Helper == null ? {{propertyInfo.FieldName}} : {{propertyInfo.FieldName}}Helper.Value); }""";
+ }
+
+ var helperTypeName = $"private ReactiveUI.ObservableAsPropertyHelper<{propertyInfo.TypeNameWithNullabilityAnnotations}>?";
+
+ // If the property is readonly, we need to change the helper to be non-nullable
+ if (propertyInfo.AccessModifier == "readonly")
+ {
+ helperTypeName = $"private readonly ReactiveUI.ObservableAsPropertyHelper<{propertyInfo.TypeNameWithNullabilityAnnotations}>";
+ }
+
+ return $$"""
+ ///
+ {{helperTypeName}} {{propertyInfo.FieldName}}Helper;
+
+ ///
+ {{propertyAttributes}}
+ public {{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} {{getter}}
+""";
+ }
+
+ ///
+ /// Get the generated property name for an input field.
+ ///
+ /// The input instance to process.
+ /// The generated property name for .
+ private static string GetGeneratedPropertyName(IFieldSymbol fieldSymbol)
+ {
+ var propertyName = fieldSymbol.Name;
+
+ if (propertyName.StartsWith("m_"))
+ {
+ propertyName = propertyName.Substring(2);
+ }
+ else if (propertyName.StartsWith("_"))
+ {
+ propertyName = propertyName.TrimStart('_');
+ }
+
+ return $"{char.ToUpper(propertyName[0], CultureInfo.InvariantCulture)}{propertyName.Substring(1)}";
+ }
+
+ ///
+ /// Validates the containing type for a given field being annotated.
+ ///
+ /// The input instance to process.
+ /// Whether or not the containing type for is valid.
+ private static bool IsTargetTypeValid(IFieldSymbol fieldSymbol)
+ {
+ var isObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.ReactiveObject");
+ var isIObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.IReactiveObject");
+ var hasObservableObjectAttribute = fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("ReactiveUI.SourceGenerators.ReactiveObjectAttribute");
+
+ return isIObservableObject || isObservableObject || hasObservableObjectAttribute;
+ }
+}
diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.cs
new file mode 100644
index 0000000..b740c55
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.cs
@@ -0,0 +1,59 @@
+// 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 System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using ReactiveUI.SourceGenerators.Extensions;
+using ReactiveUI.SourceGenerators.Helpers;
+
+namespace ReactiveUI.SourceGenerators;
+
+///
+/// A source generator for generating reative properties.
+///
+public sealed partial class ObservableAsPropertyGenerator
+{
+ private static void RunObservableAsPropertyFromField(in IncrementalGeneratorInitializationContext context)
+ {
+ var propertyInfo =
+ context.SyntaxProvider
+ .ForAttributeWithMetadataName(
+ AttributeDefinitions.ObservableAsPropertyAttributeType,
+ static (node, _) => node is VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax { Parent: ClassDeclarationSyntax or RecordDeclarationSyntax, AttributeLists.Count: > 0 } } },
+ static (context, token) => GetVariableInfo(context, token))
+ .Where(x => x != null)
+ .Select((x, _) => x!)
+ .Collect();
+
+ // Generate the requested properties and methods
+ context.RegisterSourceOutput(propertyInfo, static (context, input) =>
+ {
+ var groupedPropertyInfo = input.GroupBy(
+ static info => (info.FileHintName, info.TargetName, info.TargetNamespace, info.TargetVisibility, info.TargetType),
+ static info => info)
+ .ToImmutableArray();
+
+ if (groupedPropertyInfo.Length == 0)
+ {
+ return;
+ }
+
+ foreach (var grouping in groupedPropertyInfo)
+ {
+ var items = grouping.ToImmutableArray();
+
+ if (items.Length == 0)
+ {
+ continue;
+ }
+
+ var source = GenerateSource(grouping.Key.TargetName, grouping.Key.TargetNamespace, grouping.Key.TargetVisibility, grouping.Key.TargetType, [.. grouping]);
+ context.AddSource($"{grouping.Key.FileHintName}.ObservableAsProperties.g.cs", source);
+ }
+ });
+ }
+}
diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.FromObservableGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs
similarity index 98%
rename from src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.FromObservableGenerator.Execute.cs
rename to src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs
index 0dbbfb8..b861a96 100644
--- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.FromObservableGenerator.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs
@@ -78,7 +78,7 @@ internal static ImmutableArray GetPropertySyntax(Observ
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableAsPropertyGenerator).FullName))),
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableAsPropertyGenerator).Assembly.GetName().Version.ToString()))))))
.WithOpenBracketToken(Token(TriviaList(Comment($"/// ")), SyntaxKind.OpenBracketToken, TriviaList())),
- AttributeList(SingletonSeparatedList(Attribute(IdentifierName(AttributeDefinitions.ExcludeFromCodeCoverage)))))
+ AttributeList(SingletonSeparatedList(Attribute(IdentifierName(AttributeDefinitions.ExcludeFromCodeCoverageString)))))
.AddAttributeLists([.. forwardedAttributes])
.AddModifiers(Token(SyntaxKind.PublicKeyword))
.AddAccessorListAccessors(
@@ -113,7 +113,7 @@ internal static MethodDeclarationSyntax GetPropertyInitiliser(ObservableMethodIn
.AddArgumentListArguments(
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableAsPropertyGenerator).FullName))),
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableAsPropertyGenerator).Assembly.GetName().Version.ToString())))))),
- AttributeList(SingletonSeparatedList(Attribute(IdentifierName(AttributeDefinitions.ExcludeFromCodeCoverage)))))
+ AttributeList(SingletonSeparatedList(Attribute(IdentifierName(AttributeDefinitions.ExcludeFromCodeCoverageString)))))
.WithModifiers(TokenList(Token(SyntaxKind.ProtectedKeyword)))
.WithBody(Block(propertyInitilisers.ToImmutable()));
}
diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.FromObservableGenerator.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.cs
similarity index 99%
rename from src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.FromObservableGenerator.cs
rename to src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.cs
index 4e6845b..1caaa92 100644
--- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.FromObservableGenerator.cs
+++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.cs
@@ -24,7 +24,7 @@ namespace ReactiveUI.SourceGenerators;
///
public sealed partial class ObservableAsPropertyGenerator
{
- private static void RunObservablePropertyAsFromObservable(in IncrementalGeneratorInitializationContext context)
+ private static void RunObservableAsPropertyFromObservable(in IncrementalGeneratorInitializationContext context)
{
// Gather info for all annotated command methods (starting from method declarations with at least one attribute)
IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result Info)> propertyInfoWithErrors =
diff --git a/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs
index 988dc1c..837af44 100644
--- a/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs
@@ -26,8 +26,6 @@ public sealed partial class ReactiveGenerator
internal static readonly string GeneratorName = typeof(ReactiveGenerator).FullName!;
internal static readonly string GeneratorVersion = typeof(ReactiveGenerator).Assembly.GetName().Version.ToString();
- private static readonly string[] excludeFromCodeCoverage = ["[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"];
-
///
/// Gets the observable method information.
///
@@ -245,7 +243,7 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo)
setModifier = string.Empty;
}
- var propertyAttributes = string.Join("\n ", excludeFromCodeCoverage.Concat(propertyInfo.ForwardedAttributes));
+ var propertyAttributes = string.Join("\n ", AttributeDefinitions.ExcludeFromCodeCoverage.Concat(propertyInfo.ForwardedAttributes));
if (propertyInfo.IncludeMemberNotNullOnSetAccessor)
{
diff --git a/src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs
index 43a0b77..5be0916 100644
--- a/src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs
@@ -34,7 +34,6 @@ public partial class ReactiveCommandGenerator
private const string CreateO = ".CreateFromObservable";
private const string CreateT = ".CreateFromTask";
private const string CanExecute = "CanExecute";
- private static readonly string[] excludeFromCodeCoverage = ["[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"];
private static CommandInfo? GetMethodInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token)
{
@@ -164,7 +163,7 @@ private static string GetCommandSyntax(CommandInfo commandExtensionInfo)
}
// Prepare any forwarded property attributes
- var forwardedPropertyAttributesString = string.Join("\n ", excludeFromCodeCoverage.Concat(commandExtensionInfo.ForwardedPropertyAttributes));
+ var forwardedPropertyAttributesString = string.Join("\n ", AttributeDefinitions.ExcludeFromCodeCoverage.Concat(commandExtensionInfo.ForwardedPropertyAttributes));
return
$$"""
diff --git a/src/ReactiveUI.SourceGenerators/RoutedControlHost/RoutedControlHostGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/RoutedControlHost/RoutedControlHostGenerator.Execute.cs
index e808378..ecb2af1 100644
--- a/src/ReactiveUI.SourceGenerators/RoutedControlHost/RoutedControlHostGenerator.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators/RoutedControlHost/RoutedControlHostGenerator.Execute.cs
@@ -26,8 +26,6 @@ public partial class RoutedControlHostGenerator
private static readonly string GeneratorName = typeof(RoutedControlHostGenerator).FullName!;
private static readonly string GeneratorVersion = typeof(RoutedControlHostGenerator).Assembly.GetName().Version.ToString();
- private static readonly string[] excludeFromCodeCoverage = ["[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"];
-
private static RoutedControlHostInfo? GetClassInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token)
{
if (!(context.TargetNode is ClassDeclarationSyntax declaredClass && declaredClass.Modifiers.Any(SyntaxKind.PartialKeyword)))
@@ -86,7 +84,7 @@ public partial class RoutedControlHostGenerator
private static string GetRoutedControlHost(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, RoutedControlHostInfo vmcInfo)
{
// Prepare any forwarded property attributes
- var forwardedAttributesString = string.Join("\n ", excludeFromCodeCoverage.Concat(vmcInfo.ForwardedAttributes));
+ var forwardedAttributesString = string.Join("\n ", AttributeDefinitions.ExcludeFromCodeCoverage.Concat(vmcInfo.ForwardedAttributes));
return
$$"""
@@ -108,7 +106,7 @@ namespace {{containingNamespace}}
{{forwardedAttributesString}}
[DefaultProperty("ViewModel")]
[global::System.CodeDom.Compiler.GeneratedCode("{{GeneratorName}}", "{{GeneratorVersion}}")]
- {{containingClassVisibility}} partial class {{containingTypeName}} : {{vmcInfo.BaseTypeName}}, IReactiveObject
+ {{containingClassVisibility}} partial {{containingType}} {{containingTypeName}} : {{vmcInfo.BaseTypeName}}, IReactiveObject
{
private readonly CompositeDisposable _disposables = [];
private RoutingState? _router;
diff --git a/src/ReactiveUI.SourceGenerators/ViewModelControlHost/ViewModelControlHostGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/ViewModelControlHost/ViewModelControlHostGenerator.Execute.cs
index a362f9a..1c3b2ae 100644
--- a/src/ReactiveUI.SourceGenerators/ViewModelControlHost/ViewModelControlHostGenerator.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators/ViewModelControlHost/ViewModelControlHostGenerator.Execute.cs
@@ -26,8 +26,6 @@ public partial class ViewModelControlHostGenerator
private static readonly string GeneratorName = typeof(ViewModelControlHostGenerator).FullName!;
private static readonly string GeneratorVersion = typeof(ViewModelControlHostGenerator).Assembly.GetName().Version.ToString();
- private static readonly string[] excludeFromCodeCoverage = ["[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"];
-
private static ViewModelControlHostInfo? GetClassInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token)
{
if (!(context.TargetNode is ClassDeclarationSyntax declaredClass && declaredClass.Modifiers.Any(SyntaxKind.PartialKeyword)))
@@ -79,7 +77,7 @@ public partial class ViewModelControlHostGenerator
private static string GetViewModelControlHost(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, ViewModelControlHostInfo vmcInfo)
{
// Prepare any forwarded property attributes
- var forwardedAttributesString = string.Join("\n ", excludeFromCodeCoverage.Concat(vmcInfo.ForwardedAttributes));
+ var forwardedAttributesString = string.Join("\n ", AttributeDefinitions.ExcludeFromCodeCoverage.Concat(vmcInfo.ForwardedAttributes));
return
$$"""
@@ -101,7 +99,7 @@ namespace {{containingNamespace}}
{{forwardedAttributesString}}
[DefaultProperty("ViewModel")]
[global::System.CodeDom.Compiler.GeneratedCode("{{GeneratorName}}", "{{GeneratorVersion}}")]
- public partial class {{containingTypeName}} : {{vmcInfo.ViewModelTypeName}}, IReactiveObject, IViewFor
+ {{containingClassVisibility}} partial {{containingType}} {{containingTypeName}} : {{vmcInfo.ViewModelTypeName}}, IReactiveObject, IViewFor
{
private readonly CompositeDisposable _disposables = [];
private Control? _defaultContent;