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)
{