| File: Language\Intermediate\IntermediateNodeFormatter.cs | Web Access |
| Project: src\roslyn\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text; namespace Microsoft.AspNetCore.Razor.Language.Intermediate; public sealed class IntermediateNodeFormatter( StringBuilder builder, IntermediateNodeFormatter.FormatterMode mode = IntermediateNodeFormatter.FormatterMode.PreferContent, bool includeSource = false) { // Depending on the usage of the formatter we might prefer thoroughness (properties) // or brevity (content). Generally if a node has a single string that provides value // it has content. // // Some nodes have neither: TagHelperBody // Some nodes have content: HtmlContent // Some nodes have properties: Document // Some nodes have both: TagHelperProperty public enum FormatterMode { PreferContent, PreferProperties, } private static readonly Dictionary<Type, string> s_nodeTypeToShortNameMap = []; private static readonly char[] s_charsToEscape = ['\r', '\n', '\t']; private readonly StringBuilder _builder = builder; private readonly FormatterMode Mode = mode; private readonly bool _includeSource = includeSource; private readonly Dictionary<string, string> _properties = new(StringComparer.Ordinal); private string? _content; private static string GetNodeShortName(IntermediateNode node) { lock (s_nodeTypeToShortNameMap) { return s_nodeTypeToShortNameMap.GetOrAdd(node.GetType(), ComputeShortName); } static string ComputeShortName(Type type) { const string ShortNameSuffix = nameof(IntermediateNode); var shortName = type.Name; if (shortName.EndsWith(ShortNameSuffix, StringComparison.Ordinal)) { shortName = shortName[..^ShortNameSuffix.Length]; } return shortName; } } public void WriteChildren(IntermediateNodeCollection children) { Debug.Assert(children != null); _builder.Append(" \""); foreach (var child in children) { if (child is IntermediateToken token) { WriteEscaped(token.Content); } } _builder.Append('"'); } public void WriteContent(string? content) { if (content != null) { _content = content; } } public void WriteProperty(string key, string? value) { Debug.Assert(key != null); if (value != null) { _properties.Add(key, value); } } public void FormatNode(IntermediateNode node) { _builder.Append(GetNodeShortName(node)); if (_includeSource) { _builder.Append(' '); _builder.Append(node.Source?.ToString() ?? "(n/a)"); } node.FormatNode(this); if (ShouldWriteContent()) { _builder.Append(" \""); WriteEscaped(_content); _builder.Append('"'); } if (ShouldWriteProperties()) { _builder.Append(" { "); var first = true; foreach (var (key, value) in _properties) { if (first) { first = false; } else { _builder.Append(", "); } _builder.Append(key); _builder.Append(": \""); WriteEscaped(value); _builder.Append('"'); } _builder.Append(" }"); } _content = null; _properties.Clear(); } [MemberNotNullWhen(true, nameof(_content))] private bool ShouldWriteContent() => _content != null && (_properties.Count == 0 || Mode == FormatterMode.PreferContent); private bool ShouldWriteProperties() => _properties.Count > 0 && (_content == null || Mode == FormatterMode.PreferContent); public void FormatTree(IntermediateNode node) { var visitor = new FormatterVisitor(this); visitor.Visit(node); } private void Write(char value, int repeatCount) { _builder.Append(value, repeatCount); } private void WriteLine() { _builder.AppendLine(); } private void WriteEscaped(string content) { var startIndex = 0; int charToEscapeIndex; while ((charToEscapeIndex = content.IndexOfAny(s_charsToEscape, startIndex)) >= 0) { if (startIndex < charToEscapeIndex) { _builder.Append(content, startIndex, charToEscapeIndex - startIndex); } switch (content[charToEscapeIndex]) { case '\r': _builder.Append("\\r"); break; case '\n': _builder.Append("\\n"); break; case '\t': _builder.Append("\\t"); break; } startIndex = charToEscapeIndex + 1; } if (startIndex < content.Length) { _builder.Append(content, startIndex, content.Length - startIndex); } } private sealed class FormatterVisitor(IntermediateNodeFormatter formatter) : IntermediateNodeWalker { private const int IndentSize = 2; private readonly IntermediateNodeFormatter _formatter = formatter; private int _indent; public override void VisitDefault(IntermediateNode node) { // Indent _formatter.Write(' ', repeatCount: _indent); _formatter.FormatNode(node); _formatter.WriteLine(); // Process children _indent += IndentSize; base.VisitDefault(node); _indent -= IndentSize; } } }