using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using ImmersiveVrToolsCommon.Runtime.Logging; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace FastScriptReload.Editor.Compilation.CodeRewriting { class CreateNewFieldInitMethodRewriter: FastScriptReloadCodeRewriterBase { private readonly Dictionary> _typeToNewFieldDeclarations; private static readonly string NewFieldsToCreateValueFnDictionaryFieldName = "__Patched_NewFieldNameToInitialValueFn"; private static readonly string NewFieldsToGetTypeFnDictionaryFieldName = "__Patched_NewFieldsToGetTypeFnDictionaryFieldName"; private static readonly string DictionaryFullNamespaceTypeName = "System.Collections.Generic.Dictionary"; public static Dictionary> ResolveNewFieldsToCreateValueFn(Type forType) { return (Dictionary>) forType.GetField(NewFieldsToCreateValueFnDictionaryFieldName, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); } public static Dictionary> ResolveNewFieldsToTypeFn(Type forType) { return (Dictionary>) forType.GetField(NewFieldsToGetTypeFnDictionaryFieldName, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); } public CreateNewFieldInitMethodRewriter(Dictionary> typeToNewFieldDeclarations, bool writeRewriteReasonAsComment) :base(writeRewriteReasonAsComment) { _typeToNewFieldDeclarations = typeToNewFieldDeclarations; } public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) { var fullClassName = RoslynUtils.GetMemberFQDN(node, node.Identifier.ToString()); if (!_typeToNewFieldDeclarations.TryGetValue(fullClassName, out var newClassFields)) { LoggerScoped.LogWarning($"Unable to find new-fields for type: {fullClassName}, this is not an issue if there are no new fields for that type."); } Func getObjectFnSyntax = fieldDeclarationNode => fieldDeclarationNode.Declaration.Variables[0].Initializer?.Value //value captured from initializer ?? SyntaxFactory.DefaultExpression(SyntaxFactory.IdentifierName(fieldDeclarationNode.Declaration.Type.ToString())); var withDictionaryFieldNameToInitFieldValue = CreateNewFieldNameToGetObjectFnDictionary(node, newClassFields, getObjectFnSyntax, NewFieldsToCreateValueFnDictionaryFieldName); Func getObjectTypeFnSyntax = fieldDeclarationNode => SyntaxFactory.TypeOfExpression(fieldDeclarationNode.Declaration.Type); return CreateNewFieldNameToGetObjectFnDictionary(withDictionaryFieldNameToInitFieldValue, newClassFields, getObjectTypeFnSyntax, NewFieldsToGetTypeFnDictionaryFieldName); } private ClassDeclarationSyntax CreateNewFieldNameToGetObjectFnDictionary(ClassDeclarationSyntax node, List newClassFields, Func getObjectFnSyntax, string dictionaryFieldName) { var dictionaryKeyToInitValueNodes = newClassFields.SelectMany(fieldName => { var fieldDeclarationNode = node.ChildNodes().OfType() .Single(f => f.Declaration.Variables.First().Identifier.ToString() == fieldName); return new[] { (SyntaxNodeOrToken)SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.ImplicitElementAccess() .WithArgumentList( SyntaxFactory.BracketedArgumentList( SyntaxFactory.SingletonSeparatedList( SyntaxFactory.Argument( SyntaxFactory.LiteralExpression( SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(fieldDeclarationNode.Declaration.Variables.First() .Identifier.ToString())))))), //variable name SyntaxFactory.ParenthesizedLambdaExpression(getObjectFnSyntax(fieldDeclarationNode))), SyntaxFactory.Token(SyntaxKind.CommaToken) //comma, add for all }; }).ToArray(); var dictionaryInitializer = SyntaxFactory.InitializerExpression( SyntaxKind.ObjectInitializerExpression, SyntaxFactory.SeparatedList( dictionaryKeyToInitValueNodes.ToArray() )); var withDictionaryFieldNameToInitFieldValue = node.AddMembers( SyntaxFactory.FieldDeclaration( SyntaxFactory.VariableDeclaration( SyntaxFactory.GenericName( SyntaxFactory.Identifier(DictionaryFullNamespaceTypeName)) .WithTypeArgumentList( SyntaxFactory.TypeArgumentList( SyntaxFactory.SeparatedList( new SyntaxNodeOrToken[] { SyntaxFactory.PredefinedType( SyntaxFactory.Token(SyntaxKind.StringKeyword)), SyntaxFactory.Token(SyntaxKind.CommaToken), SyntaxFactory.GenericName( SyntaxFactory.Identifier("System.Func")) .WithTypeArgumentList( SyntaxFactory.TypeArgumentList( SyntaxFactory.SingletonSeparatedList( SyntaxFactory.PredefinedType( SyntaxFactory.Token(SyntaxKind.ObjectKeyword))))) })))) .WithVariables( SyntaxFactory.SingletonSeparatedList( SyntaxFactory.VariableDeclarator( SyntaxFactory.Identifier(dictionaryFieldName)) .WithInitializer( SyntaxFactory.EqualsValueClause( SyntaxFactory.ObjectCreationExpression( SyntaxFactory.GenericName( SyntaxFactory.Identifier(DictionaryFullNamespaceTypeName)) .WithTypeArgumentList( SyntaxFactory.TypeArgumentList( SyntaxFactory.SeparatedList( new SyntaxNodeOrToken[] { SyntaxFactory.PredefinedType( SyntaxFactory.Token(SyntaxKind.StringKeyword)), SyntaxFactory.Token(SyntaxKind.CommaToken), SyntaxFactory.QualifiedName( SyntaxFactory.IdentifierName("System"), SyntaxFactory.GenericName( SyntaxFactory.Identifier("Func")) .WithTypeArgumentList( SyntaxFactory.TypeArgumentList( SyntaxFactory .SingletonSeparatedList< TypeSyntax>( SyntaxFactory .PredefinedType( SyntaxFactory.Token( SyntaxKind .ObjectKeyword)))))) })))) .WithInitializer(dictionaryInitializer)))))) .WithTriviaFrom(node) .WithModifiers( SyntaxFactory.TokenList( SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword))) .NormalizeWhitespace() .WithLeadingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed)) .WithTrailingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed, SyntaxFactory.ElasticCarriageReturnLineFeed)) ); return AddRewriteCommentIfNeeded(withDictionaryFieldNameToInitFieldValue, $"{nameof(CreateNewFieldInitMethodRewriter)}", true); } } }