From 4ed2c650df8c1d02a615051e2c9928f38acdd9af Mon Sep 17 00:00:00 2001 From: MOCHIZUKI Natsune Date: Thu, 14 Mar 2024 19:59:06 +0900 Subject: [PATCH 1/9] fix: fix invalid reference when using extension methods without receiver --- src/Plana.CLI/Properties/launchSettings.json | 5 +++++ .../ISymbolExtensions.cs | 17 +++++++++++++++++ .../CSharpSymbolsRewriter.cs | 8 ++++++-- src/Plana/Obfuscator.cs | 3 ++- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Plana.CLI/Properties/launchSettings.json b/src/Plana.CLI/Properties/launchSettings.json index 3b45507..286394c 100644 --- a/src/Plana.CLI/Properties/launchSettings.json +++ b/src/Plana.CLI/Properties/launchSettings.json @@ -10,6 +10,11 @@ "commandLineArgs": "obfuscate --workspace ./Plana.sln --output \"../dist\" --log-level verbose --plugins ./Plana.CLI/bin/Debug/net8.0/plugins --rename-symbols --rename-namespaces --rename-classes --rename-properties --rename-fields --rename-methods --rename-variables", "workingDirectory": "../" }, + "Plana.CLI - Obfuscate (Single Project)": { + "commandName": "Project", + "commandLineArgs": "obfuscate --workspace ./Plana.CLI/Plana.CLI.csproj --output \"../dist\" --log-level verbose --plugins ./Plana.CLI/bin/Debug/net8.0/plugins --rename-symbols --rename-namespaces --rename-classes --rename-properties --rename-fields --rename-methods --rename-variables", + "workingDirectory": "../" + }, "Plana.CLI - Obfuscate (--rename-namespaces)": { "commandName": "Project", "commandLineArgs": "obfuscate --workspace ./Plana.sln --output \"../dist\" --log-level verbose --plugins ./Plana.CLI/bin/Debug/net8.0/plugins --rename-symbols --rename-namespaces", diff --git a/src/Plana.Composition.Extensions/ISymbolExtensions.cs b/src/Plana.Composition.Extensions/ISymbolExtensions.cs index d067212..2295943 100644 --- a/src/Plana.Composition.Extensions/ISymbolExtensions.cs +++ b/src/Plana.Composition.Extensions/ISymbolExtensions.cs @@ -5,6 +5,8 @@ using Microsoft.CodeAnalysis; +using Plana.Composition.Abstractions.Analysis; + namespace Plana.Composition.Extensions; // ReSharper disable once InconsistentNaming @@ -33,6 +35,21 @@ public static class ISymbolExtensions } #pragma warning restore CS8619 +#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). + public static bool IsInWorkspace(this ISymbol symbol, ISolution solution) + { + var sources = symbol.OriginalDefinition.Locations.Select(w => w.SourceTree?.FilePath).Where(w => w != null).Select(Path.GetFullPath); + var targets = solution.Projects.SelectMany(w => w.Documents).Select(w => Path.GetFullPath(w.Path)); + + return sources.All(w => targets.Contains(w)); + } +#pragma warning restore CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). + + public static bool IsNotInWorkspace(this ISymbol symbol, ISolution solution) + { + return !IsInWorkspace(symbol, solution); + } + /* // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. diff --git a/src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs b/src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs index 293e843..84dd01b 100644 --- a/src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs +++ b/src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs @@ -211,8 +211,12 @@ internal class CSharpSymbolsRewriter(IDocument document, bool keepNameOnInspecto { if (symbol is IMethodSymbol { IsExtensionMethod: true } m) { - if (dict.TryGetValue(m.ReducedFrom!, out var value1)) - return identifier.WithIdentifier(SyntaxFactory.Identifier(value1)); + if (m.ReducedFrom != null) + if (dict.TryGetValue(m.ReducedFrom, out var value1)) + return identifier.WithIdentifier(SyntaxFactory.Identifier(value1)); + + if (dict.TryGetValue(m.OriginalDefinition, out var value2)) + return identifier.WithIdentifier(SyntaxFactory.Identifier(value2)); } else { diff --git a/src/Plana/Obfuscator.cs b/src/Plana/Obfuscator.cs index cc3e8b9..eab146a 100644 --- a/src/Plana/Obfuscator.cs +++ b/src/Plana/Obfuscator.cs @@ -35,8 +35,9 @@ public async Task> RunAsync(RunKind kind, IPlanaR await instance.RunAsync(context); await workspace.CommitAsync(context.CancellationToken); } - catch + catch (Exception i) { + logger?.LogDebug(i.Message); await workspace.RollbackAsync(ct); } From be09255004ffad60a0163008fdea88e1b2b73b27 Mon Sep 17 00:00:00 2001 From: MOCHIZUKI Natsune Date: Thu, 14 Mar 2024 20:24:36 +0900 Subject: [PATCH 2/9] chore: add ci for project workspace --- .github/workflows/dotnet-compilation.yml | 61 +++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-compilation.yml b/.github/workflows/dotnet-compilation.yml index 52ce5e3..f4598c2 100644 --- a/.github/workflows/dotnet-compilation.yml +++ b/.github/workflows/dotnet-compilation.yml @@ -15,7 +15,7 @@ permissions: pull-requests: write jobs: - build: + build-on-solution: strategy: fail-fast: false matrix: @@ -73,3 +73,62 @@ jobs: ! git diff-index --quiet HEAD git --no-pager diff --ignore-all-space --ignore-blank-lines + + build-on-project: + strategy: + fail-fast: false + matrix: + plugins: + - "--shuffle-declarations" + - "--rename-symbols --rename-namespaces --rename-classes --rename-properties --rename-fields --rename-methods --rename-variables" + - "--disable-console-output --disable-symbols=T:System.Console,T:System.Diagnostics.Debug" + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup .NET tools + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "8.0.x" + + - name: Restore NuGet dependencies + run: | + dotnet restore ./src/Plana.sln + + - name: Phase.1 -- Build Original Source + run: | + dotnet build ./src/Plana.sln --no-restore --configuration=Release /p:WarningLevel=0 + + mkdir -p ./src/Plana.CLI/bin/Releases/net8.0/plugins + cp ./src/Plana.Composition.Extensions/bin/Release/net8.0/Plana.Composition.Extensions.dll ./src/Plana.CLI/bin/Releases/net8.0/plugins + cp ./src/Plana.Composition.DisableConsoleOutput/bin/Release/net8.0/Plana.Composition.DisableConsoleOutput.dll ./src/Plana.CLI/bin/Releases/net8.0/plugins + cp ./src/Plana.Composition.RenameSymbols/bin/Release/net8.0/Plana.Composition.RenameSymbols.dll ./src/Plana.CLI/bin/Releases/net8.0/plugins + cp ./src/Plana.Composition.ShuffleDeclarations/bin/Release/net8.0/Plana.Composition.ShuffleDeclarations.dll ./src/Plana.CLI/bin/Releases/net8.0/plugins + + dotnet ./src/Plana.CLI/bin/Release/net8.0/Plana.CLI.dll obfuscate --log-level verbose --workspace ./src/Plana.CLI/Plana.CLI.csproj --plugins ./src/Plana.CLI/bin/Releases/net8.0/plugins --write ${{ matrix.plugins }} + + - name: Phase.2 -- Re-Build Obfuscated Source + run: | + ! git diff-index --quiet HEAD + + dotnet build ./src/Plana.sln --no-restore --configuration=Release /p:WarningLevel=0 + + mkdir -p ./src/Plana.CLI/bin/Releases/net8.0/plugins + cp ./src/Plana.Composition.Extensions/bin/Release/net8.0/Plana.Composition.Extensions.dll ./src/Plana.CLI/bin/Releases/net8.0/plugins + cp ./src/Plana.Composition.DisableConsoleOutput/bin/Release/net8.0/Plana.Composition.DisableConsoleOutput.dll ./src/Plana.CLI/bin/Releases/net8.0/plugins + cp ./src/Plana.Composition.RenameSymbols/bin/Release/net8.0/Plana.Composition.RenameSymbols.dll ./src/Plana.CLI/bin/Releases/net8.0/plugins + cp ./src/Plana.Composition.ShuffleDeclarations/bin/Release/net8.0/Plana.Composition.ShuffleDeclarations.dll ./src/Plana.CLI/bin/Releases/net8.0/plugins + + git add -A + git stash save + + - name: Phase.3 -- Re-Test with obfuscated source + run: | + # --log-level=verbose cannot be used because the target class has been obfuscated and renamed. + # It will be possible to control it by adding the PublicAPI attribute. + dotnet ./src/Plana.CLI/bin/Release/net8.0/Plana.CLI.dll obfuscate --workspace ./src/Plana.CLI/Plana.CLI.csproj --plugins ./src/Plana.CLI/bin/Releases/net8.0/plugins --write ${{ matrix.plugins }} + + ! git diff-index --quiet HEAD + + git --no-pager diff --ignore-all-space --ignore-blank-lines From 555669e2834e93052b90f4ba4345726f2d492907 Mon Sep 17 00:00:00 2001 From: MOCHIZUKI Natsune Date: Thu, 14 Mar 2024 20:25:26 +0900 Subject: [PATCH 3/9] fix: fix invalid renaming in project level workspace --- .../RenameSymbolsPluginTest_Classes.cs | 2 +- .../RenameSymbolsPluginTest_Methods.cs | 20 +++++------ .../RenameSymbolsPluginTest_Variables.cs | 8 ++--- .../CSharpSymbolsWalker.cs | 33 ++++++++++++------- .../RenameSymbolsPlugin.cs | 1 + 5 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Classes.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Classes.cs index 71c4f10..a9ecff4 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Classes.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Classes.cs @@ -142,7 +142,7 @@ public async Task RenameClasses_Interface() Assert.Equal(identifier, b.BaseList?.Types[0].Type.ToString()); var c = await reference.GetFirstSyntax(); - Assert.Equal(identifier, c.ParameterList?.Parameters[1].Type?.ToString()); + Assert.Equal(identifier, c.ParameterList?.Parameters[2].Type?.ToString()); } [Fact] diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Methods.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Methods.cs index ad404a1..9d76d37 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Methods.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Methods.cs @@ -25,8 +25,8 @@ public async Task RenameMethods_ExtensionMethods() var originalDefinition = await container.GetSourceByPathAsync("Plana.CLI/Extensions/CommandExtensions.cs"); var reference = await container.GetSourceByPathAsync("Plana.CLI/Commands/ObfuscateCommand.cs"); - // CommandExtensions.AddOptions -> _0x4e115ed4 - const string addOptionsIdentifier = "_0x4e115ed4"; + // CommandExtensions.AddOptions -> _0xb35682f5 + const string addOptionsIdentifier = "_0xb35682f5"; var def = await originalDefinition.GetFirstSyntax((w, sm) => { @@ -116,8 +116,8 @@ public async Task RenameMethods_GenericsInterfaceMethods() var @interface = await container.GetSourceByPathAsync("Plana.Testing/ITestableObject.cs"); var implementation = await container.GetSourceByTypeAsync(typeof(InlineSource)); - // ITestableObject.ToMatchInlineSnapshot(T) -> _0x204ecb6f - const string identifier = "_0x204ecb6f"; + // ITestableObject.ToMatchInlineSnapshot(T) -> _0x84fd82f3 + const string identifier = "_0x84fd82f3"; var decl = await @interface.GetFirstSyntax(); Assert.Equal(identifier, decl.Identifier.ToString()); @@ -134,8 +134,8 @@ public async Task RenameMethods_GenericsMethods() var implementation = await container.GetSourceByTypeAsync(typeof(InlineSource)); - // InlineSource.GetSyntaxOf -> _0x2b70476f - const string identifier = "_0x2b70476f"; + // InlineSource.GetSyntaxOf -> _0xd52be804 + const string identifier = "_0xd52be804"; var def = await implementation.GetFirstSyntax((w, sm) => { @@ -234,8 +234,8 @@ bool IsMethodsHasRunAsyncSignature(MethodDeclarationSyntax w, SemanticModel sm) }); Assert.Equal(runAsyncIdentifier, ((MemberAccessExpressionSyntax)referenceDecl.Expression).Name.Identifier.ToString()); - // IPlanaPlugin2.ObfuscateAsync -> _0xba881b8e - const string obfuscateAsyncIdentifier = "_0xba881b8e"; + // IPlanaPlugin2.ObfuscateAsync -> _0xc7b29ba1 + const string obfuscateAsyncIdentifier = "_0xc7b29ba1"; var inheritAbstractionDecl2 = (await inheritAbstraction.GetSyntaxList(IsMethodsHasRunAsyncSignature))[1]; Assert.Equal(obfuscateAsyncIdentifier, inheritAbstractionDecl2.Identifier.ToString()); @@ -253,8 +253,8 @@ public async Task RenameMethods_OriginalDefinitionMethods() var originalDefinition = await container.GetSourceByPathAsync("Plana.Workspace/SolutionWorkspace.cs"); var reference = await container.GetSourceByPathAsync("Plana.CLI/Commands/ObfuscateCommand.cs"); - // SolutionWorkspace.CreateWorkspaceAsync -> _0xc686c7a5 - const string createWorkspaceAsyncIdentifier = "_0xc686c7a5"; + // SolutionWorkspace.CreateWorkspaceAsync -> _0xc09f4e99 + const string createWorkspaceAsyncIdentifier = "_0xc09f4e99"; var def = await originalDefinition.GetFirstSyntax(w => w.HasModifier(SyntaxKind.StaticKeyword)); Assert.Equal(createWorkspaceAsyncIdentifier, def.Identifier.ToString()); diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Variables.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Variables.cs index 8a0d50f..d5d101d 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Variables.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Variables.cs @@ -20,8 +20,8 @@ public async Task RenameVariables_ForEachStatement() var implementation = await container.GetSourceByTypeAsync(typeof(Obfuscator)); - // instance -> _0x3a050ac8 - const string identifier = "_0x3a050ac8"; + // instance -> _0xc22a5edb + const string identifier = "_0xc22a5edb"; var @foreach = await implementation.GetFirstSyntax(); Assert.Equal(identifier, @foreach.Identifier.ToFullString()); @@ -38,8 +38,8 @@ public async Task RenameVariables_PrimaryConstructor() var implementation = await container.GetSourceByTypeAsync(typeof(AnnotationComment)); - // AnnotationComment.Annotation -> _0xd660581b - const string identifier = "_0xd660581b"; + // AnnotationComment.Annotation -> _0xf35395eb + const string identifier = "_0xf35395eb"; var declaration = await implementation.GetFirstSyntax(); var constructor = declaration.ParameterList!.Parameters[0]; diff --git a/src/Plana.Composition.RenameSymbols/CSharpSymbolsWalker.cs b/src/Plana.Composition.RenameSymbols/CSharpSymbolsWalker.cs index abf7437..e42e52f 100644 --- a/src/Plana.Composition.RenameSymbols/CSharpSymbolsWalker.cs +++ b/src/Plana.Composition.RenameSymbols/CSharpSymbolsWalker.cs @@ -13,7 +13,7 @@ namespace Plana.Composition.RenameSymbols; -internal class CSharpSymbolsWalker(IDocument document, IPlanaSecureRandom random, bool isRenameNamespaces, bool isRenameClasses, bool isRenameProperties, bool isRenameFields, bool isRenameMethods, bool isRenameVariables, Dictionary dict) +internal class CSharpSymbolsWalker(ISolution solution, IDocument document, IPlanaSecureRandom random, bool isRenameNamespaces, bool isRenameClasses, bool isRenameProperties, bool isRenameFields, bool isRenameMethods, bool isRenameVariables, Dictionary dict) : CSharpSyntaxWalker { private static readonly List Messages = @@ -167,6 +167,12 @@ public override void VisitForEachStatement(ForEachStatementSyntax node) base.VisitForEachStatement(node); } + private string KeepOriginalName(ISymbol symbol, string? name = null) + { + dict.Add(symbol, name ?? symbol.Name); + return name ?? symbol.Name; + } + #region classes public override void VisitClassDeclaration(ClassDeclarationSyntax node) @@ -271,19 +277,17 @@ private string SetMethodIdentifier(IMethodSymbol symbol) var overridden = original.OverriddenMethod; var isExternalDefinition = overridden.Locations.Any(w => w.IsInMetadata); if (isExternalDefinition) - { - // keep - dict.Add(symbol, overridden.Name); - return overridden.Name; - } + return KeepOriginalName(symbol, overridden.Name); + + if (overridden.IsNotInWorkspace(solution)) + return KeepOriginalName(symbol, overridden.Name); } if (original.Locations.Any(w => w.IsInMetadata)) - { - // keep - dict.Add(symbol, symbol.Name); - return symbol.Name; - } + return KeepOriginalName(symbol); + + if (original.IsNotInWorkspace(solution)) + return KeepOriginalName(symbol); var @interface = symbol.GetInterfaceSymbol(); if (@interface is IMethodSymbol s) @@ -339,6 +343,10 @@ private string SetPropertyIdentifier(IPropertySymbol symbol) return val; var original = symbol.OriginalDefinition; + + if (original.IsNotInWorkspace(solution)) + return KeepOriginalName(original); + var @interface = symbol.GetInterfaceSymbol(); if (@interface is IPropertySymbol s) { @@ -395,6 +403,9 @@ private void SetNamespaceIdentifier(INamespaceSymbol symbol) if (dict.ContainsKey(symbol)) return; + if (symbol.IsNotInWorkspace(solution)) + return; + if (symbol.ContainingNamespace.IsGlobalNamespace) { // root namespace diff --git a/src/Plana.Composition.RenameSymbols/RenameSymbolsPlugin.cs b/src/Plana.Composition.RenameSymbols/RenameSymbolsPlugin.cs index 0c10db9..d3a8b36 100644 --- a/src/Plana.Composition.RenameSymbols/RenameSymbolsPlugin.cs +++ b/src/Plana.Composition.RenameSymbols/RenameSymbolsPlugin.cs @@ -57,6 +57,7 @@ public async Task ObfuscateAsync(IPlanaPluginRunContext context) foreach (var document in context.Solution.Projects.SelectMany(w => w.Documents)) { var walker = new CSharpSymbolsWalker( + context.Solution, document, context.SecureRandom, IsEnableNamespaceRenaming, From 5020e5d1abd6e088799586b23598b0cc9c4c678b Mon Sep 17 00:00:00 2001 From: MOCHIZUKI Natsune Date: Sat, 16 Mar 2024 00:13:34 +0900 Subject: [PATCH 4/9] fix: support to project level obfuscation in namespaces --- src/Plana.CLI/Properties/launchSettings.json | 4 +- .../ISymbolExtensions.cs | 47 ++++++++--- .../RenameSymbolsPluginTest_Namespaces.cs | 22 +++++- .../CSharpSymbolsRewriter.cs | 77 ++++++++++++++++++- .../CSharpSymbolsWalker.cs | 8 +- .../RenameSymbolsPlugin.cs | 2 +- 6 files changed, 142 insertions(+), 18 deletions(-) diff --git a/src/Plana.CLI/Properties/launchSettings.json b/src/Plana.CLI/Properties/launchSettings.json index 286394c..c672fcb 100644 --- a/src/Plana.CLI/Properties/launchSettings.json +++ b/src/Plana.CLI/Properties/launchSettings.json @@ -5,12 +5,12 @@ "commandLineArgs": "obfuscate --workspace ./Plana.sln --output \"../dist\" --log-level verbose --plugins ./Plana.CLI/bin/Debug/net8.0/plugins --rename-symbols --retrieve-args", "workingDirectory": "../" }, - "Plana.CLI - Obfuscate": { + "Plana.CLI - Obfuscate (Solution)": { "commandName": "Project", "commandLineArgs": "obfuscate --workspace ./Plana.sln --output \"../dist\" --log-level verbose --plugins ./Plana.CLI/bin/Debug/net8.0/plugins --rename-symbols --rename-namespaces --rename-classes --rename-properties --rename-fields --rename-methods --rename-variables", "workingDirectory": "../" }, - "Plana.CLI - Obfuscate (Single Project)": { + "Plana.CLI - Obfuscate (Project)": { "commandName": "Project", "commandLineArgs": "obfuscate --workspace ./Plana.CLI/Plana.CLI.csproj --output \"../dist\" --log-level verbose --plugins ./Plana.CLI/bin/Debug/net8.0/plugins --rename-symbols --rename-namespaces --rename-classes --rename-properties --rename-fields --rename-methods --rename-variables", "workingDirectory": "../" diff --git a/src/Plana.Composition.Extensions/ISymbolExtensions.cs b/src/Plana.Composition.Extensions/ISymbolExtensions.cs index 2295943..9839b11 100644 --- a/src/Plana.Composition.Extensions/ISymbolExtensions.cs +++ b/src/Plana.Composition.Extensions/ISymbolExtensions.cs @@ -9,10 +9,14 @@ namespace Plana.Composition.Extensions; +#pragma warning disable CS8619 +#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). + // ReSharper disable once InconsistentNaming public static class ISymbolExtensions { -#pragma warning disable CS8619 + private static readonly string[] Autogenerated = [" w.Implementation?.Equals(symbol, SymbolEqualityComparer.Default) == true).Interface; } -#pragma warning restore CS8619 -#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). - public static bool IsInWorkspace(this ISymbol symbol, ISolution solution) + public static bool IsAllDeclarationIsInWorkspace(this ISymbol symbol, ISolution solution) { - var sources = symbol.OriginalDefinition.Locations.Select(w => w.SourceTree?.FilePath).Where(w => w != null).Select(Path.GetFullPath); - var targets = solution.Projects.SelectMany(w => w.Documents).Select(w => Path.GetFullPath(w.Path)); + var sources = symbol.OriginalDefinition.Locations.Where(w => !IsDocumentIsAutoGeneratedAsync(w.SourceTree)) + .Select(w => w.SourceTree?.FilePath) + .Where(w => w != null) + .Select(Path.GetFullPath) + .ToList(); + + var targets = solution.Projects.SelectMany(w => w.Documents) + .Select(w => Path.GetFullPath(w.Path)) + .ToList(); return sources.All(w => targets.Contains(w)); } -#pragma warning restore CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). - public static bool IsNotInWorkspace(this ISymbol symbol, ISolution solution) + public static bool IsAnyDeclarationIsNotInWorkspace(this ISymbol symbol, ISolution solution) { - return !IsInWorkspace(symbol, solution); + return !IsAllDeclarationIsInWorkspace(symbol, solution); } + private static bool IsDocumentIsAutoGeneratedAsync(SyntaxTree? tree) + { + if (tree == null) + return false; + + var node = tree.GetRoot(); + if (node is { HasLeadingTrivia: true }) + { + var leading = node.GetLeadingTrivia(); + foreach (var trivia in leading) + { + var text = trivia.ToFullString(); + if (Autogenerated.Any(w => text.Contains(w))) + return true; + } + } + + return false; + } + + /* // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs index d8df4b1..acc4488 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs @@ -12,7 +12,7 @@ namespace Plana.Composition.RenameSymbols.Tests; public partial class RenameSymbolsPluginTest { [Fact] - public async Task RenameNamespaces() + public async Task RenameNamespacesInSolutionWorkspace() { var container = new PlanaContainer("rename-namespaces"); await container.RunAsync(); @@ -31,4 +31,24 @@ public async Task RenameNamespaces() var usingToAbstractions = await root.GetSyntax(); Assert.Equal("_0xb73384b5._0x23636295._0x895054f0", usingToAbstractions.Name!.ToFullString()); } + + [Fact] + public async Task RenameNamespacesInProjectWorkspace() + { + var container = new PlanaContainer("rename-namespaces"); + await container.RunAsync("../../../../Plana.CLI/Plana.CLI.csproj"); + + var source = await container.GetSourceByPathAsync("Commands/ObfuscateCommand.cs"); + + // Plana.CLI.Commands + var @namespace = await source.GetFirstSyntax(); + Assert.Equal("_0xb73384b5._0x23636295._0x0a849839", @namespace.Name.ToFullString()); + + // Plana -> Plana + + + // Plana.Composition.Abstractions -> Plana.Composition.Abstractions + + // Plana.CLI.Bindings -> + } } \ No newline at end of file diff --git a/src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs b/src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs index 84dd01b..67fd1b8 100644 --- a/src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs +++ b/src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs @@ -12,8 +12,78 @@ namespace Plana.Composition.RenameSymbols; -internal class CSharpSymbolsRewriter(IDocument document, bool keepNameOnInspector, IReadOnlyDictionary dict) : CSharpSyntaxRewriter +internal class CSharpSymbolsRewriter(ISolution solution, IDocument document, bool keepNameOnInspector, IReadOnlyDictionary dict) : CSharpSyntaxRewriter { + private readonly List _symbols = []; + + public override SyntaxNode? VisitCompilationUnit(CompilationUnitSyntax node) + { + var newNode = base.VisitCompilationUnit(node); + if (newNode is CompilationUnitSyntax compilation) + { + var originalUsings = node.Usings; + var currentUsings = compilation.Usings; + var usings = new List(); + + foreach (var us in originalUsings.Select((w, i) => (Syntax: w, Index: i))) + { + var si = document.SemanticModel.GetSymbolInfo(us.Syntax.Name!); + var ns = si.Symbol; + + if (ns != null) + { + // if any parts does not rewrite, keep original name + if (currentUsings[us.Index].Name!.ToFullString().Split(".").Any(w => ns.ToDisplayString().Contains(w))) + { + usings.Add(us.Syntax); + continue; + } + + // if any declaration in external source, keep both original and rewrite + if (ns.IsAnyDeclarationIsNotInWorkspace(solution)) + { + usings.Add(us.Syntax); + usings.Add(currentUsings[us.Index]); + continue; + } + + usings.Add(currentUsings[us.Index]); + } + } + + var namespaces = node.DescendantNodes().OfType().ToList(); + foreach (var decl in namespaces) + { + var si = document.SemanticModel.GetSymbolInfo(decl.Name); + var ns = si.Symbol as INamespaceSymbol; + + if (ns == null) + continue; + + + while (true) + { + if (ns == null || ns.IsGlobalNamespace) + break; + + if (ns.IsAnyDeclarationIsNotInWorkspace(solution)) + { + // nothing to do + } + + if (_symbols.Any(w => w.ContainingNamespace.ToDisplayString().Contains(ns.ToDisplayString()))) + usings.Add(SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(ns.ToDisplayString()))); + + ns = ns.ContainingNamespace; + } + } + + return compilation.WithUsings(SyntaxFactory.List(usings)); + } + + return newNode; + } + public override SyntaxNode? VisitClassDeclaration(ClassDeclarationSyntax node) { var newNode = base.VisitClassDeclaration(node); @@ -184,6 +254,8 @@ internal class CSharpSymbolsRewriter(IDocument document, bool keepNameOnInspecto var s = t.ConstructedFrom; if (dict.TryGetValue(s, out var value)) return generic.WithIdentifier(SyntaxFactory.Identifier(value)); + + _symbols.Add(t); } if (symbol is IMethodSymbol m) @@ -225,6 +297,9 @@ internal class CSharpSymbolsRewriter(IDocument document, bool keepNameOnInspecto if (dict.TryGetValue(symbol.OriginalDefinition, out var value2)) return identifier.WithIdentifier(SyntaxFactory.Identifier(value2)); + + if (symbol is INamedTypeSymbol t) + _symbols.Add(t); } } } diff --git a/src/Plana.Composition.RenameSymbols/CSharpSymbolsWalker.cs b/src/Plana.Composition.RenameSymbols/CSharpSymbolsWalker.cs index e42e52f..68018e5 100644 --- a/src/Plana.Composition.RenameSymbols/CSharpSymbolsWalker.cs +++ b/src/Plana.Composition.RenameSymbols/CSharpSymbolsWalker.cs @@ -279,14 +279,14 @@ private string SetMethodIdentifier(IMethodSymbol symbol) if (isExternalDefinition) return KeepOriginalName(symbol, overridden.Name); - if (overridden.IsNotInWorkspace(solution)) + if (overridden.IsAnyDeclarationIsNotInWorkspace(solution)) return KeepOriginalName(symbol, overridden.Name); } if (original.Locations.Any(w => w.IsInMetadata)) return KeepOriginalName(symbol); - if (original.IsNotInWorkspace(solution)) + if (original.IsAnyDeclarationIsNotInWorkspace(solution)) return KeepOriginalName(symbol); var @interface = symbol.GetInterfaceSymbol(); @@ -344,7 +344,7 @@ private string SetPropertyIdentifier(IPropertySymbol symbol) var original = symbol.OriginalDefinition; - if (original.IsNotInWorkspace(solution)) + if (original.IsAnyDeclarationIsNotInWorkspace(solution)) return KeepOriginalName(original); var @interface = symbol.GetInterfaceSymbol(); @@ -403,7 +403,7 @@ private void SetNamespaceIdentifier(INamespaceSymbol symbol) if (dict.ContainsKey(symbol)) return; - if (symbol.IsNotInWorkspace(solution)) + if (symbol.IsAnyDeclarationIsNotInWorkspace(solution)) return; if (symbol.ContainingNamespace.IsGlobalNamespace) diff --git a/src/Plana.Composition.RenameSymbols/RenameSymbolsPlugin.cs b/src/Plana.Composition.RenameSymbols/RenameSymbolsPlugin.cs index d3a8b36..e79ad39 100644 --- a/src/Plana.Composition.RenameSymbols/RenameSymbolsPlugin.cs +++ b/src/Plana.Composition.RenameSymbols/RenameSymbolsPlugin.cs @@ -75,7 +75,7 @@ public async Task ObfuscateAsync(IPlanaPluginRunContext context) foreach (var document in context.Solution.Projects.SelectMany(w => w.Documents)) { - var rewriter = new CSharpSymbolsRewriter(document, KeepOriginalNameInInspector, _dict); + var rewriter = new CSharpSymbolsRewriter(context.Solution, document, KeepOriginalNameInInspector, _dict); var oldNode = await document.SyntaxTree.GetRootAsync(context.CancellationToken); var newNode = (CSharpSyntaxNode)rewriter.Visit(oldNode); From e921938d41631af3e040ff324c985b6ff81e3c17 Mon Sep 17 00:00:00 2001 From: MOCHIZUKI Natsune Date: Sat, 16 Mar 2024 13:26:54 +0900 Subject: [PATCH 5/9] fix: reformat --- .../RenameSymbolsPluginTest_Namespaces.cs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs index acc4488..c1d9c4a 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs @@ -11,6 +11,26 @@ namespace Plana.Composition.RenameSymbols.Tests; public partial class RenameSymbolsPluginTest { + [Fact] + public async Task RenameNamespacesInProjectWorkspace() + { + var container = new PlanaContainer("rename-namespaces"); + await container.RunAsync("../../../../Plana.CLI/Plana.CLI.csproj"); + + var source = await container.GetSourceByPathAsync("Commands/ObfuscateCommand.cs"); + + // Plana.CLI.Commands + var @namespace = await source.GetFirstSyntax(); + Assert.Equal("_0xb73384b5._0x23636295._0x0a849839", @namespace.Name.ToFullString()); + + // Plana -> Plana + + + // Plana.Composition.Abstractions -> Plana.Composition.Abstractions + + // Plana.CLI.Bindings -> + } + [Fact] public async Task RenameNamespacesInSolutionWorkspace() { @@ -31,24 +51,4 @@ public async Task RenameNamespacesInSolutionWorkspace() var usingToAbstractions = await root.GetSyntax(); Assert.Equal("_0xb73384b5._0x23636295._0x895054f0", usingToAbstractions.Name!.ToFullString()); } - - [Fact] - public async Task RenameNamespacesInProjectWorkspace() - { - var container = new PlanaContainer("rename-namespaces"); - await container.RunAsync("../../../../Plana.CLI/Plana.CLI.csproj"); - - var source = await container.GetSourceByPathAsync("Commands/ObfuscateCommand.cs"); - - // Plana.CLI.Commands - var @namespace = await source.GetFirstSyntax(); - Assert.Equal("_0xb73384b5._0x23636295._0x0a849839", @namespace.Name.ToFullString()); - - // Plana -> Plana - - - // Plana.Composition.Abstractions -> Plana.Composition.Abstractions - - // Plana.CLI.Bindings -> - } } \ No newline at end of file From 752b3b1c74de1a771927c71ecf385552f887f87d Mon Sep 17 00:00:00 2001 From: MOCHIZUKI Natsune Date: Sat, 16 Mar 2024 17:38:30 +0900 Subject: [PATCH 6/9] chore: improve test stability --- .../RenameSymbolsPluginTest_Classes.cs | 48 ++++++++--------- .../RenameSymbolsPluginTest_Fields.cs | 10 ++-- .../RenameSymbolsPluginTest_Methods.cs | 30 +++++------ .../RenameSymbolsPluginTest_Namespaces.cs | 22 ++++++-- .../RenameSymbolsPluginTest_Properties.cs | 9 ++-- .../RenameSymbolsPluginTest_Variables.cs | 13 ++--- .../CSharpSyntaxNodeExtensions.cs | 38 ++++++++++++++ src/Plana.Testing/StringExtensions.cs | 25 +++++++++ src/Plana.Testing/SyntaxTokenExtensions.cs | 51 +++++++++++++++++++ 9 files changed, 179 insertions(+), 67 deletions(-) create mode 100644 src/Plana.Testing/CSharpSyntaxNodeExtensions.cs create mode 100644 src/Plana.Testing/StringExtensions.cs create mode 100644 src/Plana.Testing/SyntaxTokenExtensions.cs diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Classes.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Classes.cs index a9ecff4..4c4b39e 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Classes.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Classes.cs @@ -12,6 +12,8 @@ using Plana.Composition.Extensions; using Plana.Testing; +using CSharpSyntaxNodeExtensions = Plana.Composition.Extensions.CSharpSyntaxNodeExtensions; + namespace Plana.Composition.RenameSymbols.Tests; public partial class RenameSymbolsPluginTest @@ -26,15 +28,14 @@ public async Task RenameClasses_Attribute() var reference = await container.GetSourceByTypeAsync(typeof(RenameSymbolsPlugin)); // PlanaPluginAttribute -> _0x35add3ac - const string identifier = "_0x35add3ac"; - var declaration = await implementation.GetFirstSyntax((@class, sm) => { var symbol = sm.GetDeclaredSymbol(@class); return symbol?.BaseType != null && symbol.BaseType.Equals(typeof(Attribute).ToSymbol(sm), SymbolEqualityComparer.Default); }); - Assert.Equal($"{identifier}Attribute", declaration.Identifier.ToFullString()); + var identifier = declaration.Identifier.ToIdentifier(); + Assert.True(declaration.Identifier.ToHaveHexadecimalLikeString(suffix: "Attribute")); var attribute = await reference.GetFirstSyntax((w, sm) => { @@ -46,7 +47,9 @@ public async Task RenameClasses_Attribute() return true; }); - Assert.Equal(identifier, attribute.Name.ToFullString()); + + Assert.True(attribute.Name.ToHaveHexadecimalLikeString()); + Assert.Equal(identifier[.. ^"Attribute".Length], attribute.Name.ToIdentifier()); } [Fact] @@ -60,16 +63,15 @@ public async Task RenameClasses_Class() var extends = await container.GetSourceByPathAsync("Plana.Testing/PlanaContainer.cs"); // PlanaContainer -> _0xdb120989 - const string identifier = "_0xdb120989"; - var a = await implementation.GetFirstSyntax(); - Assert.Equal(identifier, a.Identifier.ToString()); + var identifier = a.Identifier.ToIdentifier(); + Assert.True(a.Identifier.ToHaveHexadecimalLikeString()); var b = await reference.GetFirstSyntax(); - Assert.Equal(identifier, ((b.Type as GenericNameSyntax)?.Identifier).ToString()); + Assert.Equal(identifier, ((b.Type as GenericNameSyntax)?.Identifier).ToIdentifier()); var c = await extends.GetFirstSyntax(); - Assert.Equal(identifier, ((c.BaseList?.Types[0].Type as GenericNameSyntax)?.Identifier).ToString()); + Assert.Equal(identifier, ((c.BaseList?.Types[0].Type as GenericNameSyntax)?.Identifier).ToIdentifier()); } [Fact] @@ -81,13 +83,12 @@ public async Task RenameClasses_Constructor() var reference = await container.GetSourceByPathAsync("Plana.Composition.Extensions/PlanaPluginOption.cs"); // PlanaPluginOption -> _0xb93c4da5 - const string identifier = "_0xb93c4da5"; - var @class = await reference.GetFirstSyntax(); - Assert.Equal(identifier, @class.Identifier.ToFullString()); + var identifier = @class.Identifier.ToIdentifier(); + Assert.True(@class.Identifier.ToHaveHexadecimalLikeString()); var constructor = await reference.GetFirstSyntax(); - Assert.Equal(identifier, constructor.Identifier.ToFullString()); + Assert.Equal(identifier, constructor.Identifier.ToIdentifier()); } [Fact] @@ -99,20 +100,18 @@ public async Task RenameClasses_HasTypeParameters() var reference = await container.GetSourceByPathAsync("Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Classes.cs"); // PlanaContainer -> _0xdb120989 - const string identifier1 = "_0xdb120989"; - var implementation = await container.GetSourceByPathAsync("Plana.Testing/PlanaContainer{T}.cs"); var a = await implementation.GetFirstSyntax(); + var identifier1 = a.Identifier.ToIdentifier(); - Assert.Equal(identifier1, a.Identifier.ToString()); + Assert.True(a.Identifier.ToHaveHexadecimalLikeString()); // RenameSymbolsPlugin -> _0xe2407d3d - const string identifier2 = "_0xe2407d3d"; - var parameter = await container.GetSourceByTypeAsync(typeof(RenameSymbolsPlugin)); var b = await parameter.GetFirstSyntax(); + var identifier2 = b.Identifier.ToIdentifier(); - Assert.Equal(identifier2, b.Identifier.ToString()); + Assert.True(b.Identifier.ToHaveHexadecimalLikeString()); var invocation = await reference.GetFirstSyntax(w => w.Type.IsKind(SyntaxKind.GenericName)); var generics = invocation.Type as GenericNameSyntax; @@ -133,10 +132,9 @@ public async Task RenameClasses_Interface() var reference = await container.GetSourceByTypeAsync(typeof(CSharpSymbolsWalker)); // IPlanaSecureRandom -> _0x32fad750 - const string identifier = "_0x32fad750"; - var a = await declaration.GetFirstSyntax(); - Assert.Equal(identifier, a.Identifier.ToString()); + var identifier = a.Identifier.ToIdentifier(); + Assert.True(a.Identifier.ToHaveHexadecimalLikeString()); var b = await implementation.GetFirstSyntax(); Assert.Equal(identifier, b.BaseList?.Types[0].Type.ToString()); @@ -155,11 +153,9 @@ public async Task RenameClasses_Record() var reference = await container.GetSourceByTypeAsync(typeof(CSharpSyntaxNodeExtensions)); // AnnotationComment -> _0xd9d2dd2a - const string identifier = "_0xd9d2dd2a"; - var a = await declaration.GetFirstSyntax(); - Assert.Equal(identifier, a.Identifier.ToString()); - + var identifier = a.Identifier.ToIdentifier(); + Assert.True(a.Identifier.ToHaveHexadecimalLikeString()); var b = await reference.GetFirstSyntax(w => { diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Fields.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Fields.cs index 2bacb59..b4a12c1 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Fields.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Fields.cs @@ -24,10 +24,9 @@ public async Task RenameFields_ExternalReference() var reference = await container.GetSourceByPathAsync("Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest.cs"); // IsEnableClassNameRenaming -> _0x935f5b12 - const string identifier = "_0x935f5b12"; - var declaration = await implementation.GetFirstSyntax(w => w.HasModifier(SyntaxKind.InternalKeyword)); - Assert.Equal(identifier, declaration.Declaration.Variables[0].Identifier.ToString()); + var identifier = declaration.Declaration.Variables[0].Identifier.ToIdentifier(); + Assert.True(declaration.Declaration.Variables[0].Identifier.ToHaveHexadecimalLikeString()); var m = await reference.GetFirstSyntax((w, sm) => { @@ -57,10 +56,9 @@ public async Task RenameFields_InternalReference() var implementation = await container.GetSourceByTypeAsync(typeof(MeaningEqualitySymbolComparator)); // SymbolDisplayFormat -> _0xcb375677 - const string identifier = "_0xcb375677"; - var declaration = await implementation.GetFirstSyntax(w => w.HasModifiers(SyntaxKind.PrivateKeyword, SyntaxKind.StaticKeyword, SyntaxKind.ReadOnlyKeyword)); - Assert.Equal(identifier, declaration.Declaration.Variables[0].Identifier.ToString()); + var identifier = declaration.Declaration.Variables[0].Identifier.ToIdentifier(); + Assert.True(declaration.Declaration.Variables[0].Identifier.ToHaveHexadecimalLikeString()); var reference = await implementation.GetFirstSyntax((w, sm) => { diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Methods.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Methods.cs index 9d76d37..96d3ce8 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Methods.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Methods.cs @@ -26,8 +26,6 @@ public async Task RenameMethods_ExtensionMethods() var reference = await container.GetSourceByPathAsync("Plana.CLI/Commands/ObfuscateCommand.cs"); // CommandExtensions.AddOptions -> _0xb35682f5 - const string addOptionsIdentifier = "_0xb35682f5"; - var def = await originalDefinition.GetFirstSyntax((w, sm) => { if (w.HasNotModifier(SyntaxKind.StaticKeyword)) @@ -38,7 +36,8 @@ public async Task RenameMethods_ExtensionMethods() return param1?.Type.ToDisplayString() == "System.CommandLine.Command" && param2?.Type.ToDisplayString() == "System.CommandLine.Option[]"; }); - Assert.Equal(addOptionsIdentifier, def.Identifier.ToString()); + var addOptionsIdentifier = def.Identifier.ToIdentifier(); + Assert.True(def.Identifier.ToHaveHexadecimalLikeString()); var r = await reference.GetFirstSyntax((w, sm) => { @@ -117,10 +116,9 @@ public async Task RenameMethods_GenericsInterfaceMethods() var implementation = await container.GetSourceByTypeAsync(typeof(InlineSource)); // ITestableObject.ToMatchInlineSnapshot(T) -> _0x84fd82f3 - const string identifier = "_0x84fd82f3"; - var decl = await @interface.GetFirstSyntax(); - Assert.Equal(identifier, decl.Identifier.ToString()); + var identifier = decl.Identifier.ToIdentifier(); + Assert.True(decl.Identifier.ToHaveHexadecimalLikeString()); var impl = await implementation.GetFirstSyntax(); Assert.Equal(identifier, impl.Identifier.ToString()); @@ -135,8 +133,6 @@ public async Task RenameMethods_GenericsMethods() var implementation = await container.GetSourceByTypeAsync(typeof(InlineSource)); // InlineSource.GetSyntaxOf -> _0xd52be804 - const string identifier = "_0xd52be804"; - var def = await implementation.GetFirstSyntax((w, sm) => { if (w.ParameterList.Parameters.Count != 0) @@ -159,7 +155,8 @@ public async Task RenameMethods_GenericsMethods() return true; }); - Assert.Equal(identifier, def.Identifier.ToString()); + var identifier = def.Identifier.ToIdentifier(); + Assert.True(def.Identifier.ToHaveHexadecimalLikeString()); var reference = await implementation.GetFirstSyntax((w, sm) => { @@ -193,8 +190,6 @@ public async Task RenameMethods_InterfaceMethods() var reference = await container.GetSourceByPathAsync("Plana/Obfuscator.cs"); // IPlanaPlugin.RunAsync -> _0xa79a150d - const string runAsyncIdentifier = "_0xa79a150d"; - bool IsMethodsHasRunAsyncSignature(MethodDeclarationSyntax w, SemanticModel sm) { var identifier = w.ParameterList.Parameters[0].Type; @@ -209,7 +204,8 @@ bool IsMethodsHasRunAsyncSignature(MethodDeclarationSyntax w, SemanticModel sm) } var rootAbstractionDecl = await rootAbstraction.GetFirstSyntax(IsMethodsHasRunAsyncSignature); - Assert.Equal(runAsyncIdentifier, rootAbstractionDecl.Identifier.ToString()); + var runAsyncIdentifier = rootAbstractionDecl.Identifier.ToIdentifier(); + Assert.True(rootAbstractionDecl.Identifier.ToHaveHexadecimalLikeString()); var inheritAbstractionDecl = await inheritAbstraction.GetFirstSyntax(IsMethodsHasRunAsyncSignature); Assert.Equal(runAsyncIdentifier, inheritAbstractionDecl.Identifier.ToString()); @@ -235,10 +231,9 @@ bool IsMethodsHasRunAsyncSignature(MethodDeclarationSyntax w, SemanticModel sm) Assert.Equal(runAsyncIdentifier, ((MemberAccessExpressionSyntax)referenceDecl.Expression).Name.Identifier.ToString()); // IPlanaPlugin2.ObfuscateAsync -> _0xc7b29ba1 - const string obfuscateAsyncIdentifier = "_0xc7b29ba1"; - var inheritAbstractionDecl2 = (await inheritAbstraction.GetSyntaxList(IsMethodsHasRunAsyncSignature))[1]; - Assert.Equal(obfuscateAsyncIdentifier, inheritAbstractionDecl2.Identifier.ToString()); + var obfuscateAsyncIdentifier = inheritAbstractionDecl2.Identifier.ToIdentifier(); + Assert.True(inheritAbstractionDecl2.Identifier.ToHaveHexadecimalLikeString()); var implementationDecl = await implementation.GetFirstSyntax(IsMethodsHasRunAsyncSignature); Assert.Equal(obfuscateAsyncIdentifier, implementationDecl.Identifier.ToString()); @@ -254,10 +249,9 @@ public async Task RenameMethods_OriginalDefinitionMethods() var reference = await container.GetSourceByPathAsync("Plana.CLI/Commands/ObfuscateCommand.cs"); // SolutionWorkspace.CreateWorkspaceAsync -> _0xc09f4e99 - const string createWorkspaceAsyncIdentifier = "_0xc09f4e99"; - var def = await originalDefinition.GetFirstSyntax(w => w.HasModifier(SyntaxKind.StaticKeyword)); - Assert.Equal(createWorkspaceAsyncIdentifier, def.Identifier.ToString()); + var createWorkspaceAsyncIdentifier = def.Identifier.ToIdentifier(); + Assert.True(def.Identifier.ToHaveHexadecimalLikeString()); var r = await reference.GetFirstSyntax((w, sm) => { diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs index c1d9c4a..82f7728 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs @@ -21,7 +21,10 @@ public async Task RenameNamespacesInProjectWorkspace() // Plana.CLI.Commands var @namespace = await source.GetFirstSyntax(); - Assert.Equal("_0xb73384b5._0x23636295._0x0a849839", @namespace.Name.ToFullString()); + var parts = @namespace.Name.ToIdentifier().Split("."); + + foreach (var part in parts) + Assert.True(part.ToHaveHexadecimalLikeString()); // Plana -> Plana @@ -42,13 +45,24 @@ public async Task RenameNamespacesInSolutionWorkspace() // Plana -> _0xb73384b5 var rootDecl = await root.GetSyntax(); - Assert.Equal("_0xb73384b5", rootDecl.Name.ToFullString()); + var a = rootDecl.Name.ToIdentifier(); + Assert.True(a.ToHaveHexadecimalLikeString()); // Plana.Composition.Abstractions -> _0xb73384b5._0x23636295._0x895054f0 var abstractionsDecl = await nest.GetSyntax(); - Assert.Equal("_0xb73384b5._0x23636295._0x895054f0", abstractionsDecl.Name.ToFullString()); + var parts1 = abstractionsDecl.Name.ToIdentifier().Split("."); + + Assert.Equal(a, parts1[0]); + + var b = parts1[1]; + Assert.True(b.ToHaveHexadecimalLikeString()); + Assert.True(parts1[2].ToHaveHexadecimalLikeString()); var usingToAbstractions = await root.GetSyntax(); - Assert.Equal("_0xb73384b5._0x23636295._0x895054f0", usingToAbstractions.Name!.ToFullString()); + var parts2 = usingToAbstractions.Name!.ToIdentifier().Split("."); + + Assert.Equal(a, parts2[0]); + Assert.Equal(b, parts2[1]); + Assert.True(parts2[2].ToHaveHexadecimalLikeString()); } } \ No newline at end of file diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Properties.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Properties.cs index 17c5da6..a4badf8 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Properties.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Properties.cs @@ -26,13 +26,12 @@ public async Task RenameProperties() var reference = await container.GetSourceByPathAsync("Plana.Composition.DisableConsoleOutput/DisableConsoleOutputPlugin.cs"); // Solution -> _0x4265cf21 - const string identifier = "_0x4265cf21"; - var solutionAbstractDecl = await abstraction.GetFirstSyntax(); - Assert.Equal(identifier, solutionAbstractDecl.Identifier.ToString()); + var identifier = solutionAbstractDecl.Identifier.ToIdentifier(); + Assert.True(identifier.ToHaveHexadecimalLikeString()); var solutionImplDecl = await implementation.GetFirstSyntax(); - Assert.Equal(identifier, solutionImplDecl.Identifier.ToString()); + Assert.Equal(identifier, solutionImplDecl.Identifier.ToIdentifier()); bool IsAccessToContextSolution(MemberAccessExpressionSyntax syntax, SemanticModel sm) { @@ -57,6 +56,6 @@ bool IsAccessToContextSolution(MemberAccessExpressionSyntax syntax, SemanticMode } var solutionRef = await reference.GetFirstSyntax(IsAccessToContextSolution); - Assert.Equal(identifier, solutionRef.Name.ToString()); + Assert.Equal(identifier, solutionRef.Name.ToIdentifier()); } } \ No newline at end of file diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Variables.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Variables.cs index d5d101d..ebfe3a5 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Variables.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Variables.cs @@ -21,10 +21,8 @@ public async Task RenameVariables_ForEachStatement() var implementation = await container.GetSourceByTypeAsync(typeof(Obfuscator)); // instance -> _0xc22a5edb - const string identifier = "_0xc22a5edb"; - var @foreach = await implementation.GetFirstSyntax(); - Assert.Equal(identifier, @foreach.Identifier.ToFullString()); + Assert.True(@foreach.Identifier.ToHaveHexadecimalLikeString()); } [Fact] @@ -39,12 +37,11 @@ public async Task RenameVariables_PrimaryConstructor() var implementation = await container.GetSourceByTypeAsync(typeof(AnnotationComment)); // AnnotationComment.Annotation -> _0xf35395eb - const string identifier = "_0xf35395eb"; var declaration = await implementation.GetFirstSyntax(); var constructor = declaration.ParameterList!.Parameters[0]; - - Assert.Equal(identifier, constructor.Identifier.ToFullString()); + var identifier = constructor.Identifier.ToIdentifier(); + Assert.True(identifier.ToHaveHexadecimalLikeString()); var internalReference = await implementation.GetFirstSyntax(w => { @@ -58,7 +55,7 @@ public async Task RenameVariables_PrimaryConstructor() return p.Identifier.ToFullString().Trim() == nameof(AnnotationComment.Comment); }); - Assert.Equal(identifier, internalReference.Identifier.ToFullString()); + Assert.Equal(identifier, internalReference.Identifier.ToIdentifier()); var externalReferenceImplementation = await container.GetSourceByPathAsync("Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Variables.cs"); var externalReference = await externalReferenceImplementation.GetFirstSyntax(w => @@ -70,6 +67,6 @@ public async Task RenameVariables_PrimaryConstructor() return assignment.Left.ToString() == "_"; }); - Assert.Equal(identifier, externalReference.Name.ToFullString()); + Assert.Equal(identifier, externalReference.Name.ToIdentifier()); } } \ No newline at end of file diff --git a/src/Plana.Testing/CSharpSyntaxNodeExtensions.cs b/src/Plana.Testing/CSharpSyntaxNodeExtensions.cs new file mode 100644 index 0000000..5d78260 --- /dev/null +++ b/src/Plana.Testing/CSharpSyntaxNodeExtensions.cs @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------------------------ +// Copyright (c) Natsuneko. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// ------------------------------------------------------------------------------------------ + +using System.Text.RegularExpressions; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Plana.Testing; + +public static class CSharpSyntaxNodeExtensions +{ + private static readonly Regex Hexadecimal = new("^0x[a-zA-Z0-9]+$", RegexOptions.Compiled); + + public static bool ToHaveHexadecimalLikeString(this CSharpSyntaxNode obj, string prefix = "_", string suffix = "") + { + var str = obj.ToFullString().Trim(); + if (!str.StartsWith(prefix)) + return false; + + if (!str.EndsWith(suffix)) + return false; + + return Hexadecimal.IsMatch(str[prefix.Length..^suffix.Length]); + } + + public static string ToNormalizedTrimmedFullString(this CSharpSyntaxNode obj) + { + return obj.NormalizeWhitespace().ToFullString().Trim(); + } + + public static string ToIdentifier(this CSharpSyntaxNode obj) + { + return obj.ToNormalizedTrimmedFullString(); + } +} \ No newline at end of file diff --git a/src/Plana.Testing/StringExtensions.cs b/src/Plana.Testing/StringExtensions.cs new file mode 100644 index 0000000..f48e934 --- /dev/null +++ b/src/Plana.Testing/StringExtensions.cs @@ -0,0 +1,25 @@ +// ------------------------------------------------------------------------------------------ +// Copyright (c) Natsuneko. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// ------------------------------------------------------------------------------------------ + +using System.Text.RegularExpressions; + +namespace Plana.Testing; + +public static class StringExtensions +{ + private static readonly Regex Hexadecimal = new("^0x[a-zA-Z0-9]+$", RegexOptions.Compiled); + + public static bool ToHaveHexadecimalLikeString(this string obj, string prefix = "_", string suffix = "") + { + var str = obj.Trim(); + if (!str.StartsWith(prefix)) + return false; + + if (!str.EndsWith(suffix)) + return false; + + return Hexadecimal.IsMatch(str[prefix.Length..^suffix.Length]); + } +} \ No newline at end of file diff --git a/src/Plana.Testing/SyntaxTokenExtensions.cs b/src/Plana.Testing/SyntaxTokenExtensions.cs new file mode 100644 index 0000000..2abdebe --- /dev/null +++ b/src/Plana.Testing/SyntaxTokenExtensions.cs @@ -0,0 +1,51 @@ +// ------------------------------------------------------------------------------------------ +// Copyright (c) Natsuneko. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. +// ------------------------------------------------------------------------------------------ + +using System.Text.RegularExpressions; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Plana.Testing; + +public static class SyntaxTokenExtensions +{ + private static readonly Regex Hexadecimal = new("^0x[a-zA-Z0-9]+$", RegexOptions.Compiled); + + public static bool ToHaveHexadecimalLikeString(this SyntaxToken obj, string prefix = "_", string suffix = "") + { + var str = obj.ToFullString().Trim(); + if (!str.StartsWith(prefix)) + return false; + + if (!str.EndsWith(suffix)) + return false; + + return Hexadecimal.IsMatch(str[prefix.Length .. ^suffix.Length]); + } + + public static string ToNormalizedTrimmedFullString(this SyntaxToken obj) + { + return obj.NormalizeWhitespace().ToFullString().Trim(); + } + + public static string ToIdentifier(this SyntaxToken obj) + { + return obj.ToNormalizedTrimmedFullString(); + } + + public static string ToNormalizedTrimmedFullString(this SyntaxToken? obj) + { + if (obj.HasValue) + return obj.Value.NormalizeWhitespace().ToFullString().Trim(); + + return ""; + } + + public static string ToIdentifier(this SyntaxToken? obj) + { + return obj.ToNormalizedTrimmedFullString(); + } +} \ No newline at end of file From d01e7c68835963ab300eeebbc19897e747713159 Mon Sep 17 00:00:00 2001 From: MOCHIZUKI Natsune Date: Sat, 16 Mar 2024 18:51:27 +0900 Subject: [PATCH 7/9] fix: support to prefix in invoke expression --- .../MeaningEqualitySymbolComparator.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Plana.Composition.Extensions/MeaningEqualitySymbolComparator.cs b/src/Plana.Composition.Extensions/MeaningEqualitySymbolComparator.cs index 084b6fc..b008164 100644 --- a/src/Plana.Composition.Extensions/MeaningEqualitySymbolComparator.cs +++ b/src/Plana.Composition.Extensions/MeaningEqualitySymbolComparator.cs @@ -52,6 +52,7 @@ public bool Equals(ISymbol? x, ISymbol? y) case IPropertySymbol: case IMethodSymbol: case IFieldSymbol: + case IParameterSymbol: return x.ToDisplayString(SymbolDisplayFormat) == y.ToDisplayString(SymbolDisplayFormat); } @@ -68,6 +69,19 @@ public int GetHashCode(ISymbol obj) case IMethodSymbol: case IFieldSymbol: return obj.ToDisplayString(SymbolDisplayFormat).GetHashCode(); + + case IParameterSymbol parameter: + { + if (parameter.ContainingSymbol is IMethodSymbol m) + { + var a = (m.IsExtensionMethod ? m.ReducedFrom ?? m.OriginalDefinition : m).ToDisplayString(SymbolDisplayFormat); + var b = obj.ToDisplayString(SymbolDisplayFormat); + + return HashCode.Combine(a, b); + } + + break; + } } return SymbolEqualityComparer.Default.GetHashCode(obj); From 319a01eae130e822ba9d47e1bc5db383623cf799 Mon Sep 17 00:00:00 2001 From: MOCHIZUKI Natsune Date: Sun, 17 Mar 2024 00:04:44 +0900 Subject: [PATCH 8/9] fix: fix invalid renaming with namespace renaming --- .../CSharpSymbolsRewriter.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs b/src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs index 67fd1b8..17edb5f 100644 --- a/src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs +++ b/src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs @@ -215,6 +215,45 @@ internal class CSharpSymbolsRewriter(ISolution solution, IDocument document, boo return newNode; } + public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node) + { + var newNode = base.VisitMemberAccessExpression(node); + if (newNode is MemberAccessExpressionSyntax accessor) + { + var si = document.SemanticModel.GetSymbolInfo(node.Expression); + var symbol = si.Symbol; + + if (symbol != null) + if (symbol is INamespaceSymbol ns) + { + // if receiver is namespace, convert to fully qualified namespace + if (dict.ContainsKey(ns)) + { + var parts = new List(); + var s = ns; + + while (true) + { + if (s.IsGlobalNamespace) + break; + + parts.Add(dict[s]); + s = s.ContainingNamespace; + } + + parts.Reverse(); + + var identifier = string.Join(".", parts); + return accessor.WithExpression(SyntaxFactory.IdentifierName(identifier)); + } + + return accessor.WithExpression(SyntaxFactory.IdentifierName(ns.ToDisplayString())); + } + } + + return newNode; + } + public override SyntaxNode? VisitVariableDeclarator(VariableDeclaratorSyntax node) { var newNode = base.VisitVariableDeclarator(node); From 8ec59f809373f0d3d0bf8c8b68ac0a46bf76a45e Mon Sep 17 00:00:00 2001 From: MOCHIZUKI Natsune Date: Sun, 17 Mar 2024 01:56:11 +0900 Subject: [PATCH 9/9] fix: fix tests --- .../RenameSymbolsPluginTest_Methods.cs | 2 +- .../RenameSymbolsPluginTest_Namespaces.cs | 10 ++++-- src/Plana.Testing/InlineSource.cs | 32 ++++++++++++++++++- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Methods.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Methods.cs index 96d3ce8..f6e1371 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Methods.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Methods.cs @@ -133,7 +133,7 @@ public async Task RenameMethods_GenericsMethods() var implementation = await container.GetSourceByTypeAsync(typeof(InlineSource)); // InlineSource.GetSyntaxOf -> _0xd52be804 - var def = await implementation.GetFirstSyntax((w, sm) => + var def = await implementation.GetLastSyntax((w, sm) => { if (w.ParameterList.Parameters.Count != 0) return false; diff --git a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs index 82f7728..8bfb563 100644 --- a/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs +++ b/src/Plana.Composition.RenameSymbols.Tests/RenameSymbolsPluginTest_Namespaces.cs @@ -26,12 +26,16 @@ public async Task RenameNamespacesInProjectWorkspace() foreach (var part in parts) Assert.True(part.ToHaveHexadecimalLikeString()); - // Plana -> Plana + var usings = (await source.GetSyntaxList()).Select(w => w.ToNormalizedTrimmedFullString()).ToList(); + // Plana -> Plana + Assert.Contains("using Plana;", usings); // Plana.Composition.Abstractions -> Plana.Composition.Abstractions + Assert.Contains("using Plana.Composition.Abstractions;", usings); - // Plana.CLI.Bindings -> + // Plana.CLI.xxx -> parts[0].parts[1].unknown + Assert.True(usings.Where(w => w.StartsWith($"{parts[0]}.{parts[1]}")).All(w => w.Split(".")[2].ToHaveHexadecimalLikeString())); } [Fact] @@ -58,7 +62,7 @@ public async Task RenameNamespacesInSolutionWorkspace() Assert.True(b.ToHaveHexadecimalLikeString()); Assert.True(parts1[2].ToHaveHexadecimalLikeString()); - var usingToAbstractions = await root.GetSyntax(); + var usingToAbstractions = await root.GetFirstSyntax(); var parts2 = usingToAbstractions.Name!.ToIdentifier().Split("."); Assert.Equal(a, parts2[0]); diff --git a/src/Plana.Testing/InlineSource.cs b/src/Plana.Testing/InlineSource.cs index 3154d8f..870c5a7 100644 --- a/src/Plana.Testing/InlineSource.cs +++ b/src/Plana.Testing/InlineSource.cs @@ -27,6 +27,11 @@ public Task ToMatchInlineSnapshot(string snapshot) return Task.CompletedTask; } + public override string ToString() + { + return document?.SyntaxTree.ToNormalizedFullString() ?? ""; + } + public Task HasDiffs() { if (document == null) @@ -84,12 +89,16 @@ public async Task> GetSyntaxList(Func predica return ret.ToList(); } + public async Task> GetSyntaxList() where T : CSharpSyntaxNode + { + return await GetSyntaxList((_, _) => true); + } + public async Task GetFirstSyntax() where T : CSharpSyntaxNode { return await GetFirstSyntax(_ => true); } - public async Task GetFirstSyntax(Func predicate) where T : CSharpSyntaxNode { var ret = (await GetSyntaxOf()).FirstOrDefault(predicate); @@ -106,6 +115,27 @@ public async Task GetFirstSyntax(Func predicate) w return ret; } + public async Task GetLastSyntax() where T : CSharpSyntaxNode + { + return await GetLastSyntax(_ => true); + } + + public async Task GetLastSyntax(Func predicate) where T : CSharpSyntaxNode + { + var ret = (await GetSyntaxOf()).LastOrDefault(predicate); + Assert.NotNull(ret); + + return ret; + } + + public async Task GetLastSyntax(Func predicate) where T : CSharpSyntaxNode + { + var ret = (await GetSyntaxOf()).LastOrDefault(w => predicate(w, document!.SemanticModel)); + Assert.NotNull(ret); + + return ret; + } + private Task> GetSyntaxOf() where T : CSharpSyntaxNode { if (document == null)