From 0b91ca444c94fbaca93d0b75512398edff91fd91 Mon Sep 17 00:00:00 2001 From: Tim M <49349513+TimothyMakkison@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:21:32 +0000 Subject: [PATCH 01/27] perf: use overload for `Any( SyntaxTriviaList` to prevent allocations (#1703) Semi reverts #1572 which overwrote #1485, I assume this was accidental. I noticed that you prefer explicit `Enumerable.Any` usage, should I convert this PR to use `ListExtensions.Any`? ## Benchmarks Saves around 15% of memory usage ### Before | Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | |------------------------------ |---------:|--------:|--------:|----------:|----------:|----------:| | Default_CodeFormatter_Tests | 128.0 ms | 2.44 ms | 3.43 ms | 3000.0000 | 1000.0000 | 34.57 MB | | Default_CodeFormatter_Complex | 260.3 ms | 5.11 ms | 7.17 ms | 5000.0000 | 2000.0000 | 53.05 MB | ### After | Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | |------------------------------ |---------:|--------:|---------:|----------:|----------:|----------:| | Default_CodeFormatter_Tests | 126.0 ms | 2.27 ms | 5.45 ms | 2000.0000 | 1000.0000 | 28.02 MB | | Default_CodeFormatter_Complex | 245.2 ms | 4.63 ms | 10.44 ms | 4000.0000 | 2000.0000 | 43.88 MB | Co-authored-by: Bela VanderVoort --- .../CSharp/SyntaxPrinter/Modifiers.cs | 1 + .../CSharp/SyntaxPrinter/RightHandSide.cs | 1 + .../SyntaxNodePrinters/Parameter.cs | 2 +- .../SyntaxNodePrinters/RecursivePattern.cs | 5 ++- .../CSharp/SyntaxPrinter/Token.cs | 35 ++++++------------- 5 files changed, 16 insertions(+), 28 deletions(-) diff --git a/Src/CSharpier.Core/CSharp/SyntaxPrinter/Modifiers.cs b/Src/CSharpier.Core/CSharp/SyntaxPrinter/Modifiers.cs index 40ffc5d52..f966d6084 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxPrinter/Modifiers.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxPrinter/Modifiers.cs @@ -1,4 +1,5 @@ using CSharpier.Core.DocTypes; +using CSharpier.Core.Utilities; using Microsoft.CodeAnalysis; namespace CSharpier.Core.CSharp.SyntaxPrinter; diff --git a/Src/CSharpier.Core/CSharp/SyntaxPrinter/RightHandSide.cs b/Src/CSharpier.Core/CSharp/SyntaxPrinter/RightHandSide.cs index 71773bfde..684e241b8 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxPrinter/RightHandSide.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxPrinter/RightHandSide.cs @@ -1,4 +1,5 @@ using CSharpier.Core.DocTypes; +using CSharpier.Core.Utilities; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/Src/CSharpier.Core/CSharp/SyntaxPrinter/SyntaxNodePrinters/Parameter.cs b/Src/CSharpier.Core/CSharp/SyntaxPrinter/SyntaxNodePrinters/Parameter.cs index 4aba50361..a802a41e3 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxPrinter/SyntaxNodePrinters/Parameter.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxPrinter/SyntaxNodePrinters/Parameter.cs @@ -19,7 +19,7 @@ public static Doc Print(ParameterSyntax node, PrintingContext context) if ( node.AttributeLists.Count < 2 && ( - Enumerable.Any(node.GetLeadingTrivia(), o => o.IsComment()) + node.GetLeadingTrivia().Any(o => o.IsComment()) || node.Parent is ParameterListSyntax { Parameters.Count: 0 } ) ) diff --git a/Src/CSharpier.Core/CSharp/SyntaxPrinter/SyntaxNodePrinters/RecursivePattern.cs b/Src/CSharpier.Core/CSharp/SyntaxPrinter/SyntaxNodePrinters/RecursivePattern.cs index ed7cfbdd0..51f3702d6 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxPrinter/SyntaxNodePrinters/RecursivePattern.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxPrinter/SyntaxNodePrinters/RecursivePattern.cs @@ -68,9 +68,8 @@ or BinaryPatternSyntax result.Add( Doc.Group( node.Type != null - && !Enumerable.Any( - node.PropertyPatternClause.OpenBraceToken.LeadingTrivia, - o => o.IsDirective || o.IsComment() + && !node.PropertyPatternClause.OpenBraceToken.LeadingTrivia.Any(o => + o.IsDirective || o.IsComment() ) ? Doc.Line : Doc.Null, diff --git a/Src/CSharpier.Core/CSharp/SyntaxPrinter/Token.cs b/Src/CSharpier.Core/CSharp/SyntaxPrinter/Token.cs index 579966134..18e11218b 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxPrinter/Token.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxPrinter/Token.cs @@ -131,7 +131,7 @@ or SyntaxKind.InterpolatedRawStringEndToken { if ( context.State.TrailingComma is not null - && Enumerable.FirstOrDefault(syntaxToken.TrailingTrivia, o => o.IsComment()) + && syntaxToken.TrailingTrivia.FirstOrDefault(o => o.IsComment()) == context.State.TrailingComma.TrailingComment ) { @@ -174,7 +174,8 @@ public static Doc PrintLeadingTrivia(SyntaxToken syntaxToken, PrintingContext co skipLastHardline: isClosingBrace ); - var hasDirective = Enumerable.Any(syntaxToken.LeadingTrivia, o => o.IsDirective); + var leadingTrivia = syntaxToken.LeadingTrivia; + var hasDirective = leadingTrivia.Any(o => o.IsDirective); if (hasDirective) { @@ -198,7 +199,7 @@ public static Doc PrintLeadingTrivia(SyntaxToken syntaxToken, PrintingContext co Doc extraNewLines = Doc.Null; - if (hasDirective || Enumerable.Any(syntaxToken.LeadingTrivia, o => o.IsComment())) + if (hasDirective || leadingTrivia.Any(o => o.IsComment())) { extraNewLines = ExtraNewLines.Print(syntaxToken.LeadingTrivia); } @@ -351,12 +352,7 @@ void AddLeadingComment(CommentType commentType) if (context.State.NextTriviaNeedsLine) { - if ( - Enumerable.Any( - leadingTrivia, - o => o.RawSyntaxKind() is SyntaxKind.IfDirectiveTrivia - ) - ) + if (leadingTrivia.Any(o => o.RawSyntaxKind() is SyntaxKind.IfDirectiveTrivia)) { docs.Insert(0, Doc.HardLineSkipBreakIfFirstInGroup); } @@ -430,17 +426,11 @@ private static Doc PrintTrailingTrivia(in SyntaxTriviaList trailingTrivia) public static bool HasComments(SyntaxToken syntaxToken) { - return Enumerable.Any( - syntaxToken.LeadingTrivia, - o => - o.RawSyntaxKind() - is not (SyntaxKind.WhitespaceTrivia or SyntaxKind.EndOfLineTrivia) + return syntaxToken.LeadingTrivia.Any(o => + o.RawSyntaxKind() is not (SyntaxKind.WhitespaceTrivia or SyntaxKind.EndOfLineTrivia) ) - || Enumerable.Any( - syntaxToken.TrailingTrivia, - o => - o.RawSyntaxKind() - is not (SyntaxKind.WhitespaceTrivia or SyntaxKind.EndOfLineTrivia) + || syntaxToken.TrailingTrivia.Any(o => + o.RawSyntaxKind() is not (SyntaxKind.WhitespaceTrivia or SyntaxKind.EndOfLineTrivia) ); } @@ -463,11 +453,8 @@ public static bool HasLeadingCommentMatching(SyntaxNode node, Regex regex) public static bool HasLeadingCommentMatching(SyntaxToken token, Regex regex) { - return Enumerable.Any( - token.LeadingTrivia, - o => - o.RawSyntaxKind() is SyntaxKind.SingleLineCommentTrivia - && regex.IsMatch(o.ToString()) + return token.LeadingTrivia.Any(o => + o.RawSyntaxKind() is SyntaxKind.SingleLineCommentTrivia && regex.IsMatch(o.ToString()) ); } } From 39f1620e87ce2c2b580ce2b3f16fcbbb36c37b56 Mon Sep 17 00:00:00 2001 From: Tim M <49349513+TimothyMakkison@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:24:16 +0000 Subject: [PATCH 02/27] perf: add `Doc.Null` check to `Argument` avoids `Concat` creation (#1706) Add check for if `modifiers` are `Doc.Null` to avoid allocating a `Concat` and `Doc[]` ### Benchmarks (timing is inaccurate) #### Before | Method | Mean | Error | StdDev | Median | Gen0 | Gen1 | Allocated | |------------------------------ |---------:|--------:|--------:|---------:|----------:|----------:|----------:| | Default_CodeFormatter_Tests | 131.9 ms | 3.30 ms | 8.86 ms | 128.0 ms | 3000.0000 | 1000.0000 | 34.57 MB | | Default_CodeFormatter_Complex | 261.3 ms | 5.16 ms | 4.03 ms | 261.3 ms | 5000.0000 | 2000.0000 | 53.03 MB | #### After | Method | Mean | Error | StdDev | Median | Gen0 | Gen1 | Allocated | |------------------------------ |---------:|---------:|---------:|---------:|----------:|----------:|----------:| | Default_CodeFormatter_Tests | 189.5 ms | 10.25 ms | 28.05 ms | 195.6 ms | 3000.0000 | 1000.0000 | 34.31 MB | | Default_CodeFormatter_Complex | 264.2 ms | 5.27 ms | 13.97 ms | 260.0 ms | 5000.0000 | 2000.0000 | 52.31 MB | Co-authored-by: Bela VanderVoort --- .../CSharp/SyntaxPrinter/SyntaxNodePrinters/Argument.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Src/CSharpier.Core/CSharp/SyntaxPrinter/SyntaxNodePrinters/Argument.cs b/Src/CSharpier.Core/CSharp/SyntaxPrinter/SyntaxNodePrinters/Argument.cs index f1d3aa223..c001e5298 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxPrinter/SyntaxNodePrinters/Argument.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxPrinter/SyntaxNodePrinters/Argument.cs @@ -9,7 +9,11 @@ internal static class Argument { public static Doc Print(ArgumentSyntax node, PrintingContext context) { - return Doc.Concat(PrintModifiers(node, context), Node.Print(node.Expression, context)); + var modifiers = PrintModifiers(node, context); + + return modifiers == Doc.Null + ? Node.Print(node.Expression, context) + : Doc.Concat(modifiers, Node.Print(node.Expression, context)); } public static Doc PrintModifiers(ArgumentSyntax node, PrintingContext context) From 5fe392534c29b0f9363e1207766131e48bc67aa5 Mon Sep 17 00:00:00 2001 From: Tim M <49349513+TimothyMakkison@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:26:34 +0000 Subject: [PATCH 03/27] perf: use `Stack.Peek` to reduce `Pop` and `Push` churn (#1708) Not sure if more changes might help here, perhaps calling `docsStack.Push(TraverseDocOnExitStackMarker);` later and adding an `else` clause might help, that or using a goto. Might be easier to measure an improvement once other changes were made. Did wonder if `ValueListBuilder` will help due to faster `Push` and `Pop`. Co-authored-by: Bela VanderVoort --- Src/CSharpier.Core/DocPrinter/PropagateBreaks.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Src/CSharpier.Core/DocPrinter/PropagateBreaks.cs b/Src/CSharpier.Core/DocPrinter/PropagateBreaks.cs index e036ed204..24d2a0189 100644 --- a/Src/CSharpier.Core/DocPrinter/PropagateBreaks.cs +++ b/Src/CSharpier.Core/DocPrinter/PropagateBreaks.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using CSharpier.Core.DocTypes; namespace CSharpier.Core.DocPrinter; @@ -27,6 +28,7 @@ void BreakParentGroup() } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] bool OnEnter(Doc doc) { if (doc is ForceFlat) @@ -87,22 +89,23 @@ void OnExit(Doc doc) docsStack.Push(document); while (docsStack.Count > 0) { - var doc = docsStack.Pop(); + var doc = docsStack.Peek(); if (doc == TraverseDocOnExitStackMarker) { + docsStack.Pop(); OnExit(docsStack.Pop()); continue; } - docsStack.Push(doc); - docsStack.Push(TraverseDocOnExitStackMarker); - if (!OnEnter(doc)) { + OnExit(docsStack.Pop()); continue; } + docsStack.Push(TraverseDocOnExitStackMarker); + if (doc is Concat concat) { // push onto stack in reverse order so they are processed in the original order From ebc893f0201aab81faf146124d21d95f4fb7b09a Mon Sep 17 00:00:00 2001 From: Tim M <49349513+TimothyMakkison@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:30:52 +0000 Subject: [PATCH 04/27] perf: set capacity of `List` in `MembersForcedLine` (#1709) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most `result` Lists are always just under `3 * members.Count`, small change but simple 🤷 ### Benchmarks (timing is inaccurate should be the same) #### Before | Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | |------------------------------ |---------:|--------:|--------:|----------:|----------:|----------:| | Default_CodeFormatter_Tests | 133.0 ms | 2.56 ms | 2.84 ms | 3000.0000 | 1000.0000 | 34.58 MB | | Default_CodeFormatter_Complex | 271.5 ms | 5.40 ms | 7.92 ms | 5000.0000 | 2000.0000 | 52.95 MB | #### After | Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | |------------------------------ |---------:|--------:|--------:|----------:|----------:|----------:| | Default_CodeFormatter_Tests | 132.4 ms | 2.64 ms | 3.87 ms | 3000.0000 | 1000.0000 | 34.56 MB | | Default_CodeFormatter_Complex | 268.2 ms | 4.92 ms | 4.60 ms | 5000.0000 | 2000.0000 | 52.78 MB | Co-authored-by: Bela VanderVoort --- .../CSharp/SyntaxPrinter/MembersWithForcedLines.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/CSharpier.Core/CSharp/SyntaxPrinter/MembersWithForcedLines.cs b/Src/CSharpier.Core/CSharp/SyntaxPrinter/MembersWithForcedLines.cs index 9e2b0d0dc..33a5b352f 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxPrinter/MembersWithForcedLines.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxPrinter/MembersWithForcedLines.cs @@ -20,7 +20,7 @@ public static List Print( ) where T : MemberDeclarationSyntax { - var result = new List(); + var result = new List(members.Count * 3); if (!skipFirstHardLine) { result.Add(Doc.HardLine); From e9b9c643b8ac90061801c8b43fd41c9253d2d894 Mon Sep 17 00:00:00 2001 From: Tim M <49349513+TimothyMakkison@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:37:41 +0000 Subject: [PATCH 05/27] perf: optimise `SyntaxNodeComparer` (#1730) It appears that `SyntaxNodeComparer` is run after a file is formated, optimisation here may be impactful - Prevent null `Func` allocations (still seeing some in the profiler, no idea how to sotp this) - Prevent boxing of `SyntaxTokenList`, `SyntaxNodeOrTokenList` - Use `OrderBy.ToArray` instead of `OrderBy.ToList` - Create `AllSeparatorsButLast` to prevent boxing `SeparatedSyntaxList` and `IEnumerable` creation I have some other idea, but I'm focusing on the unhappy path right now. ### Benchmark (comparing `complexCode`) #### Before | Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | |--------------------------- |---------:|--------:|--------:|----------:|----------:|----------:|----------:| | Default_SyntaxNodeComparer | 229.1 ms | 4.55 ms | 7.60 ms | 5000.0000 | 3000.0000 | 1000.0000 | 40.53 MB | #### After | Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | |--------------------------- |---------:|--------:|--------:|----------:|----------:|----------:| | Default_SyntaxNodeComparer | 187.4 ms | 3.51 ms | 7.64 ms | 3000.0000 | 1000.0000 | 31.39 MB | Co-authored-by: Bela VanderVoort --- .../CSharp/SyntaxNodeComparer.cs | 81 +++++++++++++++---- .../SyntaxNodeComparerGenerator.cs | 13 +-- 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/Src/CSharpier.Core/CSharp/SyntaxNodeComparer.cs b/Src/CSharpier.Core/CSharp/SyntaxNodeComparer.cs index 7d5bed4a1..ec772ad64 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxNodeComparer.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxNodeComparer.cs @@ -50,6 +50,7 @@ CancellationToken cancellationToken cSharpParseOptions, cancellationToken: cancellationToken ); + this.CompareFunc = Compare; } public string CompareSource() @@ -148,14 +149,16 @@ SyntaxNode formattedStart return Equal; } +#pragma warning disable CA1822 private CompareResult CompareLists( - IReadOnlyList originalList, - IReadOnlyList formattedList, - Func comparer, - Func getSpan, + T originalList, + T formattedList, + Func comparer, + Func getSpan, TextSpan originalParentSpan, TextSpan newParentSpan ) + where T : IReadOnlyList { for (var x = 0; x < originalList.Count || x < formattedList.Count; x++) { @@ -169,25 +172,71 @@ TextSpan newParentSpan return NotEqual(getSpan(originalList[x]), newParentSpan); } - if ( - originalList[x] is SyntaxNode originalNode - && formattedList[x] is SyntaxNode formattedNode - ) + var result = comparer(originalList[x], formattedList[x]); + if (result.IsInvalid) + { + return result; + } + } + + return Equal; + } +#pragma warning restore CA1822 + + private CompareResult CompareLists( + T originalList, + T formattedList, + Func comparer, + Func getSpan, + TextSpan originalParentSpan, + TextSpan newParentSpan + ) + where T : IReadOnlyList + { + for (var x = 0; x < originalList.Count || x < formattedList.Count; x++) + { + if (x == originalList.Count) + { + return NotEqual(originalParentSpan, getSpan(formattedList[x])); + } + + if (x == formattedList.Count) { - this.originalStack.Push((originalNode, originalNode.Parent)); - this.formattedStack.Push((formattedNode, formattedNode.Parent)); + return NotEqual(getSpan(originalList[x]), newParentSpan); } - else + + var originalNode = originalList[x]; + var formattedNode = formattedList[x]; + this.originalStack.Push((originalNode, originalNode.Parent)); + this.formattedStack.Push((formattedNode, formattedNode.Parent)); + } + + return Equal; + } + + private static SyntaxToken[] AllSeparatorsButLast(in SeparatedSyntaxList list) + { + if (list.Count <= 1) + { + return []; + } + + var tokens = new SyntaxToken[list.Count - 1]; + var tokenIndex = 0; + + foreach (var element in list.GetWithSeparators()) + { + if (element.IsToken) { - var result = comparer(originalList[x], formattedList[x]); - if (result.IsInvalid) + tokens[tokenIndex++] = element.AsToken(); + if (tokenIndex == tokens.Length) { - return result; + break; } } } - return Equal; + return tokens; } private static CompareResult NotEqual(SyntaxNode? originalNode, SyntaxNode? formattedNode) @@ -210,6 +259,8 @@ private static CompareResult NotEqual(TextSpan? originalSpan, TextSpan? formatte }; } + private Func CompareFunc { get; } + private CompareResult Compare(SyntaxToken originalToken, SyntaxToken formattedToken) { return this.Compare(originalToken, formattedToken, null, null); diff --git a/Src/CSharpier.Generators/SyntaxNodeComparerGenerator.cs b/Src/CSharpier.Generators/SyntaxNodeComparerGenerator.cs index 4985e499e..1f938810d 100644 --- a/Src/CSharpier.Generators/SyntaxNodeComparerGenerator.cs +++ b/Src/CSharpier.Generators/SyntaxNodeComparerGenerator.cs @@ -137,7 +137,7 @@ private static void GenerateMethod(StringBuilder sourceBuilder, INamedTypeSymbol $$""" private CompareResult Compare{{type.Name}}({{type.Name}} originalNode, {{type.Name}} formattedNode) { - CompareResult result; + CompareResult result; """ ); @@ -228,10 +228,13 @@ private static void GenerateMethod(StringBuilder sourceBuilder, INamedTypeSymbol } else { - var compare = propertyType.Name == nameof(SyntaxTokenList) ? "Compare" : "null"; + var compare = + propertyType.Name == nameof(SyntaxTokenList) + ? "CompareFunc" + : "static (_, _) => default"; if (propertyName == "Modifiers") { - propertyName += ".OrderBy(o => o.Text).ToList()"; + propertyName += ".OrderBy(o => o.Text).ToArray()"; } sourceBuilder.AppendLine( @@ -249,13 +252,13 @@ private static void GenerateMethod(StringBuilder sourceBuilder, INamedTypeSymbol ) { sourceBuilder.AppendLine( - $" result = this.CompareLists(originalNode.{propertyName}, formattedNode.{propertyName}, null, o => o.Span, originalNode.Span, formattedNode.Span);" + $" result = this.CompareLists(originalNode.{propertyName}, formattedNode.{propertyName}, static (_, _) => default, o => o.Span, originalNode.Span, formattedNode.Span);" ); sourceBuilder.AppendLine(" if (result.IsInvalid) return result;"); // Omit the last separator when comparing the original node with the formatted node, as it legitimately may be added or removed sourceBuilder.AppendLine( - $" result = this.CompareLists(originalNode.{propertyName}.GetSeparators().Take(originalNode.{propertyName}.Count() - 1).ToList(), formattedNode.{propertyName}.GetSeparators().Take(formattedNode.{propertyName}.Count() - 1).ToList(), Compare, o => o.Span, originalNode.Span, formattedNode.Span);" + $" result = this.CompareLists(AllSeparatorsButLast(originalNode.{propertyName}), AllSeparatorsButLast(formattedNode.{propertyName}), CompareFunc, o => o.Span, originalNode.Span, formattedNode.Span);" ); sourceBuilder.AppendLine(" if (result.IsInvalid) return result;"); } From af3212490fd3ba3cc0a86a9c245047350a81dd21 Mon Sep 17 00:00:00 2001 From: Tim M <49349513+TimothyMakkison@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:56:43 +0000 Subject: [PATCH 06/27] perf: fast comparison using `FullSpan` and source code (#1737) - Compare the old and formatted code before using `SyntaxNodeComparer` (ie new version of csharpier, no changes but hash is cache is invalidated) - Skip comparison if two `TypeDeclaration` aka (classes, struct, interfaces etc) or `MethodDeclaration` are identical - This could be done for all `SyntaxNodes` but may slow things down if most nodes have changed The CLI spends up to 30% of the time doing comparisons, with this, we can skip cases with identical source code, and only compare types/methods that have changed. The performance degradation for lots of changes (i.e. first time a project is formatted) shouldn't be that great as `CompareFullSpan` is basically a fast `SequenceEqual` and only used for type declarations and methods. Co-authored-by: Bela VanderVoort --- Src/CSharpier.Cli/CommandLineFormatter.cs | 5 ++++- Src/CSharpier.Core/CSharp/SyntaxNodeComparer.cs | 11 +++++++++++ .../SyntaxNodeComparerGenerator.cs | 12 ++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Src/CSharpier.Cli/CommandLineFormatter.cs b/Src/CSharpier.Cli/CommandLineFormatter.cs index fadb4ccb6..d45eb33ec 100644 --- a/Src/CSharpier.Cli/CommandLineFormatter.cs +++ b/Src/CSharpier.Cli/CommandLineFormatter.cs @@ -501,7 +501,10 @@ CancellationToken cancellationToken { IFormattingValidator? formattingValidator = null; - if (printerOptions.Formatter is Formatter.CSharp or Formatter.CSharpScript) + if ( + printerOptions.Formatter is Formatter.CSharp or Formatter.CSharpScript + && fileToFormatInfo.FileContents != codeFormattingResult.Code + ) { var sourceCodeKind = printerOptions.Formatter is Formatter.CSharpScript diff --git a/Src/CSharpier.Core/CSharp/SyntaxNodeComparer.cs b/Src/CSharpier.Core/CSharp/SyntaxNodeComparer.cs index ec772ad64..8675bfbf3 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxNodeComparer.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxNodeComparer.cs @@ -373,6 +373,17 @@ private CompareResult Compare(SyntaxTrivia originalTrivia, SyntaxTrivia formatte : NotEqual(originalTrivia.Span, formattedTrivia.Span); } + private bool CompareFullSpan(SyntaxNode originalStart, SyntaxNode formattedStart) + { + var originalSpan = OriginalSourceCode + .AsSpan() + .Slice(originalStart.FullSpan.Start, originalStart.FullSpan.Length); + var formattedSpan = NewSourceCode + .AsSpan() + .Slice(formattedStart.FullSpan.Start, formattedStart.FullSpan.Length); + return originalSpan == formattedSpan; + } + private static CompareResult CompareComment( string originalComment, string formattedComment, diff --git a/Src/CSharpier.Generators/SyntaxNodeComparerGenerator.cs b/Src/CSharpier.Generators/SyntaxNodeComparerGenerator.cs index 1f938810d..c884f3d7c 100644 --- a/Src/CSharpier.Generators/SyntaxNodeComparerGenerator.cs +++ b/Src/CSharpier.Generators/SyntaxNodeComparerGenerator.cs @@ -141,6 +141,18 @@ private static void GenerateMethod(StringBuilder sourceBuilder, INamedTypeSymbol """ ); + if ( + type.BaseType?.ToDisplayString() + == "Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax" + || type.ToDisplayString() + == "Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax" + ) + { + sourceBuilder.AppendLine( + $" if (CompareFullSpan(originalNode, formattedNode)) return Equal;" + ); + } + foreach (var propertySymbol in type.GetAllProperties().OrderBy(o => o.Name)) { var propertyName = propertySymbol.Name; From 7b06c03adae146277684a1cf5a22b231c89da9fd Mon Sep 17 00:00:00 2001 From: Tim M <49349513+TimothyMakkison@users.noreply.github.com> Date: Thu, 25 Dec 2025 17:00:38 +0000 Subject: [PATCH 07/27] perf: cache `IgnoreWithBasePath` (#1758) Repeatedly compiling regex for ignore files is a significant amount of runtime, caches the `IgnoreWithBasePath` to prevent this. See also #1776 ### Before | Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | |---------- |---------:|---------:|---------:|----------:|----------:|----------:| | FormatCli | 955.0 ms | 17.65 ms | 29.98 ms | 9000.0000 | 1000.0000 | 81.5 MB | ### After | Method | Mean | Error | StdDev | Gen0 | Allocated | |---------- |---------:|---------:|---------:|----------:|----------:| | FormatCli | 683.5 ms | 13.61 ms | 30.17 ms | 7000.0000 | 69.33 MB | Co-authored-by: Bela VanderVoort --- Src/CSharpier.Cli/IgnoreFile.cs | 15 +++++++++++++-- Src/CSharpier.Cli/Options/OptionsProvider.cs | 11 ++++++++++- Src/CSharpier.Tests/Cli/IgnoreFileTests.cs | 8 +++++++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Src/CSharpier.Cli/IgnoreFile.cs b/Src/CSharpier.Cli/IgnoreFile.cs index 89d2320bf..a64738e0c 100644 --- a/Src/CSharpier.Cli/IgnoreFile.cs +++ b/Src/CSharpier.Cli/IgnoreFile.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.IO.Abstractions; using CSharpier.Cli.DotIgnore; using CSharpier.Core; @@ -35,17 +36,27 @@ public bool IsIgnored(string filePath) string baseDirectoryPath, IFileSystem fileSystem, string? ignorePath, + ConcurrentDictionary? ignoreCache, CancellationToken cancellationToken ) { - Task CreateIgnore(string ignoreFilePath, string? overrideBasePath) + async Task CreateIgnore(string ignoreFilePath, string? overrideBasePath) { - return IgnoreList.CreateAsync( + if (ignoreCache is not null && ignoreCache.TryGetValue(ignoreFilePath, out var ignore)) + { + return ignore; + } + + ignore = await IgnoreList.CreateAsync( fileSystem, overrideBasePath ?? Path.GetDirectoryName(ignoreFilePath)!, ignoreFilePath, cancellationToken ); + + ignoreCache?[ignoreFilePath] = ignore; + + return ignore; } return await SharedFunc diff --git a/Src/CSharpier.Cli/Options/OptionsProvider.cs b/Src/CSharpier.Cli/Options/OptionsProvider.cs index 0eb094614..153e1d20b 100644 --- a/Src/CSharpier.Cli/Options/OptionsProvider.cs +++ b/Src/CSharpier.Cli/Options/OptionsProvider.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.IO.Abstractions; using System.Text.Json; +using CSharpier.Cli.DotIgnore; using CSharpier.Cli.EditorConfig; using CSharpier.Core; using Microsoft.Extensions.Logging; @@ -15,6 +16,7 @@ private readonly ConcurrentDictionary< string, CSharpierConfigData? > csharpierConfigsByDirectory = new(); + private readonly ConcurrentDictionary ignoreWithPathCache = new(); private readonly ConcurrentDictionary ignoreFilesByDirectory = new(); private readonly ConfigurationFileOptions? specifiedConfigFile; private readonly EditorConfigSections? specifiedEditorConfig; @@ -60,6 +62,7 @@ CancellationToken cancellationToken directoryName, fileSystem, ignorePath, + null, cancellationToken ); @@ -204,7 +207,13 @@ CancellationToken cancellationToken Path.Combine(searchingDirectory, ".csharpierignore") ), (searchingDirectory) => - IgnoreFile.CreateAsync(searchingDirectory, this.fileSystem, null, cancellationToken) + IgnoreFile.CreateAsync( + searchingDirectory, + this.fileSystem, + null, + ignoreWithPathCache, + cancellationToken + ) ); #pragma warning disable IDE0270 diff --git a/Src/CSharpier.Tests/Cli/IgnoreFileTests.cs b/Src/CSharpier.Tests/Cli/IgnoreFileTests.cs index 75e3de8f3..7398711fe 100644 --- a/Src/CSharpier.Tests/Cli/IgnoreFileTests.cs +++ b/Src/CSharpier.Tests/Cli/IgnoreFileTests.cs @@ -621,7 +621,13 @@ private void GitBasedTest(string gitignore, string[] files) var gitIgnoredFiles = files.Where(o => !gitUntrackedFiles.Contains(o)); var ignoreFile = IgnoreFile - .CreateAsync(this.gitRepository.RepoPath, this.fileSystem, null, CancellationToken.None) + .CreateAsync( + this.gitRepository.RepoPath, + this.fileSystem, + null, + null, + CancellationToken.None + ) .GetAwaiter() .GetResult(); From 8b1c0b154b7a8e141141abc6f039f3c2236162c8 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Thu, 25 Dec 2025 11:05:09 -0600 Subject: [PATCH 08/27] Adding the xml whitespace options (#1793) This is just adding the options that will eventually be used by the new xml whitespace handling code. --- ...FileParser.cs => CSharpierConfigParser.cs} | 0 .../EditorConfig/EditorConfigSections.cs | 24 +- Src/CSharpier.Cli/EditorConfig/Section.cs | 2 + .../Options/ConfigurationFileOptions.cs | 10 + Src/CSharpier.Core/PrinterOptions.cs | 2 + .../XmlWhitespaceSensitivity.cs | 8 + ...Tests.cs => CSharpierConfigParserTests.cs} | 2 +- Src/CSharpier.Tests/OptionsProviderTests.cs | 350 +++++++++++------- 8 files changed, 251 insertions(+), 147 deletions(-) rename Src/CSharpier.Cli/EditorConfig/{EditorConfigFileParser.cs => CSharpierConfigParser.cs} (100%) create mode 100644 Src/CSharpier.Core/XmlWhitespaceSensitivity.cs rename Src/CSharpier.Tests/Cli/Options/{EditorConfigFileParserTests.cs => CSharpierConfigParserTests.cs} (97%) diff --git a/Src/CSharpier.Cli/EditorConfig/EditorConfigFileParser.cs b/Src/CSharpier.Cli/EditorConfig/CSharpierConfigParser.cs similarity index 100% rename from Src/CSharpier.Cli/EditorConfig/EditorConfigFileParser.cs rename to Src/CSharpier.Cli/EditorConfig/CSharpierConfigParser.cs diff --git a/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs b/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs index c6476769a..bd7e33ec4 100644 --- a/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs +++ b/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs @@ -53,6 +53,11 @@ internal class EditorConfigSections printerOptions.EndOfLine = endOfLine; } + if (resolvedConfiguration.XmlWhitespaceSensitivity is { } xmlWhitespaceSensitivity) + { + printerOptions.XmlWhitespaceSensitivity = xmlWhitespaceSensitivity; + } + return printerOptions; } @@ -63,6 +68,7 @@ private class ResolvedConfiguration public int? TabWidth { get; } public int? MaxLineLength { get; } public EndOfLine? EndOfLine { get; } + public XmlWhitespaceSensitivity? XmlWhitespaceSensitivity { get; set; } public string? Formatter { get; } public ResolvedConfiguration(List
sections) @@ -104,9 +110,23 @@ public ResolvedConfiguration(List
sections) } var endOfLine = sections.LastOrDefault(o => o.EndOfLine != null)?.EndOfLine; - if (Enum.TryParse(endOfLine, true, out EndOfLine result)) + if (Enum.TryParse(endOfLine, true, out EndOfLine parsedEndOfLine)) + { + this.EndOfLine = parsedEndOfLine; + } + + var xmlWhitespaceSensitivity = sections + .LastOrDefault(o => o.XmlWhitespaceSensitivity != null) + ?.XmlWhitespaceSensitivity; + if ( + Enum.TryParse( + xmlWhitespaceSensitivity, + true, + out XmlWhitespaceSensitivity parsedXmlWhitespaceSensitivity + ) + ) { - this.EndOfLine = result; + this.XmlWhitespaceSensitivity = parsedXmlWhitespaceSensitivity; } this.Formatter = sections.LastOrDefault(o => o.Formatter is not null)?.Formatter; diff --git a/Src/CSharpier.Cli/EditorConfig/Section.cs b/Src/CSharpier.Cli/EditorConfig/Section.cs index e8f708831..156ccebbf 100644 --- a/Src/CSharpier.Cli/EditorConfig/Section.cs +++ b/Src/CSharpier.Cli/EditorConfig/Section.cs @@ -13,6 +13,8 @@ internal class Section(SectionData section, string directory) public string? MaxLineLength { get; } = section.Keys["max_line_length"]; public string? EndOfLine { get; } = section.Keys["end_of_line"]; public string? Formatter { get; } = section.Keys["csharpier_formatter"]; + public string? XmlWhitespaceSensitivity { get; } = + section.Keys["csharpier_xml_whitespace_sensitivity"]; public bool IsMatch(string fileName, bool ignoreDirectory) { diff --git a/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs b/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs index ee85813cb..a83508c02 100644 --- a/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs +++ b/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs @@ -10,6 +10,10 @@ internal class ConfigurationFileOptions public int? IndentSize { get; init; } public bool UseTabs { get; init; } + [JsonConverter(typeof(CaseInsensitiveEnumConverter))] + public XmlWhitespaceSensitivity XmlWhitespaceSensitivity { get; init; } = + XmlWhitespaceSensitivity.Strict; + [JsonConverter(typeof(CaseInsensitiveEnumConverter))] public EndOfLine EndOfLine { get; init; } @@ -38,6 +42,7 @@ out var parsedFormatter UseTabs = matchingOverride.UseTabs, Width = matchingOverride.PrintWidth, EndOfLine = matchingOverride.EndOfLine, + XmlWhitespaceSensitivity = matchingOverride.XmlWhitespaceSensitivity, }; } @@ -50,6 +55,7 @@ out var parsedFormatter UseTabs = this.UseTabs, Width = this.PrintWidth, EndOfLine = this.EndOfLine, + XmlWhitespaceSensitivity = this.XmlWhitespaceSensitivity, }; } @@ -73,6 +79,10 @@ internal class Override public int IndentSize { get; init; } = 4; public bool UseTabs { get; init; } + [JsonConverter(typeof(CaseInsensitiveEnumConverter))] + public XmlWhitespaceSensitivity XmlWhitespaceSensitivity { get; init; } = + XmlWhitespaceSensitivity.Strict; + [JsonConverter(typeof(CaseInsensitiveEnumConverter))] public EndOfLine EndOfLine { get; init; } diff --git a/Src/CSharpier.Core/PrinterOptions.cs b/Src/CSharpier.Core/PrinterOptions.cs index 6e0b16da6..b82dbdde2 100644 --- a/Src/CSharpier.Core/PrinterOptions.cs +++ b/Src/CSharpier.Core/PrinterOptions.cs @@ -27,6 +27,8 @@ public int IndentSize public bool TrimInitialLines { get; init; } = true; public bool IncludeGenerated { get; set; } public Formatter Formatter { get; set; } = formatter; + public XmlWhitespaceSensitivity XmlWhitespaceSensitivity { get; set; } = + XmlWhitespaceSensitivity.Strict; public const int WidthUsedByTests = 100; diff --git a/Src/CSharpier.Core/XmlWhitespaceSensitivity.cs b/Src/CSharpier.Core/XmlWhitespaceSensitivity.cs new file mode 100644 index 000000000..829ddd777 --- /dev/null +++ b/Src/CSharpier.Core/XmlWhitespaceSensitivity.cs @@ -0,0 +1,8 @@ +namespace CSharpier.Core; + +internal enum XmlWhitespaceSensitivity +{ + Strict, + Xaml, + Ignore, +} diff --git a/Src/CSharpier.Tests/Cli/Options/EditorConfigFileParserTests.cs b/Src/CSharpier.Tests/Cli/Options/CSharpierConfigParserTests.cs similarity index 97% rename from Src/CSharpier.Tests/Cli/Options/EditorConfigFileParserTests.cs rename to Src/CSharpier.Tests/Cli/Options/CSharpierConfigParserTests.cs index a143191ed..6315eda64 100644 --- a/Src/CSharpier.Tests/Cli/Options/EditorConfigFileParserTests.cs +++ b/Src/CSharpier.Tests/Cli/Options/CSharpierConfigParserTests.cs @@ -4,7 +4,7 @@ namespace CSharpier.Tests.Cli.Options; -public class EditorConfigFileParserTests +public class CSharpierConfigParserTests { [Test] public void Should_Parse_Yaml_With_Overrides() diff --git a/Src/CSharpier.Tests/OptionsProviderTests.cs b/Src/CSharpier.Tests/OptionsProviderTests.cs index 23ca9259e..b7e946150 100644 --- a/Src/CSharpier.Tests/OptionsProviderTests.cs +++ b/Src/CSharpier.Tests/OptionsProviderTests.cs @@ -97,16 +97,20 @@ public async Task Should_Return_Json_Extension_Options() var context = new TestContext(); context.WhenAFileExists( "c:/test/.csharpierrc.json", - @"{ - ""printWidth"": 10, - ""endOfLine"": ""crlf"" -}" + """ +{ + "printWidth": 10, + "endOfLine": "crlf", + "xmlWhitespaceSensitivity": "xaml" +} +""" ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); result.Width.Should().Be(10); result.EndOfLine.Should().Be(EndOfLine.CRLF); + result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Xaml); } [Test] @@ -117,16 +121,18 @@ public async Task Should_Return_Yaml_Extension_Options(string extension) var context = new TestContext(); context.WhenAFileExists( $"c:/test/.csharpierrc.{extension}", - @" + """ printWidth: 10 endOfLine: crlf -" +xmlWhitespaceSensitivity: xaml +""" ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); result.Width.Should().Be(10); result.EndOfLine.Should().Be(EndOfLine.CRLF); + result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Xaml); } [Test] @@ -251,11 +257,12 @@ public async Task Should_Return_IndentSize_For_Override_With_Yaml() context.WhenAFileExists( "c:/test/.csharpierrc", """ - overrides: - - files: "*.{override,another}" - formatter: "csharp" - indentSize: 2 - """ +overrides: + - files: "*.{override,another}" + formatter: "csharp" + indentSize: 2 + xmlWhitespaceSensitivity: ignore +""" ); var result = await context.CreateProviderAndGetOptionsFor( @@ -264,6 +271,7 @@ public async Task Should_Return_IndentSize_For_Override_With_Yaml() ); result.IndentSize.Should().Be(2); + result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Ignore); } [Test] @@ -273,16 +281,17 @@ public async Task Should_Return_IndentSize_For_Override_With_Json() context.WhenAFileExists( "c:/test/.csharpierrc", """ - { - "overrides": [ - { - "files": "*.{override,another}", - "formatter": "csharp", - "indentSize": 2 - } - ] - } - """ +{ + "overrides": [ + { + "files": "*.{override,another}", + "formatter": "csharp", + "indentSize": 2, + "xmlWhitespaceSensitivity": "ignore" + } + ] +} +""" ); var result = await context.CreateProviderAndGetOptionsFor( @@ -291,6 +300,7 @@ public async Task Should_Return_IndentSize_For_Override_With_Json() ); result.IndentSize.Should().Be(2); + result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Ignore); } [Test] @@ -332,13 +342,15 @@ public async Task Should_Support_EditorConfig_Basic() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" -[*] -indent_style = space -indent_size = 2 -max_line_length = 10 -end_of_line = crlf -" + """ + + [*] + indent_style = space + indent_size = 2 + max_line_length = 10 + end_of_line = crlf + csharpier_xml_whitespace_sensitivity = xaml + """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); @@ -347,6 +359,7 @@ public async Task Should_Support_EditorConfig_Basic() result.IndentSize.Should().Be(2); result.Width.Should().Be(10); result.EndOfLine.Should().Be(EndOfLine.CRLF); + result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Xaml); } [Test] @@ -355,19 +368,21 @@ public async Task Should_Support_EditorConfig_With_Comments() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" -# EditorConfig is awesome: https://EditorConfig.org + """ -# top-most EditorConfig file -root = true + # EditorConfig is awesome: https://EditorConfig.org -[*] -indent_style = space -indent_size = 2 -max_line_length = 10 -; Windows-style line endings -end_of_line = crlf -" + # top-most EditorConfig file + root = true + + [*] + indent_style = space + indent_size = 2 + max_line_length = 10 + ; Windows-style line endings + end_of_line = crlf + + """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); @@ -384,13 +399,15 @@ public async Task Should_Support_EditorConfig_With_Duplicated_Sections() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" -[*] -indent_size = 2 + """ -[*] -indent_size = 4 -" + [*] + indent_size = 2 + + [*] + indent_size = 4 + + """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); @@ -404,11 +421,13 @@ public async Task Should_Support_EditorConfig_With_Duplicated_Rules() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" -[*] -indent_size = 2 -indent_size = 4 -" + """ + + [*] + indent_size = 2 + indent_size = 4 + + """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); @@ -422,10 +441,12 @@ public async Task Should_Not_Fail_With_Bad_EditorConfig() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" -[* -indent_size== -" + """ + + [* + indent_size== + + """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); @@ -441,11 +462,13 @@ public async Task Should_Support_EditorConfig_Tabs(string propertyName) var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - $@" - [*] - indent_style = tab - {propertyName} = 2 - " + $""" + + [*] + indent_style = tab + {propertyName} = 2 + + """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); @@ -460,12 +483,14 @@ public async Task Should_Support_EditorConfig_Tabs_With_Tab_Width() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" - [*] - indent_style = tab - indent_size = 1 - tab_width = 3 - " + """ + + [*] + indent_style = tab + indent_size = 1 + tab_width = 3 + + """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); @@ -480,11 +505,13 @@ public async Task Should_Support_EditorConfig_Tabs_With_Indent_Size_Tab() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" - [*] - indent_size = tab - tab_width = 3 - " + """ + + [*] + indent_size = tab + tab_width = 3 + + """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); @@ -498,19 +525,23 @@ public async Task Should_Support_EditorConfig_Tabs_With_Multiple_Files() var context = new TestContext(); context.WhenAFileExists( "c:/test/subfolder/.editorconfig", - @" - [*] - indent_size = 1 - " + """ + + [*] + indent_size = 1 + + """ ); context.WhenAFileExists( "c:/test/.editorconfig", - @" - [*] - indent_size = 2 - max_line_length = 10 - " + """ + + [*] + indent_size = 2 + max_line_length = 10 + + """ ); var result = await context.CreateProviderAndGetOptionsFor( @@ -527,18 +558,22 @@ public async Task Should_Support_EditorConfig_Tabs_With_Multiple_Files_And_Unset var context = new TestContext(); context.WhenAFileExists( "c:/test/subfolder/.editorconfig", - @" - [*] - indent_size = unset - " + """ + + [*] + indent_size = unset + + """ ); context.WhenAFileExists( "c:/test/.editorconfig", - @" - [*] - indent_size = 2 - " + """ + + [*] + indent_size = 2 + + """ ); var result = await context.CreateProviderAndGetOptionsFor( @@ -554,20 +589,24 @@ public async Task Should_Support_EditorConfig_Root() var context = new TestContext(); context.WhenAFileExists( "c:/test/subfolder/.editorconfig", - @" - root = true + """ - [*] - indent_size = 2 - " + root = true + + [*] + indent_size = 2 + + """ ); context.WhenAFileExists( "c:/test/.editorconfig", - @" - [*] - max_line_length = 2 - " + """ + + [*] + max_line_length = 2 + + """ ); var result = await context.CreateProviderAndGetOptionsFor( @@ -583,13 +622,15 @@ public async Task Should_Support_EditorConfig_Tabs_With_Globs() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" -[*] -indent_size = 1 + """ + + [*] + indent_size = 1 + + [*.cs] + indent_size = 2 -[*.cs] -indent_size = 2 -" + """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); @@ -602,13 +643,15 @@ public async Task Should_Support_EditorConfig_Tabs_With_Glob_Braces() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" -[*] -indent_size = 1 + """ + + [*] + indent_size = 1 -[*.{cs}] -indent_size = 2 -" + [*.{cs}] + indent_size = 2 + + """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); @@ -621,13 +664,15 @@ public async Task Should_Support_EditorConfig_Tabs_With_Glob_Braces_Multiples() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" -[*] -indent_size = 1 + """ + + [*] + indent_size = 1 + + [*.{csx,cs}] + indent_size = 2 -[*.{csx,cs}] -indent_size = 2 -" + """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); @@ -635,7 +680,7 @@ public async Task Should_Support_EditorConfig_Tabs_With_Glob_Braces_Multiples() } [Test] - public async Task Should_Return_IndentSize_For_Formatter_In_Editorconfig() + public async Task Should_Return_Overrides_In_Editorconfig() { var context = new TestContext(); context.WhenAFileExists( @@ -644,12 +689,14 @@ public async Task Should_Return_IndentSize_For_Formatter_In_Editorconfig() [*.cst] indent_size = 2 csharpier_formatter = csharp + csharpier_xml_whitespace_sensitivity = ignore """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cst"); result.IndentSize.Should().Be(2); + result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Ignore); } [Test] @@ -658,10 +705,12 @@ public async Task Should_Find_EditorConfig_In_Parent_Directory() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" -[*.cs] -indent_size = 2 -" + """ + + [*.cs] + indent_size = 2 + + """ ); var result = await context.CreateProviderAndGetOptionsFor( @@ -677,10 +726,12 @@ public async Task Should_Prefer_CSharpierrc_In_SameFolder() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" -[*.cs] -indent_size = 2 -" + """ + + [*.cs] + indent_size = 2 + + """ ); context.WhenAFileExists("c:/test/.csharpierrc", "indentSize: 1"); @@ -694,10 +745,12 @@ public async Task Should_Not_Prefer_Closer_EditorConfig() var context = new TestContext(); context.WhenAFileExists( "c:/test/subfolder/.editorconfig", - @" -[*.cs] -indent_size = 2 -" + """ + + [*.cs] + indent_size = 2 + + """ ); context.WhenAFileExists("c:/test/.csharpierrc", "indentSize: 1"); @@ -714,11 +767,13 @@ public async Task Should_Ignore_Invalid_EditorConfig_Lines() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" -[*] -indent_size = 2 -INVALID -" + """ + + [*] + indent_size = 2 + INVALID + + """ ); var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); @@ -732,18 +787,22 @@ public async Task Should_Not_Ignore_Ignored_EditorConfig() var context = new TestContext(); context.WhenAFileExists( "c:/test/subfolder/.editorconfig", - @" - [*] - indent_size = 2 - " + """ + + [*] + indent_size = 2 + + """ ); context.WhenAFileExists( "c:/test/.editorconfig", - @" - [*] - indent_size = 1 - " + """ + + [*] + indent_size = 1 + + """ ); context.WhenAFileExists("c:/test/.csharpierignore", "/subfolder/.editorconfig"); @@ -761,10 +820,12 @@ public async Task Should_Prefer_Closer_CSharpierrc() var context = new TestContext(); context.WhenAFileExists( "c:/test/.editorconfig", - @" -[*.cs] -indent_size = 2 -" + """ + + [*.cs] + indent_size = 2 + + """ ); context.WhenAFileExists("c:/test/subfolder/.csharpierrc", "indentSize: 1"); @@ -789,6 +850,7 @@ private static void ShouldHaveDefaultXmlOptions(PrinterOptions printerOptions) printerOptions.IndentSize.Should().Be(2); printerOptions.UseTabs.Should().BeFalse(); printerOptions.EndOfLine.Should().Be(EndOfLine.Auto); + printerOptions.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Strict); } private sealed class TestContext From 6a952cfaf40fdfa2dee5cdf6a65c51f1abb4fd13 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Thu, 1 Jan 2026 15:39:05 -0600 Subject: [PATCH 09/27] Implement global setting for run on save (#1798) closes #1739 --- .github/workflows/ValidatePullRequest.yml | 8 +++ .../ClientApp/package.json | 1 + Src/CSharpier.Rider/CHANGELOG.md | 3 ++ Src/CSharpier.Rider/gradle.properties | 2 +- Src/CSharpier.Rider/package.json | 15 +++--- .../csharpier/CSharpierGlobalSettings.java | 40 +++++++++++++++ .../csharpier/CSharpierProcessProvider.java | 10 ++-- .../csharpier/CSharpierProcessServer.java | 29 ++++++----- .../intellij/csharpier/CSharpierSettings.java | 6 +-- .../csharpier/CSharpierSettingsComponent.java | 49 +++++++++++++++++-- .../ReformatWithCSharpierOnSaveListener.java | 6 ++- .../src/main/resources/META-INF/plugin.xml | 1 + 12 files changed, 137 insertions(+), 33 deletions(-) create mode 100644 Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierGlobalSettings.java diff --git a/.github/workflows/ValidatePullRequest.yml b/.github/workflows/ValidatePullRequest.yml index 0bec7f322..0cc352022 100644 --- a/.github/workflows/ValidatePullRequest.yml +++ b/.github/workflows/ValidatePullRequest.yml @@ -36,6 +36,14 @@ jobs: rm -r -f ./Tests/CSharpier.MsBuild.Test dotnet tool restore dotnet csharpier check . + cd ./Src/CSharpier.Rider + npm install + npm run prettier:check + cd .. + cd ./CSharpier.Playground/ClientApp + npm install + npm run prettier:check + check_todos: runs-on: ubuntu-latest name: Check TODOs diff --git a/Src/CSharpier.Playground/ClientApp/package.json b/Src/CSharpier.Playground/ClientApp/package.json index 6c928b5ce..7dbda4a8e 100644 --- a/Src/CSharpier.Playground/ClientApp/package.json +++ b/Src/CSharpier.Playground/ClientApp/package.json @@ -27,6 +27,7 @@ "scripts": { "build": "tsc && vite build", "prettier": "prettier --parser typescript ./src/**/*.{ts,tsx} --write", + "prettier:check": "prettier --parser typescript ./src/**/*.{ts,tsx} --check", "start": "echo Starting the development server && vite" }, "browserslist": { diff --git a/Src/CSharpier.Rider/CHANGELOG.md b/Src/CSharpier.Rider/CHANGELOG.md index 38ad45c45..5794c5800 100644 --- a/Src/CSharpier.Rider/CHANGELOG.md +++ b/Src/CSharpier.Rider/CHANGELOG.md @@ -2,6 +2,9 @@ # csharpier-rider Changelog +## [2.3.0] +- Add a global format on save option + ## [2.2.4] - Fix Stream closed exception in Rider plugin diff --git a/Src/CSharpier.Rider/gradle.properties b/Src/CSharpier.Rider/gradle.properties index d4f4ea317..6fee35ac7 100644 --- a/Src/CSharpier.Rider/gradle.properties +++ b/Src/CSharpier.Rider/gradle.properties @@ -1,4 +1,4 @@ -pluginVersion = 2.2.4 +pluginVersion = 2.3.0 # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 222 diff --git a/Src/CSharpier.Rider/package.json b/Src/CSharpier.Rider/package.json index e34b8cc46..60365126c 100644 --- a/Src/CSharpier.Rider/package.json +++ b/Src/CSharpier.Rider/package.json @@ -1,9 +1,10 @@ { - "scripts": { - "prettier": "prettier ./**/*.java --write" - }, - "devDependencies": { - "prettier": "^3.4.2", - "prettier-plugin-java": "^2.6.4" - } + "scripts": { + "prettier": "prettier ./**/*.java --write", + "prettier:check": "prettier ./**/*.java --check" + }, + "devDependencies": { + "prettier": "^3.4.2", + "prettier-plugin-java": "^2.6.4" + } } diff --git a/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierGlobalSettings.java b/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierGlobalSettings.java new file mode 100644 index 000000000..b1a5bfec2 --- /dev/null +++ b/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierGlobalSettings.java @@ -0,0 +1,40 @@ +package com.intellij.csharpier; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.util.xmlb.XmlSerializerUtil; +import org.jetbrains.annotations.NotNull; + +@State( + name = "com.intellij.csharpier.global", + storages = @Storage("$APP_CONFIG$/CSharpierPlugin.xml") +) +public class CSharpierGlobalSettings implements PersistentStateComponent { + + @NotNull + public static CSharpierGlobalSettings getInstance() { + return ApplicationManager.getApplication().getService(CSharpierGlobalSettings.class); + } + + private boolean runOnSave; + + public boolean getRunOnSave() { + return this.runOnSave; + } + + public void setRunOnSave(boolean value) { + this.runOnSave = value; + } + + @Override + public CSharpierGlobalSettings getState() { + return this; + } + + @Override + public void loadState(@NotNull CSharpierGlobalSettings state) { + XmlSerializerUtil.copyBean(state, this); + } +} diff --git a/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierProcessProvider.java b/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierProcessProvider.java index c917bac16..70019ff11 100644 --- a/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierProcessProvider.java +++ b/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierProcessProvider.java @@ -207,11 +207,15 @@ private String findVersionInCsProjOfParentsDirectories(String directoryThatConta private String findVersionInCsProj(Path currentDirectory) { this.logger.debug("Looking for " + currentDirectory + "/*.csproj"); - File[] csProjFiles = currentDirectory.toFile() + File[] csProjFiles = currentDirectory + .toFile() .listFiles((dir, name) -> name.toLowerCase().endsWith(".csproj")); if (csProjFiles == null) { - this.logger.debug("Unable to list files in directory: " + currentDirectory + - " (directory may not exist, not be a directory, or be temporarily inaccessible)"); + this.logger.debug( + "Unable to list files in directory: " + + currentDirectory + + " (directory may not exist, not be a directory, or be temporarily inaccessible)" + ); return null; } for (var pathToCsProj : csProjFiles) { diff --git a/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierProcessServer.java b/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierProcessServer.java index 9c1bb2869..d369425ff 100644 --- a/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierProcessServer.java +++ b/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierProcessServer.java @@ -51,20 +51,23 @@ private void startProcess() { var csharpierProcess = processBuilder.start(); var errorOutput = new StringBuilder(); - var stderrThread = new Thread(() -> { - try ( - var errorReader = new BufferedReader( - new InputStreamReader(csharpierProcess.getErrorStream()) - ) - ) { - String line; - while ((line = errorReader.readLine()) != null) { - errorOutput.append(line).append("\n"); + var stderrThread = new Thread( + () -> { + try ( + var errorReader = new BufferedReader( + new InputStreamReader(csharpierProcess.getErrorStream()) + ) + ) { + String line; + while ((line = errorReader.readLine()) != null) { + errorOutput.append(line).append("\n"); + } + } catch (IOException e) { + // Stream closed or process terminated - expected behavior } - } catch (IOException e) { - // Stream closed or process terminated - expected behavior - } - }, "CSharpier stderr reader"); + }, + "CSharpier stderr reader" + ); stderrThread.start(); var stdoutThread = new Thread(() -> { diff --git a/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierSettings.java b/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierSettings.java index 2e49228c1..d0a400072 100644 --- a/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierSettings.java +++ b/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierSettings.java @@ -15,13 +15,13 @@ static CSharpierSettings getInstance(@NotNull Project project) { return project.getService(CSharpierSettings.class); } - private boolean runOnSave; + private Boolean runOnSave; - public boolean getRunOnSave() { + public Boolean getRunOnSave() { return this.runOnSave; } - public void setRunOnSave(boolean value) { + public void setRunOnSave(Boolean value) { this.runOnSave = value; } diff --git a/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierSettingsComponent.java b/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierSettingsComponent.java index 8725fb3ef..825021235 100644 --- a/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierSettingsComponent.java +++ b/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/CSharpierSettingsComponent.java @@ -2,6 +2,7 @@ import com.intellij.openapi.options.SearchableConfigurable; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBTextField; @@ -14,8 +15,14 @@ public class CSharpierSettingsComponent implements SearchableConfigurable { + private final ComboItem[] runOnSaveItems = { + new ComboItem(null, "Use Global Setting"), + new ComboItem(true, "True"), + new ComboItem(false, "False"), + }; private final Project project; - private JBCheckBox runOnSaveCheckBox = new JBCheckBox("Run on Save"); + private ComboBox solutionRunOnSaveComboBox = new ComboBox<>(runOnSaveItems); + private JBCheckBox globalRunOnSaveCheckBox = new JBCheckBox("Run on Save (Global)"); private JBCheckBox disableCSharpierServerCheckBox = new JBCheckBox("Disable CSharpier Server"); private JBCheckBox useCustomPath = new JBCheckBox("Override CSharpier Executable"); private JBTextField customPathTextField = new JBTextField(); @@ -66,7 +73,13 @@ private JComponent createSectionHeader(String label) { return FormBuilder.createFormBuilder() .addComponent(createSectionHeader("General Settings")) .setFormLeftIndent(leftIndent) - .addComponent(this.runOnSaveCheckBox, topInset) + .addLabeledComponent( + new JBLabel("Run on save (Solution):"), + this.solutionRunOnSaveComboBox, + topInset, + false + ) + .addComponent(this.globalRunOnSaveCheckBox, topInset) .setFormLeftIndent(0) .addComponent(createSectionHeader("Developer Settings"), 20) .setFormLeftIndent(leftIndent) @@ -82,11 +95,15 @@ private JComponent createSectionHeader(String label) { .getPanel(); } + private Boolean getSelectedSolutionRunOnSave() { + return ((ComboItem) this.solutionRunOnSaveComboBox.getSelectedItem()).value; + } + @Override public boolean isModified() { return ( CSharpierSettings.getInstance(this.project).getRunOnSave() != - this.runOnSaveCheckBox.isSelected() || + this.getSelectedSolutionRunOnSave() || CSharpierSettings.getInstance(this.project).getCustomPath() != this.customPathTextField.getText() || CSharpierSettings.getInstance(this.project).getUseCustomPath() != @@ -100,18 +117,40 @@ public boolean isModified() { public void apply() { var settings = CSharpierSettings.getInstance(this.project); - settings.setRunOnSave(this.runOnSaveCheckBox.isSelected()); + settings.setRunOnSave(this.getSelectedSolutionRunOnSave()); settings.setCustomPath(this.customPathTextField.getText()); settings.setDisableCSharpierServer(this.disableCSharpierServerCheckBox.isSelected()); settings.setUseCustomPath(this.useCustomPath.isSelected()); + + CSharpierGlobalSettings.getInstance() + .setRunOnSave(this.globalRunOnSaveCheckBox.isSelected()); } @Override public void reset() { var settings = CSharpierSettings.getInstance(this.project); - this.runOnSaveCheckBox.setSelected(settings.getRunOnSave()); + + var index = -1; + for (var i = 0; i < runOnSaveItems.length; i++) { + if (runOnSaveItems[i].value == settings.getRunOnSave()) { + index = i; + break; + } + } + + this.solutionRunOnSaveComboBox.setSelectedIndex(index); + this.globalRunOnSaveCheckBox.setSelected( + CSharpierGlobalSettings.getInstance().getRunOnSave() + ); this.useCustomPath.setSelected(settings.getUseCustomPath()); this.customPathTextField.setText(settings.getCustomPath()); this.disableCSharpierServerCheckBox.setSelected(settings.getDisableCSharpierServer()); } + + public record ComboItem(Boolean value, String label) { + @Override + public String toString() { + return label; + } + } } diff --git a/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/ReformatWithCSharpierOnSaveListener.java b/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/ReformatWithCSharpierOnSaveListener.java index 44bf333ac..660b9ca38 100644 --- a/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/ReformatWithCSharpierOnSaveListener.java +++ b/Src/CSharpier.Rider/src/main/java/com/intellij/csharpier/ReformatWithCSharpierOnSaveListener.java @@ -20,7 +20,11 @@ public void beforeDocumentSaving(@NotNull Document document) { } var cSharpierSettings = CSharpierSettings.getInstance(project); - if (!cSharpierSettings.getRunOnSave()) { + var shouldRunOnSave = + Boolean.TRUE.equals(cSharpierSettings.getRunOnSave()) || + (cSharpierSettings.getRunOnSave() == null && + CSharpierGlobalSettings.getInstance().getRunOnSave()); + if (!shouldRunOnSave) { return; } diff --git a/Src/CSharpier.Rider/src/main/resources/META-INF/plugin.xml b/Src/CSharpier.Rider/src/main/resources/META-INF/plugin.xml index c2d559fac..6ac8835c1 100644 --- a/Src/CSharpier.Rider/src/main/resources/META-INF/plugin.xml +++ b/Src/CSharpier.Rider/src/main/resources/META-INF/plugin.xml @@ -5,6 +5,7 @@ belav com.intellij.modules.rider + From c8358b4e97acff04b4503328a024b803de49c7cf Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Mon, 19 Jan 2026 09:19:07 -0600 Subject: [PATCH 10/27] Update bug report template with playground link Added a reminder for users to recreate formatting issues on the playground. --- .github/ISSUE_TEMPLATE/bug.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 3811b066d..29c2021df 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -11,6 +11,7 @@ body: **Before submitting**, please check: - I've searched [existing issues](https://github.com/belav/csharpier/issues) to ensure this isn't a duplicate - I've read the [documentation](https://csharpier.com/) and this isn't expected behavior + - For formatting issues I have recreated the issue on the [playground](https://playground.csharpier.com/) - I'm using the latest version of CSharpier ![CSharpier](https://img.shields.io/nuget/v/CSharpier) - I'm using the latest version of CSharpier's IDE plugin and understand it is versioned independently of CSharpier From 52f7f6f20c87f53c9b89ee6a0bfac8e39f19cac7 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Mon, 19 Jan 2026 12:01:05 -0600 Subject: [PATCH 11/27] Add axaml to VS. Bump up version number on all plugins to try to help with people not realizing they are versioned differently. --- .github/ISSUE_TEMPLATE/bug.yml | 2 ++ Src/CSharpier.Rider/CHANGELOG.md | 2 ++ Src/CSharpier.Rider/gradle.properties | 2 +- Src/CSharpier.VSCode/CHANGELOG.md | 3 +++ Src/CSharpier.VSCode/package.json | 2 +- .../CSharpier.VisualStudio/CSharpierProcessProvider.cs | 2 -- .../CSharpier.VisualStudio/FormattingService.cs | 2 +- .../CSharpier.VisualStudio/source.extension.vsixmanifest | 2 +- Src/CSharpier.VisualStudio/ChangeLog.md | 5 ++++- 9 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 29c2021df..a4f9023f9 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -13,7 +13,9 @@ body: - I've read the [documentation](https://csharpier.com/) and this isn't expected behavior - For formatting issues I have recreated the issue on the [playground](https://playground.csharpier.com/) - I'm using the latest version of CSharpier ![CSharpier](https://img.shields.io/nuget/v/CSharpier) + -- `< 10.0.0` - I'm using the latest version of CSharpier's IDE plugin and understand it is versioned independently of CSharpier + -- `>= 10.0.0` - type: textarea id: description diff --git a/Src/CSharpier.Rider/CHANGELOG.md b/Src/CSharpier.Rider/CHANGELOG.md index 5794c5800..df8e788f5 100644 --- a/Src/CSharpier.Rider/CHANGELOG.md +++ b/Src/CSharpier.Rider/CHANGELOG.md @@ -1,6 +1,8 @@ # csharpier-rider Changelog +## 10.0.0 +- Make version a number that won't conflict with CSharpier itself. ## [2.3.0] - Add a global format on save option diff --git a/Src/CSharpier.Rider/gradle.properties b/Src/CSharpier.Rider/gradle.properties index 6fee35ac7..daef1d1d5 100644 --- a/Src/CSharpier.Rider/gradle.properties +++ b/Src/CSharpier.Rider/gradle.properties @@ -1,4 +1,4 @@ -pluginVersion = 2.3.0 +pluginVersion = 10.0.0 # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 222 diff --git a/Src/CSharpier.VSCode/CHANGELOG.md b/Src/CSharpier.VSCode/CHANGELOG.md index 52604319b..578a45dda 100644 --- a/Src/CSharpier.VSCode/CHANGELOG.md +++ b/Src/CSharpier.VSCode/CHANGELOG.md @@ -1,3 +1,6 @@ +## 10.0.0 +- Make version a number that won't conflict with CSharpier itself. + ## 2.1.0 - Improved support for users that work with multiple dotnet architectures by splitting out installs of csharpier based on the architecture they were installed with. - Improve formatting of documents to avoid scroll jumping around diff --git a/Src/CSharpier.VSCode/package.json b/Src/CSharpier.VSCode/package.json index 6337cdc27..08843f5b7 100644 --- a/Src/CSharpier.VSCode/package.json +++ b/Src/CSharpier.VSCode/package.json @@ -2,7 +2,7 @@ "name": "csharpier-vscode", "displayName": "CSharpier - Code formatter", "description": "Code formatter using csharpier", - "version": "2.1.0", + "version": "10.0.0", "publisher": "csharpier", "author": "CSharpier", "homepage": "https://marketplace.visualstudio.com/items?itemName=csharpier.csharpier-vscode", diff --git a/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/CSharpierProcessProvider.cs b/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/CSharpierProcessProvider.cs index 657d5ca90..23aeb8d6c 100644 --- a/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/CSharpierProcessProvider.cs +++ b/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/CSharpierProcessProvider.cs @@ -8,8 +8,6 @@ namespace CSharpier.VisualStudio { - using NuGet.Versioning; - public class CSharpierProcessProvider : IProcessKiller { private readonly CustomPathInstaller customPathInstaller; diff --git a/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/FormattingService.cs b/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/FormattingService.cs index 2bed0afa1..a01863b4a 100644 --- a/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/FormattingService.cs +++ b/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/FormattingService.cs @@ -26,7 +26,7 @@ public static bool IsSupportedLanguage(string language) // if new languages are added, follow this guide to update VSCT // https://learn.microsoft.com/en-us/visualstudio/extensibility/walkthrough-creating-a-view-adornment-commands-and-settings-column-guides?view=vs-2022 // at this point it will probably just be adding new command placements + guidSymbols like the xaml one - return language is "CSharp" or "XML" or "XAML"; + return language is "CSharp" or "XML" or "XAML" or "AXAML"; } public void Format(Document document) diff --git a/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/source.extension.vsixmanifest b/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/source.extension.vsixmanifest index 48f3b8fff..9b0d79a42 100644 --- a/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/source.extension.vsixmanifest +++ b/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + CSharpier CSharpier is an opinionated code formatter for c#. It uses Roslyn to parse your code and re-prints it using its own rules. https://github.com/belav/csharpier diff --git a/Src/CSharpier.VisualStudio/ChangeLog.md b/Src/CSharpier.VisualStudio/ChangeLog.md index 19971e0fa..9041fe14b 100644 --- a/Src/CSharpier.VisualStudio/ChangeLog.md +++ b/Src/CSharpier.VisualStudio/ChangeLog.md @@ -1,4 +1,7 @@ -## [2.2.0] +## [10.0.1] +- Add right click format command for AXAML. Requires csharpier configuration for < 1.3.0 + +## [2.2.0] - Add right click format command for XAML. Ensure format on save also works ## [2.0.2] From e0d12d9268f5bcc0d02f5fba5991496f2ceb0683 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Mon, 19 Jan 2026 12:04:39 -0600 Subject: [PATCH 12/27] Fix formatting in bug issue template --- .github/ISSUE_TEMPLATE/bug.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index a4f9023f9..977581e2a 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -13,9 +13,9 @@ body: - I've read the [documentation](https://csharpier.com/) and this isn't expected behavior - For formatting issues I have recreated the issue on the [playground](https://playground.csharpier.com/) - I'm using the latest version of CSharpier ![CSharpier](https://img.shields.io/nuget/v/CSharpier) - -- `< 10.0.0` + - `< 10.0.0` - I'm using the latest version of CSharpier's IDE plugin and understand it is versioned independently of CSharpier - -- `>= 10.0.0` + - `>= 10.0.0` - type: textarea id: description From 2fe085f14b6e576a0dc834eac1b0d304c7a07f99 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Mon, 19 Jan 2026 12:05:07 -0600 Subject: [PATCH 13/27] Improve version check formatting in bug.yml Updated formatting for version checks in bug report template. --- .github/ISSUE_TEMPLATE/bug.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 977581e2a..cbcdd0b14 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -12,10 +12,10 @@ body: - I've searched [existing issues](https://github.com/belav/csharpier/issues) to ensure this isn't a duplicate - I've read the [documentation](https://csharpier.com/) and this isn't expected behavior - For formatting issues I have recreated the issue on the [playground](https://playground.csharpier.com/) - - I'm using the latest version of CSharpier ![CSharpier](https://img.shields.io/nuget/v/CSharpier) - - `< 10.0.0` - - I'm using the latest version of CSharpier's IDE plugin and understand it is versioned independently of CSharpier - - `>= 10.0.0` + - I'm using the latest version of CSharpier ![CSharpier](https://img.shields.io/nuget/v/CSharpier) + `< 10.0.0` + - I'm using the latest version of CSharpier's IDE plugin and understand it is versioned independently of CSharpier + `>= 10.0.0` - type: textarea id: description From 26f02bd3c6d7016a746eb8a39c37cba33f277700 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Mon, 19 Jan 2026 12:09:33 -0600 Subject: [PATCH 14/27] Revise bug report template for clarity and versioning Updated the bug report template to include checkboxes for user confirmation and added specific version numbers for CSharpier. --- .github/ISSUE_TEMPLATE/bug.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index cbcdd0b14..a23506577 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -9,13 +9,12 @@ body: Thanks for taking the time to report a bug! Please fill out the information below to help us investigate. **Before submitting**, please check: - - I've searched [existing issues](https://github.com/belav/csharpier/issues) to ensure this isn't a duplicate - - I've read the [documentation](https://csharpier.com/) and this isn't expected behavior - - For formatting issues I have recreated the issue on the [playground](https://playground.csharpier.com/) - - I'm using the latest version of CSharpier ![CSharpier](https://img.shields.io/nuget/v/CSharpier) - `< 10.0.0` - - I'm using the latest version of CSharpier's IDE plugin and understand it is versioned independently of CSharpier - `>= 10.0.0` + - [ ] I've searched [existing issues](https://github.com/belav/csharpier/issues) to ensure this isn't a duplicate + - [ ] I've read the [documentation](https://csharpier.com/) and this isn't expected behavior + - [ ] For formatting issues I have recreated the issue on the [playground](https://playground.csharpier.com/) + - [ ] I'm using the latest version of CSharpier `1.2.5` + - [ ] I'm using the latest version of my IDE's CSharpier plugin `>= 10.0.0` + - type: textarea id: description From 4c5aa5e5c3146674d58a9a651bbe317f2c29e6d1 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Mon, 19 Jan 2026 12:12:32 -0600 Subject: [PATCH 15/27] Make sure we update bug template when we release --- Shell/Release.psm1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Shell/Release.psm1 b/Shell/Release.psm1 index 89f109912..7d0ba40b1 100644 --- a/Shell/Release.psm1 +++ b/Shell/Release.psm1 @@ -25,6 +25,11 @@ function CSH-Release { Set-Content -Encoding UTF8 -Path $changeLogPath -Value ($changeLog + $changeLogValue) + $bugFilePath = $PSScriptRoot + "/../.github/ISSUE_TEMPLATE/bug.yml" + $replacementText = "- [ ] I'm using the latest version of CSharpier ``$versionNumber``" + (Get-Content $bugFilePath) -replace '- \[ \] I''m using the latest version of CSharpier `\d+(\.\d+)*`', $replacementText | + Set-Content -Encoding UTF8 $bugFilePath + foreach ($file in Get-ChildItem ($PSScriptRoot + "/../docs") -Filter "*.md") { Copy-Item $file.FullName ($PSScriptRoot + "/../Src/Website/docs/" + $file.Name) From 544934f23502735aeb11bdbb6753fa773de6ba4f Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Sat, 31 Jan 2026 15:26:01 -0600 Subject: [PATCH 16/27] formatting + updating rider thing --- Src/CSharpier.Rider/CHANGELOG.md | 3 + Src/CSharpier.Rider/gradle.properties | 2 +- Src/CSharpier.Tests/Cli/IgnoreFileTests.cs | 254 ++++++++++----------- 3 files changed, 131 insertions(+), 128 deletions(-) diff --git a/Src/CSharpier.Rider/CHANGELOG.md b/Src/CSharpier.Rider/CHANGELOG.md index df8e788f5..92f1fe0c6 100644 --- a/Src/CSharpier.Rider/CHANGELOG.md +++ b/Src/CSharpier.Rider/CHANGELOG.md @@ -1,6 +1,9 @@ # csharpier-rider Changelog +## [10.0.1] +- Re-add a global format on save option + ## 10.0.0 - Make version a number that won't conflict with CSharpier itself. diff --git a/Src/CSharpier.Rider/gradle.properties b/Src/CSharpier.Rider/gradle.properties index daef1d1d5..e23e1209a 100644 --- a/Src/CSharpier.Rider/gradle.properties +++ b/Src/CSharpier.Rider/gradle.properties @@ -1,4 +1,4 @@ -pluginVersion = 10.0.0 +pluginVersion = 10.0.1 # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 222 diff --git a/Src/CSharpier.Tests/Cli/IgnoreFileTests.cs b/Src/CSharpier.Tests/Cli/IgnoreFileTests.cs index 7398711fe..1bf6af161 100644 --- a/Src/CSharpier.Tests/Cli/IgnoreFileTests.cs +++ b/Src/CSharpier.Tests/Cli/IgnoreFileTests.cs @@ -28,10 +28,10 @@ public void EmptyLines() { this.GitBasedTest( """ -# empty lines, nothing is ignored + # empty lines, nothing is ignored -""", + """, ["foo", "bar"] ); } @@ -41,9 +41,9 @@ public void TrailingWhitespaces() { this.GitBasedTest( """ -# trailing whitespaces handled -foo -""", + # trailing whitespaces handled + foo + """, ["foo", "bar"] ); } @@ -53,9 +53,9 @@ public void SimpleIgnore() { this.GitBasedTest( """ -# exclude foo -foo -""", + # exclude foo + foo + """, ["foo", "bar/foo", "foob/bar", "src/foo/bar", "src/foo/", "fooc"] ); } @@ -65,8 +65,8 @@ public void SimpleIgnore_WithFileInSubdirectory() { this.GitBasedTest( """ -bar.txt -""", + bar.txt + """, ["src/foo/bar.txt", "foo/bar.txt", "bar.txt"] ); } @@ -76,9 +76,9 @@ public void SimpleIgnore_WithSubdirs() { this.GitBasedTest( """ -# exclude foo/bar -foo/bar -""", + # exclude foo/bar + foo/bar + """, [ "src/foo/bar", "foo/bar/", @@ -95,8 +95,8 @@ public void SimpleIgnore_Dir() { this.GitBasedTest( """ -foo/ -""", + foo/ + """, ["foo/bar", "bar/foo", "foo/har", "tar/foo/bar", "tar/bar/foo"] ); } @@ -106,8 +106,8 @@ public void SimpleIgnore_Prefix() { this.GitBasedTest( """ -foo -""", + foo + """, ["src/testfoo", "testfoo", "testfoox", "bar/foo", "bar/tfoo"] ); } @@ -117,8 +117,8 @@ public void SimpleIgnore_Dotfiles() { this.GitBasedTest( """ -.foo/ -""", + .foo/ + """, [".foo/bar", ".bar/foo", ".foo/har", "tar/.foo/bar", "tar/bar/.foo"] ); } @@ -128,8 +128,8 @@ public void SimpleIgnore_Dotfiles_WithStar() { this.GitBasedTest( """ -.foo/* -""", + .foo/* + """, [".foo/bar", ".foo/.foo/bar", ".foo/har"] ); } @@ -139,8 +139,8 @@ public void SimpleIgnore_Dotfiles_WithStar2() { this.GitBasedTest( """ -*.mm.* -""", + *.mm.* + """, ["file.mm", "commonFile.txt"] ); } @@ -150,8 +150,8 @@ public void IgnoreDotFiles() { this.GitBasedTest( """ -.* -""", + .* + """, [ ".dotfile", "no.dotfile", @@ -170,8 +170,8 @@ public void StartsWithStar() { this.GitBasedTest( """ -*.cs -""", + *.cs + """, ["foo.cs", "foo/bar/foo.cs", "foo/bar/bar.csproj"] ); } @@ -181,8 +181,8 @@ public void StartsWithStar_Negated() { this.GitBasedTest( """ -!*.cs -""", + !*.cs + """, ["foo.cs", "foo/bar/foo.cs", "foo/bar/bar.csproj"] ); } @@ -192,8 +192,8 @@ public void StartsWithStar_LeadingSlash() { this.GitBasedTest( """ -/*.cs -""", + /*.cs + """, ["foo.cs", "foo/bar/foo.cs", "foo/bar/bar.csproj"] ); } @@ -203,8 +203,8 @@ public void SubdirStartsWithStar() { this.GitBasedTest( """ -foo/*.cs -""", + foo/*.cs + """, ["foo.cs", "foo/bar/foo.cs", "foo/foo.cs", "foo/bar/bar.csproj"] ); } @@ -214,8 +214,8 @@ public void TrailingStar() { this.GitBasedTest( """ -foo* -""", + foo* + """, ["fooc", "foo/bar/foo", "foo/foob.cs", "foo/bar/bar.csproj", "bar/foo"] ); } @@ -225,8 +225,8 @@ public void EscapedBang() { this.GitBasedTest( """ -\!.foo/* -""", + \!.foo/* + """, ["!.foo/bar", ".foo/.foo/bar", ".foo/har"] ); } @@ -236,8 +236,8 @@ public void EscapedHash() { this.GitBasedTest( """ -\#.foo/* -""", + \#.foo/* + """, ["!#foo/bar", ".foo/.foo/bar", "#.foo/har"] ); } @@ -247,9 +247,9 @@ public void SingleStar() { this.GitBasedTest( """ -# * ignores everything -* -""", + # * ignores everything + * + """, ["foo", "bar"] ); } @@ -259,8 +259,8 @@ public void LiteralPlus() { this.GitBasedTest( """ -*+ -""", + *+ + """, ["foo", "bar+", "foo+bar"] ); } @@ -270,9 +270,9 @@ public void MiddleStar() { this.GitBasedTest( """ -# intermediate * -fo*b -""", + # intermediate * + fo*b + """, ["foobar", "bar", "foob"] ); } @@ -282,10 +282,10 @@ public void LeadingSlash() { this.GitBasedTest( """ -# leading slash -/fo*b -/bar -""", + # leading slash + /fo*b + /bar + """, ["foobar", "bar", "foob"] ); } @@ -295,9 +295,9 @@ public void EscapedSpaces() { this.GitBasedTest( """ -# escaped spaces -/fo\ b -""", + # escaped spaces + /fo\ b + """, ["foobar", "bar", "fo b", "spacebar"] ); } @@ -307,8 +307,8 @@ public void EscapedSpaces2() { this.GitBasedTest( """ -/fo\ b/bar -""", + /fo\ b/bar + """, ["fo b/bar"] ); } @@ -318,9 +318,9 @@ public void QuestionMark() { this.GitBasedTest( """ -# ? -foo? -""", + # ? + foo? + """, ["foob", "foo"] ); } @@ -330,9 +330,9 @@ public void LeadingDoubleStar() { this.GitBasedTest( """ -# leading ** -**/foo -""", + # leading ** + **/foo + """, ["src/foo", "foo/bar", "src/bar/foo"] ); } @@ -342,9 +342,9 @@ public void LeadingDoubleStar2() { this.GitBasedTest( """ -# leading ** -**foo.txt -""", + # leading ** + **foo.txt + """, ["src/foo.txt", "foo/bar/foo.txt", "foo.txt", "foo.bar"] ); } @@ -354,9 +354,9 @@ public void LeadingDoubleStar3() { this.GitBasedTest( """ -/**/foo.json -/**/*_generated.csproj -""", + /**/foo.json + /**/*_generated.csproj + """, [ "foo.json", "bar/foo.json", @@ -375,9 +375,9 @@ public void MiddleDoubleStar() { this.GitBasedTest( """ -# middle ** -foo/**/ -""", + # middle ** + foo/**/ + """, ["src/foo/bar/char", "src/foo/tar", "src/foo/har/char/tar/har"] ); } @@ -387,9 +387,9 @@ public void MiddleDoubleStar_2() { this.GitBasedTest( """ -# middle ** -foo/**/bar -""", + # middle ** + foo/**/bar + """, [ "foo/bar", "foo/tar/bar", @@ -405,9 +405,9 @@ public void MiddleDoubleStar_3() { this.GitBasedTest( """ -# middle ** -foo/**.ps -""", + # middle ** + foo/**.ps + """, ["foo/bar/tar.ps", "foo/bar.ps", "foo/bar.js", "foo.ps"] ); } @@ -417,9 +417,9 @@ public void MiddleDoubleStar_Complex() { this.GitBasedTest( """ -# middle ** -foo/**/**/bar -""", + # middle ** + foo/**/**/bar + """, ["foo/bar", "src/foo/tar/bar", "foo/har/char/tar/bar", "foo/tar/bar", "foobar"] ); } @@ -429,9 +429,9 @@ public void MiddleDoubleStar_Complex2() { this.GitBasedTest( """ -# middle ** -**/test/**/*.json -""", + # middle ** + **/test/**/*.json + """, ["foo/test/unit/bar/car.json", "foo/test/tar.json", "src/foo/tar/car.json"] ); } @@ -441,9 +441,9 @@ public void TrailingDoubleStar() { this.GitBasedTest( """ -# trailing ** -foo/** -""", + # trailing ** + foo/** + """, ["foo/bar", "src/foo/tar/bar", "foo/har/char/tar/bar", "foo/tar/bar", "foobar"] ); } @@ -453,9 +453,9 @@ public void TrailingDoubleStar_2() { this.GitBasedTest( """ -# trailing ** -src/foo/** -""", + # trailing ** + src/foo/** + """, [ "src/foo/bar", "src/foo/tar/bar", @@ -472,9 +472,9 @@ public void MiddleSlash() { this.GitBasedTest( """ -# trailing ** -src/foo -""", + # trailing ** + src/foo + """, ["src/foo/bar", "foo/src/foo"] ); } @@ -484,9 +484,9 @@ public void TrailingSlash() { this.GitBasedTest( """ -# trailing ** -src/foo/ -""", + # trailing ** + src/foo/ + """, ["src/foo"] ); } @@ -496,9 +496,9 @@ public void TrailingSlash_2() { this.GitBasedTest( """ -# trailing ** -src/foo/ -""", + # trailing ** + src/foo/ + """, ["src/foo/", "foo/xy.txt", "src/foo/bar", "tar/src/foo/bar"] ); } @@ -508,9 +508,9 @@ public void TrailingSlash_3() { this.GitBasedTest( """ -# trailing ** -src/ -""", + # trailing ** + src/ + """, ["src/foo", "foo/src/bar", "mysrc/foo"] ); } @@ -520,12 +520,12 @@ public void TrailingSlash_4() { this.GitBasedTest( """ -[Bb]in/ -[Oo]bj/ -[Oo]ut/ -[Ll]og/ -[Ll]ogs/foo -""", + [Bb]in/ + [Oo]bj/ + [Oo]ut/ + [Ll]og/ + [Ll]ogs/foo + """, ["foo/bar", "WpfObj/bar", "MyLog/foo", "Logs/foo", "src/Logs/foo/bar"] ); } @@ -535,9 +535,9 @@ public void NoopNegate() { this.GitBasedTest( """ -# negate -!foo -""", + # negate + !foo + """, ["foo", "bar", "src/foo/tar", "har/foo", "src/bar/foo", "har/bar/foo/tar"] ); } @@ -547,10 +547,10 @@ public void SimpleNegate() { this.GitBasedTest( """ -# negate -foo -!foo -""", + # negate + foo + !foo + """, ["foo", "bar"] ); } @@ -560,10 +560,10 @@ public void SimpleNegate_2() { this.GitBasedTest( """ -# negate -foo -!foo -""", + # negate + foo + !foo + """, ["foo", "bar", "src/foo", "src/bar/foo"] ); } @@ -573,12 +573,12 @@ public void ComplexNegate() { this.GitBasedTest( """ -# negate -/* -!/foo -/foo/* -!/foo/bar -""", + # negate + /* + !/foo + /foo/* + !/foo/bar + """, ["foo/bar", "bar", "src/foo", "src/bar/foo/bar"] ); } @@ -588,9 +588,9 @@ public void Range() { this.GitBasedTest( """ -# range regex -*.py[cod] -""", + # range regex + *.py[cod] + """, ["foo.py", "bar.p", "foo.pyc", "foo.pyco", "foo.pyd"] ); } From a047a24f608273c3ba83f0ce33c4f4ca67b7730a Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Mon, 9 Feb 2026 09:29:17 -0600 Subject: [PATCH 17/27] disable Q&A --- Src/CSharpier.VSCode/CHANGELOG.md | 3 +++ Src/CSharpier.VSCode/package.json | 3 ++- .../CSharpier.VisualStudio/source.extension.vsixmanifest | 2 +- Src/CSharpier.VisualStudio/ChangeLog.md | 5 ++++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Src/CSharpier.VSCode/CHANGELOG.md b/Src/CSharpier.VSCode/CHANGELOG.md index 578a45dda..2c3a4e9f9 100644 --- a/Src/CSharpier.VSCode/CHANGELOG.md +++ b/Src/CSharpier.VSCode/CHANGELOG.md @@ -1,3 +1,6 @@ +## 10.0.1 +- Disable Q&A. Please use GitHub + ## 10.0.0 - Make version a number that won't conflict with CSharpier itself. diff --git a/Src/CSharpier.VSCode/package.json b/Src/CSharpier.VSCode/package.json index 08843f5b7..153b83f92 100644 --- a/Src/CSharpier.VSCode/package.json +++ b/Src/CSharpier.VSCode/package.json @@ -2,8 +2,9 @@ "name": "csharpier-vscode", "displayName": "CSharpier - Code formatter", "description": "Code formatter using csharpier", - "version": "10.0.0", + "version": "10.0.1", "publisher": "csharpier", + "qna": "https://github.com/belav/csharpier/issues", "author": "CSharpier", "homepage": "https://marketplace.visualstudio.com/items?itemName=csharpier.csharpier-vscode", "repository": { diff --git a/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/source.extension.vsixmanifest b/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/source.extension.vsixmanifest index 9b0d79a42..909268f7a 100644 --- a/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/source.extension.vsixmanifest +++ b/Src/CSharpier.VisualStudio/CSharpier.VisualStudio/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + CSharpier CSharpier is an opinionated code formatter for c#. It uses Roslyn to parse your code and re-prints it using its own rules. https://github.com/belav/csharpier diff --git a/Src/CSharpier.VisualStudio/ChangeLog.md b/Src/CSharpier.VisualStudio/ChangeLog.md index 9041fe14b..aaa11d9ee 100644 --- a/Src/CSharpier.VisualStudio/ChangeLog.md +++ b/Src/CSharpier.VisualStudio/ChangeLog.md @@ -1,4 +1,7 @@ -## [10.0.1] +## [10.0.2] +- Disable Q&A. Please use GitHub + +## [10.0.1] - Add right click format command for AXAML. Requires csharpier configuration for < 1.3.0 ## [2.2.0] From 148711fd9a80100eb06110720ee86800568b2308 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Mon, 29 Dec 2025 15:22:26 -0600 Subject: [PATCH 18/27] Working on adding xml whitespace ignore. --- Src/CSharpier.Core/CSharp/CSharpFormatter.cs | 2 + .../CSharp/SyntaxPrinter/PrintingContext.cs | 1 + .../Xml/XNodePrinters/Attributes.cs | 2 +- .../Xml/XNodePrinters/ElementChildren.cs | 20 ++++++- Src/CSharpier.Core/Xml/XNodePrinters/Node.cs | 2 +- Src/CSharpier.Core/Xml/XNodePrinters/Tag.cs | 32 ++++++----- Src/CSharpier.Core/Xml/XmlFormatter.cs | 1 + .../ClientApp/src/AppContext.ts | 7 +++ .../ClientApp/src/Controls.tsx | 15 ++++++ .../ClientApp/src/FormatCode.ts | 3 +- .../Controllers/FormatController.cs | 7 +++ .../SyntaxPrinter/CSharpierIgnoreTests.cs | 2 + .../FormattingTests/BaseTest.cs | 54 ++++++++++++------- .../xml_ignore/Elements.expected.test | 10 ++++ .../TestFiles/xml_ignore/Elements.test | 10 ++++ .../FormattingTests/XmlIgnoreFormatting.cs | 10 ++++ 16 files changed, 144 insertions(+), 34 deletions(-) create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements.expected.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements.test create mode 100644 Src/CSharpier.Tests/FormattingTests/XmlIgnoreFormatting.cs diff --git a/Src/CSharpier.Core/CSharp/CSharpFormatter.cs b/Src/CSharpier.Core/CSharp/CSharpFormatter.cs index 984a53912..162d17550 100644 --- a/Src/CSharpier.Core/CSharp/CSharpFormatter.cs +++ b/Src/CSharpier.Core/CSharp/CSharpFormatter.cs @@ -155,6 +155,7 @@ bool TryGetCompilationFailure(out CodeFormatterResult compilationResult) LineEnding = lineEnding, IndentSize = printerOptions.IndentSize, UseTabs = printerOptions.UseTabs, + XmlWhitespaceSensitivity = XmlWhitespaceSensitivity.Strict, }, }; var document = Node.Print(rootNode, printingContext); @@ -181,6 +182,7 @@ bool TryGetCompilationFailure(out CodeFormatterResult compilationResult) LineEnding = lineEnding, IndentSize = printerOptions.IndentSize, UseTabs = printerOptions.UseTabs, + XmlWhitespaceSensitivity = XmlWhitespaceSensitivity.Strict, }, }; document = Node.Print( diff --git a/Src/CSharpier.Core/CSharp/SyntaxPrinter/PrintingContext.cs b/Src/CSharpier.Core/CSharp/SyntaxPrinter/PrintingContext.cs index e0f5176d4..dcac53eae 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxPrinter/PrintingContext.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxPrinter/PrintingContext.cs @@ -36,6 +36,7 @@ public class PrintingContextOptions public required string LineEnding { get; init; } public required int IndentSize { get; init; } public required bool UseTabs { get; init; } + public required XmlWhitespaceSensitivity XmlWhitespaceSensitivity { get; init; } } public class PrintingContextState diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Attributes.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Attributes.cs index 2f1a8d5ae..4120605e6 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Attributes.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Attributes.cs @@ -42,7 +42,7 @@ public static Doc Print(RawNode rawNode, PrintingContext context) */ ( rawNode.Nodes.Count != 0 - && Tag.NeedsToBorrowParentOpeningTagEndMarker(rawNode.Nodes.First()) + && Tag.NeedsToBorrowParentOpeningTagEndMarker(rawNode.Nodes.First(), context) ) || doNotBreakAttributes ) { diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs b/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs index 0db54e9d4..bfb316f55 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs @@ -38,6 +38,17 @@ public static Doc Print(RawNode node, PrintingContext context) ? PrintBetweenLine(childNode, childNode.NextNode) : Doc.Null; + if ( + context.Options.XmlWhitespaceSensitivity is not XmlWhitespaceSensitivity.Strict + && childNode.PreviousNode is null + && childNode.NextNode is null + && childNode.IsTextLike() + ) + { + prevBetweenLine = Doc.SoftLine; + nextBetweenLine = Doc.SoftLine; + } + if (prevBetweenLine is not NullDoc) { if (prevBetweenLine is HardLine) @@ -50,7 +61,14 @@ public static Doc Print(RawNode node, PrintingContext context) } else { - leadingParts.Add(Doc.IfBreak(Doc.Null, Doc.SoftLine, groupIds[x - 1])); + if (groupIds.Count > 1) + { + leadingParts.Add(Doc.IfBreak(Doc.Null, Doc.SoftLine, groupIds[x - 1])); + } + else + { + leadingParts.Add(prevBetweenLine); + } } } diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs index 79436b939..1cb16dee8 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs @@ -40,7 +40,7 @@ internal static Doc Print(RawNode node, PrintingContext context) { List doc = [ - Tag.PrintOpeningTagPrefix(node), + Tag.PrintOpeningTagPrefix(node, context), GetTextValue(node), Tag.PrintClosingTagSuffix(node, context), ]; diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Tag.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Tag.cs index 1a0afb172..07d40b372 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Tag.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Tag.cs @@ -11,7 +11,7 @@ public static Doc PrintOpeningTag(RawNode rawNode, PrintingContext context) return Doc.Concat( PrintOpeningTagStart(rawNode, context), Attributes.Print(rawNode, context), - rawNode.IsEmpty ? Doc.Null : PrintOpeningTagEnd(rawNode) + rawNode.IsEmpty ? Doc.Null : PrintOpeningTagEnd(rawNode, context) ); } @@ -22,23 +22,23 @@ rawNode.PreviousNode is not null && NeedsToBorrowNextOpeningTagStartMarker(rawNode.PreviousNode) ? Doc.Null : Doc.Concat( - PrintOpeningTagPrefix(rawNode), + PrintOpeningTagPrefix(rawNode, context), PrintOpeningTagStartMarker(rawNode, context) ); } - private static Doc PrintOpeningTagEnd(RawNode rawNode) + private static Doc PrintOpeningTagEnd(RawNode rawNode, PrintingContext context) { return rawNode.Nodes.FirstOrDefault() is { } firstNode - && NeedsToBorrowParentOpeningTagEndMarker(firstNode) + && NeedsToBorrowParentOpeningTagEndMarker(firstNode, context) ? Doc.Null : ">"; } - public static Doc PrintOpeningTagPrefix(RawNode rawNode) + public static Doc PrintOpeningTagPrefix(RawNode rawNode, PrintingContext context) { - return NeedsToBorrowParentOpeningTagEndMarker(rawNode) ? ">" : ""; + return NeedsToBorrowParentOpeningTagEndMarker(rawNode, context) ? ">" : ""; } public static Doc PrintClosingTag(RawNode rawNode, PrintingContext context) @@ -53,7 +53,7 @@ public static Doc PrintClosingTagStart(RawNode rawNode, PrintingContext context) { var lastChild = rawNode.Nodes.LastOrDefault(); - return lastChild is not null && PrintParentClosingTagStartWithContent(lastChild) + return lastChild is not null && PrintParentClosingTagStartWithContent(lastChild, context) ? Doc.Null : PrintClosingTagStartMarker(rawNode, context); } @@ -78,7 +78,7 @@ public static Doc PrintClosingTagEndMarker(RawNode rawNode) public static Doc PrintClosingTagSuffix(RawNode rawNode, PrintingContext context) { - return PrintParentClosingTagStartWithContent(rawNode) + return PrintParentClosingTagStartWithContent(rawNode, context) ? PrintClosingTagStartMarker(rawNode.Parent!, context) : NeedsToBorrowNextOpeningTagStartMarker(rawNode) ? PrintOpeningTagStartMarker(rawNode.NextNode!, context) @@ -113,7 +113,10 @@ private static bool NeedsToBorrowNextOpeningTagStartMarker(RawNode rawNode) ; } - private static bool PrintParentClosingTagStartWithContent(RawNode rawNode) + private static bool PrintParentClosingTagStartWithContent( + RawNode rawNode, + PrintingContext context + ) { /* *

@@ -147,7 +150,8 @@ Life is demanding. */ - return rawNode.NextNode is null + return context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict + && rawNode.NextNode is null && rawNode.IsTextLike() && rawNode.GetLastDescendant() is { NodeType: XmlNodeType.Text } textNode && ( @@ -157,7 +161,10 @@ Life is demanding. ); } - public static bool NeedsToBorrowParentOpeningTagEndMarker(RawNode rawNode) + public static bool NeedsToBorrowParentOpeningTagEndMarker( + RawNode rawNode, + PrintingContext context + ) { /* *

{ + window.sessionStorage.setItem("xmlWhitespaceSensitivity", value); + this.xmlWhitespaceSensitivity = value; + }; + setIndentSize = (value: number) => { window.sessionStorage.setItem("indentSize", value.toString(10)); this.indentSize = value; @@ -119,6 +125,7 @@ class AppState { this.indentSize, this.useTabs, this.formatter, + this.xmlWhitespaceSensitivity, ); runInAction(() => { diff --git a/Src/CSharpier.Playground/ClientApp/src/Controls.tsx b/Src/CSharpier.Playground/ClientApp/src/Controls.tsx index 3eec52dda..ee1faa862 100644 --- a/Src/CSharpier.Playground/ClientApp/src/Controls.tsx +++ b/Src/CSharpier.Playground/ClientApp/src/Controls.tsx @@ -13,6 +13,8 @@ export const Controls = observer(() => { setUseTabs, formatter, setFormatter, + xmlWhitespaceSensitivity, + setXmlWhitespaceSensitivity, showDoc, setShowDoc, hideNull, @@ -47,6 +49,19 @@ export const Controls = observer(() => { + {formatter === "XML" && ( + <> + + + + )}

Debug

diff --git a/Src/CSharpier.Playground/ClientApp/src/FormatCode.ts b/Src/CSharpier.Playground/ClientApp/src/FormatCode.ts index 88b3c0af4..c74dfa8be 100644 --- a/Src/CSharpier.Playground/ClientApp/src/FormatCode.ts +++ b/Src/CSharpier.Playground/ClientApp/src/FormatCode.ts @@ -8,11 +8,12 @@ export const formatCode = async ( indentSize: number, useTabs: boolean, formatter: string, + xmlWhitespaceSensitivity: string, ) => { const makeRequest = async () => { const response = await fetch("/Format", { method: "POST", - body: JSON.stringify({ code, printWidth, indentSize, useTabs, formatter }), + body: JSON.stringify({ code, printWidth, indentSize, useTabs, formatter, xmlWhitespaceSensitivity }), headers: { "Content-Type": "application/json", "cache-control": "no-cache", diff --git a/Src/CSharpier.Playground/Controllers/FormatController.cs b/Src/CSharpier.Playground/Controllers/FormatController.cs index 8bf0bf7f1..c92be1a18 100644 --- a/Src/CSharpier.Playground/Controllers/FormatController.cs +++ b/Src/CSharpier.Playground/Controllers/FormatController.cs @@ -32,6 +32,7 @@ public class PostModel public int IndentSize { get; set; } public bool UseTabs { get; set; } public string Formatter { get; set; } = string.Empty; + public string XmlWhitespaceSensitivity { get; set; } = string.Empty; } [HttpPost] @@ -54,6 +55,12 @@ CancellationToken cancellationToken Width = model.PrintWidth, IndentSize = model.IndentSize, UseTabs = model.UseTabs, + XmlWhitespaceSensitivity = model.XmlWhitespaceSensitivity switch + { + "Ignore" => XmlWhitespaceSensitivity.Ignore, + "Xaml" => XmlWhitespaceSensitivity.Xaml, + _ => XmlWhitespaceSensitivity.Strict, + }, }, cancellationToken ); diff --git a/Src/CSharpier.Tests/CSharp/SyntaxPrinter/CSharpierIgnoreTests.cs b/Src/CSharpier.Tests/CSharp/SyntaxPrinter/CSharpierIgnoreTests.cs index 259a73b38..ffd281939 100644 --- a/Src/CSharpier.Tests/CSharp/SyntaxPrinter/CSharpierIgnoreTests.cs +++ b/Src/CSharpier.Tests/CSharp/SyntaxPrinter/CSharpierIgnoreTests.cs @@ -1,4 +1,5 @@ using AwesomeAssertions; +using CSharpier.Core; using CSharpier.Core.CSharp.SyntaxPrinter; namespace CSharpier.Tests.CSharp.SyntaxPrinter; @@ -71,6 +72,7 @@ private static string PrintWithoutFormatting(string code) LineEnding = Environment.NewLine, IndentSize = 4, UseTabs = false, + XmlWhitespaceSensitivity = XmlWhitespaceSensitivity.Strict, }, } ) diff --git a/Src/CSharpier.Tests/FormattingTests/BaseTest.cs b/Src/CSharpier.Tests/FormattingTests/BaseTest.cs index 339de10cd..a2114b088 100644 --- a/Src/CSharpier.Tests/FormattingTests/BaseTest.cs +++ b/Src/CSharpier.Tests/FormattingTests/BaseTest.cs @@ -33,16 +33,43 @@ public void BuildTests(DynamicTestBuilderContext context, string folder) { var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file.Name); var useTabs = fileNameWithoutExtension.EndsWith("_Tabs", StringComparison.Ordinal); + var directoryName = file.Directory!.Name; + var xmlWhitespaceSensitivity = XmlWhitespaceSensitivity.Strict; + if (directoryName.Contains('_')) + { + var parts = directoryName.Split('_'); + directoryName = parts[0]; + xmlWhitespaceSensitivity = + parts[1] is "ignore" ? XmlWhitespaceSensitivity.Ignore + : parts[1] is "xaml" ? XmlWhitespaceSensitivity.Xaml + : XmlWhitespaceSensitivity.Strict; + } + + var formatter = directoryName switch + { + "cs" => Formatter.CSharp, + "csx" => Formatter.CSharpScript, + "xml" => Formatter.XML, + _ => Formatter.Unknown, + }; + + var printerOptions = new PrinterOptions(formatter) + { + UseTabs = useTabs, + Width = PrinterOptions.WidthUsedByTests, + IndentSize = formatter == Formatter.XML ? 2 : 4, + XmlWhitespaceSensitivity = xmlWhitespaceSensitivity, + }; context.AddTest( new DynamicTest { - TestMethod = @class => @class.RunTest(string.Empty, string.Empty, false), + TestMethod = @class => @class.RunTest(string.Empty, string.Empty, null), TestMethodArguments = [ fileNameWithoutExtension, file.Directory!.Name, - useTabs, + printerOptions, ], DisplayName = fileNameWithoutExtension, } @@ -51,13 +78,17 @@ public void BuildTests(DynamicTestBuilderContext context, string folder) } } - public async Task RunTest(string fileName, string fileExtensionWithoutDot, bool useTabs = false) + internal async Task RunTest( + string fileName, + string directoryName, + PrinterOptions printerOptions + ) { var filePath = Path.Combine( this.rootDirectory.FullName, "FormattingTests", "TestFiles", - fileExtensionWithoutDot, + directoryName, fileName + ".test" ); var fileReaderResult = await FileReader.ReadFileAsync( @@ -66,22 +97,9 @@ public async Task RunTest(string fileName, string fileExtensionWithoutDot, bool CancellationToken.None ); - var formatter = fileExtensionWithoutDot switch - { - "cs" => Formatter.CSharp, - "csx" => Formatter.CSharpScript, - "xml" => Formatter.XML, - _ => Formatter.Unknown, - }; - var result = await CodeFormatter.FormatAsync( fileReaderResult.FileContents, - new PrinterOptions(formatter) - { - Width = PrinterOptions.WidthUsedByTests, - UseTabs = useTabs, - IndentSize = formatter == Formatter.XML ? 2 : 4, - }, + printerOptions, CancellationToken.None ); diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements.expected.test new file mode 100644 index 000000000..ccedaed25 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements.expected.test @@ -0,0 +1,10 @@ + + + TextValue + + + TextValue + + TextValue + TextValue + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements.test new file mode 100644 index 000000000..89f1ec8d6 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements.test @@ -0,0 +1,10 @@ + + TextValue + TextValue + + TextValue + + + TextValue + + diff --git a/Src/CSharpier.Tests/FormattingTests/XmlIgnoreFormatting.cs b/Src/CSharpier.Tests/FormattingTests/XmlIgnoreFormatting.cs new file mode 100644 index 000000000..d45bc229d --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/XmlIgnoreFormatting.cs @@ -0,0 +1,10 @@ +namespace CSharpier.Tests.FormattingTests; + +public class XmlIgnoreFormatting : BaseTest +{ + [DynamicTestBuilder] + public void BuildTests(DynamicTestBuilderContext context) + { + this.BuildTests(context, "xml_ignore"); + } +} From 93c8caf360f1740d0e148b91c8949dcc99344c0c Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Wed, 31 Dec 2025 15:44:52 -0600 Subject: [PATCH 19/27] Getting the basics of ignore whitespace in xml working. --- Src/CSharpier.Cli/Options/OptionsProvider.cs | 5 +++ Src/CSharpier.Core/PrinterOptions.cs | 2 +- .../Xml/XNodePrinters/Element.cs | 22 ++++++++++--- .../Xml/XNodePrinters/ElementChildren.cs | 33 +++++++------------ Src/CSharpier.Core/Xml/XNodePrinters/Node.cs | 9 +++-- .../XmlWhitespaceSensitivity.cs | 4 +++ .../xml_ignore/Elements2.expected.test | 10 ++++++ .../TestFiles/xml_ignore/Elements2.test | 10 ++++++ .../{xml => xml_strict}/Attributes.test | 0 .../{xml => xml_strict}/BasicProject.test | 0 .../TestFiles/{xml => xml_strict}/CData.test | 0 .../{xml => xml_strict}/CData_EdgeCase.test | 0 .../{xml => xml_strict}/CData_InText.test | 0 .../{xml => xml_strict}/Comments.test | 0 .../{xml => xml_strict}/Conditions.test | 0 .../{xml => xml_strict}/DoNotBreak.test | 0 .../DoubleQuotesForced.expected.test | 0 .../DoubleQuotesForced.test | 0 .../Element_DoesNotAddPrefix.test | 0 .../{xml => xml_strict}/Elements.test | 0 .../Elements_HtmlValues.test | 0 .../{xml => xml_strict}/EmptyLines.test | 0 .../EmptyLines_RemoveSome.expected.test | 0 .../EmptyLines_RemoveSome.test | 0 .../{xml => xml_strict}/EncodedValues.test | 0 .../{xml => xml_strict}/LongAttributes.test | 0 .../PrefixedElementNames.test | 0 .../{xml => xml_strict}/RetainXmlElement.test | 0 .../{xml => xml_strict}/StrictWhitespace.test | 0 .../XProcessingInstruction.test | 0 .../XmlDeclarationWorksWithMapping.test | 0 .../{xml => xml_strict}/XmlDocumentType.test | 0 32 files changed, 65 insertions(+), 30 deletions(-) create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements2.expected.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements2.test rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/Attributes.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/BasicProject.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/CData.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/CData_EdgeCase.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/CData_InText.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/Comments.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/Conditions.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/DoNotBreak.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/DoubleQuotesForced.expected.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/DoubleQuotesForced.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/Element_DoesNotAddPrefix.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/Elements.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/Elements_HtmlValues.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/EmptyLines.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/EmptyLines_RemoveSome.expected.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/EmptyLines_RemoveSome.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/EncodedValues.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/LongAttributes.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/PrefixedElementNames.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/RetainXmlElement.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/StrictWhitespace.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/XProcessingInstruction.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/XmlDeclarationWorksWithMapping.test (100%) rename Src/CSharpier.Tests/FormattingTests/TestFiles/{xml => xml_strict}/XmlDocumentType.test (100%) diff --git a/Src/CSharpier.Cli/Options/OptionsProvider.cs b/Src/CSharpier.Cli/Options/OptionsProvider.cs index 153e1d20b..6ce1c2da4 100644 --- a/Src/CSharpier.Cli/Options/OptionsProvider.cs +++ b/Src/CSharpier.Cli/Options/OptionsProvider.cs @@ -117,6 +117,11 @@ await EditorConfigLocator.FindForDirectoryNameAsync( CancellationToken cancellationToken ) { + // TODO #1794 the xml whitespace should default based on the file + // possible defaults + // xaml files = xaml + // msbuild ones = ignore + // everything else = strict if (this.specifiedConfigFile is not null) { return this.specifiedConfigFile.ConvertToPrinterOptions(filePath); diff --git a/Src/CSharpier.Core/PrinterOptions.cs b/Src/CSharpier.Core/PrinterOptions.cs index b82dbdde2..48a78d678 100644 --- a/Src/CSharpier.Core/PrinterOptions.cs +++ b/Src/CSharpier.Core/PrinterOptions.cs @@ -26,7 +26,7 @@ public int IndentSize public EndOfLine EndOfLine { get; set; } = EndOfLine.Auto; public bool TrimInitialLines { get; init; } = true; public bool IncludeGenerated { get; set; } - public Formatter Formatter { get; set; } = formatter; + public Formatter Formatter { get; } = formatter; public XmlWhitespaceSensitivity XmlWhitespaceSensitivity { get; set; } = XmlWhitespaceSensitivity.Strict; diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs index 8941a1811..9e35335d1 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs @@ -34,19 +34,27 @@ Doc PrintLineBeforeChildren() } if ( - rawNode.Nodes.FirstOrDefault() is - { NodeType: XmlNodeType.Text, Value: ['\n', ..] or ['\r', ..] } + context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict + && rawNode.Nodes.FirstOrDefault() + is { NodeType: XmlNodeType.Text, Value: ['\n', ..] or ['\r', ..] } ) { return Doc.LiteralLine; } - if (rawNode.Attributes.Length == 0 && rawNode.Nodes is [{ NodeType: XmlNodeType.Text }]) + if ( + rawNode.Attributes.Length == 0 + && rawNode.Nodes is [{ NodeType: XmlNodeType.Text }] + && context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict + ) { return Doc.Null; } - if (rawNode.Nodes.Any(o => o.NodeType is XmlNodeType.Text && o.Value.Contains('\n'))) + if ( + context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict + && rawNode.Nodes.Any(o => o.NodeType is XmlNodeType.Text && o.Value.Contains('\n')) + ) { return Doc.HardLine; } @@ -62,7 +70,11 @@ Doc PrintLineAfterChildren() return Doc.IfBreak(Doc.SoftLine, "", attrGroupId); } - if (rawNode.Attributes.Length == 0 && rawNode.Nodes is [{ NodeType: XmlNodeType.Text }]) + if ( + rawNode.Attributes.Length == 0 + && rawNode.Nodes is [{ NodeType: XmlNodeType.Text }] + && context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict + ) { return Doc.Null; } diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs b/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs index bfb316f55..913090511 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs @@ -12,10 +12,10 @@ public static Doc Print(RawNode node, PrintingContext context) var groupIds = new List(); foreach (var _ in node.Nodes) { - groupIds.Add(context.GroupFor("symbol")); + groupIds.Add(context.GroupFor("children group")); } - var result = new DocListBuilder(node.Nodes.Count * 5); + var result = new List(); var x = 0; foreach (var childNode in node.Nodes) { @@ -25,10 +25,10 @@ public static Doc Print(RawNode node, PrintingContext context) continue; } - var prevParts = new DocListBuilder(2); - var leadingParts = new DocListBuilder(2); - var trailingParts = new DocListBuilder(2); - var nextParts = new DocListBuilder(2); + var prevParts = new List(); + var leadingParts = new List(); + var trailingParts = new List(); + var nextParts = new List(); var prevBetweenLine = childNode.PreviousNode is not null ? PrintBetweenLine(childNode.PreviousNode, childNode) @@ -38,17 +38,6 @@ public static Doc Print(RawNode node, PrintingContext context) ? PrintBetweenLine(childNode, childNode.NextNode) : Doc.Null; - if ( - context.Options.XmlWhitespaceSensitivity is not XmlWhitespaceSensitivity.Strict - && childNode.PreviousNode is null - && childNode.NextNode is null - && childNode.IsTextLike() - ) - { - prevBetweenLine = Doc.SoftLine; - nextBetweenLine = Doc.SoftLine; - } - if (prevBetweenLine is not NullDoc) { if (prevBetweenLine is HardLine) @@ -87,22 +76,22 @@ context.Options.XmlWhitespaceSensitivity is not XmlWhitespaceSensitivity.Strict } } - result.Add(prevParts.AsSpan()); + result.AddRange(prevParts); result.Add( Doc.Group( - Doc.Concat(ref leadingParts), + Doc.Concat(leadingParts), Doc.GroupWithId( groupIds[x], PrintChild(childNode, context), - Doc.Concat(ref trailingParts) + Doc.Concat(trailingParts) ) ) ); - result.Add(nextParts.AsSpan()); + result.AddRange(nextParts); x++; } - return Doc.Concat(ref result); + return Doc.Concat(result); } public static Doc PrintChild(RawNode child, PrintingContext context) diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs index 1cb16dee8..7e14c6f2d 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs @@ -41,7 +41,7 @@ internal static Doc Print(RawNode node, PrintingContext context) List doc = [ Tag.PrintOpeningTagPrefix(node, context), - GetTextValue(node), + GetTextValue(node, context), Tag.PrintClosingTagSuffix(node, context), ]; @@ -77,7 +77,7 @@ or XmlNodeType.CDATA throw new Exception("Need to handle + " + node.NodeType); } - private static Doc GetTextValue(RawNode rawNode) + private static Doc GetTextValue(RawNode rawNode, PrintingContext context) { var textValue = rawNode.Value; @@ -86,6 +86,11 @@ private static Doc GetTextValue(RawNode rawNode) return Doc.Null; } + if (context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Ignore) + { + textValue = textValue.Trim(); + } + if (rawNode.Parent?.Nodes.First() == rawNode) { if (textValue[0] is '\r') diff --git a/Src/CSharpier.Core/XmlWhitespaceSensitivity.cs b/Src/CSharpier.Core/XmlWhitespaceSensitivity.cs index 829ddd777..9186f4191 100644 --- a/Src/CSharpier.Core/XmlWhitespaceSensitivity.cs +++ b/Src/CSharpier.Core/XmlWhitespaceSensitivity.cs @@ -3,6 +3,10 @@ namespace CSharpier.Core; internal enum XmlWhitespaceSensitivity { Strict, + + // TODO #1794 figure out what this actually means code wise, should it be named something besides xaml? Xaml, + + // TODO #1794 do a lot more testing with this, can review the repos once the file extension stuff is figured out. Ignore, } diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements2.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements2.expected.test new file mode 100644 index 000000000..ccedaed25 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements2.expected.test @@ -0,0 +1,10 @@ + + + TextValue + + + TextValue + + TextValue + TextValue + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements2.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements2.test new file mode 100644 index 000000000..ccedaed25 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements2.test @@ -0,0 +1,10 @@ + + + TextValue + + + TextValue + + TextValue + TextValue + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Attributes.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/Attributes.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Attributes.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/Attributes.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/BasicProject.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/BasicProject.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/BasicProject.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/BasicProject.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/CData.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CData.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/CData.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CData.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/CData_EdgeCase.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CData_EdgeCase.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/CData_EdgeCase.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CData_EdgeCase.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/CData_InText.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CData_InText.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/CData_InText.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CData_InText.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Comments.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/Comments.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Comments.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/Comments.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Conditions.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/Conditions.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Conditions.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/Conditions.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/DoNotBreak.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/DoNotBreak.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/DoNotBreak.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/DoNotBreak.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/DoubleQuotesForced.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/DoubleQuotesForced.expected.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/DoubleQuotesForced.expected.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/DoubleQuotesForced.expected.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/DoubleQuotesForced.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/DoubleQuotesForced.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/DoubleQuotesForced.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/DoubleQuotesForced.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Element_DoesNotAddPrefix.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/Element_DoesNotAddPrefix.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Element_DoesNotAddPrefix.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/Element_DoesNotAddPrefix.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Elements.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/Elements.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Elements.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/Elements.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Elements_HtmlValues.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/Elements_HtmlValues.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/Elements_HtmlValues.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/Elements_HtmlValues.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/EmptyLines.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/EmptyLines.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/EmptyLines.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/EmptyLines.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/EmptyLines_RemoveSome.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/EmptyLines_RemoveSome.expected.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/EmptyLines_RemoveSome.expected.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/EmptyLines_RemoveSome.expected.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/EmptyLines_RemoveSome.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/EmptyLines_RemoveSome.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/EmptyLines_RemoveSome.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/EmptyLines_RemoveSome.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/EncodedValues.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/EncodedValues.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/EncodedValues.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/EncodedValues.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/LongAttributes.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/LongAttributes.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/LongAttributes.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/LongAttributes.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/PrefixedElementNames.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/PrefixedElementNames.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/PrefixedElementNames.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/PrefixedElementNames.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/RetainXmlElement.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/RetainXmlElement.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/RetainXmlElement.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/RetainXmlElement.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/StrictWhitespace.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/StrictWhitespace.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/StrictWhitespace.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/StrictWhitespace.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/XProcessingInstruction.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/XProcessingInstruction.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/XProcessingInstruction.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/XProcessingInstruction.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/XmlDeclarationWorksWithMapping.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/XmlDeclarationWorksWithMapping.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/XmlDeclarationWorksWithMapping.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/XmlDeclarationWorksWithMapping.test diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml/XmlDocumentType.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/XmlDocumentType.test similarity index 100% rename from Src/CSharpier.Tests/FormattingTests/TestFiles/xml/XmlDocumentType.test rename to Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/XmlDocumentType.test From 06a9bef0001083bc3b76507eca13b1469169fae2 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Fri, 2 Jan 2026 11:08:26 -0600 Subject: [PATCH 20/27] Adding some tests to figure out how to format things with whitespace ignore --- CSharpier.sln.DotSettings | 1 + .../FormattingTests/BaseTest.cs | 2 +- .../TestFiles/xml_ignore/CData.test | 10 ++ .../TestFiles/xml_ignore/CData_EdgeCase.test | 5 + .../TestFiles/xml_ignore/CData_InText.test | 4 + .../TestFiles/xml_ignore/Comments.test | 12 +++ .../TestFiles/xml_ignore/Conditions.test | 11 +++ .../TestFiles/xml_ignore/DoNotBreak.test | 8 ++ .../xml_ignore/Elements_HtmlValues.test | 8 ++ .../TestFiles/xml_ignore/EmptyLines.test | 14 +++ .../EmptyLines_RemoveSome.expected.test | 5 + .../xml_ignore/EmptyLines_RemoveSome.test | 8 ++ .../TestFiles/xml_ignore/EncodedValues.test | 6 ++ .../TestFiles/xml_ignore/LongAttributes.test | 98 +++++++++++++++++++ .../xml_ignore/PrefixedElementNames.test | 6 ++ .../xml_ignore/RetainXmlElement.test | 2 + .../xml_ignore/StrictWhitespace.test | 36 +++++++ .../xml_ignore/XProcessingInstruction.test | 3 + .../XmlDeclarationWorksWithMapping.test | 6 ++ .../TestFiles/xml_ignore/XmlDocumentType.test | 6 ++ ...mlFormatting.cs => XmlStrictFormatting.cs} | 4 +- 21 files changed, 252 insertions(+), 3 deletions(-) create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData_EdgeCase.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData_InText.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Comments.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Conditions.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/DoNotBreak.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_HtmlValues.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EmptyLines.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EmptyLines_RemoveSome.expected.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EmptyLines_RemoveSome.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EncodedValues.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/LongAttributes.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/PrefixedElementNames.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/RetainXmlElement.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/StrictWhitespace.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/XProcessingInstruction.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/XmlDeclarationWorksWithMapping.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/XmlDocumentType.test rename Src/CSharpier.Tests/FormattingTests/{XmlFormatting.cs => XmlStrictFormatting.cs} (56%) diff --git a/CSharpier.sln.DotSettings b/CSharpier.sln.DotSettings index 176f4143d..8ebf030aa 100644 --- a/CSharpier.sln.DotSettings +++ b/CSharpier.sln.DotSettings @@ -1,5 +1,6 @@  False + False True True True diff --git a/Src/CSharpier.Tests/FormattingTests/BaseTest.cs b/Src/CSharpier.Tests/FormattingTests/BaseTest.cs index a2114b088..8d902d2a3 100644 --- a/Src/CSharpier.Tests/FormattingTests/BaseTest.cs +++ b/Src/CSharpier.Tests/FormattingTests/BaseTest.cs @@ -64,7 +64,7 @@ parts[1] is "ignore" ? XmlWhitespaceSensitivity.Ignore context.AddTest( new DynamicTest { - TestMethod = @class => @class.RunTest(string.Empty, string.Empty, null), + TestMethod = @class => @class.RunTest(string.Empty, string.Empty, null!), TestMethodArguments = [ fileNameWithoutExtension, diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData.test new file mode 100644 index 000000000..687730eda --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData.test @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData_EdgeCase.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData_EdgeCase.test new file mode 100644 index 000000000..1878d288c --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData_EdgeCase.test @@ -0,0 +1,5 @@ + + + entryPointFullTypeName + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData_InText.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData_InText.test new file mode 100644 index 000000000..ba805631b --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData_InText.test @@ -0,0 +1,4 @@ +SomeText + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Comments.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Comments.test new file mode 100644 index 000000000..b6a2db72e --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Comments.test @@ -0,0 +1,12 @@ + + + + + CA1031; + IDE0005; + + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Conditions.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Conditions.test new file mode 100644 index 000000000..fea994830 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Conditions.test @@ -0,0 +1,11 @@ + + + + v4.5.2 + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/DoNotBreak.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/DoNotBreak.test new file mode 100644 index 000000000..56bfda863 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/DoNotBreak.test @@ -0,0 +1,8 @@ + + + + ..\..\packages\Newtonsoft.Json.Bson.1.0.2\lib\net45\Newtonsoft.Json.Bson.dll + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_HtmlValues.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_HtmlValues.test new file mode 100644 index 000000000..06a7c906b --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_HtmlValues.test @@ -0,0 +1,8 @@ + + Some text
url.com more text. + Some long text to make things break url.com more text. + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EmptyLines.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EmptyLines.test new file mode 100644 index 000000000..48a72189a --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EmptyLines.test @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EmptyLines_RemoveSome.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EmptyLines_RemoveSome.expected.test new file mode 100644 index 000000000..f7c26ecb7 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EmptyLines_RemoveSome.expected.test @@ -0,0 +1,5 @@ + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EmptyLines_RemoveSome.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EmptyLines_RemoveSome.test new file mode 100644 index 000000000..7ac67cf30 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EmptyLines_RemoveSome.test @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EncodedValues.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EncodedValues.test new file mode 100644 index 000000000..5ad7a455f --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/EncodedValues.test @@ -0,0 +1,6 @@ + + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/LongAttributes.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/LongAttributes.test new file mode 100644 index 000000000..6ef7a04ba --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/LongAttributes.test @@ -0,0 +1,98 @@ + + + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/PrefixedElementNames.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/PrefixedElementNames.test new file mode 100644 index 000000000..6eed22404 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/PrefixedElementNames.test @@ -0,0 +1,6 @@ + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/RetainXmlElement.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/RetainXmlElement.test new file mode 100644 index 000000000..820a64750 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/RetainXmlElement.test @@ -0,0 +1,2 @@ + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/StrictWhitespace.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/StrictWhitespace.test new file mode 100644 index 000000000..67cc6ad47 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/StrictWhitespace.test @@ -0,0 +1,36 @@ + + + + Because whitespace is strict + The indentation of the closing element can't change + + + Shorter Text with indentation that can't change + + + A containing a value that reflects the sort order of + as compared to . The following table defines the conditions + under which the returned value is a negative number, zero, or a positive + number. + + Some text that ends with a space. + Some loooooooooooooooooooooooooooooooooong text with this
and a space after this.
+ The current instance is read-only and a set operation was attempted. + + public void MethodName() { +} + + + + + + +
diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/XProcessingInstruction.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/XProcessingInstruction.test new file mode 100644 index 000000000..692c52632 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/XProcessingInstruction.test @@ -0,0 +1,3 @@ + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/XmlDeclarationWorksWithMapping.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/XmlDeclarationWorksWithMapping.test new file mode 100644 index 000000000..5e9f4ffe7 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/XmlDeclarationWorksWithMapping.test @@ -0,0 +1,6 @@ + + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/XmlDocumentType.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/XmlDocumentType.test new file mode 100644 index 000000000..0f8095c88 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/XmlDocumentType.test @@ -0,0 +1,6 @@ + + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/XmlFormatting.cs b/Src/CSharpier.Tests/FormattingTests/XmlStrictFormatting.cs similarity index 56% rename from Src/CSharpier.Tests/FormattingTests/XmlFormatting.cs rename to Src/CSharpier.Tests/FormattingTests/XmlStrictFormatting.cs index 390d7f837..d71d5a429 100644 --- a/Src/CSharpier.Tests/FormattingTests/XmlFormatting.cs +++ b/Src/CSharpier.Tests/FormattingTests/XmlStrictFormatting.cs @@ -1,10 +1,10 @@ namespace CSharpier.Tests.FormattingTests; -public class XmlFormatting : BaseTest +public class XmlStrictFormatting : BaseTest { [DynamicTestBuilder] public void BuildTests(DynamicTestBuilderContext context) { - this.BuildTests(context, "xml"); + this.BuildTests(context, "xml_strict"); } } From 394fb1ae4b64171bf821029931d265635ffaea89 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Fri, 2 Jan 2026 11:13:26 -0600 Subject: [PATCH 21/27] Possibly have the tests set up how we want them to be formatted --- .../TestFiles/xml_ignore/CData_InText.test | 4 +- .../xml_ignore/Elements_HtmlValues.test | 16 ++++--- .../xml_ignore/StrictWhitespace.test | 47 ++++++++++--------- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData_InText.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData_InText.test index ba805631b..285e169af 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData_InText.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/CData_InText.test @@ -1,4 +1,4 @@ -SomeText + + SomeText diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_HtmlValues.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_HtmlValues.test index 06a7c906b..5e89039bd 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_HtmlValues.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_HtmlValues.test @@ -1,8 +1,12 @@ - Some texturl.com more text. - Some long text to make things break url.com more text. + + Some text + url.com + more text. + + + Some long text to make things break + url.com + more text. + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/StrictWhitespace.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/StrictWhitespace.test index 67cc6ad47..d8d9226df 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/StrictWhitespace.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/StrictWhitespace.test @@ -1,31 +1,32 @@ - - Because whitespace is strict - The indentation of the closing element can't change - - - Shorter Text with indentation that can't change - + Because whitespace is strict The indentation of the closing element can't + change + + Shorter Text with indentation that can't change - A containing a value that reflects the sort order of - as compared to . The following table defines the conditions - under which the returned value is a negative number, zero, or a positive - number. + + A + + containing a value that reflects the sort order of + + as compared to + + . The following table defines the conditions under which the returned + value is a negative number, zero, or a positive number. + - Some text that ends with a space. - Some loooooooooooooooooooooooooooooooooong text with this
and a space after this.
- The current instance is read-only and a set operation was attempted. + Some text that ends with a space. + + Some loooooooooooooooooooooooooooooooooong text with this +
+ and a space after this. +
+ + The current instance is read-only and a set operation was attempted. + - public void MethodName() { -} - + public void MethodName() { } From 2d48adbccf03b21d8f7e084b217b29e21022b81c Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Fri, 9 Jan 2026 11:09:03 -0600 Subject: [PATCH 22/27] Fixing some more issues with ignoring whitespace in xml --- CSharpier.sln.DotSettings | 2 + .../Xml/XNodePrinters/Element.cs | 7 +-- Src/CSharpier.Core/Xml/XNodePrinters/Node.cs | 10 +++- .../ClientApp/src/Controls.tsx | 53 ++++++++++++++----- Src/CSharpier.VSCode/src/FormattingService.ts | 2 +- 5 files changed, 56 insertions(+), 18 deletions(-) diff --git a/CSharpier.sln.DotSettings b/CSharpier.sln.DotSettings index 8ebf030aa..0c7f8f6fb 100644 --- a/CSharpier.sln.DotSettings +++ b/CSharpier.sln.DotSettings @@ -1,6 +1,8 @@  False + True False + True True True diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs index 9e35335d1..59cd81310 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs @@ -52,7 +52,7 @@ context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict } if ( - context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict + rawNode.Nodes.Count > 1 && rawNode.Nodes.Any(o => o.NodeType is XmlNodeType.Text && o.Value.Contains('\n')) ) { @@ -80,8 +80,9 @@ Doc PrintLineAfterChildren() } if ( - rawNode.Nodes is [{ NodeType: XmlNodeType.Text }] - && rawNode.Nodes[0].Value.TrimEnd(' ')[^1] is 'r' or '\n' + context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict + && rawNode.Nodes is [{ NodeType: XmlNodeType.Text }] + && rawNode.Nodes[0].Value.TrimEnd(' ')[^1] is '\r' or '\n' ) { return Doc.Null; diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs index 7e14c6f2d..2121b075a 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs @@ -88,7 +88,15 @@ private static Doc GetTextValue(RawNode rawNode, PrintingContext context) if (context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Ignore) { - textValue = textValue.Trim(); + if (rawNode.PreviousNode is null) + { + textValue = textValue.TrimStart(); + } + + if (rawNode.NextNode is null) + { + textValue = textValue.TrimEnd(); + } } if (rawNode.Parent?.Nodes.First() == rawNode) diff --git a/Src/CSharpier.Playground/ClientApp/src/Controls.tsx b/Src/CSharpier.Playground/ClientApp/src/Controls.tsx index ee1faa862..99c727255 100644 --- a/Src/CSharpier.Playground/ClientApp/src/Controls.tsx +++ b/Src/CSharpier.Playground/ClientApp/src/Controls.tsx @@ -44,22 +44,49 @@ export const Controls = observer(() => { Use Tabs - + + + {formatter === "XML" && ( <> - + + + )}
diff --git a/Src/CSharpier.VSCode/src/FormattingService.ts b/Src/CSharpier.VSCode/src/FormattingService.ts index a1cd8011b..73091f889 100644 --- a/Src/CSharpier.VSCode/src/FormattingService.ts +++ b/Src/CSharpier.VSCode/src/FormattingService.ts @@ -89,7 +89,7 @@ export class FormattingService { private minimalEdit(document: TextDocument, newText: string) { let existingText = document.getText(); - + let i = 0; while (i < existingText.length && i < newText.length && existingText[i] === newText[i]) { ++i; From 42fc013f5d9bb37bed27082329fa667bb5616a47 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Fri, 20 Feb 2026 14:07:57 -0600 Subject: [PATCH 23/27] Ditching XAML xml sensitivity, add support for xml:space so "ignore" works as xaml --- Src/CSharpier.Benchmarks/Program.cs | 6 +- Src/CSharpier.Cli/Options/OptionsProvider.cs | 14 +++- Src/CSharpier.Core/CSharpier.Core.csproj | 1 + Src/CSharpier.Core/Xml/RawNode.cs | 1 + Src/CSharpier.Core/Xml/RawNodeReader.cs | 74 ++++++++++++++++--- .../Xml/XNodePrinters/Element.cs | 8 +- Src/CSharpier.Core/Xml/XNodePrinters/Node.cs | 2 +- Src/CSharpier.Core/Xml/XNodePrinters/Tag.cs | 4 +- Src/CSharpier.Core/Xml/XmlFormatter.cs | 6 +- .../XmlWhitespaceSensitivity.cs | 5 -- .../Controllers/FormatController.cs | 1 - .../FormattingTests/BaseTest.cs | 5 +- .../Elements_XmlSpace.expected.test | 11 +++ .../xml_ignore/Elements_XmlSpace.test | 13 ++++ Src/CSharpier.Tests/OptionsProviderTests.cs | 70 ------------------ Src/CSharpier.Tests/RawNodeReaderTests.cs | 71 +++++++++++++++++- 16 files changed, 190 insertions(+), 102 deletions(-) create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_XmlSpace.expected.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_XmlSpace.test diff --git a/Src/CSharpier.Benchmarks/Program.cs b/Src/CSharpier.Benchmarks/Program.cs index e86c5b63d..b8bdbfe3d 100644 --- a/Src/CSharpier.Benchmarks/Program.cs +++ b/Src/CSharpier.Benchmarks/Program.cs @@ -28,7 +28,11 @@ public void XDocument_Parse() [Benchmark] public void CustomParser_Parse() { - _ = RawNodeReader.ParseXml(this.largeXmlCode, Environment.NewLine); + _ = RawNodeReader.ParseXml( + this.largeXmlCode, + Environment.NewLine, + XmlWhitespaceSensitivity.Strict + ); } [Benchmark] diff --git a/Src/CSharpier.Cli/Options/OptionsProvider.cs b/Src/CSharpier.Cli/Options/OptionsProvider.cs index 6ce1c2da4..d731813ec 100644 --- a/Src/CSharpier.Cli/Options/OptionsProvider.cs +++ b/Src/CSharpier.Cli/Options/OptionsProvider.cs @@ -117,11 +117,19 @@ await EditorConfigLocator.FindForDirectoryNameAsync( CancellationToken cancellationToken ) { + // TODO #1794 what other tests do we need for xml:space preserve? compare the strict to ignore tests to see what changed + // TODO #1794 do a lot more testing with this, can review the repos once the file extension stuff is figured out. // TODO #1794 the xml whitespace should default based on the file // possible defaults - // xaml files = xaml - // msbuild ones = ignore - // everything else = strict + // xaml/axaml files = ignore + // everything else = strict - csproj - the whitespace before/after properties has to be handled where you use it, otherwise it can cause issues + /* this will include the whitespace around 'One', so you can't use Condition="'$(MyProperty)' == 'One'", you have to trim it + + + One + + + */ if (this.specifiedConfigFile is not null) { return this.specifiedConfigFile.ConvertToPrinterOptions(filePath); diff --git a/Src/CSharpier.Core/CSharpier.Core.csproj b/Src/CSharpier.Core/CSharpier.Core.csproj index eb67d0e95..9edf84f98 100644 --- a/Src/CSharpier.Core/CSharpier.Core.csproj +++ b/Src/CSharpier.Core/CSharpier.Core.csproj @@ -9,6 +9,7 @@ 002400000480000094000000060200000024000052534131000400000100010049d266ea1aeae09c0abfce28b8728314d4e4807126ee8bc56155a7ddc765997ed3522908b469ae133fc49ef0bfa957df36082c1c2e0ec8cdc05a4ca4dbd4e1bea6c17fc1008555e15af13a8fc871a04ffc38f5e60e6203bfaf01d16a2a283b90572ade79135801c1675bf38b7a5a60ec8353069796eb53a26ffdddc9ee1273be 13 true + One diff --git a/Src/CSharpier.Core/Xml/RawNode.cs b/Src/CSharpier.Core/Xml/RawNode.cs index 8642cf2b9..2cca831fd 100644 --- a/Src/CSharpier.Core/Xml/RawNode.cs +++ b/Src/CSharpier.Core/Xml/RawNode.cs @@ -21,6 +21,7 @@ internal class RawNode public RawAttribute[] Attributes { get; set; } = []; public List Nodes { get; set; } = []; public string Value { get; set; } = string.Empty; + public required XmlWhitespaceSensitivity XmlWhitespaceSensitivity { get; set; } public bool IsTextLike() { diff --git a/Src/CSharpier.Core/Xml/RawNodeReader.cs b/Src/CSharpier.Core/Xml/RawNodeReader.cs index e9cdd56db..676e7dbfd 100644 --- a/Src/CSharpier.Core/Xml/RawNodeReader.cs +++ b/Src/CSharpier.Core/Xml/RawNodeReader.cs @@ -12,6 +12,7 @@ class RawNodeReader { private readonly string originalXml; private readonly string lineEnding; + private XmlWhitespaceSensitivity currentXmlWhitespaceSensitivity; private int position; private readonly Stack elementStack = new(); @@ -22,7 +23,11 @@ class RawNodeReader private static readonly Regex NewlineRegex = new(@"\r\n|\n|\r", RegexOptions.Compiled); #endif - private RawNodeReader(string xml, string lineEnding) + private RawNodeReader( + string xml, + string lineEnding, + XmlWhitespaceSensitivity xmlWhitespaceSensitivity + ) { this.originalXml = NewlineRegex #if !NETSTANDARD2_0 @@ -30,17 +35,26 @@ private RawNodeReader(string xml, string lineEnding) #endif .Replace(xml, "\n"); this.lineEnding = lineEnding; + this.currentXmlWhitespaceSensitivity = xmlWhitespaceSensitivity; } - public static RawNode ParseXml(string originalXml, string lineEnding) + public static RawNode ParseXml( + string originalXml, + string lineEnding, + XmlWhitespaceSensitivity xmlWhitespaceSensitivity + ) { - var reader = new RawNodeReader(originalXml, lineEnding); + var reader = new RawNodeReader(originalXml, lineEnding, xmlWhitespaceSensitivity); return reader.ParseXml(); } private RawNode ParseXml() { - var rootNode = new RawNode { NodeType = XmlNodeType.Document }; + var rootNode = new RawNode + { + NodeType = XmlNodeType.Document, + XmlWhitespaceSensitivity = this.currentXmlWhitespaceSensitivity, + }; this.elementStack.Push(rootNode); while (this.position < this.originalXml.Length) { @@ -63,7 +77,14 @@ private RawNode ParseXml() // we turn all whitespace into a single new line if it has more than a single newline in it // then we remove the leading/trailing whitespace nodes from a nodes child nodes before sending it to printing // then during printing when we find this we add a new line - this.AddNode(new RawNode { NodeType = XmlNodeType.Whitespace, Value = "\n" }); + this.AddNode( + new RawNode + { + NodeType = XmlNodeType.Whitespace, + Value = "\n", + XmlWhitespaceSensitivity = this.currentXmlWhitespaceSensitivity, + } + ); } this.SkipWhitespace(); @@ -169,7 +190,12 @@ private void ParseComment() this.position++; } - var node = new RawNode { NodeType = XmlNodeType.Comment, Value = $"" }; + var node = new RawNode + { + NodeType = XmlNodeType.Comment, + Value = $"", + XmlWhitespaceSensitivity = this.currentXmlWhitespaceSensitivity, + }; this.AddNode(node); } @@ -198,7 +224,12 @@ private void ParseCData() this.position++; } - var node = new RawNode { NodeType = XmlNodeType.CDATA, Value = $"" }; + var node = new RawNode + { + NodeType = XmlNodeType.CDATA, + Value = $"", + XmlWhitespaceSensitivity = this.currentXmlWhitespaceSensitivity, + }; this.AddNode(node); } @@ -235,6 +266,7 @@ private void ParseProcessingInstruction() Name = name, NodeType = XmlNodeType.ProcessingInstruction, Value = $"", + XmlWhitespaceSensitivity = this.currentXmlWhitespaceSensitivity, }; this.AddNode(node); @@ -250,6 +282,7 @@ private void ParseEndElement() if (this.elementStack.Count > 0) { var element = this.elementStack.Pop(); + this.currentXmlWhitespaceSensitivity = element.XmlWhitespaceSensitivity; // we don't want to keep around any leading or trailing newlines in an elements children // it is easier to remove them here instead of dealing with it in the printer for (var x = element.Nodes.Count - 1; x >= 0; x--) @@ -296,12 +329,24 @@ private void ParseStartElement() this.SkipToChar('>'); + var xmlWhitespaceSensitivity = this.currentXmlWhitespaceSensitivity; + + var spaceAttribute = attributes.FirstOrDefault(o => o.Name == "xml:space"); + if (spaceAttribute != null) + { + xmlWhitespaceSensitivity = + spaceAttribute.Value == "preserve" + ? XmlWhitespaceSensitivity.Strict + : XmlWhitespaceSensitivity.Ignore; + } + var node = new RawNode { Name = name, NodeType = XmlNodeType.Element, IsEmpty = isEmpty, Attributes = attributes.ToArray(), + XmlWhitespaceSensitivity = xmlWhitespaceSensitivity, }; this.AddNode(node); @@ -309,6 +354,7 @@ private void ParseStartElement() if (!isEmpty) { this.elementStack.Push(node); + this.currentXmlWhitespaceSensitivity = xmlWhitespaceSensitivity; } } @@ -339,7 +385,12 @@ private void ParseText() { return; } - var node = new RawNode { NodeType = XmlNodeType.Text, Value = text }; + var node = new RawNode + { + NodeType = XmlNodeType.Text, + Value = text, + XmlWhitespaceSensitivity = this.currentXmlWhitespaceSensitivity, + }; this.AddNode(node); } @@ -516,7 +567,12 @@ private void ParseDocType() this.position++; } - var node = new RawNode { NodeType = XmlNodeType.DocumentType, Value = content.ToString() }; + var node = new RawNode + { + NodeType = XmlNodeType.DocumentType, + Value = content.ToString(), + XmlWhitespaceSensitivity = this.currentXmlWhitespaceSensitivity, + }; this.AddNode(node); } diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs index 59cd81310..2dd2124b9 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs @@ -34,7 +34,7 @@ Doc PrintLineBeforeChildren() } if ( - context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict + rawNode.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict && rawNode.Nodes.FirstOrDefault() is { NodeType: XmlNodeType.Text, Value: ['\n', ..] or ['\r', ..] } ) @@ -45,7 +45,7 @@ context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict if ( rawNode.Attributes.Length == 0 && rawNode.Nodes is [{ NodeType: XmlNodeType.Text }] - && context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict + && rawNode.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict ) { return Doc.Null; @@ -73,14 +73,14 @@ Doc PrintLineAfterChildren() if ( rawNode.Attributes.Length == 0 && rawNode.Nodes is [{ NodeType: XmlNodeType.Text }] - && context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict + && rawNode.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict ) { return Doc.Null; } if ( - context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict + rawNode.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict && rawNode.Nodes is [{ NodeType: XmlNodeType.Text }] && rawNode.Nodes[0].Value.TrimEnd(' ')[^1] is '\r' or '\n' ) diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs index 2121b075a..8e06a352b 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs @@ -86,7 +86,7 @@ private static Doc GetTextValue(RawNode rawNode, PrintingContext context) return Doc.Null; } - if (context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Ignore) + if (rawNode.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Ignore) { if (rawNode.PreviousNode is null) { diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Tag.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Tag.cs index 07d40b372..1c148e432 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Tag.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Tag.cs @@ -150,7 +150,7 @@ Life is demanding. */ - return context.Options.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict + return rawNode.XmlWhitespaceSensitivity is XmlWhitespaceSensitivity.Strict && rawNode.NextNode is null && rawNode.IsTextLike() && rawNode.GetLastDescendant() is { NodeType: XmlNodeType.Text } textNode @@ -175,7 +175,7 @@ PrintingContext context * > XmlWhitespaceSensitivity.Ignore, - "Xaml" => XmlWhitespaceSensitivity.Xaml, _ => XmlWhitespaceSensitivity.Strict, }, }, diff --git a/Src/CSharpier.Tests/FormattingTests/BaseTest.cs b/Src/CSharpier.Tests/FormattingTests/BaseTest.cs index 8d902d2a3..435159cc7 100644 --- a/Src/CSharpier.Tests/FormattingTests/BaseTest.cs +++ b/Src/CSharpier.Tests/FormattingTests/BaseTest.cs @@ -39,9 +39,8 @@ public void BuildTests(DynamicTestBuilderContext context, string folder) { var parts = directoryName.Split('_'); directoryName = parts[0]; - xmlWhitespaceSensitivity = - parts[1] is "ignore" ? XmlWhitespaceSensitivity.Ignore - : parts[1] is "xaml" ? XmlWhitespaceSensitivity.Xaml + xmlWhitespaceSensitivity = parts[1] is "ignore" + ? XmlWhitespaceSensitivity.Ignore : XmlWhitespaceSensitivity.Strict; } diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_XmlSpace.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_XmlSpace.expected.test new file mode 100644 index 000000000..265f2c726 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_XmlSpace.expected.test @@ -0,0 +1,11 @@ + + + TextValue + + + + TextValue + + TextValue + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_XmlSpace.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_XmlSpace.test new file mode 100644 index 000000000..0da1bc645 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/Elements_XmlSpace.test @@ -0,0 +1,13 @@ + + + TextValue + + + + TextValue + + + TextValue + + + diff --git a/Src/CSharpier.Tests/OptionsProviderTests.cs b/Src/CSharpier.Tests/OptionsProviderTests.cs index b7e946150..8f314961e 100644 --- a/Src/CSharpier.Tests/OptionsProviderTests.cs +++ b/Src/CSharpier.Tests/OptionsProviderTests.cs @@ -91,50 +91,6 @@ public async Task Should_Return_Default_Options_With_Empty_File(string fileName) ShouldHaveDefaultCSharpOptions(result); } - [Test] - public async Task Should_Return_Json_Extension_Options() - { - var context = new TestContext(); - context.WhenAFileExists( - "c:/test/.csharpierrc.json", - """ -{ - "printWidth": 10, - "endOfLine": "crlf", - "xmlWhitespaceSensitivity": "xaml" -} -""" - ); - - var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); - - result.Width.Should().Be(10); - result.EndOfLine.Should().Be(EndOfLine.CRLF); - result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Xaml); - } - - [Test] - [Arguments("yaml")] - [Arguments("yml")] - public async Task Should_Return_Yaml_Extension_Options(string extension) - { - var context = new TestContext(); - context.WhenAFileExists( - $"c:/test/.csharpierrc.{extension}", - """ -printWidth: 10 -endOfLine: crlf -xmlWhitespaceSensitivity: xaml -""" - ); - - var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); - - result.Width.Should().Be(10); - result.EndOfLine.Should().Be(EndOfLine.CRLF); - result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Xaml); - } - [Test] [Arguments("{ \"printWidth\": 10 }")] [Arguments("printWidth: 10")] @@ -336,32 +292,6 @@ public async Task Should_Return_Default_Xml_Options_With_Empty_EditorConfig(stri ShouldHaveDefaultXmlOptions(result); } - [Test] - public async Task Should_Support_EditorConfig_Basic() - { - var context = new TestContext(); - context.WhenAFileExists( - "c:/test/.editorconfig", - """ - - [*] - indent_style = space - indent_size = 2 - max_line_length = 10 - end_of_line = crlf - csharpier_xml_whitespace_sensitivity = xaml - """ - ); - - var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.cs"); - - result.UseTabs.Should().BeFalse(); - result.IndentSize.Should().Be(2); - result.Width.Should().Be(10); - result.EndOfLine.Should().Be(EndOfLine.CRLF); - result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Xaml); - } - [Test] public async Task Should_Support_EditorConfig_With_Comments() { diff --git a/Src/CSharpier.Tests/RawNodeReaderTests.cs b/Src/CSharpier.Tests/RawNodeReaderTests.cs index ba2661de4..0ad762266 100644 --- a/Src/CSharpier.Tests/RawNodeReaderTests.cs +++ b/Src/CSharpier.Tests/RawNodeReaderTests.cs @@ -1,6 +1,7 @@ #pragma warning disable using AwesomeAssertions; +using CSharpier.Core; using CSharpier.Core.Xml; namespace CSharpier.Tests; @@ -116,8 +117,74 @@ public void Should_Read_Various_Attributes(string xml, string attributeValue) attribute.Value.Should().Be(attributeValue.Replace("\"", """)); } - private static List ReadAllNodes(string xml) + [Test] + public void Should_Set_Top_Level_Whitespace() + { + var nodes = ReadAllNodes( + """ + + """, + XmlWhitespaceSensitivity.Ignore + ); + nodes.First().XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Ignore); + } + + [Test] + public void Should_Override_Whitespace() + { + var nodes = ReadAllNodes( + """ + + """, + XmlWhitespaceSensitivity.Ignore + ); + nodes.First().XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Strict); + } + + [Test] + public void Should_Override_Whitespace_On_Children() + { + var nodes = ReadAllNodes( + """ + + + + """, + XmlWhitespaceSensitivity.Ignore + ); + nodes + .First() + .Nodes.First() + .XmlWhitespaceSensitivity.Should() + .Be(XmlWhitespaceSensitivity.Strict); + } + + [Test] + public void Should_Override_Whitespace_On_Children2() + { + var nodes = ReadAllNodes( + """ + + + + + + """, + XmlWhitespaceSensitivity.Ignore + ); + nodes + .First() + .Nodes.First() + .Nodes.First() + .XmlWhitespaceSensitivity.Should() + .Be(XmlWhitespaceSensitivity.Ignore); + } + + private static List ReadAllNodes( + string xml, + XmlWhitespaceSensitivity xmlWhitespaceSensitivity = XmlWhitespaceSensitivity.Strict + ) { - return RawNodeReader.ParseXml(xml, Environment.NewLine).Nodes; + return RawNodeReader.ParseXml(xml, Environment.NewLine, xmlWhitespaceSensitivity).Nodes; } } From 7dd8e762794857dcf11f11abcd8d8959ef0b73e0 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Sun, 22 Feb 2026 12:06:59 -0600 Subject: [PATCH 24/27] Defaulting xaml and axaml to xml ignore whitespace --- Src/CSharpier.Benchmarks/Program.cs | 10 ++++- .../EditorConfig/EditorConfigSections.cs | 5 ++- .../Options/ConfigurationFileOptions.cs | 20 +++++---- Src/CSharpier.Cli/Options/OptionsProvider.cs | 17 +++---- Src/CSharpier.Core/CodeFormatterOptions.cs | 4 +- Src/CSharpier.Core/PrinterOptions.cs | 29 +++++++++--- Src/CSharpier.Core/PublicAPI.Unshipped.txt | 6 ++- .../XmlWhitespaceSensitivity.cs | 2 +- .../Controllers/FormatController.cs | 14 +++--- Src/CSharpier.Tests/DeepRecursionTests.cs | 5 ++- Src/CSharpier.Tests/DocPrinterTests.cs | 8 +++- .../FormattingTests/BaseTest.cs | 3 +- .../FormattingTests/LineEndingEdgeCase.cs | 5 ++- Src/CSharpier.Tests/LineEndingTests.cs | 14 ++++-- Src/CSharpier.Tests/OptionsProviderTests.cs | 45 ++++++++++++++++--- Src/CSharpier.Tests/PrinterOptionsTests.cs | 10 ++++- Src/CSharpier.Tests/Samples/Samples.cs | 6 ++- 17 files changed, 146 insertions(+), 57 deletions(-) diff --git a/Src/CSharpier.Benchmarks/Program.cs b/Src/CSharpier.Benchmarks/Program.cs index b8bdbfe3d..d929eb8c3 100644 --- a/Src/CSharpier.Benchmarks/Program.cs +++ b/Src/CSharpier.Benchmarks/Program.cs @@ -53,7 +53,10 @@ public void XmlReader_Parse() public void Default_CodeFormatter_Tests() { CSharpFormatter - .FormatAsync(this.largeTestCode, new PrinterOptions(Formatter.CSharp)) + .FormatAsync( + this.largeTestCode, + new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict) + ) .GetAwaiter() .GetResult(); } @@ -62,7 +65,10 @@ public void Default_CodeFormatter_Tests() public void Default_CodeFormatter_Complex() { CSharpFormatter - .FormatAsync(this.largeComplexCode, new PrinterOptions(Formatter.CSharp)) + .FormatAsync( + this.largeComplexCode, + new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict) + ) .GetAwaiter() .GetResult(); } diff --git a/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs b/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs index bd7e33ec4..058807dd8 100644 --- a/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs +++ b/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs @@ -26,7 +26,10 @@ internal class EditorConfigSections return null; } - var printerOptions = new PrinterOptions(parsedFormatter); + var printerOptions = new PrinterOptions( + parsedFormatter, + PrinterOptions.GetXmlWhitespaceSensitivity(filePath) + ); if (resolvedConfiguration.MaxLineLength is { } maxLineLength) { diff --git a/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs b/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs index a83508c02..73b4c10d3 100644 --- a/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs +++ b/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs @@ -11,8 +11,7 @@ internal class ConfigurationFileOptions public bool UseTabs { get; init; } [JsonConverter(typeof(CaseInsensitiveEnumConverter))] - public XmlWhitespaceSensitivity XmlWhitespaceSensitivity { get; init; } = - XmlWhitespaceSensitivity.Strict; + public XmlWhitespaceSensitivity? XmlWhitespaceSensitivity { get; init; } [JsonConverter(typeof(CaseInsensitiveEnumConverter))] public EndOfLine EndOfLine { get; init; } @@ -36,26 +35,32 @@ out var parsedFormatter return null; } - return new PrinterOptions(parsedFormatter) + return new PrinterOptions( + parsedFormatter, + matchingOverride.XmlWhitespaceSensitivity + ?? PrinterOptions.GetXmlWhitespaceSensitivity(filePath) + ) { IndentSize = matchingOverride.IndentSize, UseTabs = matchingOverride.UseTabs, Width = matchingOverride.PrintWidth, EndOfLine = matchingOverride.EndOfLine, - XmlWhitespaceSensitivity = matchingOverride.XmlWhitespaceSensitivity, }; } var formatter = PrinterOptions.GetFormatter(filePath); if (formatter != Formatter.Unknown) { - return new PrinterOptions(formatter) + return new PrinterOptions( + formatter, + this.XmlWhitespaceSensitivity + ?? PrinterOptions.GetXmlWhitespaceSensitivity(filePath) + ) { IndentSize = this.IndentSize ?? (formatter == Formatter.XML ? 2 : 4), UseTabs = this.UseTabs, Width = this.PrintWidth, EndOfLine = this.EndOfLine, - XmlWhitespaceSensitivity = this.XmlWhitespaceSensitivity, }; } @@ -80,8 +85,7 @@ internal class Override public bool UseTabs { get; init; } [JsonConverter(typeof(CaseInsensitiveEnumConverter))] - public XmlWhitespaceSensitivity XmlWhitespaceSensitivity { get; init; } = - XmlWhitespaceSensitivity.Strict; + public XmlWhitespaceSensitivity? XmlWhitespaceSensitivity { get; init; } [JsonConverter(typeof(CaseInsensitiveEnumConverter))] public EndOfLine EndOfLine { get; init; } diff --git a/Src/CSharpier.Cli/Options/OptionsProvider.cs b/Src/CSharpier.Cli/Options/OptionsProvider.cs index d731813ec..17b084e81 100644 --- a/Src/CSharpier.Cli/Options/OptionsProvider.cs +++ b/Src/CSharpier.Cli/Options/OptionsProvider.cs @@ -119,17 +119,8 @@ CancellationToken cancellationToken { // TODO #1794 what other tests do we need for xml:space preserve? compare the strict to ignore tests to see what changed // TODO #1794 do a lot more testing with this, can review the repos once the file extension stuff is figured out. - // TODO #1794 the xml whitespace should default based on the file - // possible defaults - // xaml/axaml files = ignore - // everything else = strict - csproj - the whitespace before/after properties has to be handled where you use it, otherwise it can cause issues - /* this will include the whitespace around 'One', so you can't use Condition="'$(MyProperty)' == 'One'", you have to trim it - - - One - - - */ + // TODO #1794 do we need other tests for xml whitespace? I think it still defaults properly based on file extension even with no value in config files + if (this.specifiedConfigFile is not null) { return this.specifiedConfigFile.ConvertToPrinterOptions(filePath); @@ -160,7 +151,9 @@ CancellationToken cancellationToken } var formatter = PrinterOptions.GetFormatter(filePath); - return formatter != Formatter.Unknown ? new PrinterOptions(formatter) : null; + return formatter != Formatter.Unknown + ? new PrinterOptions(formatter, PrinterOptions.GetXmlWhitespaceSensitivity(filePath)) + : null; } private Task FindCSharpierConfigAsync(string directoryName) diff --git a/Src/CSharpier.Core/CodeFormatterOptions.cs b/Src/CSharpier.Core/CodeFormatterOptions.cs index 8246d8b61..5f123d9c8 100644 --- a/Src/CSharpier.Core/CodeFormatterOptions.cs +++ b/Src/CSharpier.Core/CodeFormatterOptions.cs @@ -7,10 +7,12 @@ public class CodeFormatterOptions public int IndentSize { get; init; } = 4; public EndOfLine EndOfLine { get; init; } = EndOfLine.Auto; public bool IncludeGenerated { get; init; } + public XmlWhitespaceSensitivity XmlWhitespaceSensitivity { get; init; } = + XmlWhitespaceSensitivity.Strict; internal PrinterOptions ToPrinterOptions() { - return new(Formatter.CSharp) + return new(Formatter.CSharp, this.XmlWhitespaceSensitivity) { Width = this.Width, UseTabs = this.IndentStyle == IndentStyle.Tabs, diff --git a/Src/CSharpier.Core/PrinterOptions.cs b/Src/CSharpier.Core/PrinterOptions.cs index 48a78d678..f5208158c 100644 --- a/Src/CSharpier.Core/PrinterOptions.cs +++ b/Src/CSharpier.Core/PrinterOptions.cs @@ -2,7 +2,10 @@ namespace CSharpier.Core; -internal class PrinterOptions(Formatter formatter) +internal class PrinterOptions( + Formatter formatter, + XmlWhitespaceSensitivity xmlWhitespaceSensitivity +) { public bool IncludeAST { get; init; } public bool IncludeDocTree { get; init; } @@ -28,7 +31,7 @@ public int IndentSize public bool IncludeGenerated { get; set; } public Formatter Formatter { get; } = formatter; public XmlWhitespaceSensitivity XmlWhitespaceSensitivity { get; set; } = - XmlWhitespaceSensitivity.Strict; + xmlWhitespaceSensitivity; public const int WidthUsedByTests = 100; @@ -62,15 +65,31 @@ public static Formatter GetFormatter(string filePath) var extension = possibleExtension[1..].ToLower(CultureInfo.InvariantCulture); - var formatter = extension switch + return extension switch { "cs" => Formatter.CSharp, "csx" => Formatter.CSharpScript, - "config" or "csproj" or "props" or "slnx" or "targets" or "xaml" or "xml" => + "config" or "csproj" or "props" or "slnx" or "targets" or "xaml" or "xml" or "axaml" => Formatter.XML, _ => Formatter.Unknown, }; - return formatter; + } + + public static XmlWhitespaceSensitivity GetXmlWhitespaceSensitivity(string filePath) + { + var possibleExtension = Path.GetExtension(filePath); + if (possibleExtension == string.Empty) + { + return XmlWhitespaceSensitivity.Strict; + } + + var extension = possibleExtension[1..].ToLower(CultureInfo.InvariantCulture); + + return extension switch + { + "xaml" or "axaml" => XmlWhitespaceSensitivity.Ignore, + _ => XmlWhitespaceSensitivity.Strict, + }; } } diff --git a/Src/CSharpier.Core/PublicAPI.Unshipped.txt b/Src/CSharpier.Core/PublicAPI.Unshipped.txt index 5f282702b..f02a42556 100644 --- a/Src/CSharpier.Core/PublicAPI.Unshipped.txt +++ b/Src/CSharpier.Core/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ - \ No newline at end of file +CSharpier.Core.CodeFormatterOptions.XmlWhitespaceSensitivity.get -> CSharpier.Core.XmlWhitespaceSensitivity +CSharpier.Core.CodeFormatterOptions.XmlWhitespaceSensitivity.init -> void +CSharpier.Core.XmlWhitespaceSensitivity +CSharpier.Core.XmlWhitespaceSensitivity.Ignore = 1 -> CSharpier.Core.XmlWhitespaceSensitivity +CSharpier.Core.XmlWhitespaceSensitivity.Strict = 0 -> CSharpier.Core.XmlWhitespaceSensitivity \ No newline at end of file diff --git a/Src/CSharpier.Core/XmlWhitespaceSensitivity.cs b/Src/CSharpier.Core/XmlWhitespaceSensitivity.cs index 0fab517f7..f4af93102 100644 --- a/Src/CSharpier.Core/XmlWhitespaceSensitivity.cs +++ b/Src/CSharpier.Core/XmlWhitespaceSensitivity.cs @@ -1,6 +1,6 @@ namespace CSharpier.Core; -internal enum XmlWhitespaceSensitivity +public enum XmlWhitespaceSensitivity { Strict, Ignore, diff --git a/Src/CSharpier.Playground/Controllers/FormatController.cs b/Src/CSharpier.Playground/Controllers/FormatController.cs index 28094e9c5..d65f434af 100644 --- a/Src/CSharpier.Playground/Controllers/FormatController.cs +++ b/Src/CSharpier.Playground/Controllers/FormatController.cs @@ -48,18 +48,20 @@ CancellationToken cancellationToken var result = await CodeFormatter.FormatAsync( model.Code, - new PrinterOptions(parsedFormatter) + new PrinterOptions( + parsedFormatter, + model.XmlWhitespaceSensitivity switch + { + "Ignore" => XmlWhitespaceSensitivity.Ignore, + _ => XmlWhitespaceSensitivity.Strict, + } + ) { IncludeAST = true, IncludeDocTree = true, Width = model.PrintWidth, IndentSize = model.IndentSize, UseTabs = model.UseTabs, - XmlWhitespaceSensitivity = model.XmlWhitespaceSensitivity switch - { - "Ignore" => XmlWhitespaceSensitivity.Ignore, - _ => XmlWhitespaceSensitivity.Strict, - }, }, cancellationToken ); diff --git a/Src/CSharpier.Tests/DeepRecursionTests.cs b/Src/CSharpier.Tests/DeepRecursionTests.cs index e8059a9c9..099bfcd9d 100644 --- a/Src/CSharpier.Tests/DeepRecursionTests.cs +++ b/Src/CSharpier.Tests/DeepRecursionTests.cs @@ -10,7 +10,10 @@ public class DeepRecursionTests public async Task Format_Should_Return_Error_For_Deep_Recursion() { var code = this.uglyLongConcatenatedString; - var result = await CSharpFormatter.FormatAsync(code, new PrinterOptions(Formatter.CSharp)); + var result = await CSharpFormatter.FormatAsync( + code, + new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict) + ); result.FailureMessage.Should().Be("We can't handle this deep of recursion yet."); } diff --git a/Src/CSharpier.Tests/DocPrinterTests.cs b/Src/CSharpier.Tests/DocPrinterTests.cs index 4bbd22752..b02297275 100644 --- a/Src/CSharpier.Tests/DocPrinterTests.cs +++ b/Src/CSharpier.Tests/DocPrinterTests.cs @@ -728,7 +728,11 @@ public void Print_Should_Include_Single_NewLine_To_End_File(int instances, strin doc += endOfLine; } - var result = DocPrinter.Print(doc, new PrinterOptions(Formatter.CSharp), endOfLine); + var result = DocPrinter.Print( + doc, + new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict), + endOfLine + ); result.Should().Be($"1{endOfLine}"); } @@ -786,7 +790,7 @@ private static string Print( return DocPrinter .Print( doc, - new PrinterOptions(Formatter.CSharp) + new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict) { Width = width, TrimInitialLines = trimInitialLines, diff --git a/Src/CSharpier.Tests/FormattingTests/BaseTest.cs b/Src/CSharpier.Tests/FormattingTests/BaseTest.cs index 435159cc7..a77c2cfe4 100644 --- a/Src/CSharpier.Tests/FormattingTests/BaseTest.cs +++ b/Src/CSharpier.Tests/FormattingTests/BaseTest.cs @@ -52,12 +52,11 @@ public void BuildTests(DynamicTestBuilderContext context, string folder) _ => Formatter.Unknown, }; - var printerOptions = new PrinterOptions(formatter) + var printerOptions = new PrinterOptions(formatter, xmlWhitespaceSensitivity) { UseTabs = useTabs, Width = PrinterOptions.WidthUsedByTests, IndentSize = formatter == Formatter.XML ? 2 : 4, - XmlWhitespaceSensitivity = xmlWhitespaceSensitivity, }; context.AddTest( diff --git a/Src/CSharpier.Tests/FormattingTests/LineEndingEdgeCase.cs b/Src/CSharpier.Tests/FormattingTests/LineEndingEdgeCase.cs index 137eedfdc..68a0af82c 100644 --- a/Src/CSharpier.Tests/FormattingTests/LineEndingEdgeCase.cs +++ b/Src/CSharpier.Tests/FormattingTests/LineEndingEdgeCase.cs @@ -32,7 +32,10 @@ void MethodName() var result = await CSharpFormatter.FormatAsync( unformattedCode, - new PrinterOptions(Formatter.CSharp) { EndOfLine = endOfLine }, + new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict) + { + EndOfLine = endOfLine, + }, CancellationToken.None ); diff --git a/Src/CSharpier.Tests/LineEndingTests.cs b/Src/CSharpier.Tests/LineEndingTests.cs index 137896ff9..445b90bae 100644 --- a/Src/CSharpier.Tests/LineEndingTests.cs +++ b/Src/CSharpier.Tests/LineEndingTests.cs @@ -22,7 +22,7 @@ public async Task LineEndings_Should_Not_Affect_Printed_Output_With_Verbatim_Str var codeWithLf = code.Replace("\r\n", "\n"); var codeWithCrLf = codeWithLf.Replace("\n", "\r\n"); - var printerOptions = new PrinterOptions(Formatter.CSharp) + var printerOptions = new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict) { EndOfLine = EndOfLine.Auto, Width = 80, @@ -49,7 +49,7 @@ public async Task LineEndings_Should_Not_Affect_Printed_Output_With_Interpolated var codeWithLf = code.Replace("\r\n", "\n"); var codeWithCrLf = codeWithLf.Replace("\n", "\r\n"); - var printerOptions = new PrinterOptions(Formatter.CSharp) + var printerOptions = new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict) { EndOfLine = EndOfLine.Auto, Width = 80, @@ -74,7 +74,10 @@ EndOfLine endOfLine string value = @""one{newLine}two""; }} "; - var printerOptions = new PrinterOptions(Formatter.CSharp) { EndOfLine = endOfLine }; + var printerOptions = new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict) + { + EndOfLine = endOfLine, + }; var result = await CSharpFormatter.FormatAsync(code, printerOptions); result.Code.Should().NotContain($"one{newLine}two"); } @@ -93,7 +96,10 @@ EndOfLine endOfLine string value = @""one{escapedNewLine}two""; }} "; - var printerOptions = new PrinterOptions(Formatter.CSharp) { EndOfLine = endOfLine }; + var printerOptions = new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict) + { + EndOfLine = endOfLine, + }; var result = await CSharpFormatter.FormatAsync(code, printerOptions); result.Code.Should().Contain(escapedNewLine); } diff --git a/Src/CSharpier.Tests/OptionsProviderTests.cs b/Src/CSharpier.Tests/OptionsProviderTests.cs index 8f314961e..8149b7e37 100644 --- a/Src/CSharpier.Tests/OptionsProviderTests.cs +++ b/Src/CSharpier.Tests/OptionsProviderTests.cs @@ -21,14 +21,19 @@ public async Task Should_Return_Default_CSharp_Options_With_Empty_Json() } [Test] - public async Task Should_Return_Default_Xml_Options_With_Empty_Json() + [Arguments("xml")] + [Arguments("xaml")] + public async Task Should_Return_Default_Xml_Options_With_Empty_Json(string extension) { var context = new TestContext(); context.WhenAFileExists("c:/test/.csharpierrc", "{}"); - var result = await context.CreateProviderAndGetOptionsFor("c:/test", "c:/test/test.xml"); + var result = await context.CreateProviderAndGetOptionsFor( + "c:/test", + "c:/test/test." + extension + ); - ShouldHaveDefaultXmlOptions(result); + ShouldHaveDefaultXmlOptions(result, extension); } [Test] @@ -65,7 +70,7 @@ string extension "c:/test/test." + extension ); - ShouldHaveDefaultXmlOptions(result); + ShouldHaveDefaultXmlOptions(result, extension); } [Test] @@ -276,6 +281,7 @@ public async Task Should_Return_Default_CSharp_Options_With_Empty_EditorConfig(s [Test] [Arguments("xml")] + [Arguments("xaml")] [Arguments("csproj")] [Arguments("props")] [Arguments("targets")] @@ -289,7 +295,7 @@ public async Task Should_Return_Default_Xml_Options_With_Empty_EditorConfig(stri "c:/test", "c:/test/test." + extension ); - ShouldHaveDefaultXmlOptions(result); + ShouldHaveDefaultXmlOptions(result, extension); } [Test] @@ -766,6 +772,25 @@ public async Task Should_Prefer_Closer_CSharpierrc() result.IndentSize.Should().Be(1); } + [Test] + [Arguments("xml", XmlWhitespaceSensitivity.Strict)] + [Arguments("xaml", XmlWhitespaceSensitivity.Ignore)] + [Arguments("axaml", XmlWhitespaceSensitivity.Ignore)] + public async Task Should_Default_XmlWhitespaceSensitivity( + string fileExtension, + XmlWhitespaceSensitivity expectedXmlWhitespaceSensitivity + ) + { + var context = new TestContext(); + + var result = await context.CreateProviderAndGetOptionsFor( + "c:/test", + "c:/test/file." + fileExtension + ); + + result.XmlWhitespaceSensitivity.Should().Be(expectedXmlWhitespaceSensitivity); + } + private static void ShouldHaveDefaultCSharpOptions(PrinterOptions printerOptions) { printerOptions.Width.Should().Be(100); @@ -774,13 +799,19 @@ private static void ShouldHaveDefaultCSharpOptions(PrinterOptions printerOptions printerOptions.EndOfLine.Should().Be(EndOfLine.Auto); } - private static void ShouldHaveDefaultXmlOptions(PrinterOptions printerOptions) + private static void ShouldHaveDefaultXmlOptions(PrinterOptions printerOptions, string extension) { printerOptions.Width.Should().Be(100); printerOptions.IndentSize.Should().Be(2); printerOptions.UseTabs.Should().BeFalse(); printerOptions.EndOfLine.Should().Be(EndOfLine.Auto); - printerOptions.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Strict); + printerOptions + .XmlWhitespaceSensitivity.Should() + .Be( + extension is "xaml" or "axaml" + ? XmlWhitespaceSensitivity.Ignore + : XmlWhitespaceSensitivity.Strict + ); } private sealed class TestContext diff --git a/Src/CSharpier.Tests/PrinterOptionsTests.cs b/Src/CSharpier.Tests/PrinterOptionsTests.cs index 692de9614..0f3db6f4a 100644 --- a/Src/CSharpier.Tests/PrinterOptionsTests.cs +++ b/Src/CSharpier.Tests/PrinterOptionsTests.cs @@ -13,7 +13,10 @@ public void GetLineEndings_Should_Return_Easy_Cases(EndOfLine endOfLine, string var code = "tester\n"; var result = PrinterOptions.GetLineEnding( code, - new PrinterOptions(Formatter.CSharp) { EndOfLine = endOfLine } + new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict) + { + EndOfLine = endOfLine, + } ); result.Should().Be(expected); @@ -26,7 +29,10 @@ public void GetLineEndings_Should_Return_Easy_Cases(EndOfLine endOfLine, string [Arguments("tester", "\n")] public void GetLineEndings_With_Auto_Should_Detect(string code, string expected) { - var result = PrinterOptions.GetLineEnding(code, new PrinterOptions(Formatter.CSharp)); + var result = PrinterOptions.GetLineEnding( + code, + new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict) + ); result.Should().Be(expected); } diff --git a/Src/CSharpier.Tests/Samples/Samples.cs b/Src/CSharpier.Tests/Samples/Samples.cs index 12d95c179..a0c31a4a6 100644 --- a/Src/CSharpier.Tests/Samples/Samples.cs +++ b/Src/CSharpier.Tests/Samples/Samples.cs @@ -27,7 +27,11 @@ public static async Task RunTest(string fileName) var code = await File.ReadAllTextAsync(file); var result = await CSharpFormatter.FormatAsync( code, - new PrinterOptions(Formatter.CSharp) { IncludeDocTree = true, IncludeAST = true } + new PrinterOptions(Formatter.CSharp, XmlWhitespaceSensitivity.Strict) + { + IncludeDocTree = true, + IncludeAST = true, + } ); var syntaxNodeComparer = new SyntaxNodeComparer( From 68f85fc94e73f4c16cf1ead8f9e3f7e7a5f1fe4c Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Sat, 4 Apr 2026 13:15:57 -0500 Subject: [PATCH 25/27] fixing merge issues --- Shell/UpdateCSharpierRepos.psm1 | 3 ++- .../Options/ConfigurationFileOptions.cs | 2 -- Src/CSharpier.Cli/Options/OptionsProvider.cs | 1 - Src/CSharpier.Core/PrinterOptions.cs | 2 +- Src/CSharpier.Tests/OptionsProviderTests.cs | 12 ++++++------ 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Shell/UpdateCSharpierRepos.psm1 b/Shell/UpdateCSharpierRepos.psm1 index b72b809a1..cf6618c0c 100644 --- a/Shell/UpdateCSharpierRepos.psm1 +++ b/Shell/UpdateCSharpierRepos.psm1 @@ -15,6 +15,7 @@ function CSH-UpdateCSharpierRepos() $repositories += "https://github.com/dotnet/runtime.git" $repositories += "https://github.com/mono/mono.git" $repositories += "https://github.com/increase-POS/Res-Server.git" + $repositories += "https://github.com/stride3d/stride.git" $tempLocation = "c:\temp\UpdateRepos" @@ -46,7 +47,7 @@ function CSH-UpdateCSharpierRepos() Set-Location $destination & git checkout main - $extensions = (".csproj", ".props", ".targets", ".xml", ".config", ".cs") + $extensions = (".csproj", ".props", ".targets", ".xml", ".config", ".cs", ".xaml", ".axaml") foreach ($extension in $extensions) { diff --git a/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs b/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs index 6bf11fc78..73b4c10d3 100644 --- a/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs +++ b/Src/CSharpier.Cli/Options/ConfigurationFileOptions.cs @@ -45,7 +45,6 @@ out var parsedFormatter UseTabs = matchingOverride.UseTabs, Width = matchingOverride.PrintWidth, EndOfLine = matchingOverride.EndOfLine, - XmlWhitespaceSensitivity = matchingOverride.XmlWhitespaceSensitivity, }; } @@ -62,7 +61,6 @@ out var parsedFormatter UseTabs = this.UseTabs, Width = this.PrintWidth, EndOfLine = this.EndOfLine, - XmlWhitespaceSensitivity = this.XmlWhitespaceSensitivity, }; } diff --git a/Src/CSharpier.Cli/Options/OptionsProvider.cs b/Src/CSharpier.Cli/Options/OptionsProvider.cs index ac3ba31ab..079312e93 100644 --- a/Src/CSharpier.Cli/Options/OptionsProvider.cs +++ b/Src/CSharpier.Cli/Options/OptionsProvider.cs @@ -119,7 +119,6 @@ CancellationToken cancellationToken { // TODO #1794 what other tests do we need for xml:space preserve? compare the strict to ignore tests to see what changed // TODO #1794 do a lot more testing with this, can review the repos once the file extension stuff is figured out. - // TODO #1794 do we need other tests for xml whitespace? I think it still defaults properly based on file extension even with no value in config files if (this.specifiedConfigFile is not null) { diff --git a/Src/CSharpier.Core/PrinterOptions.cs b/Src/CSharpier.Core/PrinterOptions.cs index 64c617daf..6f31acb68 100644 --- a/Src/CSharpier.Core/PrinterOptions.cs +++ b/Src/CSharpier.Core/PrinterOptions.cs @@ -69,7 +69,7 @@ public static Formatter GetFormatter(string filePath) { "cs" => Formatter.CSharp, "csx" => Formatter.CSharpScript, - "config" or "csproj" or "props" or "slnx" or "targets" or "xaml" or "xml" => + "config" or "csproj" or "props" or "slnx" or "targets" or "axaml" or "xaml" or "xml" => Formatter.XML, _ => Formatter.Unknown, }; diff --git a/Src/CSharpier.Tests/OptionsProviderTests.cs b/Src/CSharpier.Tests/OptionsProviderTests.cs index d3f8d064a..f3f1e1ffe 100644 --- a/Src/CSharpier.Tests/OptionsProviderTests.cs +++ b/Src/CSharpier.Tests/OptionsProviderTests.cs @@ -107,7 +107,7 @@ public async Task Should_Return_Json_Extension_Options() { "printWidth": 10, "endOfLine": "crlf", - "xmlWhitespaceSensitivity": "xaml" + "xmlWhitespaceSensitivity": "ignore" } """ ); @@ -116,7 +116,7 @@ public async Task Should_Return_Json_Extension_Options() result.Width.Should().Be(10); result.EndOfLine.Should().Be(EndOfLine.CRLF); - result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Xaml); + result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Ignore); } [Test] @@ -130,7 +130,7 @@ public async Task Should_Return_Yaml_Extension_Options(string extension) """ printWidth: 10 endOfLine: crlf -xmlWhitespaceSensitivity: xaml +xmlWhitespaceSensitivity: ignore """ ); @@ -138,7 +138,7 @@ public async Task Should_Return_Yaml_Extension_Options(string extension) result.Width.Should().Be(10); result.EndOfLine.Should().Be(EndOfLine.CRLF); - result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Xaml); + result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Ignore); } [Test] @@ -356,7 +356,7 @@ public async Task Should_Support_EditorConfig_Basic() indent_size = 2 max_line_length = 10 end_of_line = crlf - csharpier_xml_whitespace_sensitivity = xaml + csharpier_xml_whitespace_sensitivity = ignore """ ); @@ -366,7 +366,7 @@ public async Task Should_Support_EditorConfig_Basic() result.IndentSize.Should().Be(2); result.Width.Should().Be(10); result.EndOfLine.Should().Be(EndOfLine.CRLF); - result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Xaml); + result.XmlWhitespaceSensitivity.Should().Be(XmlWhitespaceSensitivity.Ignore); } [Test] From e93c53aec13e26564efe0d801501026f3c39657f Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Sat, 4 Apr 2026 13:53:29 -0500 Subject: [PATCH 26/27] fixing an edge case --- Src/CSharpier.Core/Xml/XNodePrinters/Node.cs | 11 +++++++++++ Src/CSharpier.Playground/ClientApp/src/Controls.tsx | 8 -------- .../TestFiles/xml_ignore/NewLines.test | 6 ++++++ .../TestFiles/xml_ignore/StrictWhitespace.test | 3 +-- 4 files changed, 18 insertions(+), 10 deletions(-) create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_ignore/NewLines.test diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs index 8e06a352b..6d0a8b406 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Node.cs @@ -1,3 +1,4 @@ +using System.Text.RegularExpressions; using System.Xml; using CSharpier.Core.CSharp.SyntaxPrinter; using CSharpier.Core.DocTypes; @@ -97,6 +98,16 @@ private static Doc GetTextValue(RawNode rawNode, PrintingContext context) { textValue = textValue.TrimEnd(); } + + if (rawNode.Parent.Nodes.Count == 1) + { + if (textValue.Length > 2) + { + var innerValue = textValue[1..^1]; + textValue = + textValue[0] + Regex.Replace(innerValue, "\\s+", " ") + textValue[^1]; + } + } } if (rawNode.Parent?.Nodes.First() == rawNode) diff --git a/Src/CSharpier.Playground/ClientApp/src/Controls.tsx b/Src/CSharpier.Playground/ClientApp/src/Controls.tsx index 99c727255..df906f932 100644 --- a/Src/CSharpier.Playground/ClientApp/src/Controls.tsx +++ b/Src/CSharpier.Playground/ClientApp/src/Controls.tsx @@ -71,14 +71,6 @@ export const Controls = observer(() => { /> Strict -