From 815e5a02855522d51434e9bdf132ef0d1e99ffab Mon Sep 17 00:00:00 2001 From: Ryan Davis Date: Sun, 13 Nov 2022 19:08:47 +1100 Subject: [PATCH] feat: add automatic addition of registerattribute to nsobject subtypes --- .../IncrementalCompiler.cs | 25 +++++++-- ...iOSDynamicRegistrationAttributeRewriter.cs | 56 +++++++++++++++++++ .../Config/AssemblyCompilationOptions.cs | 1 + .../Config/iOSDynamicRegistrationOptions.cs | 6 ++ 4 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 src/components/tbc.host/Components/IncrementalCompiler/iOSDynamicRegistrationAttributeRewriter.cs create mode 100644 src/components/tbc.host/Config/iOSDynamicRegistrationOptions.cs diff --git a/src/components/tbc.host/Components/IncrementalCompiler/IncrementalCompiler.cs b/src/components/tbc.host/Components/IncrementalCompiler/IncrementalCompiler.cs index 2da5d5b..5803785 100644 --- a/src/components/tbc.host/Components/IncrementalCompiler/IncrementalCompiler.cs +++ b/src/components/tbc.host/Components/IncrementalCompiler/IncrementalCompiler.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Emit; using Microsoft.Extensions.Logging; +using Roslyn.Reflection; using Tbc.Core.Models; using Tbc.Host.Components.Abstractions; using Tbc.Host.Components.CommandProcessor.Models; @@ -122,10 +123,6 @@ public EmittedAssembly StageFile(ChangedFile file, bool silent = false) { var sw = Stopwatch.StartNew(); - file.Contents = file.Contents.Replace( - "_MYGUID_MYGUID_MYGUID_MYGUID_MYGUID_", - Guid.NewGuid().ToString().Replace("-", "_")); - var syntaxTree = CSharpSyntaxTree.ParseText( file.Contents, @@ -174,7 +171,25 @@ public EmittedAssembly StageFile(ChangedFile file, bool silent = false) } var compilation = (CSharpCompilation)(sourceGeneratedCompilation ?? newCompilation); - var result = EmitAssembly(sourceGeneratedCompilation ?? newCompilation, out emittedAssembly); + + if (_options.iOSDynamicRegistrationOptions.Enabled) + { + var dynamicRegistrationCompilation = compilation; + foreach (var tree in compilation.SyntaxTrees) + { + var rewriter = new iOSDynamicRegistrationAttributeRewriter( + new MetadataLoadContext(dynamicRegistrationCompilation), + dynamicRegistrationCompilation.GetSemanticModel(tree, ignoreAccessibility: true) + ); + + dynamicRegistrationCompilation = dynamicRegistrationCompilation + .ReplaceSyntaxTree(tree, SyntaxFactory.SyntaxTree(rewriter.Visit(tree.GetRoot()), tree.Options, tree.FilePath, tree.Encoding)); + } + + compilation = dynamicRegistrationCompilation; + } + + var result = EmitAssembly(compilation, out emittedAssembly); if (!result.Success && _options.FixerOptions.Enabled diff --git a/src/components/tbc.host/Components/IncrementalCompiler/iOSDynamicRegistrationAttributeRewriter.cs b/src/components/tbc.host/Components/IncrementalCompiler/iOSDynamicRegistrationAttributeRewriter.cs new file mode 100644 index 0000000..fabb924 --- /dev/null +++ b/src/components/tbc.host/Components/IncrementalCompiler/iOSDynamicRegistrationAttributeRewriter.cs @@ -0,0 +1,56 @@ +using System; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Reflection; + +namespace Tbc.Host.Components.IncrementalCompiler; + +// adds a unique [Register] attribute to every nsobject-derived type +public class iOSDynamicRegistrationAttributeRewriter : CSharpSyntaxRewriter +{ + private readonly MetadataLoadContext _metadataLoadContext; + private readonly SemanticModel _semanticModel; + private readonly Type _nsObject; + + public iOSDynamicRegistrationAttributeRewriter(MetadataLoadContext metadataLoadContext, SemanticModel semanticModel) + { + _metadataLoadContext = metadataLoadContext; + _semanticModel = semanticModel; + + _nsObject = _metadataLoadContext.ResolveType("Foundation.NSObject"); + } + + public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) + { + var symbol = _semanticModel.GetDeclaredSymbolForNode(node); + if (symbol is null) return base.VisitClassDeclaration(node); + + var symbolReference = $"{symbol.ContainingNamespace}.{symbol.MetadataName}"; + var type = _metadataLoadContext.ResolveType(symbolReference); + if (type is null) + return base.VisitClassDeclaration(node); + + if (type.IsSubclassOf(_nsObject)) + { + var attributeArgument = + SyntaxFactory.AttributeList + (SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Register"), + SyntaxFactory.AttributeArgumentList( + SyntaxFactory.SeparatedList(new[] { + SyntaxFactory.AttributeArgument( + SyntaxFactory.LiteralExpression( + SyntaxKind.StringLiteralExpression, + SyntaxFactory.Literal($"{symbol.MetadataName}_{Guid.NewGuid()}")) + ) + }))))); + + var updatedNode = node.WithAttributeLists(node.AttributeLists.Add(attributeArgument)); + + return updatedNode; + } + + return base.VisitClassDeclaration(node); + } +} diff --git a/src/components/tbc.host/Config/AssemblyCompilationOptions.cs b/src/components/tbc.host/Config/AssemblyCompilationOptions.cs index a1a1bbc..f863655 100644 --- a/src/components/tbc.host/Config/AssemblyCompilationOptions.cs +++ b/src/components/tbc.host/Config/AssemblyCompilationOptions.cs @@ -17,5 +17,6 @@ public class AssemblyCompilationOptions public List SourceGeneratorReferences { get; set; } = new(); public List GlobalUsingsSources { get; set; } = new(); public AssemblyFixerOptions FixerOptions { get; set; } = new() { Enabled = true }; + public iOSDynamicRegistrationOptions iOSDynamicRegistrationOptions { get; set; } = new(); } } diff --git a/src/components/tbc.host/Config/iOSDynamicRegistrationOptions.cs b/src/components/tbc.host/Config/iOSDynamicRegistrationOptions.cs new file mode 100644 index 0000000..7897062 --- /dev/null +++ b/src/components/tbc.host/Config/iOSDynamicRegistrationOptions.cs @@ -0,0 +1,6 @@ +namespace Tbc.Host.Config; + +public class iOSDynamicRegistrationOptions +{ + public bool Enabled { get; set; } +} \ No newline at end of file