From 6d382f7f88b1eb3f511c09e9415b0055c7ab1b61 Mon Sep 17 00:00:00 2001 From: John Lenz Date: Thu, 2 Jul 2026 10:27:17 -0500 Subject: [PATCH] Support closed and union syntax via preview Roslyn Add closed to modifier ordering and route Roslyn's preview UnionDeclaration syntax kind through the existing base type declaration printer. Add formatting coverage for closed record classes and union declarations. This draft intentionally references the .NET 11 Preview 5 SDK Roslyn assemblies for net10.0 builds because matching Microsoft.CodeAnalysis.CSharp NuGet packages are not yet published. Replace RoslynPreviewSdkReferences.props with a normal NuGet package update before merging. --- RoslynPreviewSdkReferences.props | 19 ++++++++++++++++++ Src/CSharpier.Cli/CSharpier.Cli.csproj | 1 + .../CSharp/SyntaxPrinter/Modifiers.cs | 1 + Src/CSharpier.Core/CSharpier.Core.csproj | 5 ++++- .../NodePrinterGenerator.cs | 8 +++++++- Src/CSharpier.Tests/CSharpier.Tests.csproj | 1 + .../cs/ClosedAndUnionModifiers.expected.test | 20 +++++++++++++++++++ .../TestFiles/cs/ClosedAndUnionModifiers.test | 19 ++++++++++++++++++ 8 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 RoslynPreviewSdkReferences.props create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/cs/ClosedAndUnionModifiers.expected.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/cs/ClosedAndUnionModifiers.test diff --git a/RoslynPreviewSdkReferences.props b/RoslynPreviewSdkReferences.props new file mode 100644 index 000000000..b33089fac --- /dev/null +++ b/RoslynPreviewSdkReferences.props @@ -0,0 +1,19 @@ + + + /usr/share/dotnet/sdk/11.0.100-preview.5.26302.115/Roslyn/bincore + + + + + + $(RoslynPreviewSdkPath)/Microsoft.CodeAnalysis.dll + + + $(RoslynPreviewSdkPath)/Microsoft.CodeAnalysis.CSharp.dll + + + diff --git a/Src/CSharpier.Cli/CSharpier.Cli.csproj b/Src/CSharpier.Cli/CSharpier.Cli.csproj index a2f9b21a9..72828af41 100644 --- a/Src/CSharpier.Cli/CSharpier.Cli.csproj +++ b/Src/CSharpier.Cli/CSharpier.Cli.csproj @@ -1,5 +1,6 @@ + $(NoWarn);NU1510 Exe diff --git a/Src/CSharpier.Core/CSharp/SyntaxPrinter/Modifiers.cs b/Src/CSharpier.Core/CSharp/SyntaxPrinter/Modifiers.cs index f966d6084..05239d327 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxPrinter/Modifiers.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxPrinter/Modifiers.cs @@ -21,6 +21,7 @@ private class DefaultOrder : IComparer "new", "virtual", "abstract", + "closed", "sealed", "override", "readonly", diff --git a/Src/CSharpier.Core/CSharpier.Core.csproj b/Src/CSharpier.Core/CSharpier.Core.csproj index 9edf84f98..c1f53d93a 100644 --- a/Src/CSharpier.Core/CSharpier.Core.csproj +++ b/Src/CSharpier.Core/CSharpier.Core.csproj @@ -1,5 +1,6 @@ + CSharpier.Core net8.0;net9.0;net10.0;netstandard2.0 @@ -13,7 +14,6 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -24,6 +24,9 @@ + + + diff --git a/Src/CSharpier.Generators/NodePrinterGenerator.cs b/Src/CSharpier.Generators/NodePrinterGenerator.cs index da37af63b..c33944122 100644 --- a/Src/CSharpier.Generators/NodePrinterGenerator.cs +++ b/Src/CSharpier.Generators/NodePrinterGenerator.cs @@ -74,6 +74,7 @@ public class NodePrinterGenerator : IIncrementalGenerator nameof(SyntaxKind.RecordStructDeclaration), nameof(SyntaxKind.EnumDeclaration), nameof(SyntaxKind.ExtensionBlockDeclaration), + "(SyntaxKind)9082", // SyntaxKind.UnionDeclaration in .NET 11 Preview 5 } }, { @@ -275,7 +276,12 @@ private object GetModel(ImmutableArray syntaxTrees) PrinterName = fileName, SyntaxNodeName = $"{fileName}Syntax", SyntaxKinds = SpecialCase.TryGetValue($"{fileName}Syntax", out var kinds) - ? string.Join(" or ", kinds.Select(x => $"SyntaxKind.{x}")) + ? string.Join( + " or ", + kinds.Select(x => + x.StartsWith("(", StringComparison.Ordinal) ? x : $"SyntaxKind.{x}" + ) + ) : $"SyntaxKind.{fileName}", }) .OrderBy(o => o.SyntaxNodeName) diff --git a/Src/CSharpier.Tests/CSharpier.Tests.csproj b/Src/CSharpier.Tests/CSharpier.Tests.csproj index 761bc5540..1dd22f205 100644 --- a/Src/CSharpier.Tests/CSharpier.Tests.csproj +++ b/Src/CSharpier.Tests/CSharpier.Tests.csproj @@ -1,4 +1,5 @@ + false CSharpier.Tests diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/ClosedAndUnionModifiers.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/ClosedAndUnionModifiers.expected.test new file mode 100644 index 000000000..381ae8956 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/ClosedAndUnionModifiers.expected.test @@ -0,0 +1,20 @@ +public closed record class JobStatus; + +public record class Queued : JobStatus; + +public record class Running(int PercentComplete) : JobStatus; + +public union Pet(Cat, Dog, Bird); + +public union Length(Meters, Feet) +{ + public double TotalMeters => + this switch + { + Meters m => m.Value, + Feet f => f.Value * 0.3048, + _ => throw new InvalidOperationException("The Length has no value."), + }; + + public Length Add(Length other) => new Meters(TotalMeters + other.TotalMeters); +} diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/ClosedAndUnionModifiers.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/ClosedAndUnionModifiers.test new file mode 100644 index 000000000..341f3761c --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/ClosedAndUnionModifiers.test @@ -0,0 +1,19 @@ +public closed record class JobStatus; + +public record class Queued : JobStatus; + +public record class Running(int PercentComplete) : JobStatus; + +public union Pet(Cat, Dog, Bird); + +public union Length(Meters, Feet) +{ + public double TotalMeters => this switch + { + Meters m => m.Value, + Feet f => f.Value * 0.3048, + _ => throw new InvalidOperationException("The Length has no value."), + }; + + public Length Add(Length other) => new Meters(TotalMeters + other.TotalMeters); +}