From db45fc70a2ce73e6ad24e44c042b179141429df6 Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Tue, 1 Apr 2025 20:45:46 -0700 Subject: [PATCH 1/3] Fix regression: UseNestedDirectoriesForNamespaces --- .../CSharp/ProjectDecompiler/WholeProjectDecompiler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index 2299b6fec..bfaffacb8 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -743,7 +743,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler public static string CleanUpPath(string text) { - return CleanUpName(text, separateAtDots: true, treatAsFileName: true, treatAsPath: true); + return CleanUpName(text, separateAtDots: true, treatAsFileName: false, treatAsPath: true) + .Replace('.', Path.DirectorySeparatorChar); } static bool IsReservedFileSystemName(string name) From fb180b00103f4ec63a7c6928065a3887cd15f155 Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Tue, 1 Apr 2025 21:44:13 -0700 Subject: [PATCH 2/3] Add tests --- .../ICSharpCode.Decompiler.Tests.csproj | 1 + .../ProjectDecompiler/TargetFrameworkTests.cs | 2 +- .../WholeProjectDecompilerTests.cs | 101 ++++++++++++++++++ .../WholeProjectDecompiler.cs | 38 ++++--- 4 files changed, 127 insertions(+), 15 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/ProjectDecompiler/WholeProjectDecompilerTests.cs diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 0d3ef2bbe..e6053e558 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -131,6 +131,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs b/ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs index 1a9557c77..7479edd25 100644 --- a/ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs +++ b/ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs @@ -23,7 +23,7 @@ using ICSharpCode.Decompiler.Metadata; using NUnit.Framework; -namespace ICSharpCode.Decompiler.Tests +namespace ICSharpCode.Decompiler.Tests.ProjectDecompiler { [TestFixture] public sealed class TargetFrameworkTests diff --git a/ICSharpCode.Decompiler.Tests/ProjectDecompiler/WholeProjectDecompilerTests.cs b/ICSharpCode.Decompiler.Tests/ProjectDecompiler/WholeProjectDecompilerTests.cs new file mode 100644 index 000000000..bbf11fd4a --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/ProjectDecompiler/WholeProjectDecompilerTests.cs @@ -0,0 +1,101 @@ +// Copyright (c) 2025 Daniel Grunwald +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; + +using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; +using ICSharpCode.Decompiler.Metadata; + +using NUnit.Framework; + +namespace ICSharpCode.Decompiler.Tests.ProjectDecompiler; + +[TestFixture] +public sealed class WholeProjectDecompilerTests +{ + [Test] + public void UseNestedDirectoriesForNamespacesTrueWorks() + { + string targetDirectory = Path.Combine(Environment.CurrentDirectory, Path.GetRandomFileName()); + TestFriendlyProjectDecompiler decompiler = new(new UniversalAssemblyResolver(null, false, null)); + decompiler.Settings.UseNestedDirectoriesForNamespaces = true; + decompiler.DecompileProject(new PEFile("ICSharpCode.Decompiler.dll"), targetDirectory); + AssertDirectoryDoesntExist(targetDirectory); + + string projectDecompilerDirectory = Path.Combine(targetDirectory, "ICSharpCode", "Decompiler", "CSharp", "ProjectDecompiler"); + string projectDecompilerFile = Path.Combine(projectDecompilerDirectory, $"{nameof(WholeProjectDecompiler)}.cs"); + + using (Assert.EnterMultipleScope()) + { + Assert.That(decompiler.Files.ContainsKey(projectDecompilerFile), Is.True); + Assert.That(decompiler.Directories.Contains(projectDecompilerDirectory), Is.True); + } + } + + [Test] + public void UseNestedDirectoriesForNamespacesFalseWorks() + { + string targetDirectory = Path.Combine(Environment.CurrentDirectory, Path.GetRandomFileName()); + TestFriendlyProjectDecompiler decompiler = new(new UniversalAssemblyResolver(null, false, null)); + decompiler.Settings.UseNestedDirectoriesForNamespaces = false; + decompiler.DecompileProject(new PEFile("ICSharpCode.Decompiler.dll"), targetDirectory); + AssertDirectoryDoesntExist(targetDirectory); + + string projectDecompilerDirectory = Path.Combine(targetDirectory, "ICSharpCode.Decompiler.CSharp.ProjectDecompiler"); + string projectDecompilerFile = Path.Combine(projectDecompilerDirectory, $"{nameof(WholeProjectDecompiler)}.cs"); + + using (Assert.EnterMultipleScope()) + { + Assert.That(decompiler.Files.ContainsKey(projectDecompilerFile), Is.True); + Assert.That(decompiler.Directories.Contains(projectDecompilerDirectory), Is.True); + } + } + + static void AssertDirectoryDoesntExist(string directory) + { + if (Directory.Exists(directory)) + { + Directory.Delete(directory, recursive: true); + Assert.Fail("Directory should not have been created."); + } + } + + sealed class TestFriendlyProjectDecompiler(IAssemblyResolver assemblyResolver) : WholeProjectDecompiler(assemblyResolver) + { + public Dictionary Files { get; } = []; + public HashSet Directories { get; } = []; + + protected override TextWriter CreateFile(string path) + { + StringWriter writer = new(); + Files[path] = writer; + return writer; + } + + protected override void CreateDirectory(string path) + { + Directories.Add(path); + } + + protected override IEnumerable WriteMiscellaneousFilesInProject(PEFile module) => []; + + protected override IEnumerable WriteResourceFilesInProject(MetadataFile module) => []; + } +} diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index bfaffacb8..3b117d8ea 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -137,7 +137,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler public void DecompileProject(MetadataFile file, string targetDirectory, CancellationToken cancellationToken = default(CancellationToken)) { string projectFileName = Path.Combine(targetDirectory, CleanUpFileName(file.Name, ".csproj")); - using (var writer = new StreamWriter(projectFileName)) + using (var writer = CreateFile(projectFileName)) { DecompileProject(file, targetDirectory, writer, cancellationToken); } @@ -186,6 +186,24 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler return true; } + protected virtual TextWriter CreateFile(string path) + { + return new StreamWriter(path); + } + + protected virtual void CreateDirectory(string path) + { + try + { + Directory.CreateDirectory(path); + } + catch (IOException) + { + File.Delete(path); + Directory.CreateDirectory(path); + } + } + CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts) { var decompiler = new CSharpDecompiler(ts, Settings); @@ -204,9 +222,9 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler const string prop = "Properties"; if (directories.Add(prop)) - Directory.CreateDirectory(Path.Combine(TargetDirectory, prop)); + CreateDirectory(Path.Combine(TargetDirectory, prop)); string assemblyInfo = Path.Combine(prop, "AssemblyInfo.cs"); - using (StreamWriter w = new StreamWriter(Path.Combine(TargetDirectory, assemblyInfo))) + using (var w = CreateFile(Path.Combine(TargetDirectory, assemblyInfo))) { syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, Settings.CSharpFormattingOptions)); } @@ -251,15 +269,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler if (directories.Add(dir)) { var path = Path.Combine(TargetDirectory, dir); - try - { - Directory.CreateDirectory(path); - } - catch (IOException) - { - File.Delete(path); - Directory.CreateDirectory(path); - } + CreateDirectory(path); } return Path.Combine(dir, file); } @@ -277,7 +287,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler delegate (IGrouping file) { try { - using StreamWriter w = new StreamWriter(Path.Combine(TargetDirectory, file.Key)); + using var w = CreateFile(Path.Combine(TargetDirectory, file.Key)); CSharpDecompiler decompiler = CreateDecompiler(ts); foreach (var partialType in partialTypes) @@ -344,7 +354,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler string dirName = Path.GetDirectoryName(fileName); if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName)) { - Directory.CreateDirectory(Path.Combine(TargetDirectory, dirName)); + CreateDirectory(Path.Combine(TargetDirectory, dirName)); } Stream entryStream = (Stream)value; entryStream.Position = 0; From 8439e1ca1e80650e3e75db86a36bf0093dca1712 Mon Sep 17 00:00:00 2001 From: ds5678 <49847914+ds5678@users.noreply.github.com> Date: Tue, 8 Apr 2025 12:29:07 -0700 Subject: [PATCH 3/3] Fix BamlResourceNodeFactory --- ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs b/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs index 681a92d5a..cc9d6b19d 100644 --- a/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs +++ b/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs @@ -62,7 +62,7 @@ namespace ILSpy.BamlDecompiler var typeDefinition = result.TypeName.HasValue ? typeSystem.MainModule.GetTypeDefinition(result.TypeName.Value.TopLevelTypeName) : null; if (typeDefinition != null) { - fileName = WholeProjectDecompiler.CleanUpPath(typeDefinition.ReflectionName + ".xaml"); + fileName = WholeProjectDecompiler.SanitizeFileName(typeDefinition.ReflectionName + ".xaml"); var partialTypeInfo = new PartialTypeInfo(typeDefinition); foreach (var member in result.GeneratedMembers) {