From adea3b25b3e4eaad4cfe0116145df8a2418dfe51 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Sat, 18 Apr 2026 10:49:53 -0500 Subject: [PATCH 1/2] Implementing the basic csharpier-ignore for xml closes #1788 --- Src/CSharpier.Core/CSharp/CSharpFormatter.cs | 2 + .../CSharp/SyntaxPrinter/CSharpierIgnore.cs | 20 +- .../CSharp/SyntaxPrinter/PrintingContext.cs | 1 + Src/CSharpier.Core/Xml/RawNode.cs | 3 + Src/CSharpier.Core/Xml/RawNodeReader.cs | 215 ++++++++++-------- .../Xml/XNodePrinters/Element.cs | 7 + .../Xml/XNodePrinters/ElementChildren.cs | 1 + Src/CSharpier.Core/Xml/XmlFormatter.cs | 3 +- .../SyntaxPrinter/CSharpierIgnoreTests.cs | 1 + .../xml_strict/CSharpierIgnore.expected.test | 16 ++ .../TestFiles/xml_strict/CSharpierIgnore.test | 16 ++ Src/CSharpier.Tests/RawNodeReaderTests.cs | 4 +- 12 files changed, 191 insertions(+), 98 deletions(-) create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.expected.test create mode 100644 Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.test diff --git a/Src/CSharpier.Core/CSharp/CSharpFormatter.cs b/Src/CSharpier.Core/CSharp/CSharpFormatter.cs index 162d17550..1b13084fe 100644 --- a/Src/CSharpier.Core/CSharp/CSharpFormatter.cs +++ b/Src/CSharpier.Core/CSharp/CSharpFormatter.cs @@ -150,6 +150,7 @@ bool TryGetCompilationFailure(out CodeFormatterResult compilationResult) var lineEnding = PrinterOptions.GetLineEnding(syntaxTree.ToString(), printerOptions); var printingContext = new PrintingContext { + NormalizedXml = string.Empty, Options = new PrintingContext.PrintingContextOptions { LineEnding = lineEnding, @@ -177,6 +178,7 @@ bool TryGetCompilationFailure(out CodeFormatterResult compilationResult) var formattingContext2 = new PrintingContext { + NormalizedXml = string.Empty, Options = new PrintingContext.PrintingContextOptions { LineEnding = lineEnding, diff --git a/Src/CSharpier.Core/CSharp/SyntaxPrinter/CSharpierIgnore.cs b/Src/CSharpier.Core/CSharp/SyntaxPrinter/CSharpierIgnore.cs index eef5e770a..e0c25271b 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxPrinter/CSharpierIgnore.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxPrinter/CSharpierIgnore.cs @@ -29,10 +29,22 @@ internal static partial class CSharpierIgnore public static readonly Regex IgnoreEndRegex = IgnoreEndRegexGenerator(); public static readonly Regex WhiteSpaceLineEndingsRegex = WhiteSpaceLineEndingsGenerator(); #else - private static readonly Regex IgnoreRegex = new("^// csharpier-ignore($| -)"); - public static readonly Regex IgnoreStartRegex = new("^// csharpier-ignore-start($| -)"); - public static readonly Regex IgnoreEndRegex = new("^// csharpier-ignore-end($| -)"); - public static readonly Regex WhiteSpaceLineEndingsRegex = new(@"[\t\v\f ]*(\r\n?|\n)"); + private static readonly Regex IgnoreRegex = new( + "^// csharpier-ignore($| -)", + RegexOptions.Compiled + ); + public static readonly Regex IgnoreStartRegex = new( + "^// csharpier-ignore-start($| -)", + RegexOptions.Compiled + ); + public static readonly Regex IgnoreEndRegex = new( + "^// csharpier-ignore-end($| -)", + RegexOptions.Compiled + ); + public static readonly Regex WhiteSpaceLineEndingsRegex = new( + @"[\t\v\f ]*(\r\n?|\n)", + RegexOptions.Compiled + ); #endif public static bool HasIgnoreComment(SyntaxNode syntaxNode) => diff --git a/Src/CSharpier.Core/CSharp/SyntaxPrinter/PrintingContext.cs b/Src/CSharpier.Core/CSharp/SyntaxPrinter/PrintingContext.cs index dcac53eae..b895605e7 100644 --- a/Src/CSharpier.Core/CSharp/SyntaxPrinter/PrintingContext.cs +++ b/Src/CSharpier.Core/CSharp/SyntaxPrinter/PrintingContext.cs @@ -6,6 +6,7 @@ namespace CSharpier.Core.CSharp.SyntaxPrinter; internal class PrintingContext { + public required string NormalizedXml { get; init; } public required PrintingContextOptions Options { get; init; } public PrintingContextState State { get; } = new(); diff --git a/Src/CSharpier.Core/Xml/RawNode.cs b/Src/CSharpier.Core/Xml/RawNode.cs index 2cca831fd..e3eb93c48 100644 --- a/Src/CSharpier.Core/Xml/RawNode.cs +++ b/Src/CSharpier.Core/Xml/RawNode.cs @@ -22,6 +22,9 @@ internal class RawNode public List Nodes { get; set; } = []; public string Value { get; set; } = string.Empty; public required XmlWhitespaceSensitivity XmlWhitespaceSensitivity { get; set; } + public int StartPosition { get; set; } + public int EndPosition { get; set; } + public bool IsCSharpierIgnore { get; set; } public bool IsTextLike() { diff --git a/Src/CSharpier.Core/Xml/RawNodeReader.cs b/Src/CSharpier.Core/Xml/RawNodeReader.cs index 676e7dbfd..140be2817 100644 --- a/Src/CSharpier.Core/Xml/RawNodeReader.cs +++ b/Src/CSharpier.Core/Xml/RawNodeReader.cs @@ -10,16 +10,26 @@ namespace CSharpier.Core.Xml; #endif class RawNodeReader { - private readonly string originalXml; + private readonly string normalizedXml; private readonly string lineEnding; private XmlWhitespaceSensitivity currentXmlWhitespaceSensitivity; private int position; private readonly Stack elementStack = new(); #if !NETSTANDARD2_0 + [GeneratedRegex("^ csharpier-ignore ($|- )")] + private static partial Regex IgnoreRegexGenerator(); + [GeneratedRegex(@"\r\n|\n|\r", RegexOptions.Compiled)] - private static partial Regex NewlineRegex(); + private static partial Regex NewlineRegexGenerator(); + + private static readonly Regex IgnoreRegex = IgnoreRegexGenerator(); + private static readonly Regex NewlineRegex = NewlineRegexGenerator(); #else + private static readonly Regex IgnoreRegex = new( + "^ csharpier-ignore ($|- )", + RegexOptions.Compiled + ); private static readonly Regex NewlineRegex = new(@"\r\n|\n|\r", RegexOptions.Compiled); #endif @@ -29,16 +39,12 @@ private RawNodeReader( XmlWhitespaceSensitivity xmlWhitespaceSensitivity ) { - this.originalXml = NewlineRegex -#if !NETSTANDARD2_0 - () -#endif - .Replace(xml, "\n"); + this.normalizedXml = NewlineRegex.Replace(xml, "\n"); this.lineEnding = lineEnding; this.currentXmlWhitespaceSensitivity = xmlWhitespaceSensitivity; } - public static RawNode ParseXml( + public static (RawNode rawNode, string normalizedXml) ParseXml( string originalXml, string lineEnding, XmlWhitespaceSensitivity xmlWhitespaceSensitivity @@ -48,7 +54,7 @@ XmlWhitespaceSensitivity xmlWhitespaceSensitivity return reader.ParseXml(); } - private RawNode ParseXml() + private (RawNode rawNode, string normalizedXml) ParseXml() { var rootNode = new RawNode { @@ -56,15 +62,15 @@ private RawNode ParseXml() XmlWhitespaceSensitivity = this.currentXmlWhitespaceSensitivity, }; this.elementStack.Push(rootNode); - while (this.position < this.originalXml.Length) + while (this.position < this.normalizedXml.Length) { var newLines = 0; while ( - this.position < this.originalXml.Length - && char.IsWhiteSpace(this.originalXml[this.position]) + this.position < this.normalizedXml.Length + && char.IsWhiteSpace(this.normalizedXml[this.position]) ) { - if (this.originalXml[this.position] == '\n') + if (this.normalizedXml[this.position] == '\n') { newLines++; } @@ -88,12 +94,12 @@ private RawNode ParseXml() } this.SkipWhitespace(); - if (this.position >= this.originalXml.Length) + if (this.position >= this.normalizedXml.Length) { break; } - if (this.originalXml[this.position] == '<') + if (this.normalizedXml[this.position] == '<') { this.ParseTag(); } @@ -107,14 +113,14 @@ private RawNode ParseXml() } } - return rootNode; + return (rootNode, this.normalizedXml); } private void SkipWhitespace() { while ( - this.position < this.originalXml.Length - && char.IsWhiteSpace(this.originalXml[this.position]) + this.position < this.normalizedXml.Length + && char.IsWhiteSpace(this.normalizedXml[this.position]) ) { this.position++; @@ -123,40 +129,40 @@ private void SkipWhitespace() private void ParseTag() { - if (this.position + 1 >= this.originalXml.Length) + if (this.position + 1 >= this.normalizedXml.Length) { return; } - if (this.originalXml[this.position + 1] == '!') + if (this.normalizedXml[this.position + 1] == '!') { if ( - this.position + 4 < this.originalXml.Length - && this.originalXml.Substring(this.position, 4) == "") + if (this.normalizedXml.Substring(this.position, 3) == "-->") { this.position += 3; break; } - if (this.originalXml[this.position] == '\n') + if (this.normalizedXml[this.position] == '\n') { content.Append(this.lineEnding); } else { - content.Append(this.originalXml[this.position]); + content.Append(this.normalizedXml[this.position]); } this.position++; } + var actualContent = content.ToString(); + var node = new RawNode { NodeType = XmlNodeType.Comment, - Value = $"", + Value = $"", XmlWhitespaceSensitivity = this.currentXmlWhitespaceSensitivity, + IsCSharpierIgnore = IgnoreRegex.IsMatch(actualContent), }; this.AddNode(node); @@ -205,21 +214,21 @@ private void ParseCData() this.position += 9; // Skip "") + if (this.normalizedXml.Substring(this.position, 3) == "]]>") { this.position += 3; break; } - if (this.originalXml[this.position] == '\n') + if (this.normalizedXml[this.position] == '\n') { content.Append(this.lineEnding); } else { - content.Append(this.originalXml[this.position]); + content.Append(this.normalizedXml[this.position]); } this.position++; } @@ -242,21 +251,21 @@ private void ParseProcessingInstruction() this.SkipWhitespace(); var content = new StringBuilder(); - while (this.position + 1 < this.originalXml.Length) + while (this.position + 1 < this.normalizedXml.Length) { - if (this.originalXml.Substring(this.position, 2) == "?>") + if (this.normalizedXml.Substring(this.position, 2) == "?>") { this.position += 2; break; } - if (this.originalXml[this.position] == '\n') + if (this.normalizedXml[this.position] == '\n') { content.Append(this.lineEnding); } else { - content.Append(this.originalXml[this.position]); + content.Append(this.normalizedXml[this.position]); } this.position++; } @@ -279,49 +288,60 @@ private void ParseEndElement() this.ReadName(); this.SkipToChar('>'); - if (this.elementStack.Count > 0) + if (this.elementStack.Count <= 0) + { + return; + } + + var element = this.elementStack.Pop(); + element.EndPosition = this.position; + 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--) { - 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--) + if ( + (x == element.Nodes.Count - 1 || x == 0) + && element.Nodes[x].NodeType is XmlNodeType.Whitespace + ) { - if ( - (x == element.Nodes.Count - 1 || x == 0) - && element.Nodes[x].NodeType is XmlNodeType.Whitespace - ) - { - element.Nodes.RemoveAt(x); - } + element.Nodes.RemoveAt(x); } + } - for (var x = 0; x < element.Nodes.Count; x++) + for (var x = 0; x < element.Nodes.Count; x++) + { + if (x > 0) { - if (x > 0) - { - element.Nodes[x - 1].NextNode = element.Nodes[x]; - element.Nodes[x].PreviousNode = element.Nodes[x - 1]; - } + element.Nodes[x - 1].NextNode = element.Nodes[x]; + element.Nodes[x].PreviousNode = element.Nodes[x - 1]; + } - if (x == element.Nodes.Count - 2) - { - element.Nodes[x].NextNode = element.Nodes[x + 1]; - element.Nodes[x + 1].PreviousNode = element.Nodes[x]; - } + if (x == element.Nodes.Count - 2) + { + element.Nodes[x].NextNode = element.Nodes[x + 1]; + element.Nodes[x + 1].PreviousNode = element.Nodes[x]; } } } private void ParseStartElement() { + var originalPosition = this.position; + while ( + originalPosition > 0 && this.normalizedXml[originalPosition - 1] is ' ' or '\n' or '\r' + ) + { + originalPosition--; + } + this.position++; // Skip "<" var name = this.ReadName(); var attributes = this.ParseAttributes(); var isEmpty = false; - if (this.position < this.originalXml.Length && this.originalXml[this.position] == '/') + if (this.position < this.normalizedXml.Length && this.normalizedXml[this.position] == '/') { isEmpty = true; this.position++; @@ -347,6 +367,8 @@ private void ParseStartElement() IsEmpty = isEmpty, Attributes = attributes.ToArray(), XmlWhitespaceSensitivity = xmlWhitespaceSensitivity, + StartPosition = originalPosition, + EndPosition = this.position, // set to the end of the start tag which is correct for empty elements and will be adjusted later for non-empty }; this.AddNode(node); @@ -362,20 +384,22 @@ private void ParseText() { var content = new StringBuilder(); // we skip all whitespace in the main read, so for parsing text go backwards until we find the actual start - while (this.position >= 0 && this.originalXml[this.position - 1] != '>') + while (this.position >= 0 && this.normalizedXml[this.position - 1] != '>') { this.position--; } - while (this.position < this.originalXml.Length && this.originalXml[this.position] != '<') + while ( + this.position < this.normalizedXml.Length && this.normalizedXml[this.position] != '<' + ) { - if (this.originalXml[this.position] == '\n') + if (this.normalizedXml[this.position] == '\n') { content.Append(this.lineEnding); } else { - content.Append(this.originalXml[this.position]); + content.Append(this.normalizedXml[this.position]); } this.position++; } @@ -399,13 +423,13 @@ private List ParseAttributes() { var attributes = new List(); - while (this.position < this.originalXml.Length) + while (this.position < this.normalizedXml.Length) { this.SkipWhitespace(); if ( - this.position >= this.originalXml.Length - || this.originalXml[this.position] == '>' - || this.originalXml[this.position] == '/' + this.position >= this.normalizedXml.Length + || this.normalizedXml[this.position] == '>' + || this.normalizedXml[this.position] == '/' ) { break; @@ -414,7 +438,10 @@ private List ParseAttributes() var attrName = this.ReadName(); this.SkipWhitespace(); - if (this.position < this.originalXml.Length && this.originalXml[this.position] == '=') + if ( + this.position < this.normalizedXml.Length + && this.normalizedXml[this.position] == '=' + ) { this.position++; this.SkipWhitespace(); @@ -437,17 +464,17 @@ private string ReadName() { var name = new StringBuilder(); while ( - this.position < this.originalXml.Length + this.position < this.normalizedXml.Length && ( - char.IsLetterOrDigit(this.originalXml[this.position]) - || this.originalXml[this.position] == '_' - || this.originalXml[this.position] == ':' - || this.originalXml[this.position] == '-' - || this.originalXml[this.position] == '.' + char.IsLetterOrDigit(this.normalizedXml[this.position]) + || this.normalizedXml[this.position] == '_' + || this.normalizedXml[this.position] == ':' + || this.normalizedXml[this.position] == '-' + || this.normalizedXml[this.position] == '.' ) ) { - name.Append(this.originalXml[this.position]); + name.Append(this.normalizedXml[this.position]); this.position++; } return name.ToString(); @@ -455,12 +482,12 @@ private string ReadName() private string ReadQuotedValue() { - if (this.position >= this.originalXml.Length) + if (this.position >= this.normalizedXml.Length) { return string.Empty; } - var quote = this.originalXml[this.position]; + var quote = this.normalizedXml[this.position]; if (quote is not ('"' or '\'')) { return string.Empty; @@ -469,20 +496,22 @@ private string ReadQuotedValue() this.position++; // Skip opening quote var value = new StringBuilder(); - while (this.position < this.originalXml.Length && this.originalXml[this.position] != quote) + while ( + this.position < this.normalizedXml.Length && this.normalizedXml[this.position] != quote + ) { - if (this.originalXml[this.position] == '\n') + if (this.normalizedXml[this.position] == '\n') { value.Append(this.lineEnding); } else { - value.Append(this.originalXml[this.position]); + value.Append(this.normalizedXml[this.position]); } this.position++; } - if (this.position < this.originalXml.Length) + if (this.position < this.normalizedXml.Length) { this.position++; // Skip closing quote } @@ -492,12 +521,14 @@ private string ReadQuotedValue() private void SkipToChar(char target) { - while (this.position < this.originalXml.Length && this.originalXml[this.position] != target) + while ( + this.position < this.normalizedXml.Length && this.normalizedXml[this.position] != target + ) { this.position++; } - if (this.position < this.originalXml.Length) + if (this.position < this.normalizedXml.Length) { this.position++; // Skip the target character } @@ -524,9 +555,9 @@ private void ParseDocType() var inQuotes = false; var quoteChar = '\0'; - while (this.position < this.originalXml.Length) + while (this.position < this.normalizedXml.Length) { - var ch = this.originalXml[this.position]; + var ch = this.normalizedXml[this.position]; if (!inQuotes) { diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs index 2dd2124b9..2e3a9c762 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs @@ -8,6 +8,13 @@ internal static class Element { internal static Doc Print(RawNode rawNode, PrintingContext context) { + if (rawNode.PreviousNode?.IsCSharpierIgnore is true) + { + return context + .NormalizedXml[rawNode.StartPosition..rawNode.EndPosition] + .Replace("\n", context.Options.LineEnding); + } + var shouldHugContent = false; var attrGroupId = context.GroupFor("element-attr-group-id"); diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs b/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs index 6ef60d5ec..6994dda92 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs @@ -145,6 +145,7 @@ prevNode.NodeType is XmlNodeType.Text or XmlNodeType.CDATA prevNode.NodeType is XmlNodeType.Element && nextNode.NodeType is XmlNodeType.Text or XmlNodeType.CDATA ) + || prevNode.IsCSharpierIgnore ? Doc.Null : Doc.HardLine; } diff --git a/Src/CSharpier.Core/Xml/XmlFormatter.cs b/Src/CSharpier.Core/Xml/XmlFormatter.cs index 9b0d12629..e152e2eb2 100644 --- a/Src/CSharpier.Core/Xml/XmlFormatter.cs +++ b/Src/CSharpier.Core/Xml/XmlFormatter.cs @@ -27,13 +27,14 @@ PrinterOptions printerOptions var validationTask = ValidateXmlAsync(xml); var lineEnding = PrinterOptions.GetLineEnding(xml, printerOptions); - var rootNode = RawNodeReader.ParseXml( + var (rootNode, normalizedXml) = RawNodeReader.ParseXml( xml, lineEnding, printerOptions.XmlWhitespaceSensitivity ); var printingContext = new PrintingContext { + NormalizedXml = normalizedXml, Options = new PrintingContext.PrintingContextOptions { LineEnding = lineEnding, diff --git a/Src/CSharpier.Tests/CSharp/SyntaxPrinter/CSharpierIgnoreTests.cs b/Src/CSharpier.Tests/CSharp/SyntaxPrinter/CSharpierIgnoreTests.cs index ffd281939..3f7c6d8da 100644 --- a/Src/CSharpier.Tests/CSharp/SyntaxPrinter/CSharpierIgnoreTests.cs +++ b/Src/CSharpier.Tests/CSharp/SyntaxPrinter/CSharpierIgnoreTests.cs @@ -67,6 +67,7 @@ private static string PrintWithoutFormatting(string code) code, new PrintingContext { + NormalizedXml = code, Options = new PrintingContext.PrintingContextOptions { LineEnding = Environment.NewLine, diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.expected.test new file mode 100644 index 000000000..8a1a3cd45 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.expected.test @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.test new file mode 100644 index 000000000..0805c3c53 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.test @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/Src/CSharpier.Tests/RawNodeReaderTests.cs b/Src/CSharpier.Tests/RawNodeReaderTests.cs index 0ad762266..46681b68d 100644 --- a/Src/CSharpier.Tests/RawNodeReaderTests.cs +++ b/Src/CSharpier.Tests/RawNodeReaderTests.cs @@ -185,6 +185,8 @@ private static List ReadAllNodes( XmlWhitespaceSensitivity xmlWhitespaceSensitivity = XmlWhitespaceSensitivity.Strict ) { - return RawNodeReader.ParseXml(xml, Environment.NewLine, xmlWhitespaceSensitivity).Nodes; + return RawNodeReader + .ParseXml(xml, Environment.NewLine, xmlWhitespaceSensitivity) + .rawNode.Nodes; } } From 4d5cf26a5a6e042040647c9736e2396a1a3b9ccf Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Sun, 26 Apr 2026 09:45:12 -0500 Subject: [PATCH 2/2] Adding basic support for ranged ignore on xml --- Src/CSharpier.Core/Xml/RawNode.cs | 11 +++- Src/CSharpier.Core/Xml/RawNodeReader.cs | 22 +++++++- .../Xml/XNodePrinters/Element.cs | 2 +- .../Xml/XNodePrinters/ElementChildren.cs | 50 +++++++++---------- .../xml_strict/CSharpierIgnore.expected.test | 7 +++ .../TestFiles/xml_strict/CSharpierIgnore.test | 7 +++ 6 files changed, 70 insertions(+), 29 deletions(-) diff --git a/Src/CSharpier.Core/Xml/RawNode.cs b/Src/CSharpier.Core/Xml/RawNode.cs index e3eb93c48..b5fa3720d 100644 --- a/Src/CSharpier.Core/Xml/RawNode.cs +++ b/Src/CSharpier.Core/Xml/RawNode.cs @@ -1,5 +1,6 @@ using System.Text.Json.Serialization; using System.Xml; +using CSharpier.Core.CSharp.SyntaxPrinter; namespace CSharpier.Core.Xml; @@ -24,7 +25,7 @@ internal class RawNode public required XmlWhitespaceSensitivity XmlWhitespaceSensitivity { get; set; } public int StartPosition { get; set; } public int EndPosition { get; set; } - public bool IsCSharpierIgnore { get; set; } + public CSharpierIgnoreType CSharpierIgnoreType { get; set; } public bool IsTextLike() { @@ -61,3 +62,11 @@ public RawNode GetLastDescendant() return base.ToString(); } } + +internal enum CSharpierIgnoreType +{ + None, + Ignore, + IgnoreStart, + IgnoreEnd, +} diff --git a/Src/CSharpier.Core/Xml/RawNodeReader.cs b/Src/CSharpier.Core/Xml/RawNodeReader.cs index 140be2817..6a55a8be8 100644 --- a/Src/CSharpier.Core/Xml/RawNodeReader.cs +++ b/Src/CSharpier.Core/Xml/RawNodeReader.cs @@ -20,16 +20,32 @@ class RawNodeReader [GeneratedRegex("^ csharpier-ignore ($|- )")] private static partial Regex IgnoreRegexGenerator(); + [GeneratedRegex("^ csharpier-ignore-start ($|- )")] + private static partial Regex IgnoreStartRegexGenerator(); + + [GeneratedRegex("^ csharpier-ignore-end ($|- )")] + private static partial Regex IgnoreEndRegexGenerator(); + [GeneratedRegex(@"\r\n|\n|\r", RegexOptions.Compiled)] private static partial Regex NewlineRegexGenerator(); private static readonly Regex IgnoreRegex = IgnoreRegexGenerator(); + private static readonly Regex IgnoreStartRegex = IgnoreStartRegexGenerator(); + private static readonly Regex IgnoreEndRegex = IgnoreEndRegexGenerator(); private static readonly Regex NewlineRegex = NewlineRegexGenerator(); #else private static readonly Regex IgnoreRegex = new( "^ csharpier-ignore ($|- )", RegexOptions.Compiled ); + private static readonly Regex IgnoreStartRegex = new( + "^ csharpier-ignore-start ($|- )", + RegexOptions.Compiled + ); + private static readonly Regex IgnoreEndRegex = new( + "^ csharpier-ignore-end ($|- )", + RegexOptions.Compiled + ); private static readonly Regex NewlineRegex = new(@"\r\n|\n|\r", RegexOptions.Compiled); #endif @@ -203,7 +219,11 @@ private void ParseComment() NodeType = XmlNodeType.Comment, Value = $"", XmlWhitespaceSensitivity = this.currentXmlWhitespaceSensitivity, - IsCSharpierIgnore = IgnoreRegex.IsMatch(actualContent), + CSharpierIgnoreType = + IgnoreRegex.IsMatch(actualContent) ? CSharpierIgnoreType.Ignore + : IgnoreStartRegex.IsMatch(actualContent) ? CSharpierIgnoreType.IgnoreStart + : IgnoreEndRegex.IsMatch(actualContent) ? CSharpierIgnoreType.IgnoreEnd + : CSharpierIgnoreType.None, }; this.AddNode(node); diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs b/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs index 2e3a9c762..5c2f71829 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/Element.cs @@ -8,7 +8,7 @@ internal static class Element { internal static Doc Print(RawNode rawNode, PrintingContext context) { - if (rawNode.PreviousNode?.IsCSharpierIgnore is true) + if (rawNode.PreviousNode?.CSharpierIgnoreType is CSharpierIgnoreType.Ignore) { return context .NormalizedXml[rawNode.StartPosition..rawNode.EndPosition] diff --git a/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs b/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs index 6994dda92..c57020da3 100644 --- a/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs +++ b/Src/CSharpier.Core/Xml/XNodePrinters/ElementChildren.cs @@ -1,7 +1,6 @@ using System.Xml; using CSharpier.Core.CSharp.SyntaxPrinter; using CSharpier.Core.DocTypes; -using CSharpier.Core.Utilities; namespace CSharpier.Core.Xml.XNodePrinters; @@ -17,8 +16,25 @@ public static Doc Print(RawNode node, PrintingContext context) var result = new List(); var x = 0; + var printIgnored = false; foreach (var childNode in node.Nodes) { + if (childNode.CSharpierIgnoreType is CSharpierIgnoreType.IgnoreEnd) + { + printIgnored = false; + x++; + } + + if (printIgnored) + { + result.Add( + context + .NormalizedXml[childNode.StartPosition..childNode.EndPosition] + .Replace("\n", context.Options.LineEnding) + ); + continue; + } + if (childNode.NodeType is XmlNodeType.Whitespace) { if (childNode.NextNode is not { NodeType: XmlNodeType.Text }) @@ -86,41 +102,23 @@ public static Doc Print(RawNode node, PrintingContext context) Doc.Concat(leadingParts), Doc.GroupWithId( groupIds[x], - PrintChild(childNode, context), + Node.Print(childNode, context), Doc.Concat(trailingParts) ) ) ); result.AddRange(nextParts); x++; + + if (childNode.CSharpierIgnoreType is CSharpierIgnoreType.IgnoreStart) + { + printIgnored = true; + } } return Doc.Concat(result); } - public static Doc PrintChild(RawNode child, PrintingContext context) - { - // should we try to support csharpier-ignore some day? - // if (HasPrettierIgnore(child)) - // { - // int endLocation = GetEndLocation(child); - // - // return new List - // { - // PrintOpeningTagPrefix(child, options), - // ReplaceEndOfLine(TrimEnd(options.OriginalText.Substring( - // LocStart(child) + (child.Prev != null && NeedsToBorrowNextOpeningTagStartMarker(child.Prev) - // ? PrintOpeningTagStartMarker(child).Length : 0), - // endLocation - (child.Next != null && NeedsToBorrowPrevClosingTagEndMarker(child.Next) - // ? PrintClosingTagEndMarker(child, options).Length : 0) - // ))), - // PrintClosingTagSuffix(child, options) - // }; - // } - - return Node.Print(child, context); - } - public static Doc PrintBetweenLine(RawNode prevNode, RawNode nextNode) { return @@ -145,7 +143,7 @@ prevNode.NodeType is XmlNodeType.Text or XmlNodeType.CDATA prevNode.NodeType is XmlNodeType.Element && nextNode.NodeType is XmlNodeType.Text or XmlNodeType.CDATA ) - || prevNode.IsCSharpierIgnore + || prevNode.CSharpierIgnoreType is CSharpierIgnoreType.Ignore ? Doc.Null : Doc.HardLine; } diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.expected.test index 8a1a3cd45..92a240710 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.expected.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.expected.test @@ -13,4 +13,11 @@ + + + + + + + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.test index 0805c3c53..0f4e99718 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/xml_strict/CSharpierIgnore.test @@ -13,4 +13,11 @@ + + + + + + +