diff --git a/src/Publicizer.Tests/PublicizerTests.cs b/src/Publicizer.Tests/PublicizerTests.cs
index 6c47ff8..34b0754 100644
--- a/src/Publicizer.Tests/PublicizerTests.cs
+++ b/src/Publicizer.Tests/PublicizerTests.cs
@@ -916,4 +916,82 @@ class PrivateClass
Assert.That(buildAppProcess.ExitCode, Is.Zero, buildAppProcess.Output);
Assert.That(File.Exists(logFilePath), Is.True, buildAppProcess.Output);
}
+
+ [Test]
+ public void PublicizeNestedTypeMember_AlsoPublicizesEnclosingType_CompilesAndRunsWithExitCode0()
+ {
+ using var libraryFolder = new TemporaryFolder();
+ string libraryCodePath = Path.Combine(libraryFolder.Path, "Outer.cs");
+ string libraryCode = """
+ namespace PrivateNamespace;
+ class Outer
+ {
+ class Inner
+ {
+ private static string PrivateField = "foobar";
+ }
+ }
+ """;
+ File.WriteAllText(libraryCodePath, libraryCode);
+
+ string libraryCsprojPath = Path.Combine(libraryFolder.Path, "PrivateAssembly.csproj");
+ string libraryCsproj = $"""
+
+
+
+ {TestTargetFramework}
+ false
+ {libraryFolder.Path}
+
+
+
+
+
+
+
+ """;
+
+ File.WriteAllText(libraryCsprojPath, libraryCsproj);
+ ProcessResult buildLibraryResult = Runner.Run("dotnet", "build", libraryCsprojPath);
+ Assert.That(buildLibraryResult.ExitCode, Is.Zero, buildLibraryResult.Output);
+
+ using var appFolder = new TemporaryFolder();
+ string appCodePath = Path.Combine(appFolder.Path, "Program.cs");
+ // Reachable only if both Inner and its enclosing Outer are made accessible.
+ string appCode = "System.Console.Write(PrivateNamespace.Outer.Inner.PrivateField);";
+ File.WriteAllText(appCodePath, appCode);
+ string libraryPath = Path.Combine(libraryFolder.Path, "PrivateAssembly.dll");
+
+ string appCsproj = $"""
+
+
+
+ {TestTargetFramework}
+ false
+ exe
+ {appFolder.Path}
+
+
+
+
+
+
+
+
+
+
+ """;
+
+ string appCsprojPath = Path.Combine(appFolder.Path, "App.csproj");
+ File.WriteAllText(appCsprojPath, appCsproj);
+ string appPath = Path.Combine(appFolder.Path, "App.dll");
+ NugetConfigMaker.CreateConfigThatRestoresPublicizerLocally(appFolder.Path);
+
+ ProcessResult buildAppProcess = Runner.Run("dotnet", "build", appCsprojPath);
+ ProcessResult runAppProcess = Runner.Run("dotnet", appPath);
+
+ Assert.That(buildAppProcess.ExitCode, Is.Zero, buildAppProcess.Output);
+ Assert.That(runAppProcess.ExitCode, Is.Zero, runAppProcess.Output);
+ Assert.That(runAppProcess.Output, Is.EqualTo("foobar"), runAppProcess.Output);
+ }
}
diff --git a/src/Publicizer/AssemblyEditor.cs b/src/Publicizer/AssemblyEditor.cs
index 4bbc802..c1e38f8 100644
--- a/src/Publicizer/AssemblyEditor.cs
+++ b/src/Publicizer/AssemblyEditor.cs
@@ -9,18 +9,28 @@ internal static class AssemblyEditor
{
internal static bool PublicizeType(TypeDef type)
{
- TypeAttributes oldAttributes = type.Attributes;
- type.Attributes &= ~TypeAttributes.VisibilityMask;
+ bool modified = false;
- if (type.IsNested)
+ // A nested type is only reachable if every enclosing type is accessible too,
+ // so walk up the declaring-type chain and publicize each one.
+ for (TypeDef? current = type; current is not null; current = current.DeclaringType)
{
- type.Attributes |= TypeAttributes.NestedPublic;
- }
- else
- {
- type.Attributes |= TypeAttributes.Public;
+ TypeAttributes oldAttributes = current.Attributes;
+ current.Attributes &= ~TypeAttributes.VisibilityMask;
+
+ if (current.IsNested)
+ {
+ current.Attributes |= TypeAttributes.NestedPublic;
+ }
+ else
+ {
+ current.Attributes |= TypeAttributes.Public;
+ }
+
+ modified |= current.Attributes != oldAttributes;
}
- return type.Attributes != oldAttributes;
+
+ return modified;
}
internal static bool PublicizeProperty(PropertyDef property, bool includeVirtual = true)