From 2036f17d8935e1af7efaf3353c67b89cfa57ed20 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sun, 12 Jul 2026 22:17:28 -0700 Subject: [PATCH] Don't emit a namespace closing brace for file-scoped namespaces in single-file output When generating a single output file, the namespace closing brace was written unconditionally after all output builders. File-scoped namespaces emit no opening brace, so this produced a stray trailing brace and invalid C#. Skip the closing brace (for both the primary and test streams) when file-scoped namespaces are used. Fixes #555 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../PInvokeGenerator.Close.cs | 7 ++- .../FileScopedNamespaceTest.cs | 58 +++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 tests/ClangSharp.PInvokeGenerator.UnitTests/FileScopedNamespaceTest.cs diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Close.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Close.cs index e63c552ec..c26564af3 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Close.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Close.cs @@ -286,7 +286,10 @@ public void Close() if (_config.OutputMode == PInvokeGeneratorOutputMode.CSharp) { - sw.WriteLine('}'); + if (!_config.GenerateFileScopedNamespaces) + { + sw.WriteLine('}'); + } } else if (_config.OutputMode == PInvokeGeneratorOutputMode.Xml) { @@ -299,7 +302,7 @@ public void Close() using var tsw = new StreamWriter(testStream, s_defaultStreamWriterEncoding, DefaultStreamWriterBufferSize, leaveStreamOpen); tsw.NewLine = "\n"; - if (_config.OutputMode == PInvokeGeneratorOutputMode.CSharp) + if ((_config.OutputMode == PInvokeGeneratorOutputMode.CSharp) && !_config.GenerateFileScopedNamespaces) { tsw.WriteLine('}'); } diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/FileScopedNamespaceTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/FileScopedNamespaceTest.cs new file mode 100644 index 000000000..9afe9e635 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/FileScopedNamespaceTest.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Threading.Tasks; +using NUnit.Framework; + +namespace ClangSharp.UnitTests; + +/// +/// Regression test for https://github.com/dotnet/ClangSharp/issues/555. +/// When generate-file-scoped-namespaces is used with a single output file, no namespace +/// opening brace is emitted, so no closing brace must be emitted either. +/// +[Platform("win")] +public sealed class FileScopedNamespaceTest : PInvokeGeneratorTest +{ + [Test] + public Task MethodClassHasNoTrailingBrace() + { + var inputContents = @"extern ""C"" void MyFunction(); +"; + + var expectedOutputContents = @"using System.Runtime.InteropServices; + +namespace ClangSharp.Test; + +public static partial class Methods +{ + [DllImport(""ClangSharpPInvokeGenerator"", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void MyFunction(); +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, PInvokeGeneratorConfigurationOptions.GenerateFileScopedNamespaces); + } + + [Test] + public Task StructHasNoTrailingBrace() + { + var inputContents = @"struct Point +{ + int x; + int y; +}; +"; + + var expectedOutputContents = @"namespace ClangSharp.Test; + +public partial struct Point +{ + public int x; + + public int y; +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, PInvokeGeneratorConfigurationOptions.GenerateFileScopedNamespaces); + } +}