diff --git a/src/ReactiveUI.SourceGenerators.sln b/src/ReactiveUI.SourceGenerators.sln
index 19dd8cd..7da20f8 100644
--- a/src/ReactiveUI.SourceGenerators.sln
+++ b/src/ReactiveUI.SourceGenerators.sln
@@ -1,5 +1,5 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
+# 17
VisualStudioVersion = 17.10.35027.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionConfig", "SolutionConfig", "{F29AF2F3-DEC8-58BC-043A-1447862C832D}"
diff --git a/src/ReactiveUI.SourceGenerators/AnalyzerReleases.Shipped.md b/src/ReactiveUI.SourceGenerators/AnalyzerReleases.Shipped.md
index 4aab8de..8e2ed2b 100644
--- a/src/ReactiveUI.SourceGenerators/AnalyzerReleases.Shipped.md
+++ b/src/ReactiveUI.SourceGenerators/AnalyzerReleases.Shipped.md
@@ -1,78 +1,82 @@
+; Shipped analyzer releases
+; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
+
+## Release 1.0
+
+### New Rules
+
+Rule ID | Category | Severity | Notes
+--------|----------|----------|-------
+RXUISG0001 | ReactiveUI.SourceGenerators.UnsupportedCSharpLanguageVersionAnalyzer | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0002 | ReactiveUI.SourceGenerators.ReactiveCommandGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0003 | ReactiveUI.SourceGenerators.ReactiveCommandGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0004 | ReactiveUI.SourceGenerators.ReactiveCommandGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0005 | ReactiveUI.SourceGenerators.ReactiveCommandGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0006 | ReactiveUI.SourceGenerators.ReactiveCommandGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0007 | ReactiveUI.SourceGenerators.ReactiveCommandGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0008 | ReactiveUI.SourceGenerators.AsyncVoidReturningReactiveCommandMethodAnalyzer | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0009 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0010 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0011 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0012 | ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0013 | ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0014 | ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0015 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0016 | ReactiveUI.SourceGenerators.PropertyToReactiveFieldCodeFixProvider | Info | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+RXUISG0017 | ReactiveUI.SourceGenerators.ObservableAsPropertyFromObservableGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
+
+
## Rules
Shipped in ReactiveUI.SourceGenerators
-### RXUISG0001 - Unsupported C# Language Version
-This rule checks if the project is using an unsupported C# language version. The supported versions are C# 7.3 and above. If the project is using an unsupported version, the rule will raise an error.
+- RXUISG0001 - Unsupported C# Language Version
+This rule checks if the project is using an unsupported C# language version. The supported versions are C# 12.0 and above. If the project is using an unsupported version, the rule will raise an error.
-### RXUISG0002 - ReactiveCommandGenerator
+- RXUISG0002 - ReactiveCommandGenerator
This rule checks if the `ReactiveCommand` has a Invalid ReactiveCommand method signature.
-### RXUISG0003 - ReactiveCommandGenerator
+- RXUISG0003 - ReactiveCommandGenerator
This rule checks if the `ReactiveCommand` has a Invalid ReactiveCommand.CanExecute member name.
-### RXUISG0004 - ReactiveCommandGenerator
+- RXUISG0004 - ReactiveCommandGenerator
This rule checks if the `ReactiveCommand` has Multiple ReactiveCommand.CanExecute member name matches.
-### RXUISG0005 - ReactiveCommandGenerator
+- RXUISG0005 - ReactiveCommandGenerator
This rule checks if the `ReactiveCommand` has No valid ReactiveCommand.CanExecute member match.
-### RXUISG0006 - ReactiveCommandGenerator
+- RXUISG0006 - ReactiveCommandGenerator
This rule checks if the `ReactiveCommand` has Invalid field or property targeted attribute type.
-### RXUISG0007 - ReactiveCommandGenerator
+- RXUISG0007 - ReactiveCommandGenerator
This rule checks if the `ReactiveCommand` has Invalid field or property targeted attribute expression.
-### RXUISG0008 - AsyncVoidReturningReactiveCommandMethodAnalyzer
+- RXUISG0008 - AsyncVoidReturningReactiveCommandMethodAnalyzer
This rule checks if the `ReactiveCommand` has Async void returning method annotated with ReactiveCommand.
-### RXUISG0009 - ReactiveGenerator
+- RXUISG0009 - ReactiveGenerator
This rule checks if the `Reactive` has Name collision for generated property.
-### RXUISG0010 - ReactiveGenerator
+- RXUISG0010 - ReactiveGenerator
This rule checks if the `Reactive` has Invalid property targeted attribute type.
-### RXUISG0011 - ReactiveGenerator
+- RXUISG0011 - ReactiveGenerator
This rule checks if the `Reactive` has Invalid property targeted attribute expression.
-### RXUISG0012 - ObservableAsPropertyGenerator
+- RXUISG0012 - ObservableAsPropertyGenerator
This rule checks if the `ObservableAsProperty` has Invalid property targeted attribute type.
-### RXUISG0013 - ObservableAsPropertyGenerator
+- RXUISG0013 - ObservableAsPropertyGenerator
This rule checks if the `ObservableAsProperty` has Invalid property targeted attribute expression.
-### RXUISG0014 - ObservableAsPropertyGenerator
+- RXUISG0014 - ObservableAsPropertyGenerator
This rule checks if the `ObservableAsProperty` has Invalid generated property declaration.
-### RXUISG0015 - ReactiveGenerator
+- RXUISG0015 - ReactiveGenerator
This rule checks if the `Reactive` attribute is being used correctly. If the `Reactive` has Invalid generated property declaration.
-### RXUISG0016 - PropertyToReactiveFieldCodeFixProvider
+- RXUISG0016 - PropertyToReactiveFieldCodeFixProvider
This rule checks if there are any Properties to change to Reactive Field, change to [Reactive] private type _fieldName;.
-### RXUISG0017 - ObservableAsPropertyFromObservableGenerator
+- RXUISG0017 - ObservableAsPropertyFromObservableGenerator
This rule checks if the `ObservableAsProperty` has Invalid generated property declaration.
-
-## Release 1.0
-
-### New Rules
-
-Rule ID | Category | Severity | Notes
---------|----------|----------|-------
-RXUISG0001 | ReactiveUI.SourceGenerators.UnsupportedCSharpLanguageVersionAnalyzer | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0002 | ReactiveUI.SourceGenerators.ReactiveCommandGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0003 | ReactiveUI.SourceGenerators.ReactiveCommandGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0004 | ReactiveUI.SourceGenerators.ReactiveCommandGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0005 | ReactiveUI.SourceGenerators.ReactiveCommandGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0006 | ReactiveUI.SourceGenerators.ReactiveCommandGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0007 | ReactiveUI.SourceGenerators.ReactiveCommandGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0008 | ReactiveUI.SourceGenerators.AsyncVoidReturningReactiveCommandMethodAnalyzer | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0009 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0010 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0011 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0012 | ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0013 | ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0014 | ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0015 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0016 | ReactiveUI.SourceGenerators.PropertyToReactiveFieldCodeFixProvider | Info | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
-RXUISG0017 | ReactiveUI.SourceGenerators.ObservableAsPropertyFromObservableGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
diff --git a/src/ReactiveUI.SourceGenerators/AnalyzerReleases.Unshipped.md b/src/ReactiveUI.SourceGenerators/AnalyzerReleases.Unshipped.md
index 0aee6aa..11aac41 100644
--- a/src/ReactiveUI.SourceGenerators/AnalyzerReleases.Unshipped.md
+++ b/src/ReactiveUI.SourceGenerators/AnalyzerReleases.Unshipped.md
@@ -1 +1,2 @@
-[Unshipped analyzer release](https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)
+; Unshipped analyzer releases
+; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
diff --git a/src/ReactiveUI.SourceGenerators/Core/Extensions/AttributeDataExtensions.cs b/src/ReactiveUI.SourceGenerators/Core/Extensions/AttributeDataExtensions.cs
index 484dd70..fdf577c 100644
--- a/src/ReactiveUI.SourceGenerators/Core/Extensions/AttributeDataExtensions.cs
+++ b/src/ReactiveUI.SourceGenerators/Core/Extensions/AttributeDataExtensions.cs
@@ -193,4 +193,76 @@ public static string FormatAttributes(this PropertyAttributeData attr)
return $"[{namespacePrefix}{attr.AttributeSyntax}]";
}
+
+ ///
+ /// Gathers the forwarded attributes from class.
+ ///
+ /// The attribute data.
+ /// The semantic model.
+ /// The class declaration.
+ /// The token.
+ /// The class attributes information.
+ public static void GatherForwardedAttributesFromClass(
+ this AttributeData attributeData,
+ SemanticModel semanticModel,
+ ClassDeclarationSyntax classDeclaration,
+ CancellationToken token,
+ out ImmutableArray classAttributesInfo)
+ {
+ using var classAttributesInfoBuilder = ImmutableArrayBuilder.Rent();
+
+ static void GatherForwardedAttributes(
+ AttributeData attributeData,
+ SemanticModel semanticModel,
+ ClassDeclarationSyntax classDeclaration,
+ CancellationToken token,
+ ImmutableArrayBuilder classAttributesInfo)
+ {
+ // Gather explicit forwarded attributes info
+ foreach (var attributeList in classDeclaration.AttributeLists)
+ {
+ foreach (var attribute in attributeList.Attributes)
+ {
+ if (!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, semanticModel, attributeArguments, token, out var attributeInfo))
+ {
+ continue;
+ }
+
+ var ignoreAttribute = attributeData.AttributeClass?.GetFullyQualifiedMetadataName();
+ if (attributeInfo.TypeName.Contains(ignoreAttribute))
+ {
+ continue;
+ }
+
+ // Add the new attribute info to the right builder
+ classAttributesInfo.Add(attributeInfo);
+ }
+ }
+ }
+
+ // If the method is not a partial definition/implementation, just gather attributes from the method with no modifications
+ GatherForwardedAttributes(attributeData, semanticModel, classDeclaration, token, classAttributesInfoBuilder);
+
+ classAttributesInfo = classAttributesInfoBuilder.ToImmutable();
+ }
+
+ ///
+ /// Gets the type of the generic.
+ ///
+ /// The attribute data.
+ /// A String.
+ public static string? GetGenericType(this AttributeData attributeData)
+ {
+ var success = attributeData?.AttributeClass?.ToDisplayString();
+ var start = success?.IndexOf('<') + 1 ?? 0;
+ return success?.Substring(start, success.Length - start - 1);
+ }
}
diff --git a/src/ReactiveUI.SourceGenerators/IViewFor/IViewForGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/IViewFor/IViewForGenerator.Execute.cs
index 8246ca0..268289a 100644
--- a/src/ReactiveUI.SourceGenerators/IViewFor/IViewForGenerator.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators/IViewFor/IViewForGenerator.Execute.cs
@@ -3,14 +3,16 @@
// 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.CodeDom.Compiler;
-using System.IO;
+using System.Collections.Immutable;
+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.Input.Models;
-using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+using ReactiveUI.SourceGenerators.Models;
namespace ReactiveUI.SourceGenerators;
@@ -20,411 +22,296 @@ namespace ReactiveUI.SourceGenerators;
///
public partial class IViewForGenerator
{
- internal static class Execute
+ internal static readonly string GeneratorName = typeof(IViewForGenerator).FullName!;
+ internal static readonly string GeneratorVersion = typeof(IViewForGenerator).Assembly.GetName().Version.ToString();
+
+ private static readonly string[] excludeFromCodeCoverage = ["[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"];
+
+ private static IViewForInfo? GetClassInfo(in SGeneratorAttributeSyntaxContext context, CancellationToken token)
{
- internal static CompilationUnitSyntax GetIViewForWpfWinUiUno(IViewForInfo iViewForInfo)
+ if (!(context.TargetNode is ClassDeclarationSyntax declaredClass && declaredClass.Modifiers.Any(SyntaxKind.PartialKeyword)))
{
- UsingDirectiveSyntax[] usings = [];
- if (iViewForInfo.BaseType == IViewForBaseType.Wpf)
- {
- usings =
- [
- UsingDirective(ParseName("ReactiveUI")),
- UsingDirective(ParseName("System.Windows")),
- ];
- }
- else if (iViewForInfo.BaseType == IViewForBaseType.WinUI)
- {
- usings =
- [
- UsingDirective(ParseName("ReactiveUI")),
- UsingDirective(ParseName("Microsoft.UI.Xaml")),
- ];
- }
- else if (iViewForInfo.BaseType == IViewForBaseType.Uno)
- {
- usings =
- [
- UsingDirective(ParseName("ReactiveUI")),
- UsingDirective(ParseName("Windows.UI.Xaml")),
- ];
- }
+ return default;
+ }
+
+ var symbol = context.TargetSymbol;
+ token.ThrowIfCancellationRequested();
+
+ if (!symbol.TryGetAttributeWithFullyQualifiedMetadataName(AttributeDefinitions.IViewForAttributeType, out var attributeData))
+ {
+ return default;
+ }
+
+ token.ThrowIfCancellationRequested();
+ if (symbol is not INamedTypeSymbol classSymbol)
+ {
+ return default;
+ }
- var code = CompilationUnit().AddMembers(
- NamespaceDeclaration(IdentifierName(iViewForInfo.ClassNamespace))
- .WithLeadingTrivia(TriviaList(
- Comment("// "),
- Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)),
- Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true))))
- .AddMembers(
- ClassDeclaration(iViewForInfo.ClassName)
- .AddBaseListTypes(
- SimpleBaseType(
- GenericName(Identifier("IViewFor"))
- .WithTypeArgumentList(
- TypeArgumentList(
- SingletonSeparatedList(
- IdentifierName(iViewForInfo.ViewModelTypeName))))))
- .AddModifiers([.. iViewForInfo.DeclarationSyntax.Modifiers])
- .AddAttributeLists(AttributeList(SingletonSeparatedList(
- Attribute(IdentifierName(AttributeDefinitions.GeneratedCode))
- .AddArgumentListArguments(
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).FullName))),
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).Assembly.GetName().Version.ToString())))))))))
- .WithUsings(List(usings))
- .NormalizeWhitespace().ToFullString();
-
- // Remove the last 4 characters to remove the closing brackets
- var baseCode = code.Remove(code.Length - 4);
-
- // Prepare all necessary type names with type arguments
- using var stringStream = new StringWriter();
- using var writer = new IndentedTextWriter(stringStream, "\t");
- writer.WriteLine(baseCode);
- writer.Indent++;
- writer.Indent++;
-
- // Add the necessary properties and methods for IViewFor.
- writer.WriteLine("/// ");
- writer.WriteLine("/// The view model dependency property.");
- writer.WriteLine("/// ");
- writer.WriteLine("public static readonly DependencyProperty ViewModelProperty =");
- writer.Indent++;
- writer.WriteLine("DependencyProperty.Register(");
- writer.WriteLine("nameof(ViewModel),");
- writer.WriteLine($"typeof({iViewForInfo.ViewModelTypeName}),");
- writer.WriteLine($"typeof({iViewForInfo.ClassName}),");
- writer.WriteLine("new PropertyMetadata(null));");
- writer.WriteLine();
-
- writer.Indent--;
- writer.WriteLine("/// ");
- writer.WriteLine("/// Gets the binding root view model.");
- writer.WriteLine("/// ");
- writer.WriteLine($"public {iViewForInfo.ViewModelTypeName}? BindingRoot => ViewModel;");
- writer.WriteLine();
-
- writer.WriteLine("/// ");
- writer.WriteLine($"public {iViewForInfo.ViewModelTypeName}? ViewModel");
- writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
- writer.Indent++;
- writer.WriteLine($"get => ({iViewForInfo.ViewModelTypeName}?)GetValue(ViewModelProperty);");
- writer.WriteLine("set => SetValue(ViewModelProperty, value);");
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.WriteLine();
-
- writer.WriteLine("/// ");
- writer.WriteLine("object? IViewFor.ViewModel");
- writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
- writer.Indent++;
- writer.WriteLine("get => ViewModel;");
- writer.WriteLine($"set => ViewModel = ({iViewForInfo.ViewModelTypeName}?)value;");
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.WriteLine(TriviaList(
- Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)),
- Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))
- .NormalizeWhitespace());
-
- var output = stringStream.ToString();
- return ParseCompilationUnit(output).NormalizeWhitespace();
+ token.ThrowIfCancellationRequested();
+
+ var genericArgument = attributeData.GetGenericType();
+ token.ThrowIfCancellationRequested();
+ if (!(genericArgument is string viewModelTypeName && viewModelTypeName.Length > 0))
+ {
+ return default;
}
- internal static CompilationUnitSyntax GetIViewForWinForms(IViewForInfo iViewForInfo)
+ var compilation = context.SemanticModel.Compilation;
+ var semanticModel = compilation.GetSemanticModel(context.SemanticModel.SyntaxTree);
+ token.ThrowIfCancellationRequested();
+ attributeData.GatherForwardedAttributesFromClass(semanticModel, declaredClass, token, out var classAttributesInfo);
+ var forwardedClassAttributes = classAttributesInfo.Select(static a => a.ToString())
+ .Where(x => !x.Contains(AttributeDefinitions.IViewForAttributeType))
+ .ToImmutableArray();
+ token.ThrowIfCancellationRequested();
+
+ var viewForBaseType = IViewForBaseType.None;
+ if (classSymbol.InheritsFromFullyQualifiedMetadataNameStartingWith("System.Windows.Forms"))
+ {
+ viewForBaseType = IViewForBaseType.WinForms;
+ }
+ else if (classSymbol.InheritsFromFullyQualifiedMetadataNameStartingWith("System.Windows") || classSymbol.InheritsFromFullyQualifiedMetadataNameStartingWith("System.Windows.Controls"))
+ {
+ viewForBaseType = IViewForBaseType.Wpf;
+ }
+ else if (classSymbol.InheritsFromFullyQualifiedMetadataNameStartingWith("Microsoft.UI.Xaml") || classSymbol.InheritsFromFullyQualifiedMetadataNameStartingWith("Microsoft.UI.Xaml.Controls"))
+ {
+ viewForBaseType = IViewForBaseType.WinUI;
+ }
+ else if (classSymbol.InheritsFromFullyQualifiedMetadataNameStartingWith("Microsoft.Maui"))
{
- UsingDirectiveSyntax[] usings =
- [
- UsingDirective(ParseName("ReactiveUI")),
- UsingDirective(ParseName("System.ComponentModel")),
- ];
-
- var code = CompilationUnit().AddMembers(
- NamespaceDeclaration(IdentifierName(iViewForInfo.ClassNamespace))
- .WithLeadingTrivia(TriviaList(
- Comment("// "),
- Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)),
- Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true))))
- .AddMembers(
- ClassDeclaration(iViewForInfo.ClassName)
- .AddBaseListTypes(
- SimpleBaseType(
- GenericName(Identifier("IViewFor"))
- .WithTypeArgumentList(
- TypeArgumentList(
- SingletonSeparatedList(
- IdentifierName(iViewForInfo.ViewModelTypeName))))))
- .AddModifiers([.. iViewForInfo.DeclarationSyntax.Modifiers])
- .AddAttributeLists(AttributeList(SingletonSeparatedList(
- Attribute(IdentifierName(AttributeDefinitions.GeneratedCode))
- .AddArgumentListArguments(
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).FullName))),
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).Assembly.GetName().Version.ToString())))))))))
- .WithUsings(List(usings))
- .NormalizeWhitespace().ToFullString();
-
- // Remove the last 4 characters to remove the closing brackets
- var baseCode = code.Remove(code.Length - 4);
-
- // Prepare all necessary type names with type arguments
- using var stringStream = new StringWriter();
- using var writer = new IndentedTextWriter(stringStream, "\t");
- writer.WriteLine(baseCode);
- writer.Indent++;
- writer.Indent++;
-
- // Add the necessary properties and methods for IViewFor.
- writer.WriteLine("/// ");
- writer.WriteLine("[Category(\"ReactiveUI\")]");
- writer.WriteLine("[Description(\"The ViewModel.\")]");
- writer.WriteLine("[Bindable(true)]");
- writer.WriteLine("[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]");
- writer.WriteLine($"public {iViewForInfo.ViewModelTypeName}? ViewModel " + "{ get; set; }");
- writer.WriteLine();
-
- writer.WriteLine("/// ");
- writer.WriteLine("object? IViewFor.ViewModel");
- writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
- writer.Indent++;
- writer.WriteLine("get => ViewModel;");
- writer.WriteLine($"set => ViewModel = ({iViewForInfo.ViewModelTypeName}?)value;");
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.WriteLine(TriviaList(
- Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)),
- Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))
- .NormalizeWhitespace());
-
- var output = stringStream.ToString();
- return ParseCompilationUnit(output).NormalizeWhitespace();
+ viewForBaseType = IViewForBaseType.Maui;
}
+ else if (classSymbol.InheritsFromFullyQualifiedMetadataNameStartingWith("Avalonia"))
+ {
+ viewForBaseType = IViewForBaseType.Avalonia;
+ }
+ else if (classSymbol.InheritsFromFullyQualifiedMetadataNameStartingWith("Windows.UI.Xaml") || classSymbol.InheritsFromFullyQualifiedMetadataNameStartingWith("Windows.UI.Xaml.Controls"))
+ {
+ viewForBaseType = IViewForBaseType.Uno;
+ }
+
+ // Get the containing type info
+ var targetInfo = TargetInfo.From(classSymbol);
+
+ token.ThrowIfCancellationRequested();
+
+ return new IViewForInfo(
+ targetInfo.FileHintName,
+ targetInfo.TargetName,
+ targetInfo.TargetNamespace,
+ targetInfo.TargetNamespaceWithNamespace,
+ targetInfo.TargetVisibility,
+ targetInfo.TargetType,
+ viewModelTypeName!,
+ viewForBaseType,
+ forwardedClassAttributes);
+ }
+
+ private static string GenerateSource(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, IViewForInfo iviewForInfo)
+ {
+ // Prepare any forwarded property attributes
+ var forwardedAttributesString = string.Join("\n\t\t", excludeFromCodeCoverage.Concat(iviewForInfo.ForwardedAttributes));
- internal static CompilationUnitSyntax GetIViewForAvalonia(IViewForInfo iViewForInfo)
+ switch (iviewForInfo.BaseType)
{
- UsingDirectiveSyntax[] usings =
- [
- UsingDirective(ParseName("System")),
- UsingDirective(ParseName("ReactiveUI")),
- UsingDirective(ParseName("Avalonia")),
- UsingDirective(ParseName("Avalonia.Controls")),
- ];
-
- var code = CompilationUnit().AddMembers(
- NamespaceDeclaration(IdentifierName(iViewForInfo.ClassNamespace))
- .WithLeadingTrivia(TriviaList(
- Comment("// "),
- Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)),
- Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true))))
- .AddMembers(
- ClassDeclaration(iViewForInfo.ClassName)
- .AddBaseListTypes(
- SimpleBaseType(
- GenericName(Identifier("IViewFor"))
- .WithTypeArgumentList(
- TypeArgumentList(
- SingletonSeparatedList(
- IdentifierName(iViewForInfo.ViewModelTypeName))))))
- .AddModifiers([.. iViewForInfo.DeclarationSyntax.Modifiers])
- .AddAttributeLists(AttributeList(SingletonSeparatedList(
- Attribute(IdentifierName(AttributeDefinitions.GeneratedCode))
- .AddArgumentListArguments(
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).FullName))),
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).Assembly.GetName().Version.ToString())))))))))
- .WithUsings(List(usings))
- .NormalizeWhitespace().ToFullString();
-
- // Remove the last 4 characters to remove the closing brackets
- var baseCode = code.Remove(code.Length - 4);
-
- // Prepare all necessary type names with type arguments
- using var stringStream = new StringWriter();
- using var writer = new IndentedTextWriter(stringStream, "\t");
- writer.WriteLine(baseCode);
- writer.Indent++;
- writer.Indent++;
-
- // Add the necessary properties and methods for IViewFor.
- writer.WriteLine("/// ");
- writer.WriteLine("/// The view model dependency property.");
- writer.WriteLine("/// ");
- writer.WriteLine("[System.Diagnostics.CodeAnalysis.SuppressMessage(\"AvaloniaProperty\", \"AVP1002\", Justification = \"Generic avalonia property is expected here.\")]");
- writer.WriteLine($"public static readonly StyledProperty<{iViewForInfo.ViewModelTypeName}?> ViewModelProperty =");
- writer.Indent++;
- writer.WriteLine("AvaloniaProperty");
- writer.WriteLine($".Register<{iViewForInfo.ClassName}, {iViewForInfo.ViewModelTypeName}?>(nameof(ViewModel));");
-
- writer.Indent--;
- writer.WriteLine("/// ");
- writer.WriteLine("/// Gets the binding root view model.");
- writer.WriteLine("/// ");
- writer.WriteLine($"public {iViewForInfo.ViewModelTypeName}? BindingRoot => ViewModel;");
- writer.WriteLine();
-
- writer.WriteLine("/// ");
- writer.WriteLine($"public {iViewForInfo.ViewModelTypeName}? ViewModel");
- writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
- writer.Indent++;
- writer.WriteLine($"get => ({iViewForInfo.ViewModelTypeName}?)GetValue(ViewModelProperty);");
- writer.WriteLine("set => SetValue(ViewModelProperty, value);");
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.WriteLine();
-
- writer.WriteLine("/// ");
- writer.WriteLine("object? IViewFor.ViewModel");
- writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
- writer.Indent++;
- writer.WriteLine("get => ViewModel;");
- writer.WriteLine($"set => ViewModel = ({iViewForInfo.ViewModelTypeName}?)value;");
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.WriteLine();
- writer.WriteLine(
- $$"""
-
- protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
- {
- base.OnPropertyChanged(change);
-
- if (change.Property == DataContextProperty)
- {
- if (ReferenceEquals(change.OldValue, ViewModel)
- && change.NewValue is null or {{iViewForInfo.ViewModelTypeName}})
- {
- SetCurrentValue(ViewModelProperty, change.NewValue);
- }
- }
- else if (change.Property == ViewModelProperty)
- {
- if (ReferenceEquals(change.OldValue, DataContext))
- {
- SetCurrentValue(DataContextProperty, change.NewValue);
- }
- }
- }
-
- """);
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.WriteLine(TriviaList(
- Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)),
- Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))
- .NormalizeWhitespace());
-
- var output = stringStream.ToString();
- return ParseCompilationUnit(output).NormalizeWhitespace();
+ case IViewForBaseType.None:
+ break;
+ case IViewForBaseType.Wpf:
+ case IViewForBaseType.WinUI:
+ case IViewForBaseType.Uno:
+ var usings = iviewForInfo.BaseType switch
+ {
+ IViewForBaseType.Wpf => """
+ using ReactiveUI;
+ using System.Windows;
+ """,
+ IViewForBaseType.WinUI => """
+ using ReactiveUI;
+ using Microsoft.UI.Xaml;
+ """,
+ IViewForBaseType.Uno => """
+ using ReactiveUI;
+ using Windows.UI.Xaml;
+ """,
+ _ => string.Empty,
+ };
+ return
+$$"""
+//
+{{usings}}
+
+#pragma warning disable
+#nullable enable
+
+namespace {{containingNamespace}}
+{
+ ///
+ /// Partial class for the {{containingTypeName}} which contains ReactiveUI IViewFor initialization.
+ ///
+ {{forwardedAttributesString}}
+ {{containingClassVisibility}} partial {{containingType}} {{containingTypeName}} : IViewFor<{{iviewForInfo.ViewModelTypeName}}>
+ {
+ ///
+ /// The view model dependency property.
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("{{GeneratorName}}", "{{GeneratorVersion}}")]
+ public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(nameof(ViewModel), typeof({{iviewForInfo.ViewModelTypeName}}), typeof({{containingTypeName}}), new PropertyMetadata(null));
+
+ ///
+ /// Gets the binding root view model.
+ ///
+ public {{iviewForInfo.ViewModelTypeName}} BindingRoot => ViewModel;
+
+ ///
+ public {{iviewForInfo.ViewModelTypeName}} ViewModel { get => ({{iviewForInfo.ViewModelTypeName}})GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); }
+
+ ///
+ object? IViewFor.ViewModel { get => ViewModel; set => ViewModel = ({{iviewForInfo.ViewModelTypeName}})value; }
+ }
+}
+#nullable restore
+#pragma warning restore
+""";
+ case IViewForBaseType.WinForms:
+ return
+$$"""
+//
+using ReactiveUI;
+using System.ComponentModel;
+#nullable restore
+#pragma warning disable
+
+namespace {{containingNamespace}}
+{
+ ///
+ /// Partial class for the {{containingTypeName}} which contains ReactiveUI IViewFor initialization.
+ ///
+ {{forwardedAttributesString}}
+ partial class {{containingTypeName}} : IViewFor<{{iviewForInfo.ViewModelTypeName}}>
+ {
+ ///
+ [Category("ReactiveUI")]
+ [Description("The ViewModel.")]
+ [Bindable(true)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ [global::System.CodeDom.Compiler.GeneratedCode("{{GeneratorName}}", "{{GeneratorVersion}}")]
+ public {{iviewForInfo.ViewModelTypeName}}? ViewModel {get; set; }
+
+ ///
+ object? IViewFor.ViewModel {get => ViewModel; set => ViewModel = ({{iviewForInfo.ViewModelTypeName}}? )value; }
+ }
+}
+#nullable restore
+#pragma warning restore
+""";
+ case IViewForBaseType.Avalonia:
+ return
+$$"""
+//
+using System;
+using ReactiveUI;
+using Avalonia;
+using Avalonia.Controls;
+#nullable restore
+#pragma warning disable
+
+namespace {{containingNamespace}}
+{
+ ///
+ /// Partial class for the {{containingTypeName}} which contains ReactiveUI IViewFor initialization.
+ ///
+ {{forwardedAttributesString}}
+ public partial class {{containingTypeName}} : IViewFor<{{iviewForInfo.ViewModelTypeName}}>
+ {
+ ///
+ /// The view model dependency property.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1002", Justification = "Generic avalonia property is expected here.")]
+ public static readonly StyledProperty<{{iviewForInfo.ViewModelTypeName}}?> ViewModelProperty = AvaloniaProperty.Register<{{containingTypeName}}, {{iviewForInfo.ViewModelTypeName}}>(nameof(ViewModel));
+
+ ///
+ /// Gets the binding root view model.
+ ///
+ public {{iviewForInfo.ViewModelTypeName}}? BindingRoot => ViewModel;
+
+ ///
+ public {{iviewForInfo.ViewModelTypeName}}? ViewModel { get => ({{iviewForInfo.ViewModelTypeName}}?)GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); }
+
+ ///
+ object? IViewFor.ViewModel { get => ViewModel; set => ViewModel = ({{iviewForInfo.ViewModelTypeName}}?)value; }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == DataContextProperty)
+ {
+ if (ReferenceEquals(change.OldValue, ViewModel) && change.NewValue is null or {{iviewForInfo.ViewModelTypeName}})
+ {
+ SetCurrentValue(ViewModelProperty, change.NewValue);
+ }
+ }
+ else if (change.Property == ViewModelProperty)
+ {
+ if (ReferenceEquals(change.OldValue, DataContext))
+ {
+ SetCurrentValue(DataContextProperty, change.NewValue);
+ }
+ }
}
+ }
+}
+#nullable restore
+#pragma warning restore
+""";
+ case IViewForBaseType.Maui:
+ return
+$$"""
+//
+using System;
+using ReactiveUI;
+using Microsoft.Maui.Controls;
+#nullable restore
+#pragma warning disable
+
+namespace {{containingNamespace}}
+{
+ ///
+ /// Partial class for the {{containingTypeName}} which contains ReactiveUI IViewFor initialization.
+ ///
+ {{forwardedAttributesString}}
+ public partial class {{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);
- internal static CompilationUnitSyntax GetIViewForMaui(IViewForInfo iViewForInfo)
+ ///
+ /// Gets the binding root view model.
+ ///
+ public {{iviewForInfo.ViewModelTypeName}}? BindingRoot => ViewModel;
+
+ ///
+ public {{iviewForInfo.ViewModelTypeName}}? ViewModel { get => ({{iviewForInfo.ViewModelTypeName}}?)GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); }
+
+ ///
+ object? IViewFor.ViewModel { get => ViewModel; set => ViewModel = ({{iviewForInfo.ViewModelTypeName}}?)value; }
+
+ ///
+ protected override void OnBindingContextChanged()
{
- UsingDirectiveSyntax[] usings =
- [
- UsingDirective(ParseName("System")),
- UsingDirective(ParseName("ReactiveUI")),
- UsingDirective(ParseName("Microsoft.Maui.Controls")),
- ];
-
- var code = CompilationUnit().AddMembers(
- NamespaceDeclaration(IdentifierName(iViewForInfo.ClassNamespace))
- .WithLeadingTrivia(TriviaList(
- Comment("// "),
- Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)),
- Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true))))
- .AddMembers(
- ClassDeclaration(iViewForInfo.ClassName)
- .AddBaseListTypes(
- SimpleBaseType(
- GenericName(Identifier("IViewFor"))
- .WithTypeArgumentList(
- TypeArgumentList(
- SingletonSeparatedList(
- IdentifierName(iViewForInfo.ViewModelTypeName))))))
- .AddModifiers([.. iViewForInfo.DeclarationSyntax.Modifiers])
- .AddAttributeLists(AttributeList(SingletonSeparatedList(
- Attribute(IdentifierName(AttributeDefinitions.GeneratedCode))
- .AddArgumentListArguments(
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).FullName))),
- AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).Assembly.GetName().Version.ToString())))))))))
- .WithUsings(List(usings))
- .NormalizeWhitespace().ToFullString();
-
- // Remove the last 4 characters to remove the closing brackets
- var baseCode = code.Remove(code.Length - 4);
-
- // Prepare all necessary type names with type arguments
- using var stringStream = new StringWriter();
- using var writer = new IndentedTextWriter(stringStream, "\t");
- writer.WriteLine(baseCode);
- writer.Indent++;
- writer.Indent++;
-
- // Add the necessary properties and methods for IViewFor.
- writer.WriteLine("public static readonly BindableProperty ViewModelProperty =");
- writer.WriteLine("BindableProperty.Create(");
- writer.WriteLine("nameof(ViewModel),");
- writer.WriteLine($"typeof({iViewForInfo.ViewModelTypeName}),");
- writer.WriteLine($"typeof(IViewFor<{iViewForInfo.ViewModelTypeName}>),");
- writer.WriteLine($"default({iViewForInfo.ViewModelTypeName}),");
- writer.WriteLine("BindingMode.OneWay,");
- writer.WriteLine("propertyChanged: OnViewModelChanged);");
- writer.WriteLine();
-
- writer.Indent--;
- writer.WriteLine("/// ");
- writer.WriteLine("/// Gets the binding root view model.");
- writer.WriteLine("/// ");
- writer.WriteLine($"public {iViewForInfo.ViewModelTypeName}? BindingRoot => ViewModel;");
- writer.WriteLine();
-
- writer.WriteLine("/// ");
- writer.WriteLine($"public {iViewForInfo.ViewModelTypeName}? ViewModel");
- writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
- writer.Indent++;
- writer.WriteLine($"get => ({iViewForInfo.ViewModelTypeName}?)GetValue(ViewModelProperty);");
- writer.WriteLine("set => SetValue(ViewModelProperty, value);");
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.WriteLine();
-
- writer.WriteLine("/// ");
- writer.WriteLine("object? IViewFor.ViewModel");
- writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
- writer.Indent++;
- writer.WriteLine("get => ViewModel;");
- writer.WriteLine($"set => ViewModel = ({iViewForInfo.ViewModelTypeName}?)value;");
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
-
- writer.WriteLine();
- writer.WriteLine("/// ");
- writer.WriteLine("protected override void OnBindingContextChanged()");
- writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
- writer.WriteLine("base.OnBindingContextChanged();");
- writer.WriteLine($"ViewModel = BindingContext as {iViewForInfo.ViewModelTypeName};");
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
-
- writer.WriteLine("private static void OnViewModelChanged(BindableObject bindableObject, object oldValue, object newValue) => bindableObject.BindingContext = newValue;");
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.Indent--;
- writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
- writer.WriteLine(TriviaList(
- Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)),
- Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))
- .NormalizeWhitespace());
-
- var output = stringStream.ToString();
- return ParseCompilationUnit(output).NormalizeWhitespace();
+ base.OnBindingContextChanged();
+ ViewModel = BindingContext as {{iviewForInfo.ViewModelTypeName}};
+ }
+
+ private static void OnViewModelChanged(BindableObject bindableObject, object oldValue, object newValue) => bindableObject.BindingContext = newValue;
+ }
+}
+#nullable restore
+#pragma warning restore
+""";
}
+
+ return string.Empty;
}
}
diff --git a/src/ReactiveUI.SourceGenerators/IViewFor/IViewForGenerator.cs b/src/ReactiveUI.SourceGenerators/IViewFor/IViewForGenerator.cs
index 4107559..66f2977 100644
--- a/src/ReactiveUI.SourceGenerators/IViewFor/IViewForGenerator.cs
+++ b/src/ReactiveUI.SourceGenerators/IViewFor/IViewForGenerator.cs
@@ -6,15 +6,11 @@
using System.Collections.Immutable;
using System.Linq;
using System.Text;
-using System.Threading;
using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using ReactiveUI.SourceGenerators.Extensions;
using ReactiveUI.SourceGenerators.Helpers;
-using ReactiveUI.SourceGenerators.Input.Models;
-using ReactiveUI.SourceGenerators.Models;
namespace ReactiveUI.SourceGenerators;
@@ -31,174 +27,41 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
ctx.AddSource($"{AttributeDefinitions.IViewForAttributeType}.g.cs", SourceText.From(AttributeDefinitions.IViewForAttribute, Encoding.UTF8)));
// Gather info for all annotated IViewFor Classes
- IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result Info)> iViewForInfoWithErrors =
+ var iViewForInfo =
context.SyntaxProvider
.ForAttributeWithMetadataNameInternal(
AttributeDefinitions.IViewForAttributeType,
static (node, _) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0 },
- static (context, token) =>
- {
- token.ThrowIfCancellationRequested();
- using var hierarchys = ImmutableArrayBuilder.Rent();
- IViewForInfo iViewForInfo = default!;
- HierarchyInfo hierarchy = default!;
-
- if (context.TargetNode is ClassDeclarationSyntax declaredClass && declaredClass.Modifiers.Any(SyntaxKind.PartialKeyword))
- {
- token.ThrowIfCancellationRequested();
- var compilation = context.SemanticModel.Compilation;
- var semanticModel = compilation.GetSemanticModel(context.SemanticModel.SyntaxTree);
- var symbol = ModelExtensions.GetDeclaredSymbol(semanticModel, declaredClass, token)!;
- if (symbol.TryGetAttributeWithFullyQualifiedMetadataName(AttributeDefinitions.IViewForAttributeType, out var attributeData))
- {
- token.ThrowIfCancellationRequested();
- var classSymbol = symbol as INamedTypeSymbol;
- var classNamespace = classSymbol?.ContainingNamespace.ToString();
- var className = declaredClass.Identifier.ValueText;
- token.ThrowIfCancellationRequested();
-
- var genericArgument = GetGenericType(attributeData);
- token.ThrowIfCancellationRequested();
- if (genericArgument is string viewModelTypeName && viewModelTypeName.Length > 0)
- {
- token.ThrowIfCancellationRequested();
- GatherForwardedAttributes(attributeData, semanticModel, declaredClass, token, out var classAttributesInfo);
- token.ThrowIfCancellationRequested();
-
- var viewForBaseType = IViewForBaseType.None;
- if (classSymbol?.InheritsFromFullyQualifiedMetadataNameStartingWith("System.Windows.Forms") == true)
- {
- viewForBaseType = IViewForBaseType.WinForms;
- }
- else if (classSymbol?.InheritsFromFullyQualifiedMetadataNameStartingWith("System.Windows") == true || classSymbol?.InheritsFromFullyQualifiedMetadataNameStartingWith("System.Windows.Controls") == true)
- {
- viewForBaseType = IViewForBaseType.Wpf;
- }
- else if (classSymbol?.InheritsFromFullyQualifiedMetadataNameStartingWith("Microsoft.UI.Xaml") == true || classSymbol?.InheritsFromFullyQualifiedMetadataNameStartingWith("Microsoft.UI.Xaml.Controls") == true)
- {
- viewForBaseType = IViewForBaseType.WinUI;
- }
- else if (classSymbol?.InheritsFromFullyQualifiedMetadataNameStartingWith("Microsoft.Maui") == true)
- {
- viewForBaseType = IViewForBaseType.Maui;
- }
- else if (classSymbol?.InheritsFromFullyQualifiedMetadataNameStartingWith("Avalonia") == true)
- {
- viewForBaseType = IViewForBaseType.Avalonia;
- }
- else if (classSymbol?.InheritsFromFullyQualifiedMetadataNameStartingWith("Windows.UI.Xaml") == true || classSymbol?.InheritsFromFullyQualifiedMetadataNameStartingWith("Windows.UI.Xaml.Controls") == true)
- {
- viewForBaseType = IViewForBaseType.Uno;
- }
-
- iViewForInfo = new IViewForInfo(
- classNamespace!,
- className,
- viewModelTypeName!,
- viewForBaseType,
- declaredClass,
- classAttributesInfo);
-
- hierarchy = HierarchyInfo.From(classSymbol!);
- }
- }
- }
-
- token.ThrowIfCancellationRequested();
- ImmutableArray diagnostics = default;
- return (Hierarchy: hierarchy, new Result(iViewForInfo, diagnostics));
- })
- .Where(static item => item.Hierarchy is not null)!;
-
- ////// Output the diagnostics
- ////context.ReportDiagnostics(iViewForInfoWithErrors.Select(static (item, _) => item.Info.Errors));
-
- // Get the filtered sequence to enable caching
- var iViewForInfo =
- iViewForInfoWithErrors
- .Where(static item => item.Info.Value is not null)!;
+ static (context, token) => GetClassInfo(context, token))
+ .Where(x => x != null)
+ .Select((x, _) => x!)
+ .Collect();
// Generate the requested properties and methods for IViewFor
- context.RegisterSourceOutput(iViewForInfo, static (context, item) =>
+ context.RegisterSourceOutput(iViewForInfo, static (context, input) =>
{
- switch (item.Info.Value.BaseType)
+ var groupedPropertyInfo = input.GroupBy(
+ static info => (info.FileHintName, info.TargetName, info.TargetNamespace, info.TargetVisibility, info.TargetType),
+ static info => info)
+ .ToImmutableArray();
+
+ if (groupedPropertyInfo.Length == 0)
{
- case IViewForBaseType.None:
- break;
- case IViewForBaseType.Wpf:
- case IViewForBaseType.WinUI:
- case IViewForBaseType.Uno:
- context.AddSource($"{item.Hierarchy.FilenameHint}.IViewFor.g.cs", Execute.GetIViewForWpfWinUiUno(item.Info.Value));
- break;
- case IViewForBaseType.WinForms:
- context.AddSource($"{item.Hierarchy.FilenameHint}.IViewFor.g.cs", Execute.GetIViewForWinForms(item.Info.Value));
- break;
- case IViewForBaseType.Avalonia:
- context.AddSource($"{item.Hierarchy.FilenameHint}.IViewFor.g.cs", Execute.GetIViewForAvalonia(item.Info.Value));
- break;
- case IViewForBaseType.Maui:
- context.AddSource($"{item.Hierarchy.FilenameHint}.IViewFor.g.cs", Execute.GetIViewForMaui(item.Info.Value));
- break;
+ return;
}
- });
- }
- private static void GatherForwardedAttributes(
- AttributeData attributeData,
- SemanticModel semanticModel,
- ClassDeclarationSyntax classDeclaration,
- CancellationToken token,
- out ImmutableArray classAttributesInfo)
- {
- using var classAttributesInfoBuilder = ImmutableArrayBuilder.Rent();
-
- static void GatherForwardedAttributes(
- AttributeData attributeData,
- SemanticModel semanticModel,
- ClassDeclarationSyntax classDeclaration,
- CancellationToken token,
- ImmutableArrayBuilder classAttributesInfo)
- {
- // Gather explicit forwarded attributes info
- foreach (var attributeList in classDeclaration.AttributeLists)
+ foreach (var grouping in groupedPropertyInfo)
{
- foreach (var attribute in attributeList.Attributes)
- {
- if (!semanticModel.GetSymbolInfo(attribute, token).TryGetAttributeTypeSymbol(out var attributeTypeSymbol))
- {
- continue;
- }
-
- var attributeArguments = attribute.ArgumentList?.Arguments ?? Enumerable.Empty();
+ var items = grouping.ToImmutableArray();
- // Try to extract the forwarded attribute
- if (!AttributeInfo.TryCreate(attributeTypeSymbol, semanticModel, attributeArguments, token, out var attributeInfo))
- {
- continue;
- }
-
- var ignoreAttribute = attributeData.AttributeClass?.GetFullyQualifiedMetadataName();
- if (attributeInfo.TypeName.Contains(ignoreAttribute))
- {
- continue;
- }
-
- // Add the new attribute info to the right builder
- classAttributesInfo.Add(attributeInfo);
+ if (items.Length == 0)
+ {
+ continue;
}
- }
- }
-
- // If the method is not a partial definition/implementation, just gather attributes from the method with no modifications
- GatherForwardedAttributes(attributeData, semanticModel, classDeclaration, token, classAttributesInfoBuilder);
-
- classAttributesInfo = classAttributesInfoBuilder.ToImmutable();
- }
- private static string? GetGenericType(AttributeData attributeData)
- {
- var success = attributeData?.AttributeClass?.ToDisplayString();
- var start = success?.IndexOf('<') + 1 ?? 0;
- return success?.Substring(start, success.Length - start - 1);
+ var source = GenerateSource(grouping.Key.TargetName, grouping.Key.TargetNamespace, grouping.Key.TargetVisibility, grouping.Key.TargetType, grouping.FirstOrDefault());
+ context.AddSource($"{grouping.Key.FileHintName}.IViewFor.g.cs", source);
+ }
+ });
}
}
diff --git a/src/ReactiveUI.SourceGenerators/IViewFor/Models/IViewForInfo.cs b/src/ReactiveUI.SourceGenerators/IViewFor/Models/IViewForInfo.cs
index 4a2fc98..205d789 100644
--- a/src/ReactiveUI.SourceGenerators/IViewFor/Models/IViewForInfo.cs
+++ b/src/ReactiveUI.SourceGenerators/IViewFor/Models/IViewForInfo.cs
@@ -3,7 +3,6 @@
// 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.CSharp.Syntax;
using ReactiveUI.SourceGenerators.Helpers;
namespace ReactiveUI.SourceGenerators.Input.Models;
@@ -12,9 +11,12 @@ namespace ReactiveUI.SourceGenerators.Input.Models;
/// A model with gathered info on a given command method.
///
internal sealed record IViewForInfo(
- string ClassNamespace,
- string ClassName,
+ string FileHintName,
+ string TargetName,
+ string TargetNamespace,
+ string TargetNamespaceWithNamespace,
+ string TargetVisibility,
+ string TargetType,
string ViewModelTypeName,
IViewForBaseType BaseType,
- TypeDeclarationSyntax DeclarationSyntax,
- EquatableArray ForwardedAttributes);
+ EquatableArray ForwardedAttributes);