1
This commit is contained in:
@@ -1,84 +0,0 @@
|
||||
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)}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,150 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,127 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,135 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,51 +0,0 @@
|
||||
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)}");
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user