This commit is contained in:
CortexCore
2023-10-20 19:31:12 +08:00
parent 5cd094ed9a
commit a160813262
1878 changed files with 630581 additions and 4485 deletions

View File

@@ -0,0 +1,84 @@
using System.Linq;
using FastScriptReload.Runtime;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
class ConstructorRewriter : FastScriptReloadCodeRewriterBase
{
private readonly bool _adjustCtorOnlyForNonNestedTypes;
public ConstructorRewriter(bool adjustCtorOnlyForNonNestedTypes, bool writeRewriteReasonAsComment)
: base(writeRewriteReasonAsComment)
{
_adjustCtorOnlyForNonNestedTypes = adjustCtorOnlyForNonNestedTypes;
}
public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
{
if (_adjustCtorOnlyForNonNestedTypes)
{
var typeNestedLevel = node.Ancestors().Count(a => a is TypeDeclarationSyntax);
if (typeNestedLevel == 1)
{
return AdjustCtorOrDestructorNameForTypeAdjustment(node, node.Identifier);
}
}
else
{
return AdjustCtorOrDestructorNameForTypeAdjustment(node, node.Identifier);
}
return base.VisitConstructorDeclaration(node);
}
public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSyntax node)
{
if (_adjustCtorOnlyForNonNestedTypes)
{
var typeNestedLevel = node.Ancestors().Count(a => a is TypeDeclarationSyntax);
if (typeNestedLevel == 1)
{
return AdjustCtorOrDestructorNameForTypeAdjustment(node, node.Identifier);
}
}
else
{
return AdjustCtorOrDestructorNameForTypeAdjustment(node, node.Identifier);
}
return base.VisitDestructorDeclaration(node);
}
private SyntaxNode AdjustCtorOrDestructorNameForTypeAdjustment(BaseMethodDeclarationSyntax node, SyntaxToken nodeIdentifier)
{
var typeName = (node.Ancestors().First(n => n is TypeDeclarationSyntax) as TypeDeclarationSyntax).Identifier.ToString();
if (!nodeIdentifier.ToFullString().Contains(typeName))
{
//Used Roslyn version bug, some static methods are also interpreted as ctors, eg
// public static void Method()
// {
// Bar(); //treated as Ctor declaration...
// }
//
// private static void Bar()
// {
//
// }
return node;
}
if (!typeName.EndsWith(AssemblyChangesLoader.ClassnamePatchedPostfix))
{
typeName += AssemblyChangesLoader.ClassnamePatchedPostfix;
}
return AddRewriteCommentIfNeeded(
node.ReplaceToken(nodeIdentifier, SyntaxFactory.Identifier(typeName)),
$"{nameof(ConstructorRewriter)}:{nameof(AdjustCtorOrDestructorNameForTypeAdjustment)}"
);
}
}
}

View File

@@ -0,0 +1,150 @@
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<string, List<string>> _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<string, Func<object>> ResolveNewFieldsToCreateValueFn(Type forType)
{
return (Dictionary<string, Func<object>>) forType.GetField(NewFieldsToCreateValueFnDictionaryFieldName, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
}
public static Dictionary<string, Func<object>> ResolveNewFieldsToTypeFn(Type forType)
{
return (Dictionary<string, Func<object>>) forType.GetField(NewFieldsToGetTypeFnDictionaryFieldName, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
}
public CreateNewFieldInitMethodRewriter(Dictionary<string, List<string>> 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<FieldDeclarationSyntax, ExpressionSyntax> 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<FieldDeclarationSyntax, ExpressionSyntax> getObjectTypeFnSyntax = fieldDeclarationNode => SyntaxFactory.TypeOfExpression(fieldDeclarationNode.Declaration.Type);
return CreateNewFieldNameToGetObjectFnDictionary(withDictionaryFieldNameToInitFieldValue, newClassFields, getObjectTypeFnSyntax, NewFieldsToGetTypeFnDictionaryFieldName);
}
private ClassDeclarationSyntax CreateNewFieldNameToGetObjectFnDictionary(ClassDeclarationSyntax node,
List<string> newClassFields, Func<FieldDeclarationSyntax, ExpressionSyntax> getObjectFnSyntax, string dictionaryFieldName)
{
var dictionaryKeyToInitValueNodes = newClassFields.SelectMany(fieldName =>
{
var fieldDeclarationNode = node.ChildNodes().OfType<FieldDeclarationSyntax>()
.Single(f => f.Declaration.Variables.First().Identifier.ToString() == fieldName);
return new[]
{
(SyntaxNodeOrToken)SyntaxFactory.AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
SyntaxFactory.ImplicitElementAccess()
.WithArgumentList(
SyntaxFactory.BracketedArgumentList(
SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>(
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<ExpressionSyntax>(
dictionaryKeyToInitValueNodes.ToArray()
));
var withDictionaryFieldNameToInitFieldValue = node.AddMembers(
SyntaxFactory.FieldDeclaration(
SyntaxFactory.VariableDeclaration(
SyntaxFactory.GenericName(
SyntaxFactory.Identifier(DictionaryFullNamespaceTypeName))
.WithTypeArgumentList(
SyntaxFactory.TypeArgumentList(
SyntaxFactory.SeparatedList<TypeSyntax>(
new SyntaxNodeOrToken[]
{
SyntaxFactory.PredefinedType(
SyntaxFactory.Token(SyntaxKind.StringKeyword)),
SyntaxFactory.Token(SyntaxKind.CommaToken),
SyntaxFactory.GenericName(
SyntaxFactory.Identifier("System.Func"))
.WithTypeArgumentList(
SyntaxFactory.TypeArgumentList(
SyntaxFactory.SingletonSeparatedList<TypeSyntax>(
SyntaxFactory.PredefinedType(
SyntaxFactory.Token(SyntaxKind.ObjectKeyword)))))
}))))
.WithVariables(
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
SyntaxFactory.VariableDeclarator(
SyntaxFactory.Identifier(dictionaryFieldName))
.WithInitializer(
SyntaxFactory.EqualsValueClause(
SyntaxFactory.ObjectCreationExpression(
SyntaxFactory.GenericName(
SyntaxFactory.Identifier(DictionaryFullNamespaceTypeName))
.WithTypeArgumentList(
SyntaxFactory.TypeArgumentList(
SyntaxFactory.SeparatedList<TypeSyntax>(
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);
}
}
}

View File

@@ -0,0 +1,64 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
public abstract class FastScriptReloadCodeRewriterBase : CSharpSyntaxRewriter
{
protected readonly bool _writeRewriteReasonAsComment;
protected FastScriptReloadCodeRewriterBase(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false) : base(visitIntoStructuredTrivia)
{
_writeRewriteReasonAsComment = writeRewriteReasonAsComment;
}
protected SyntaxToken AddRewriteCommentIfNeeded(SyntaxToken syntaxToken, string commentText, bool append = false)
{
return AddRewriteCommentIfNeeded(syntaxToken, commentText, _writeRewriteReasonAsComment, append);
}
public static SyntaxToken AddRewriteCommentIfNeeded(SyntaxToken syntaxToken, string commentText, bool writeRewriteReasonAsComment, bool append)
{
if (writeRewriteReasonAsComment)
{
if (append)
{
return syntaxToken.WithLeadingTrivia(
syntaxToken.LeadingTrivia.Add(SyntaxFactory.Comment($"/*FSR:{commentText}*/")));
}
else
{
return syntaxToken.WithTrailingTrivia(
syntaxToken.TrailingTrivia.Add(SyntaxFactory.Comment($"/*FSR:{commentText}*/")));
}
}
return syntaxToken;
}
protected T AddRewriteCommentIfNeeded<T>(T syntaxNode, string commentText, bool append = false)
where T : SyntaxNode
{
return AddRewriteCommentIfNeeded(syntaxNode, commentText, _writeRewriteReasonAsComment, append);
}
public static T AddRewriteCommentIfNeeded<T>(T syntaxNode, string commentText, bool writeRewriteReasonAsComment, bool append) where T : SyntaxNode
{
if (writeRewriteReasonAsComment)
{
if (append)
{
return syntaxNode.WithLeadingTrivia(syntaxNode.GetLeadingTrivia()
.Add(SyntaxFactory.Comment($"/*FSR:{commentText}*/")));
}
else
{
return syntaxNode.WithTrailingTrivia(syntaxNode.GetTrailingTrivia()
.Add(SyntaxFactory.Comment($"/*FSR:{commentText}*/")));
}
}
return syntaxNode;
}
}
}

View File

@@ -0,0 +1,38 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
class FieldsWalker : CSharpSyntaxWalker {
private readonly Dictionary<string, List<NewFieldDeclaration>> _typeNameToFieldDeclarations = new Dictionary<string, List<NewFieldDeclaration>>();
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
{
var className = node.Identifier;
var fullClassName = RoslynUtils.GetMemberFQDN(node, className.ToString());
if(!_typeNameToFieldDeclarations.ContainsKey(fullClassName)) {
_typeNameToFieldDeclarations[fullClassName] = new List<NewFieldDeclaration>();
}
base.VisitClassDeclaration(node);
}
public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
{
var fieldName = node.Declaration.Variables.First().Identifier.ToString();
var fullClassName = RoslynUtils.GetMemberFQDNWithoutMemberName(node);
if(!_typeNameToFieldDeclarations.ContainsKey(fullClassName)) {
_typeNameToFieldDeclarations[fullClassName] = new List<NewFieldDeclaration>();
}
_typeNameToFieldDeclarations[fullClassName].Add(new NewFieldDeclaration(fieldName, node.Declaration.Type.ToString(), node));
base.VisitFieldDeclaration(node);
}
public Dictionary<string, List<NewFieldDeclaration>> GetTypeToFieldDeclarations() {
return _typeNameToFieldDeclarations;
}
}
}

View File

@@ -0,0 +1,69 @@
using System.Collections.Generic;
using FastScriptReload.Runtime;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
class HotReloadCompliantRewriter : FastScriptReloadCodeRewriterBase
{
public List<string> StrippedUsingDirectives = new List<string>();
public HotReloadCompliantRewriter(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
{
}
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
{
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
//if subclasses need to be adjusted, it's done via recursion.
// foreach (var childNode in node.ChildNodes().OfType<ClassDeclarationSyntax>())
// {
// var changed = Visit(childNode);
// node = node.ReplaceNode(childNode, changed);
// }
}
public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node)
{
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
}
public override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node)
{
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
}
public override SyntaxNode VisitDelegateDeclaration(DelegateDeclarationSyntax node)
{
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
}
public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
{
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
}
public override SyntaxNode VisitUsingDirective(UsingDirectiveSyntax node)
{
if (node.Parent is CompilationUnitSyntax)
{
StrippedUsingDirectives.Add(node.ToFullString());
return null;
}
return base.VisitUsingDirective(node);
}
private SyntaxNode AddPatchedPostfixToTopLevelDeclarations(CSharpSyntaxNode node, SyntaxToken identifier)
{
var newIdentifier = SyntaxFactory.Identifier(identifier + AssemblyChangesLoader.ClassnamePatchedPostfix);
newIdentifier = AddRewriteCommentIfNeeded(newIdentifier, $"{nameof(HotReloadCompliantRewriter)}:{nameof(AddPatchedPostfixToTopLevelDeclarations)}");
node = node.ReplaceToken(identifier, newIdentifier);
return node;
}
}
}

View File

@@ -0,0 +1,127 @@
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
public class ManualUserDefinedScriptOverridesRewriter : FastScriptReloadCodeRewriterBase
{
private readonly SyntaxNode _userDefinedOverridesRoot;
public ManualUserDefinedScriptOverridesRewriter(SyntaxNode userDefinedOverridesRoot, bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
{
_userDefinedOverridesRoot = userDefinedOverridesRoot;
}
//TODO: refactor to use OverrideDeclarationWithMatchingUserDefinedIfExists
public override SyntaxNode VisitConversionOperatorDeclaration(ConversionOperatorDeclarationSyntax node)
{
var methodFQDN = RoslynUtils.GetMemberFQDN(node, "operator");
var matchingInOverride = _userDefinedOverridesRoot.DescendantNodes()
//implicit conversion operators do not have name, just parameter list
.OfType<BaseMethodDeclarationSyntax>()
.FirstOrDefault(m => m.ParameterList.ToString() == node.ParameterList.ToString() //parameter lists is type / order / names, all good for targetting if there's a proper match
&& methodFQDN == RoslynUtils.GetMemberFQDN(m, "operator") //make sure same FQDN, even though there's no name there could be more implicit operators in file
);
if (matchingInOverride != null)
{
return AddRewriteCommentIfNeeded(matchingInOverride.WithTriviaFrom(node), $"User defined custom conversion override", true);
}
else {
return base.VisitConversionOperatorDeclaration(node);
}
}
//TODO: refactor to use OverrideDeclarationWithMatchingUserDefinedIfExists
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
{
var methodName = node.Identifier.ValueText;
var methodFQDN = RoslynUtils.GetMemberFQDN(node, node.Identifier.ToString());
var matchingInOverride = _userDefinedOverridesRoot.DescendantNodes()
.OfType<MethodDeclarationSyntax>()
.FirstOrDefault(m => m.Identifier.ValueText == methodName
&& m.ParameterList.Parameters.Count == node.ParameterList.Parameters.Count
&& m.ParameterList.ToString() == node.ParameterList.ToString() //parameter lists is type / order / names, all good for targetting if there's a proper match
&& m.TypeParameterList?.ToString() == node.TypeParameterList?.ToString() //typed paratemets are for generics, also check
&& methodFQDN == RoslynUtils.GetMemberFQDN(m, m.Identifier.ToString()) //last check for mathod FQDN (potentially slower than others)
);
if (matchingInOverride != null)
{
return AddRewriteCommentIfNeeded(matchingInOverride.WithTriviaFrom(node), $"User defined custom method override", true);
}
else {
return base.VisitMethodDeclaration(node);
}
}
public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
{
return OverrideDeclarationWithMatchingUserDefinedIfExists(
node,
(d) => d.Identifier.ValueText,
(d) => HasSameParametersPredicate(node.ParameterList)(d.ParameterList),
(d) => base.VisitConstructorDeclaration(d)
);
}
public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSyntax node)
{
return OverrideDeclarationWithMatchingUserDefinedIfExists(
node,
(d) => d.Identifier.ValueText,
(d) => HasSameParametersPredicate(node.ParameterList)(d.ParameterList),
(d) => base.VisitDestructorDeclaration(d)
);
}
public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)
{
return OverrideDeclarationWithMatchingUserDefinedIfExists(
node,
(d) => d.Identifier.ValueText,
(d) => true,
(d) => base.VisitPropertyDeclaration(d)
);
}
private SyntaxNode OverrideDeclarationWithMatchingUserDefinedIfExists<T>(T node, Func<T, string> getName,
Func<T, bool> customFindMatchInOverridePredicate, Func<T, SyntaxNode> visitDefault)
where T: MemberDeclarationSyntax
{
var name = getName(node);
var fqdn = RoslynUtils.GetMemberFQDN(node, name);
var matchingInOverride = _userDefinedOverridesRoot.DescendantNodes()
.OfType<T>()
.FirstOrDefault(d =>
{
var declarationName = getName(d);
return declarationName == name
&& customFindMatchInOverridePredicate(d)
&& fqdn == RoslynUtils.GetMemberFQDN(d, declarationName); //last check for mathod FQDN (potentially slower than others)
}
);
if (matchingInOverride != null)
{
return AddRewriteCommentIfNeeded(matchingInOverride.WithTriviaFrom(node),
$"User defined custom {typeof(T)} override", true);
}
else
{
return visitDefault(node);
}
}
private Func<ParameterListSyntax, bool> HasSameParametersPredicate(ParameterListSyntax parameters)
{
return (resolvedParams) => resolvedParams.Parameters.Count == parameters.Parameters.Count
&& resolvedParams.ToString() == parameters.ToString();
}
}
}

View File

@@ -0,0 +1,18 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
public class NewFieldDeclaration
{
public string FieldName { get; }
public string TypeName { get; }
public FieldDeclarationSyntax FieldDeclarationSyntax { get; } //TODO: PERF: will that block whole tree from being garbage collected
public NewFieldDeclaration(string fieldName, string typeName, FieldDeclarationSyntax fieldDeclarationSyntax)
{
FieldName = fieldName;
TypeName = typeName;
FieldDeclarationSyntax = fieldDeclarationSyntax;
}
}
}

View File

@@ -0,0 +1,135 @@
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<string, List<string>> _typeToNewFieldDeclarations;
public NewFieldsRewriter(Dictionary<string, List<string>> typeToNewFieldDeclarations, bool writeRewriteReasonAsComment)
:base(writeRewriteReasonAsComment)
{
_typeToNewFieldDeclarations = typeToNewFieldDeclarations;
}
public static List<MemberInfo> GetReplaceableMembers(Type type)
{ //TODO: later other might need to be included? props?
return type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Cast<MemberInfo>().ToList();
}
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
{
if (node.Expression.ToString() == "nameof")
{
var classNode = node.Ancestors().OfType<ClassDeclarationSyntax>().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<ClassDeclarationSyntax>().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<InvocationExpressionSyntax>().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<TypeSyntax>(
SyntaxFactory.IdentifierName(fullClassName + AssemblyChangesLoader.ClassnamePatchedPostfix))))))
.WithArgumentList(
SyntaxFactory.ArgumentList(
SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>(
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);
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
public class RoslynUtils
{
public static string GetMemberFQDN(MemberDeclarationSyntax memberNode, string memberName) //TODO: try get rid of member name (needs to cast to whatever member it could be to get identifier)
{
var outer = GetMemberFQDNWithoutMemberName(memberNode);
return !string.IsNullOrEmpty(outer)
? $"{outer}.{memberName}"
: memberName;
}
public static string GetMemberFQDNWithoutMemberName(MemberDeclarationSyntax memberNode) //TODO: move out to helper class
{
var fullTypeContibutingAncestorNames = memberNode.Ancestors().OfType<MemberDeclarationSyntax>().Select(da =>
{
if (da is TypeDeclarationSyntax t) return t.Identifier.ToString();
else if (da is NamespaceDeclarationSyntax n) return n.Name.ToString();
else throw new Exception("Unable to resolve full field name");
}).Reverse().ToList();
return string.Join(".", fullTypeContibutingAncestorNames);
}
}
}

View File

@@ -0,0 +1,21 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
class ThisAssignmentRewriter: ThisRewriterBase {
public ThisAssignmentRewriter(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
{
}
public override SyntaxNode VisitThisExpression(ThisExpressionSyntax node)
{
if (node.Parent is AssignmentExpressionSyntax) {
return CreateCastedThisExpression(node);
}
return base.VisitThisExpression(node);
}
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
class ThisCallRewriter : ThisRewriterBase
{
public ThisCallRewriter(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
{
}
public override SyntaxNode VisitThisExpression(ThisExpressionSyntax node)
{
if (node.Parent is ArgumentSyntax)
{
return CreateCastedThisExpression(node);
}
return base.VisitThisExpression(node);
}
}
}

View File

@@ -0,0 +1,51 @@
using System.Linq;
using ImmersiveVrToolsCommon.Runtime.Logging;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
abstract class ThisRewriterBase : FastScriptReloadCodeRewriterBase
{
protected ThisRewriterBase(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
{
}
protected SyntaxNode CreateCastedThisExpression(ThisExpressionSyntax node)
{
var ancestors = node.Ancestors().Where(n => n is TypeDeclarationSyntax).Cast<TypeDeclarationSyntax>().ToList();
if (ancestors.Count() > 1)
{
LoggerScoped.LogWarning($"ThisRewriter: for class: '{ancestors.First().Identifier}' - 'this' call/assignment in nested class / struct. Dynamic cast will be used but this could cause issues in some cases:" +
$"\r\n\r\n1) - If called method has multiple overrides, using dynamic will cause compiler issue as it'll no longer be able to pick correct one" +
$"\r\n\r\n If you see any issues with that message, please look at 'Limitation' section in documentation as this outlines how to deal with it.");
//TODO: casting to dynamic seems to be best option (and one that doesn't fail for nested classes), what's the performance overhead?
return SyntaxFactory.CastExpression(
SyntaxFactory.ParseTypeName("dynamic"),
node
);
}
var firstAncestor = ancestors.FirstOrDefault();
if (firstAncestor == null)
{
LoggerScoped.LogWarning($"Unable to find first ancestor for node: {node.ToFullString()}, this rewrite will not be applied");
return node;
}
var methodInType = firstAncestor.Identifier.ToString();
var resultNode = SyntaxFactory.CastExpression(
SyntaxFactory.ParseTypeName(methodInType),
SyntaxFactory.CastExpression(
SyntaxFactory.ParseTypeName(typeof(object).FullName),
node
)
);
return AddRewriteCommentIfNeeded(resultNode, $"{nameof(ThisRewriterBase)}:{nameof(CreateCastedThisExpression)}");
}
}
}