using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using FastScriptReload.Runtime; using FastScriptReload.Scripts.Runtime; using ImmersiveVrToolsCommon.Runtime.Logging; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace FastScriptReload.Editor.Compilation.CodeRewriting { class NewFieldsRewriter : FastScriptReloadCodeRewriterBase { private readonly Dictionary> _typeToNewFieldDeclarations; public NewFieldsRewriter(Dictionary> typeToNewFieldDeclarations, bool writeRewriteReasonAsComment) :base(writeRewriteReasonAsComment) { _typeToNewFieldDeclarations = typeToNewFieldDeclarations; } public static List GetReplaceableMembers(Type type) { //TODO: later other might need to be included? props? return type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Cast().ToList(); } public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) { if (node.Expression.ToString() == "nameof") { var classNode = node.Ancestors().OfType().FirstOrDefault(); if (classNode != null) { var fullClassName = RoslynUtils.GetMemberFQDN(classNode, classNode.Identifier.ToString()); if (!string.IsNullOrEmpty(fullClassName)) { var nameofExpressionParts = node.ArgumentList.Arguments.First().ToFullString().Split('.'); //nameof could have multiple . like NewFieldCustomClass.FieldInThatClass var fieldName = nameofExpressionParts.First(); // should take first part only to determine if new field eg. 'NewFieldCustomClass' if (_typeToNewFieldDeclarations.TryGetValue(fullClassName, out var allNewFieldNamesForClass)) { if (allNewFieldNamesForClass.Contains(fieldName)) { return AddRewriteCommentIfNeeded( SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(nameofExpressionParts.Last())), // should take last part only to for actual string eg. 'FieldInThatClass' $"{nameof(NewFieldsRewriter)}:{nameof(VisitInvocationExpression)}"); } } } } } return base.VisitInvocationExpression(node); } public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) { var classNode = node.Ancestors().OfType().FirstOrDefault(); if (classNode != null) { var fullClassName = RoslynUtils.GetMemberFQDN(classNode, classNode.Identifier.ToString()); if (!string.IsNullOrEmpty(fullClassName)) { var fieldName = node.Identifier.ToString(); if (_typeToNewFieldDeclarations.TryGetValue(fullClassName, out var allNewFieldNamesForClass)) { if (allNewFieldNamesForClass.Contains(fieldName)) { var isNameOfExpression = node.Ancestors().OfType().Any(e => e.Expression.ToString() == "nameof"); if (!isNameOfExpression) //nameof expression will be rewritten via VisitInvocationExpression { return AddRewriteCommentIfNeeded( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName(typeof(TemporaryNewFieldValues).FullName), SyntaxFactory.GenericName( SyntaxFactory.Identifier(nameof(TemporaryNewFieldValues.ResolvePatchedObject))) .WithTypeArgumentList( SyntaxFactory.TypeArgumentList( SyntaxFactory.SingletonSeparatedList( SyntaxFactory.IdentifierName(fullClassName + AssemblyChangesLoader.ClassnamePatchedPostfix)))))) .WithArgumentList( SyntaxFactory.ArgumentList( SyntaxFactory.SingletonSeparatedList( SyntaxFactory.Argument( SyntaxFactory.ThisExpression())))), SyntaxFactory.IdentifierName(fieldName)) .WithTriviaFrom(node), $"{nameof(NewFieldsRewriter)}:{nameof(VisitIdentifierName)}" ); } } } else { LoggerScoped.LogWarning($"Unable to find type: {fullClassName}"); } } } return base.VisitIdentifierName(node); } public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node) { var fieldName = node.Declaration.Variables.First().Identifier.ToString(); var fullClassName = RoslynUtils.GetMemberFQDNWithoutMemberName(node); if (_typeToNewFieldDeclarations.TryGetValue(fullClassName, out var newFields)) { if (newFields.Contains(fieldName)) { var existingLeading = node.GetLeadingTrivia(); var existingTrailing = node.GetTrailingTrivia(); return AddRewriteCommentIfNeeded( node .WithLeadingTrivia(existingLeading.Add(SyntaxFactory.Comment("/* "))) .WithTrailingTrivia(existingTrailing.Insert(0, SyntaxFactory.Comment(" */ //Auto-excluded to prevent exceptions - see docs"))), $"{nameof(NewFieldsRewriter)}:{nameof(VisitFieldDeclaration)}" ); } } return base.VisitFieldDeclaration(node); } } }