Essentials + extra crap

Loadings
Startups
Movement
Camera
This commit is contained in:
ThomasCharester committed 2026-01-12 18:43:51 +03:00
1 parent 662f4a1d05
commit 5f5ace1b52
734 files changed
+107323 -1713

No files matched your search

@@ -0,0 +1,36 @@
using System.Linq;
using ImmersiveVrToolsCommon.Runtime.Logging;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
class BuilderPatternFunctionsRewriter : FastScriptReloadCodeRewriterBase
{
public BuilderPatternFunctionsRewriter(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
{
}
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
{
var ancestorName = node.Ancestors().OfType<TypeDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText;
if (string.IsNullOrEmpty(ancestorName)) //TODO: that happens too often for some methods, odd. Why? At very least don't spam user
{
LoggerScoped.LogWarning($"Unable to find ancestor for node '{node.ToFullString()}'");
}
else
{
var ancestorNameWithoutPatchedPostfix = ancestorName.Replace(FastScriptReload.Runtime.AssemblyChangesLoader.ClassnamePatchedPostfix, "");
if (node.ReturnType is IdentifierNameSyntax name && name.Identifier.ValueText == ancestorNameWithoutPatchedPostfix)
{
return node.WithReturnType(SyntaxFactory.IdentifierName(ancestorName + " "));
}
}
return base.VisitMethodDeclaration(node);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ad3d5960a86c431cbea11b8962fa361e
timeCreated: 1678960928
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/BuilderPatternFunctionsRewriter.cs
uploadId: 710802
@@ -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)}"
);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 1d2a76467ecd40529f59bc68be8d7289
timeCreated: 1672992987
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/ConstructorRewriter.cs
uploadId: 710802
@@ -0,0 +1,153 @@
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 = "global::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);
//TODO: slightly odd scenario 'When explicit #nullable enable is used, reference types should be rewritten to typeof(type) for initialization and value types should remain typeof(type?)'
// Func<FieldDeclarationSyntax, ExpressionSyntax> getObjectTypeFnSyntax = fieldDeclarationNode => SyntaxFactory.TypeOfExpression(
// SyntaxFactory.ParseTypeName(fieldDeclarationNode.Declaration.Type.ToFullString().Replace("?", ""))
// );
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("global::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("global::System"),
SyntaxFactory.GenericName(
SyntaxFactory.Identifier("Func"))
.WithTypeArgumentList(
SyntaxFactory.TypeArgumentList(
SyntaxFactory
.SingletonSeparatedList<
TypeSyntax>(
SyntaxFactory
.PredefinedType(
SyntaxFactory.Token(
SyntaxKind
.ObjectKeyword))))))
}))))
.WithInitializer(dictionaryInitializer))))))
.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);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 6bbeaafe1bf54bda9b46e237bec3ccc0
timeCreated: 1674215064
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/CreateNewFieldInitMethodRewriter.cs
uploadId: 710802
@@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
// When calling other extension methods from same file compilation would fail with 'The call is ambiguous between the following methods or properties'
class ExtensionMethodsCallingOtherExtensionMethodsInSameFileRewriter : FastScriptReloadCodeRewriterBase
{
private Dictionary<string, Dictionary<string, string>> _typeNameToDeclaredMethodNameToThisArgName = new Dictionary<string, Dictionary<string, string>>();
public ExtensionMethodsCallingOtherExtensionMethodsInSameFileRewriter(bool writeRewriteReasonAsComment)
: base(writeRewriteReasonAsComment)
{
}
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
{
var classDeclaredMethodNameToThisArgName = new Dictionary<string, string>();
_typeNameToDeclaredMethodNameToThisArgName[node.Identifier.Text] = classDeclaredMethodNameToThisArgName;
foreach (var methodDeclaration in node.Members
.Where(n => n.Kind() == SyntaxKind.MethodDeclaration)
.Select(n => (MethodDeclarationSyntax)n))
{
var parameters = methodDeclaration.ParameterList.Parameters;
if (parameters.Count > 0 && parameters[0].Modifiers.Any(SyntaxKind.ThisKeyword))
{
var thisArgumentName = parameters[0].Identifier.Text;
classDeclaredMethodNameToThisArgName.TryAdd(methodDeclaration.Identifier.Text, thisArgumentName);
}
}
return base.VisitClassDeclaration(node);
}
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
{
// Check if the invocation is an instance method with a member access (e.g., selfName.CallingOtherTest())
if (node.Expression is MemberAccessExpressionSyntax memberAccess)
{
var methodName = memberAccess.Name.Identifier.Text;
var instanceArgName = memberAccess.Expression;
var className = node.Ancestors().OfType<ClassDeclarationSyntax>().First().Identifier.Text;
if (_typeNameToDeclaredMethodNameToThisArgName.TryGetValue(className, out var declaredMethodNameToThisArgName)
&& declaredMethodNameToThisArgName.TryGetValue(methodName, out var definedInstanceArgName))
{
if (instanceArgName.ToString() == definedInstanceArgName)
{
// Rewrite the invocation to a static method call
var newInvocation = SyntaxFactory.InvocationExpression(SyntaxFactory.IdentifierName(methodName))
.WithArgumentList(
SyntaxFactory.ArgumentList(
SyntaxFactory.SeparatedList(
node.ArgumentList.Arguments.Insert(0, SyntaxFactory.Argument(instanceArgName)) // Add instance as first argument
)
)
).NormalizeWhitespace();
return AddRewriteCommentIfNeeded(
newInvocation,
$"{nameof(ExtensionMethodsCallingOtherExtensionMethodsInSameFileRewriter)}:Replaced extension method call with static call"
);
}
}
}
return base.VisitInvocationExpression(node);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 9f739c197f9040efabf7f64461882d7b
timeCreated: 1727194643
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/ExtensionMethodsCallingOtherExtensionMethodsInSameFileRewriter.cs
uploadId: 710802
@@ -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;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: bcc848f0140d41cdbf1003b0f594694e
timeCreated: 1677233811
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/FastScriptReloadCodeRewriterBase.cs
uploadId: 710802
@@ -0,0 +1,43 @@
using System.Collections.Generic;
using System.Linq;
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;
}
public List<string> GetTypeNames() {
return _typeNameToFieldDeclarations.Select(kv => kv.Key).ToList();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: e4a18d5133af410bab07d9c1a973fe7a
timeCreated: 1672993042
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/FieldsWalker.cs
uploadId: 710802
@@ -0,0 +1,71 @@
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 List<string> OriginalIdentifiersRenamedToContainPatchedPostfix = 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)
{
OriginalIdentifiersRenamedToContainPatchedPostfix.Add(identifier.ValueText);
var newIdentifier = SyntaxFactory.Identifier(identifier + AssemblyChangesLoader.ClassnamePatchedPostfix);
newIdentifier = AddRewriteCommentIfNeeded(newIdentifier, $"{nameof(HotReloadCompliantRewriter)}:{nameof(AddPatchedPostfixToTopLevelDeclarations)}");
node = node.ReplaceToken(identifier, newIdentifier);
return node;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f75e3e02acfa4397a558a923f7fa4f8e
timeCreated: 1672993042
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/HotReloadCompliantRewriter.cs
uploadId: 710802
@@ -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();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7d61f64c891c45b68a7db1ea67854127
timeCreated: 1678101034
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/ManualUserDefinedScriptOverridesRewriter.cs
uploadId: 710802
@@ -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;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ad6e49db99ff4065b14a3fc416af560b
timeCreated: 1673019285
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/NewFieldDeclaration.cs
uploadId: 710802
@@ -0,0 +1,144 @@
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))
{
if(node.Parent is MemberAccessExpressionSyntax memberAccessExpressionSyntax)
{
//for member access expressions only rewrite if used with .this, eg this.NewField, that prevents rewriting of static class fields with same name
if (!memberAccessExpressionSyntax.ToString().StartsWith("this."))
{
return base.VisitIdentifierName(node);
}
}
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);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 0e9e687bdfc0444395deab1e7c68ed3f
timeCreated: 1672993042
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/NewFieldsRewriter.cs
uploadId: 710802
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 919d4ff85d4d4156bddf5c48bcc99da1
timeCreated: 1719699288
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
public static class PartialClassFinder
{
//TODO: optimize lookup, currently this loads all files in directory and checks if they have 'partial' string in them. For directories with many files this will not be sufficient
public static IEnumerable<string> FindPartialClassFilesInDirectory(string filePath, int maxDepth = int.MaxValue)
{
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
return Enumerable.Empty<string>();
string directory = Path.GetDirectoryName(filePath);
string fileContent = File.ReadAllText(filePath);
string partialClassName = ExtractPartialClassName(fileContent);
if (string.IsNullOrEmpty(partialClassName))
{
Debug.LogWarning($"No partial class/struct found in file: {filePath}");
return Enumerable.Empty<string>();
}
return FindPartialClassFilesRecursively(directory, partialClassName, maxDepth, currentDepth: 0);
}
private static IEnumerable<string> FindPartialClassFilesRecursively(string directory, string partialClassName, int maxDepth, int currentDepth)
{
var files = new List<string>();
try
{
// Search in current directory
files.AddRange(Directory.GetFiles(directory, "*.cs")
.Where(file => IsPartialClassFile(file, partialClassName)));
// If we haven't reached max depth, search in subdirectories
if (currentDepth < maxDepth)
{
foreach (var subDir in Directory.GetDirectories(directory))
{
files.AddRange(FindPartialClassFilesRecursively(subDir, partialClassName, maxDepth, currentDepth + 1));
}
}
}
catch (UnauthorizedAccessException ex)
{
Debug.LogWarning($"Access denied to directory: {directory}. Error: {ex.Message}");
}
catch (Exception ex)
{
Debug.LogError($"Error searching directory: {directory}. Error: {ex.Message}");
}
return files;
}
private static bool IsPartialClassFile(string filePath, string className)
{
try
{
string content = File.ReadAllText(filePath);
return content.Contains($"partial class {className}")
|| content.Contains($"partial struct {className}");
}
catch (Exception ex)
{
Debug.LogError($"Error reading file: {filePath}. Error: {ex.Message}");
return false;
}
}
private static string ExtractPartialClassName(string fileContent)
{
// This is a simple implementation and might need to be more robust
// depending on your specific needs
var partialClassDeclaration = fileContent.Split(new[] { "partial class", "partial struct" }, StringSplitOptions.RemoveEmptyEntries)
.Skip(1)
.FirstOrDefault();
if (partialClassDeclaration == null)
return string.Empty;
return partialClassDeclaration.TrimStart().Split(new[] { ' ', '{', ':' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault().Trim();
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 6f182f9db524441e80f367f8f99d8a5f
timeCreated: 1719778688
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/Partials/PartialClassFinder.cs
uploadId: 710802
@@ -0,0 +1,92 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
internal static class PartialTreeProcessor
{
/// <summary>
/// Processes a syntax tree containing partial type definitions, merging it with other partial definitions found in the same directory.
/// </summary>
/// <param name="tree">The original syntax tree to process.</param>
/// <param name="definedPreprocessorSymbols">A collection of defined preprocessor symbols.</param>
/// <returns>A new SyntaxTree with merged partial types and added InternalsVisibleTo attribute.</returns>
/// <remarks>
/// This method performs the following steps:
/// 1. Checks if the tree contains partial types.
/// 2. If it does, processes the current tree and other partial files in the same directory.
/// 3. Combines all partial type declarations.
/// 4. Creates a new compilation unit with merged types and using directives.
/// 5. Adds the InternalsVisibleTo attribute to the resulting tree.
/// </remarks>
internal static SyntaxTree ProcessPartialTree(SyntaxTree tree, IEnumerable<string> definedPreprocessorSymbols)
{
if (!HasPartialTypes(tree))
{
return tree;
}
var combinedTypes = new Dictionary<string, List<TypeDeclarationSyntax>>();
var combinedUsingDirectives = new HashSet<UsingDirectiveSyntax>();
var combinedTypesDefined = new HashSet<string>();
ProcessTree(tree, combinedTypes, combinedUsingDirectives, combinedTypesDefined);
const int fileSearchMaxDepth = 5;
var otherPartialFiles = PartialClassFinder.FindPartialClassFilesInDirectory(tree.FilePath, fileSearchMaxDepth)
.Where(file => file != tree.FilePath);
foreach (var file in otherPartialFiles)
{
var partialTree = CSharpSyntaxTree.ParseText(File.ReadAllText(file), path: file);
ProcessTree(partialTree, combinedTypes, combinedUsingDirectives, combinedTypesDefined);
}
var combinedTypeDeclarations = combinedTypes
.Select(kvp => PartialTypeCombiner.CombinePartialType(kvp.Value))
.ToList<MemberDeclarationSyntax>();
var newRoot = SyntaxFactory.CompilationUnit()
.WithUsings(SyntaxFactory.List(combinedUsingDirectives))
.WithMembers(SyntaxFactory.List(combinedTypeDeclarations))
.WithAdditionalAnnotations(new SyntaxAnnotation("PreprocessorSymbols", string.Join(",", definedPreprocessorSymbols)));
return CSharpSyntaxTree.Create(newRoot, null, tree.FilePath)
.AddInternalsVisibleToAttribute();
}
private static bool HasPartialTypes(SyntaxTree tree)
{
var root = tree.GetCompilationUnitRoot();
return root.DescendantNodes().OfType<TypeDeclarationSyntax>().Any(type => type.Modifiers.Any(SyntaxKind.PartialKeyword));
}
private static void ProcessTree(
SyntaxTree tree,
Dictionary<string, List<TypeDeclarationSyntax>> combinedTypes,
HashSet<UsingDirectiveSyntax> combinedUsingDirectives,
HashSet<string> combinedTypesDefined)
{
var root = tree.GetCompilationUnitRoot();
combinedUsingDirectives.UnionWith(root.Usings);
foreach (var typeDecl in root.DescendantNodes().OfType<TypeDeclarationSyntax>())
{
var fullName = NamespaceHelper.GetFullyQualifiedName(typeDecl);
combinedTypesDefined.Add(fullName);
if (!combinedTypes.TryGetValue(fullName, out var declarations))
{
declarations = new List<TypeDeclarationSyntax>();
combinedTypes[fullName] = declarations;
}
declarations.Add(typeDecl);
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4aafad9b85b44ebe902ba3cee7bda205
timeCreated: 1719954990
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/Partials/PartialTreeProcessor.cs
uploadId: 710802
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
internal static class PartialTypeCombiner
{
internal static TypeDeclarationSyntax CombinePartialType(List<TypeDeclarationSyntax> declarations)
{
if (declarations.Count == 1)
return declarations[0];
var firstDeclaration = declarations[0];
var combinedMembers = declarations.SelectMany(d => d.Members).ToList();
var combinedModifiers = declarations
.SelectMany(d => d.Modifiers)
.Where(m => !m.IsKind(SyntaxKind.PartialKeyword))
.GroupBy(m => m.ValueText)
.Select(g => g.First().WithTrailingTrivia(SyntaxFactory.Space))
.ToList();
if (!combinedModifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
combinedModifiers.Add(SyntaxFactory.Token(SyntaxKind.PartialKeyword).WithTrailingTrivia(SyntaxFactory.Space));
var combinedBaseList = declarations
.Where(d => d.BaseList != null)
.SelectMany(d => d.BaseList.Types)
.GroupBy(t => t.ToString())
.Select(g => g.First())
.ToList();
return firstDeclaration
.WithMembers(SyntaxFactory.List(combinedMembers))
.WithModifiers(SyntaxFactory.TokenList(combinedModifiers))
.WithKeyword(firstDeclaration.Keyword.WithLeadingTrivia(SyntaxFactory.Space))
.WithBaseList(combinedBaseList.Any()
? SyntaxFactory.BaseList(SyntaxFactory.SeparatedList(combinedBaseList))
: null);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: cc8170320f3a463590d434f7e0570ea0
timeCreated: 1719955225
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/Partials/PartialTypeCombiner.cs
uploadId: 710802
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
internal static class NamespaceHelper
{
internal static string GetFullyQualifiedName(TypeDeclarationSyntax typeDeclaration)
{
var namespaces = new List<string>();
var currentNode = typeDeclaration.Parent;
while (currentNode != null)
{
if (currentNode is NamespaceDeclarationSyntax namespaceDeclaration)
{
namespaces.Add(namespaceDeclaration.Name.ToString());
}
currentNode = currentNode.Parent;
}
namespaces.Reverse();
var namespaceName = string.Join(".", namespaces);
return string.IsNullOrEmpty(namespaceName)
? typeDeclaration.Identifier.Text
: $"{namespaceName}.{typeDeclaration.Identifier.Text}";
}
}
internal static class SyntaxTreeExtensions
{
internal static SyntaxTree AddInternalsVisibleToAttribute(this SyntaxTree tree)
{
var root = tree.GetCompilationUnitRoot();
var attributeArgument = SyntaxFactory.AttributeArgument(
SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("Assembly-CSharp"))
);
var attribute = SyntaxFactory.Attribute(
SyntaxFactory.ParseName("System.Runtime.CompilerServices.InternalsVisibleTo"),
SyntaxFactory.AttributeArgumentList(SyntaxFactory.SeparatedList(new[] { attributeArgument }))
);
var attributeList = SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(attribute))
.WithTarget(SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.AssemblyKeyword)));
var newRoot = root.AddAttributeLists(attributeList);
return tree.WithRootAndOptions(newRoot, tree.Options);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 1c2f0e22f1a24fcda6d0c0516b428420
timeCreated: 1719954970
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/Partials/SyntaxTreeHelpers.cs
uploadId: 710802
@@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
public static class SyntaxTreeMergePartialsExtensions
{
public static IEnumerable<SyntaxTree> MergePartials(
this IEnumerable<SyntaxTree> trees,
IEnumerable<string> definedPreprocessorSymbols)
{
return trees
.Select(tree => PartialTreeProcessor.ProcessPartialTree(tree, definedPreprocessorSymbols))
.ToList();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 550c942c025f4fd5a1062891f3623c60
timeCreated: 1719174342
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/Partials/SyntaxTreeMergePartialsExtensions.cs
uploadId: 710802
@@ -0,0 +1,36 @@
using FastScriptReload.Runtime;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
class RecordeRewriter : FastScriptReloadCodeRewriterBase
{
public RecordeRewriter(bool writeRewriteReasonAsComment)
: base(writeRewriteReasonAsComment)
{
}
public override SyntaxNode VisitRecordDeclaration(RecordDeclarationSyntax node)
{
return AdjustRecordName(node, node.Identifier);
}
private SyntaxNode AdjustRecordName(RecordDeclarationSyntax node, SyntaxToken nodeIdentifier)
{
var typeName = nodeIdentifier.ToString(); //Not ToFullString() as it may include spaces and break.
if (!typeName.EndsWith(AssemblyChangesLoader.ClassnamePatchedPostfix))
{
typeName += AssemblyChangesLoader.ClassnamePatchedPostfix;
}
return AddRewriteCommentIfNeeded(
node.ReplaceToken(nodeIdentifier, SyntaxFactory.Identifier(typeName)),
$"{nameof(RecordeRewriter)}:{nameof(AdjustRecordName)}"
);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: bb801c55172044ed9294ef722937f12f
timeCreated: 1705523787
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/RecordRewriter.cs
uploadId: 710802
@@ -0,0 +1,41 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace FastScriptReload.Editor.Compilation.CodeRewriting
{
//ideally we'd use Simplifier but FSR doesn't have access to semantic model on which simplifier works
//Removes redundant type name qualifiers, eg: RootClass.NestedEnum.Value -> NestedEnum.Value, root class component is rewritten to have
//__Patched_ postfix and if original remains it'll cause type mismatch
class RedundantTypeNameQualifierRewriter : FastScriptReloadCodeRewriterBase
{
public RedundantTypeNameQualifierRewriter(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
{
}
public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
{
if(node.Parent is MemberAccessExpressionSyntax)
return base.VisitMemberAccessExpression(node); //only target outermost member access for full rewrite
var firstNode = node.DescendantNodes().OfType<IdentifierNameSyntax>().FirstOrDefault();
if(firstNode is IdentifierNameSyntax idNode && node.DescendantNodes().Count() > 1)
{
//enclosing type name same as first id node
if((idNode.Ancestors().First(n => n is TypeDeclarationSyntax) as TypeDeclarationSyntax)?.Identifier.ToString() == idNode.Identifier.ToString())
{
return AddRewriteCommentIfNeeded(
SyntaxFactory.ParseExpression(node.ToString().Replace($"{firstNode.Identifier.ToString()}.", string.Empty)),
$"{nameof(RedundantTypeNameQualifierRewriter)}"
);
}
}
return base.VisitMemberAccessExpression(node);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 80a540048e524157a41df1241bd5c168
timeCreated: 1679325667
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/RedundantTypeNameQualifierRewriter.cs
uploadId: 710802
@@ -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);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 29bd7fad38034046a8dd4c7eea68907c
timeCreated: 1673017013
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/RoslynUtils.cs
uploadId: 710802
@@ -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);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: c5a960528b2b44edbf644e0ac0950d35
timeCreated: 1672993042
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/ThisAssignmentRewriter.cs
uploadId: 710802
@@ -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);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f7e7aad39cec445db504fe5a117beae5
timeCreated: 1672993042
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/ThisCallRewriter.cs
uploadId: 710802
@@ -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)}");
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 0fd0b779590247168b3873c939c42d71
timeCreated: 1672993042
AssetOrigin:
serializedVersion: 1
productId: 239351
packageName: Fast Script Reload
packageVersion: 1.6.1
assetPath: Assets/FastScriptReload/Scripts/Editor/Compilation/CodeRewriting/ThisRewriterBase.cs
uploadId: 710802