diff --git a/sources/SilkTouch/SilkTouch/Mods/AddApiProfiles.cs b/sources/SilkTouch/SilkTouch/Mods/AddApiProfiles.cs index 947b06a6d2..0df1ec07ea 100644 --- a/sources/SilkTouch/SilkTouch/Mods/AddApiProfiles.cs +++ b/sources/SilkTouch/SilkTouch/Mods/AddApiProfiles.cs @@ -265,9 +265,7 @@ rewriter.Profile is null logger.LogDebug("No profile identified for {}", path); } - ctx.SourceProject = doc.WithSyntaxRoot( - rewriter.Visit(root).NormalizeWhitespace() - ).Project; + ctx.SourceProject = doc.WithSyntaxRoot(rewriter.Visit(root)).Project; rewriter.Profile = null; } } diff --git a/sources/SilkTouch/SilkTouch/Mods/AddOpaqueStructs.cs b/sources/SilkTouch/SilkTouch/Mods/AddOpaqueStructs.cs index 34d4e13355..5d8cafcb0e 100644 --- a/sources/SilkTouch/SilkTouch/Mods/AddOpaqueStructs.cs +++ b/sources/SilkTouch/SilkTouch/Mods/AddOpaqueStructs.cs @@ -72,11 +72,10 @@ public Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) { var qualified = name.LastIndexOf('.'); var ns = - qualified != -1 - ? ModUtils.NamespaceIntoIdentifierName(name.AsSpan()[..qualified]) - : _defaultNamespaces.TryGetValue(ctx.JobKey, out var def) - ? ModUtils.NamespaceIntoIdentifierName(def) - : null; + qualified != -1 ? ModUtils.NamespaceIntoIdentifierName(name.AsSpan()[..qualified]) + : _defaultNamespaces.TryGetValue(ctx.JobKey, out var def) + ? ModUtils.NamespaceIntoIdentifierName(def) + : null; if (ns is null) { logger.LogWarning( @@ -107,8 +106,7 @@ public Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) ) ) ) - ) - .NormalizeWhitespace(), + ), filePath: proj.FullPath(fname) ).Project; } diff --git a/sources/SilkTouch/SilkTouch/Mods/AddVTables.cs b/sources/SilkTouch/SilkTouch/Mods/AddVTables.cs index e3d3326dfd..c2fc2b5c99 100644 --- a/sources/SilkTouch/SilkTouch/Mods/AddVTables.cs +++ b/sources/SilkTouch/SilkTouch/Mods/AddVTables.cs @@ -1454,8 +1454,7 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) rw.Reset(); proj = doc.WithSyntaxRoot( - rw.Visit(node)?.NormalizeWhitespace() - ?? throw new InvalidOperationException("Visit returned null") + rw.Visit(node) ?? throw new InvalidOperationException("Visit returned null") ).Project; if (rw.InterfacePartials.Count == 0) { @@ -1515,11 +1514,7 @@ rw.ClassName is not null ) { proj = proj - ?.AddDocument( - Path.GetFileName(fname), - root.NormalizeWhitespace(), - filePath: proj.FullPath(fname) - ) + ?.AddDocument(Path.GetFileName(fname), root, filePath: proj.FullPath(fname)) .Project; } diff --git a/sources/SilkTouch/SilkTouch/Mods/BakeSourceSets.cs b/sources/SilkTouch/SilkTouch/Mods/BakeSourceSets.cs index 16885d4a3a..36f5d61732 100644 --- a/sources/SilkTouch/SilkTouch/Mods/BakeSourceSets.cs +++ b/sources/SilkTouch/SilkTouch/Mods/BakeSourceSets.cs @@ -391,7 +391,7 @@ .. ctx { proj = proj.AddDocument( Path.GetFileName(bakedPath), - bakedRoot.NormalizeWhitespace(), + bakedRoot, // we can forgive the below nulls because RelativePath checks them, and returns null if they're null. filePath: proj.FullPath(bakedPath) ).Project; diff --git a/sources/SilkTouch/SilkTouch/Mods/ChangeNamespace.cs b/sources/SilkTouch/SilkTouch/Mods/ChangeNamespace.cs index 56edb5619f..ce19c5cca8 100644 --- a/sources/SilkTouch/SilkTouch/Mods/ChangeNamespace.cs +++ b/sources/SilkTouch/SilkTouch/Mods/ChangeNamespace.cs @@ -59,7 +59,8 @@ public Task> BeforeScrapeAsync(string key, List rsps[i] = rsp with { - GeneratorConfiguration = rsp.GeneratorConfiguration.ToWrapper() with { + GeneratorConfiguration = rsp.GeneratorConfiguration.ToWrapper() with + { DefaultNamespace = def, WithNamespaces = with, }, @@ -91,7 +92,7 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) var doc = proj!.GetDocument(docId) ?? throw new InvalidOperationException("Document missing"); proj = doc.WithSyntaxRoot( - rewriter.Visit(await doc.GetSyntaxRootAsync(ct))?.NormalizeWhitespace() + rewriter.Visit(await doc.GetSyntaxRootAsync(ct)) ?? throw new InvalidOperationException("Visit returned null.") ).Project; } @@ -116,17 +117,16 @@ public Rewriter( _usingsToAdd.Clear(); return base.VisitCompilationUnit(node) switch { - CompilationUnitSyntax syntax - => syntax.AddUsings( - _usingsToAdd - .Select(x => UsingDirective(ModUtils.NamespaceIntoIdentifierName(x))) - .Where(x => - syntax.Usings.All(y => x.Name?.ToString() != y.Name?.ToString()) - ) - .ToArray() - ), + CompilationUnitSyntax syntax => syntax.AddUsings( + _usingsToAdd + .Select(x => UsingDirective(ModUtils.NamespaceIntoIdentifierName(x))) + .Where(x => + syntax.Usings.All(y => x.Name?.ToString() != y.Name?.ToString()) + ) + .ToArray() + ), { } ret => ret, - null => null + null => null, }; } @@ -140,10 +140,11 @@ CompilationUnitSyntax syntax } return base.VisitNamespaceDeclaration(node) switch { - NamespaceDeclarationSyntax syntax - => syntax.WithName(ModUtils.NamespaceIntoIdentifierName(newNs)), + NamespaceDeclarationSyntax syntax => syntax.WithName( + ModUtils.NamespaceIntoIdentifierName(newNs) + ), { } ret => ret, - null => null + null => null, }; } @@ -159,10 +160,11 @@ FileScopedNamespaceDeclarationSyntax node } return base.VisitFileScopedNamespaceDeclaration(node) switch { - FileScopedNamespaceDeclarationSyntax syntax - => syntax.WithName(ModUtils.NamespaceIntoIdentifierName(newNs)), + FileScopedNamespaceDeclarationSyntax syntax => syntax.WithName( + ModUtils.NamespaceIntoIdentifierName(newNs) + ), { } ret => ret, - null => null + null => null, }; } } diff --git a/sources/SilkTouch/SilkTouch/Mods/Common/MSBuildModContext.cs b/sources/SilkTouch/SilkTouch/Mods/Common/MSBuildModContext.cs index 793a0ed1c7..4ade6bb2a9 100644 --- a/sources/SilkTouch/SilkTouch/Mods/Common/MSBuildModContext.cs +++ b/sources/SilkTouch/SilkTouch/Mods/Common/MSBuildModContext.cs @@ -308,13 +308,8 @@ private static async Task ToNormalisedStringAsync( CancellationToken ct = default ) { - var result = await CodeFormatter.FormatAsync( - root.NormalizeWhitespace().SyntaxTree, - _opts, - ct - ); - return !result.CompilationErrors.Any() - ? result.Code - : root.NormalizeWhitespace(eol: "\n").ToFullString(); + var normalizedRoot = root.NormalizeWhitespace(); + var result = await CodeFormatter.FormatAsync(normalizedRoot.SyntaxTree, _opts, ct); + return !result.CompilationErrors.Any() ? result.Code : normalizedRoot.ToFullString(); } } diff --git a/sources/SilkTouch/SilkTouch/Mods/ExtractHandles.cs b/sources/SilkTouch/SilkTouch/Mods/ExtractHandles.cs index d1018b9caf..f38e321042 100644 --- a/sources/SilkTouch/SilkTouch/Mods/ExtractHandles.cs +++ b/sources/SilkTouch/SilkTouch/Mods/ExtractHandles.cs @@ -51,7 +51,7 @@ public override async Task ExecuteAsync(IModContext ctx, CancellationToken ct = project = project .AddDocument( Path.GetFileName(relativePath), - node.NormalizeWhitespace(), + node, filePath: project.FullPath(relativePath) ) .Project; diff --git a/sources/SilkTouch/SilkTouch/Mods/ExtractNestedTyping.cs b/sources/SilkTouch/SilkTouch/Mods/ExtractNestedTyping.cs index e5073907e0..4d5ff5fb28 100644 --- a/sources/SilkTouch/SilkTouch/Mods/ExtractNestedTyping.cs +++ b/sources/SilkTouch/SilkTouch/Mods/ExtractNestedTyping.cs @@ -86,7 +86,7 @@ public override async Task ExecuteAsync(IModContext ctx, CancellationToken ct = // This is also where extracted enums are processed. rewriter.File = fname; project = doc.WithSyntaxRoot( - rewriter.Visit(node)?.NormalizeWhitespace() + rewriter.Visit(node) ?? throw new InvalidOperationException("Rewriter returned null") ).Project; @@ -110,8 +110,7 @@ rewriter.Namespace is not null ) ) : SingletonList(newStruct) - ) - .NormalizeWhitespace(), + ), filePath: project.FullPath( $"{fname.AsSpan()[..fname.LastIndexOf('/')]}/{newStruct.Identifier}.gen.cs" ) @@ -174,8 +173,7 @@ rewriter.Namespace is not null .WithMembers(SingletonList(typeDecl)) ) : SingletonList(typeDecl) - ) - .NormalizeWhitespace(), + ), filePath: project.FullPath($"{dir}/{identifier}.gen.cs") ) .Project; diff --git a/sources/SilkTouch/SilkTouch/Mods/InterceptNativeFunctions.cs b/sources/SilkTouch/SilkTouch/Mods/InterceptNativeFunctions.cs index cb563d3bf5..0c36df137a 100644 --- a/sources/SilkTouch/SilkTouch/Mods/InterceptNativeFunctions.cs +++ b/sources/SilkTouch/SilkTouch/Mods/InterceptNativeFunctions.cs @@ -48,7 +48,7 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) var doc = proj!.GetDocument(docId) ?? throw new InvalidOperationException("Document missing"); proj = doc.WithSyntaxRoot( - rewriter.Visit(await doc.GetSyntaxRootAsync(ct))?.NormalizeWhitespace() + rewriter.Visit(await doc.GetSyntaxRootAsync(ct)) ?? throw new InvalidOperationException("Visit returned null.") ).Project; } diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/IdentifierRenamingTransformer.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/IdentifierRenamingTransformer.cs index 3dcd4a281a..d2d459e11b 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/IdentifierRenamingTransformer.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/IdentifierRenamingTransformer.cs @@ -14,19 +14,25 @@ public class IdentifierRenamingTransformer : LocationTransformer { private ISymbol symbol = null!; - private readonly IReadOnlyDictionary> newNameLookup; + private readonly IReadOnlyDictionary< + string, + List<(ISymbol Symbol, string NewName)> + > newNameLookup; /// /// Creates a new IdentifierRenamingTransformer. /// /// The new names for each symbol - public IdentifierRenamingTransformer(IEnumerable<(ISymbol Symbol, string NewName)> newNames) : this(CreateNameLookup(newNames)) {} + public IdentifierRenamingTransformer(IEnumerable<(ISymbol Symbol, string NewName)> newNames) + : this(CreateNameLookup(newNames)) { } /// /// Creates a new IdentifierRenamingTransformer. /// /// The new names for each symbol grouped by symbol name. - public IdentifierRenamingTransformer(IReadOnlyDictionary> newNameLookup) + public IdentifierRenamingTransformer( + IReadOnlyDictionary> newNameLookup + ) { this.newNameLookup = newNameLookup; } @@ -34,9 +40,14 @@ public IdentifierRenamingTransformer(IReadOnlyDictionary /// Creates a name lookup dictionary designed for . /// - public static IReadOnlyDictionary> CreateNameLookup(IEnumerable<(ISymbol Symbol, string NewName)> names) + public static IReadOnlyDictionary< + string, + List<(ISymbol Symbol, string NewName)> + > CreateNameLookup(IEnumerable<(ISymbol Symbol, string NewName)> names) { - return names.GroupBy(t => t.Symbol.Name).ToDictionary(group => group.Key, group => group.ToList()); + return names + .GroupBy(t => t.Symbol.Name) + .ToDictionary(group => group.Key, group => group.ToList()); } /// @@ -68,7 +79,7 @@ private SyntaxToken GetRenamed(ISymbol symbol, SyntaxToken currentNameIdentifier return currentNameIdentifier; } - // ----- Types ----- + // ----- Types ----- /// public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs index 47f48c8014..213ea19db5 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs @@ -14,36 +14,74 @@ namespace Silk.NET.SilkTouch.Mods.LocationTransformation; /// and modifies the nodes only when coming back up. ///
/// Modifying nodes cause them to be detached from the semantic model (meaning no symbol information), -/// so this ensures that we gather all of the data we need before making changes. +/// so this ensures that we gather all the data we need before making changes. /// -/// Symbols to search for. -/// Transformers to use on each found symbol reference. -public class LocationTransformationRewriter(HashSet symbols, List transformers) : CSharpSyntaxRewriter +public class LocationTransformationRewriter : CSharpSyntaxRewriter { // Symbols can also be referenced within XML doc, which are trivia nodes. /// public override bool VisitIntoStructuredTrivia => true; - private readonly Dictionary queuedTransformations = new(); + private readonly Dictionary _queuedTransformations = new(); /// The symbol for the node. /// The index of the transformer that should be used when continuing the transformation process. private record struct QueuedTransformation(ISymbol Symbol, int TransformerIndex); - private readonly List tempNodeList = new(); + private readonly List _tempNodeList = new(); /// /// The semantic model of the currently processed document. /// - private SemanticModel semanticModel = null!; + private SemanticModel _semanticModel = null!; + + private readonly HashSet _symbols; + private readonly List _transformers; + private readonly HashSet _relevantIdentifiers; + + /// Symbols to search for. + /// Transformers to use on each found symbol reference. + public LocationTransformationRewriter( + HashSet symbols, + List transformers + ) + { + _symbols = symbols; + _transformers = transformers; + + // Used to skip symbol lookups + // Does not handle the omission of the "-Attribute" suffix, but generally, we don't need to transform attributes + _relevantIdentifiers = new HashSet(_symbols.Count); + foreach (var symbol in _symbols) + { + _relevantIdentifiers.Add(symbol.Name); + } + } + + private LocationTransformationRewriter( + HashSet symbols, + List transformers, + HashSet relevantIdentifiers + ) + { + _symbols = symbols; + _transformers = transformers; + _relevantIdentifiers = relevantIdentifiers; + } /// /// Initializes the renamer to work for a new document. Must be called before visiting any nodes. /// - public void Initialize(SemanticModel semanticModel) - { - this.semanticModel = semanticModel; - } + public void Initialize(SemanticModel semanticModel) => _semanticModel = semanticModel; + + /// + /// Clone this rewriter for purposes of thread safety. + /// + /// + /// This is allowed to return the current instance and share data. + /// + public LocationTransformationRewriter GetThreadSafeCopy() => + new(_symbols, [.. _transformers.Select(t => t.GetThreadSafeCopy())], _relevantIdentifiers); /// [return: NotNullIfNotNull("unmodifiedNode")] @@ -60,24 +98,25 @@ public void Initialize(SemanticModel semanticModel) // Check for queued transformation // To apply a transformation, we must be in the same level in the hierarchy as the selected node // We also must apply transformations when going back up in the hierarchy so we don't overwrite previous transformations - if (queuedTransformations.Remove(unmodifiedNode, out var transformation)) + if (_queuedTransformations.Remove(unmodifiedNode, out var transformation)) { if (transformation.TransformerIndex >= 0) { // Apply deferred transformer - var deferredTransformer = transformers[transformation.TransformerIndex]; - modifiedNode = deferredTransformer.Visit(modifiedNode) + var deferredTransformer = _transformers[transformation.TransformerIndex]; + modifiedNode = deferredTransformer + .Visit(modifiedNode) .WithLeadingTrivia(unmodifiedNode.GetLeadingTrivia().Select(VisitTrivia)) .WithTrailingTrivia(unmodifiedNode.GetTrailingTrivia()); } // Continue applying remaining transformers - for (var i = transformation.TransformerIndex + 1; i < transformers.Count; i++) + for (var i = transformation.TransformerIndex + 1; i < _transformers.Count; i++) { - var transformer = transformers[i]; + var transformer = _transformers[i]; // Calculate hierarchy - var hierarchy = tempNodeList; + var hierarchy = _tempNodeList; { hierarchy.Clear(); @@ -105,13 +144,17 @@ public void Initialize(SemanticModel semanticModel) { // We can't directly transform the node since we are at the wrong place in the hierarchy // Defer it so it is processed later - queuedTransformations.Add(selectedNode, new QueuedTransformation(transformation.Symbol, i)); + _queuedTransformations.Add( + selectedNode, + new QueuedTransformation(transformation.Symbol, i) + ); break; } // Transform the node - modifiedNode = transformer.Visit(modifiedNode) + modifiedNode = transformer + .Visit(modifiedNode) .WithLeadingTrivia(unmodifiedNode.GetLeadingTrivia().Select(VisitTrivia)) .WithTrailingTrivia(unmodifiedNode.GetTrailingTrivia()); } @@ -122,12 +165,12 @@ public void Initialize(SemanticModel semanticModel) private void ReportSymbol(SyntaxNode node, ISymbol? symbol) { - if (symbol == null || !symbols.Contains(symbol)) + if (symbol == null || !_symbols.Contains(symbol)) { return; } - queuedTransformations.Add(node, new QueuedTransformation(symbol, -1)); + _queuedTransformations.Add(node, new QueuedTransformation(symbol, -1)); } // ----- Types ----- @@ -135,7 +178,7 @@ private void ReportSymbol(SyntaxNode node, ISymbol? symbol) /// public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitClassDeclaration(node)!; @@ -144,7 +187,7 @@ public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) /// public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitStructDeclaration(node)!; @@ -153,7 +196,7 @@ public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) /// public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitInterfaceDeclaration(node)!; @@ -162,7 +205,7 @@ public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax /// public override SyntaxNode VisitRecordDeclaration(RecordDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitRecordDeclaration(node)!; @@ -171,7 +214,7 @@ public override SyntaxNode VisitRecordDeclaration(RecordDeclarationSyntax node) /// public override SyntaxNode VisitDelegateDeclaration(DelegateDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitDelegateDeclaration(node)!; @@ -180,7 +223,7 @@ public override SyntaxNode VisitDelegateDeclaration(DelegateDeclarationSyntax no /// public override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitEnumDeclaration(node)!; @@ -191,7 +234,7 @@ public override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node) /// public override SyntaxNode VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitEnumMemberDeclaration(node)!; @@ -200,7 +243,7 @@ public override SyntaxNode VisitEnumMemberDeclaration(EnumMemberDeclarationSynta /// public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitPropertyDeclaration(node)!; @@ -209,7 +252,7 @@ public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax no /// public override SyntaxNode VisitEventDeclaration(EventDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitEventDeclaration(node)!; @@ -218,7 +261,7 @@ public override SyntaxNode VisitEventDeclaration(EventDeclarationSyntax node) /// public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitMethodDeclaration(node)!; @@ -227,7 +270,7 @@ public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) /// public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitConstructorDeclaration(node)!; @@ -236,7 +279,7 @@ public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyn /// public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitDestructorDeclaration(node)!; @@ -247,7 +290,12 @@ public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSynta /// public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) { - var symbol = semanticModel.GetSymbolInfo(node).Symbol ?? semanticModel.GetTypeInfo(node).Type; + if (!_relevantIdentifiers.Contains(node.Identifier.Text)) + { + return node; + } + + var symbol = _semanticModel.GetSymbolInfo(node).Symbol; ReportSymbol(node, symbol); return base.VisitIdentifierName(node)!; @@ -257,9 +305,15 @@ public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) /// public override SyntaxNode VisitVariableDeclarator(VariableDeclaratorSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitVariableDeclarator(node)!; } + + // ----- Skipped nodes ----- + + // Using statements contain a lot of identifier nodes, but never any symbol references that we care about. + /// + public override SyntaxNode VisitUsingDirective(UsingDirectiveSyntax node) => node; } diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs index 8ae9303d72..a2faa241f3 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs @@ -26,7 +26,8 @@ public static async Task ModifyAllReferencesAsync( IEnumerable symbols, IEnumerable transformers, ILogger? logger = null, - CancellationToken ct = default) + CancellationToken ct = default + ) { var sourceProject = ctx.SourceProject; if (sourceProject == null) @@ -37,33 +38,42 @@ public static async Task ModifyAllReferencesAsync( // We need to track both the original solution and modified solution // The original is where we retrieve documents and semantic models // The modified solution is where we place the results - IReadOnlyList documentIds = [.. sourceProject.DocumentIds, .. ctx.TestProject?.DocumentIds ?? []]; + IReadOnlyList documentIds = + [ + .. sourceProject.DocumentIds, + .. ctx.TestProject?.DocumentIds ?? [], + ]; var originalSolution = sourceProject.Solution; var newDocuments = new ConcurrentDictionary(); var symbolSet = new HashSet(symbols, SymbolEqualityComparer.Default); - await Parallel.ForEachAsync(documentIds, ct, async (documentId, _) => { - var originalDocument = originalSolution.GetDocument(documentId); - if (originalDocument == null) + var baseRewriter = new LocationTransformationRewriter(symbolSet, transformers.ToList()); + await Parallel.ForEachAsync( + documentIds, + ct, + async (documentId, _) => { - return; - } + var originalDocument = originalSolution.GetDocument(documentId); + if (originalDocument == null) + { + return; + } - var originalRoot = await originalDocument.GetSyntaxRootAsync(ct); - var semanticModel = await originalDocument.GetSemanticModelAsync(ct); + var originalRoot = await originalDocument.GetSyntaxRootAsync(ct); + var semanticModel = await originalDocument.GetSemanticModelAsync(ct); + if (originalRoot == null || semanticModel == null) + { + return; + } - if (originalRoot == null || semanticModel == null) - { - return; - } - - // Since this is multithreaded, each thread needs their own copy of the rewriter and transformers - var rewriter = new LocationTransformationRewriter(symbolSet, [..transformers.Select(t => t.GetThreadSafeCopy())]); - rewriter.Initialize(semanticModel); + // Since this is multithreaded, each thread needs their own copy of the rewriter and transformers + var rewriter = baseRewriter.GetThreadSafeCopy(); - var newRoot = rewriter.Visit(originalRoot); - newDocuments.TryAdd(documentId, newRoot); - }); + rewriter.Initialize(semanticModel); + var newRoot = rewriter.Visit(originalRoot); + newDocuments.TryAdd(documentId, newRoot); + } + ); var modifiedSolution = sourceProject.Solution; foreach (var (documentId, newRoot) in newDocuments) diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformer.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformer.cs index 56c9884937..c1c466a171 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformer.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformer.cs @@ -24,12 +24,16 @@ public abstract class LocationTransformer : CSharpSyntaxRewriter /// The node hierarchy as a reversed stack. Index 0 is the current node. Index 1 is its parent and so on. /// The symbol that is associated with this node. /// The given node, another node, or null. - public abstract SyntaxNode? GetNodeToModify(IReadOnlyList hierarchy, ISymbol symbol); + public abstract SyntaxNode? GetNodeToModify( + IReadOnlyList hierarchy, + ISymbol symbol + ); /// /// Clone this location transformer for purposes of thread safety. - /// If the location transformer is already thread safe, the location transformer - /// does not need to be cloned. /// + /// + /// This is allowed to return the current instance and share data. + /// public abstract LocationTransformer GetThreadSafeCopy(); } diff --git a/sources/SilkTouch/SilkTouch/Mods/MarkNativeNames.cs b/sources/SilkTouch/SilkTouch/Mods/MarkNativeNames.cs index ebf68b4b0e..9f8aa554d1 100644 --- a/sources/SilkTouch/SilkTouch/Mods/MarkNativeNames.cs +++ b/sources/SilkTouch/SilkTouch/Mods/MarkNativeNames.cs @@ -37,7 +37,7 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) var doc = proj.GetDocument(docId) ?? throw new InvalidOperationException("Document missing"); proj = doc.WithSyntaxRoot( - rewriter.Visit(await doc.GetSyntaxRootAsync(ct))?.NormalizeWhitespace() + rewriter.Visit(await doc.GetSyntaxRootAsync(ct)) ?? throw new InvalidOperationException("Visit returned null.") ).Project; } diff --git a/sources/SilkTouch/SilkTouch/Mods/MixKhronosData.cs b/sources/SilkTouch/SilkTouch/Mods/MixKhronosData.cs index 98395e7517..5a48a038a4 100644 --- a/sources/SilkTouch/SilkTouch/Mods/MixKhronosData.cs +++ b/sources/SilkTouch/SilkTouch/Mods/MixKhronosData.cs @@ -363,7 +363,7 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) var doc = proj.GetDocument(docId) ?? throw new InvalidOperationException("Document missing"); proj = doc.WithSyntaxRoot( - rewriter1.Visit(await doc.GetSyntaxRootAsync(ct))?.NormalizeWhitespace() + rewriter1.Visit(await doc.GetSyntaxRootAsync(ct)) ?? throw new InvalidOperationException("Visit returned null.") ).Project; } @@ -373,7 +373,7 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) { proj = proj.AddDocument( Path.GetFileName(filePath), - node.NormalizeWhitespace(), + node, filePath: proj.FullPath(filePath) ).Project; } @@ -385,7 +385,7 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) var doc = proj.GetDocument(docId) ?? throw new InvalidOperationException("Document missing"); proj = doc.WithSyntaxRoot( - rewriter2.Visit(await doc.GetSyntaxRootAsync(ct))?.NormalizeWhitespace() + rewriter2.Visit(await doc.GetSyntaxRootAsync(ct)) ?? throw new InvalidOperationException("Visit returned null.") ).Project; } @@ -397,7 +397,7 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) var doc = proj.GetDocument(docId) ?? throw new InvalidOperationException("Document missing"); proj = doc.WithSyntaxRoot( - rewriter3.Visit(await doc.GetSyntaxRootAsync(ct))?.NormalizeWhitespace() + rewriter3.Visit(await doc.GetSyntaxRootAsync(ct)) ?? throw new InvalidOperationException("Visit returned null.") ).Project; } diff --git a/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs b/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs index 100f43ad25..afccdb95ab 100644 --- a/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs +++ b/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs @@ -195,9 +195,15 @@ await proj.GetCompilationAsync(ct) // Change the filenames where appropriate. proj = ctx.SourceProject; + var typeNames = newNames.GetValueOrDefault("", []); var typeNamesLongestFirst = typeNames.OrderByDescending(x => x.Key.Length).ToArray(); + var documentPaths = proj + .Documents.Select(d => d.RelativePath()) + .Where(d => d != null) + .ToHashSet(); + foreach (var docId in proj.DocumentIds) { var doc = proj.GetDocument(docId); @@ -206,6 +212,7 @@ await proj.GetCompilationAsync(ct) continue; } + // Find best matching document for renamed types var firstMatch = typeNamesLongestFirst.FirstOrDefault(x => doc.FilePath.Contains(x.Key) || doc.Name.Contains(x.Key) ); @@ -214,43 +221,36 @@ await proj.GetCompilationAsync(ct) continue; } - var originalName = doc.Name; - doc = doc.ReplaceNameAndPath(oldName, newName); - - var found = false; - if (doc.FilePath is not null) + // Save syntax tree so we can restore it later + // This is because modifying the document can cause it to be reparsed + // C# discord: https://discord.com/channels/143867839282020352/598678594750775301/1494176147351535687 + var syntaxRoot = await doc.GetSyntaxRootAsync(ct); + if (syntaxRoot == null) { - foreach (var checkDocId in proj.DocumentIds) - { - if (checkDocId == docId) - { - continue; - } + continue; + } - var checkDoc = proj.GetDocument(checkDocId); - if (checkDoc?.FilePath is null) - { - continue; - } + // Rename doc and update path + var originalName = doc.Name; + var originalPath = doc.RelativePath(); + doc = doc.ReplaceNameAndPath(oldName, newName); + var newPath = doc.RelativePath(); - if (checkDoc.FilePath == doc.FilePath) - { - found = true; - break; - } - } - } + // Restore syntax tree + doc = doc.WithSyntaxRoot(syntaxRoot); - if (found) + // Check for path conflict + documentPaths.Remove(originalPath); + if (!documentPaths.Add(newPath)) { logger.LogError( - $"{originalName} -> {doc.Name} failed to rename file as a file already exists at {doc.FilePath}" + $"{originalName} -> {doc.Name} failed to rename file as a file already exists at {newPath}" ); + + continue; } - else - { - proj = doc.Project; - } + + proj = doc.Project; } ctx.SourceProject = proj; @@ -1132,17 +1132,6 @@ public void ProcessNames(NameProcessorContext context) } } - // These collections are used later. - // These keep track of method discriminators to determine whether we have incompatible overloads. - // We keep track of the first original name so that we can add it to conflictingOriginalNames when we - // do discover a conflict (along with the original name of the actual conflict). - var methodDiscriminators = - new Dictionary< - string, - (string? FirstOriginalName, List Methods) - >(); - var conflictingOriginalNames = new HashSet(); - // This loop cannot be part of the loop below because it modifies the primaries foreach (var (scope, members) in context.Names) { @@ -1208,6 +1197,17 @@ d is not MethodDeclarationSyntax } } + // These collections are used later. + // These keep track of method discriminators to determine whether we have incompatible overloads. + // We keep track of the first original name so that we can add it to conflictingOriginalNames when we + // do discover a conflict (along with the original name of the actual conflict). + var methodDiscriminators = + new Dictionary< + string, + (string? FirstOriginalName, List Methods) + >(); + var conflictingOriginalNames = new HashSet(); + foreach (var (scope, members) in context.Names) { nameData.Names.TryGetValue(scope, out var scopeData); diff --git a/sources/SilkTouch/SilkTouch/Mods/StripAttributes.cs b/sources/SilkTouch/SilkTouch/Mods/StripAttributes.cs index 9bf511bf3b..c043f71293 100644 --- a/sources/SilkTouch/SilkTouch/Mods/StripAttributes.cs +++ b/sources/SilkTouch/SilkTouch/Mods/StripAttributes.cs @@ -48,7 +48,7 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) var doc = proj.GetDocument(docId) ?? throw new InvalidOperationException("Document missing"); proj = doc.WithSyntaxRoot( - rewriter.Visit(await doc.GetSyntaxRootAsync(ct))?.NormalizeWhitespace() + rewriter.Visit(await doc.GetSyntaxRootAsync(ct)) ?? throw new InvalidOperationException("Visit returned null.") ).Project; } diff --git a/sources/SilkTouch/SilkTouch/Mods/TransformEnums.cs b/sources/SilkTouch/SilkTouch/Mods/TransformEnums.cs index e3fa94640f..e5ee37046e 100644 --- a/sources/SilkTouch/SilkTouch/Mods/TransformEnums.cs +++ b/sources/SilkTouch/SilkTouch/Mods/TransformEnums.cs @@ -190,7 +190,7 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) var doc = proj.GetDocument(docId) ?? throw new InvalidOperationException("Document missing"); proj = doc.WithSyntaxRoot( - rewriter.Visit(await doc.GetSyntaxRootAsync(ct))?.NormalizeWhitespace() + rewriter.Visit(await doc.GetSyntaxRootAsync(ct)) ?? throw new InvalidOperationException("Visit returned null.") ).Project; } diff --git a/sources/SilkTouch/SilkTouch/Mods/TransformFunctions.cs b/sources/SilkTouch/SilkTouch/Mods/TransformFunctions.cs index 222c974af8..1d97c3a230 100644 --- a/sources/SilkTouch/SilkTouch/Mods/TransformFunctions.cs +++ b/sources/SilkTouch/SilkTouch/Mods/TransformFunctions.cs @@ -60,7 +60,7 @@ public async Task ExecuteAsync(IModContext ctx, CancellationToken ct = default) proj!.GetDocument(docId) ?? throw new InvalidOperationException("Document missing"); if (await doc.GetSyntaxRootAsync(ct) is { } root) { - proj = doc.WithSyntaxRoot(Visit(root).NormalizeWhitespace()).Project; + proj = doc.WithSyntaxRoot(Visit(root)).Project; } } diff --git a/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs b/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs index 04feaa8cd0..c2011b0ba4 100644 --- a/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs +++ b/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs @@ -86,8 +86,8 @@ public override async Task ExecuteAsync(IModContext ctx, CancellationToken ct = } } - // Do the two following transformation to all references of the handle types: - // 2. Reduce pointer dimensions + // Reduce pointer dimensions + // The -Handle suffix will be applied later by PrettifyNames if the user configures it to do so ctx.SourceProject = project; await LocationTransformationUtils.ModifyAllReferencesAsync( ctx, @@ -115,9 +115,7 @@ [new PointerDimensionReductionTransformer()], var syntaxRoot = await syntaxTree.GetRootAsync(ct); // Rewrite handle struct to include handle members - document = document.WithSyntaxRoot( - handleTypeRewriter.Visit(syntaxRoot).NormalizeWhitespace() - ); + document = document.WithSyntaxRoot(handleTypeRewriter.Visit(syntaxRoot)); project = document.Project; } diff --git a/sources/SilkTouch/SilkTouch/Mods/TransformProperties.cs b/sources/SilkTouch/SilkTouch/Mods/TransformProperties.cs index bed5366d8a..4ce2a40291 100644 --- a/sources/SilkTouch/SilkTouch/Mods/TransformProperties.cs +++ b/sources/SilkTouch/SilkTouch/Mods/TransformProperties.cs @@ -58,7 +58,7 @@ is GenericNameSyntax && lit.IsKind(SyntaxKind.Utf8StringLiteralExpression) ) { - node = node.WithType(IdentifierName("Utf8String")).NormalizeWhitespace(); + node = node.WithType(IdentifierName("Utf8String")); } return base.VisitPropertyDeclaration(node); diff --git a/sources/SilkTouch/SilkTouch/Mods/Transformation/FunctionTransformer.cs b/sources/SilkTouch/SilkTouch/Mods/Transformation/FunctionTransformer.cs index ba663df169..9ce3347aac 100644 --- a/sources/SilkTouch/SilkTouch/Mods/Transformation/FunctionTransformer.cs +++ b/sources/SilkTouch/SilkTouch/Mods/Transformation/FunctionTransformer.cs @@ -117,7 +117,6 @@ meth.Body.Statements[0] as ExpressionStatementSyntax .WithBody(null) .WithExpressionBody(null) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) - .NormalizeWhitespace(eol: "\n") .ToFullString() ); continue; diff --git a/sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs b/sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs new file mode 100644 index 0000000000..03989bda03 --- /dev/null +++ b/sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace Silk.NET.SilkTouch.Profiling; + +/// +/// Lightweight way to profile a section of code. +/// +internal readonly struct ProfilingScope : IDisposable +{ + private readonly string _name; + private readonly long _timestamp; + + public ProfilingScope(string name) + { + _name = name; + _timestamp = Stopwatch.GetTimestamp(); + } + + public void Dispose() + { + var elapsed = Stopwatch.GetElapsedTime(_timestamp); + Console.WriteLine( + "Elapsed time for scope \"{0}\": {1:F3} ms", + _name, + elapsed.TotalMilliseconds + ); + } +}