From 150781086aea4e453dab19e52a7a0ea8559cde89 Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Wed, 30 Oct 2024 21:13:45 +0000 Subject: [PATCH] Update IViewFor Generator for Performance (#100) * Update IViewFor Generator for Performance * Remove test apps --- src/ReactiveUI.SourceGenerators.sln | 2 +- .../AnalyzerReleases.Shipped.md | 88 +-- .../AnalyzerReleases.Unshipped.md | 3 +- .../Extensions/AttributeDataExtensions.cs | 72 ++ .../IViewFor/IViewForGenerator.Execute.cs | 679 ++++++++---------- .../IViewFor/IViewForGenerator.cs | 181 +---- .../IViewFor/Models/IViewForInfo.cs | 12 +- 7 files changed, 433 insertions(+), 604 deletions(-) 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);