aspnetcore/test/Microsoft.AspNetCore.Compon.../Razor/IntermediateNodeWriter.cs

317 lines
11 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Components.Razor;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
// Serializes single IR nodes (shallow).
public class IntermediateNodeWriter :
IntermediateNodeVisitor,
IExtensionIntermediateNodeVisitor<HtmlElementIntermediateNode>,
IExtensionIntermediateNodeVisitor<HtmlBlockIntermediateNode>,
IExtensionIntermediateNodeVisitor<ComponentExtensionNode>,
IExtensionIntermediateNodeVisitor<ComponentAttributeExtensionNode>,
IExtensionIntermediateNodeVisitor<ComponentChildContentIntermediateNode>,
IExtensionIntermediateNodeVisitor<ComponentTypeArgumentExtensionNode>,
IExtensionIntermediateNodeVisitor<ComponentTypeInferenceMethodIntermediateNode>,
IExtensionIntermediateNodeVisitor<RouteAttributeExtensionNode>,
IExtensionIntermediateNodeVisitor<RefExtensionNode>
{
private readonly TextWriter _writer;
public IntermediateNodeWriter(TextWriter writer)
{
_writer = writer;
}
public int Depth { get; set; }
public override void VisitDefault(IntermediateNode node)
{
WriteBasicNode(node);
}
public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
{
WriteContentNode(node, string.Join(" ", node.Modifiers), node.ClassName, node.BaseType, string.Join(", ", node.Interfaces ?? new List<string>()));
}
public override void VisitCSharpExpressionAttributeValue(CSharpExpressionAttributeValueIntermediateNode node)
{
WriteContentNode(node, node.Prefix);
}
public override void VisitCSharpCodeAttributeValue(CSharpCodeAttributeValueIntermediateNode node)
{
WriteContentNode(node, node.Prefix);
}
public override void VisitToken(IntermediateToken node)
{
WriteContentNode(node, node.Kind.ToString(), node.Content);
}
public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node)
{
WriteContentNode(node, node.DirectiveName);
}
public override void VisitDirective(DirectiveIntermediateNode node)
{
WriteContentNode(node, node.DirectiveName);
}
public override void VisitDirectiveToken(DirectiveTokenIntermediateNode node)
{
WriteContentNode(node, node.Content);
}
public override void VisitFieldDeclaration(FieldDeclarationIntermediateNode node)
{
WriteContentNode(node, string.Join(" ", node.Modifiers), node.FieldType, node.FieldName);
}
public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
{
WriteContentNode(node, node.Prefix, node.Suffix);
}
public override void VisitHtmlAttributeValue(HtmlAttributeValueIntermediateNode node)
{
WriteContentNode(node, node.Prefix);
}
public override void VisitNamespaceDeclaration(NamespaceDeclarationIntermediateNode node)
{
WriteContentNode(node, node.Content);
}
public override void VisitMethodDeclaration(MethodDeclarationIntermediateNode node)
{
WriteContentNode(node, string.Join(" ", node.Modifiers), node.ReturnType, node.MethodName);
}
public override void VisitUsingDirective(UsingDirectiveIntermediateNode node)
{
WriteContentNode(node, node.Content);
}
public override void VisitTagHelper(TagHelperIntermediateNode node)
{
WriteContentNode(node, node.TagName, string.Format("{0}.{1}", nameof(TagMode), node.TagMode));
}
public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
{
WriteContentNode(node, node.AttributeName, node.BoundAttribute.DisplayName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure));
}
public override void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node)
{
WriteContentNode(node, node.AttributeName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure));
}
public override void VisitExtension(ExtensionIntermediateNode node)
{
// This will be called for nodes that are internal implementation details of Razor,
// like the design time directive nodes.
if (node.GetType().Assembly == typeof(RazorCodeDocument).Assembly)
{
WriteBasicNode(node);
}
else
{
throw new InvalidOperationException("Unknown node type: " + node.GetType());
}
}
protected void WriteBasicNode(IntermediateNode node)
{
WriteIndent();
WriteName(node);
WriteSeparator();
WriteSourceRange(node);
}
protected void WriteContentNode(IntermediateNode node, params string[] content)
{
WriteIndent();
WriteName(node);
WriteSeparator();
WriteSourceRange(node);
for (var i = 0; i < content.Length; i++)
{
WriteSeparator();
WriteContent(content[i]);
}
}
protected void WriteIndent()
{
for (var i = 0; i < Depth; i++)
{
for (var j = 0; j < 4; j++)
{
_writer.Write(' ');
}
}
}
protected void WriteSeparator()
{
_writer.Write(" - ");
}
protected void WriteNewLine()
{
_writer.WriteLine();
}
protected void WriteName(IntermediateNode node)
{
var typeName = node.GetType().Name;
if (typeName.EndsWith("IntermediateNode"))
{
_writer.Write(typeName.Substring(0, typeName.Length - "IntermediateNode".Length));
}
else
{
_writer.Write(typeName);
}
}
protected void WriteSourceRange(IntermediateNode node)
{
if (node.Source != null)
{
WriteSourceRange(node.Source.Value);
}
}
protected void WriteSourceRange(SourceSpan sourceRange)
{
_writer.Write("(");
_writer.Write(sourceRange.AbsoluteIndex);
_writer.Write(":");
_writer.Write(sourceRange.LineIndex);
_writer.Write(",");
_writer.Write(sourceRange.CharacterIndex);
_writer.Write(" [");
_writer.Write(sourceRange.Length);
_writer.Write("] ");
if (sourceRange.FilePath != null)
{
var fileName = sourceRange.FilePath.Substring(sourceRange.FilePath.LastIndexOf('/') + 1);
_writer.Write(fileName);
}
_writer.Write(")");
}
protected void WriteDiagnostics(IntermediateNode node)
{
if (node.HasDiagnostics)
{
_writer.Write("| ");
for (var i = 0; i < node.Diagnostics.Count; i++)
{
var diagnostic = node.Diagnostics[i];
_writer.Write("{");
WriteSourceRange(diagnostic.Span);
_writer.Write(": ");
_writer.Write(diagnostic.Severity);
_writer.Write(" ");
_writer.Write(diagnostic.Id);
_writer.Write(": ");
// Purposefully not writing out the entire message to ensure readable IR and because messages
// can span multiple lines. Not using string.GetHashCode because we can't have any collisions.
using (var md5 = MD5.Create())
{
var diagnosticMessage = diagnostic.GetMessage();
var messageBytes = Encoding.UTF8.GetBytes(diagnosticMessage);
var messageHash = md5.ComputeHash(messageBytes);
var stringHashBuilder = new StringBuilder();
for (var j = 0; j < messageHash.Length; j++)
{
stringHashBuilder.Append(messageHash[j].ToString("x2"));
}
var stringHash = stringHashBuilder.ToString();
_writer.Write(stringHash);
}
_writer.Write("} ");
}
}
}
protected void WriteContent(string content)
{
if (content == null)
{
return;
}
// We explicitly escape newlines in node content so that the IR can be compared line-by-line. The escaped
// newline cannot be platform specific so we need to drop the windows \r.
// Also, escape our separator so we can search for ` - `to find delimiters.
_writer.Write(content.Replace("\r", string.Empty).Replace("\n", "\\n").Replace(" - ", "\\-"));
}
void IExtensionIntermediateNodeVisitor<HtmlElementIntermediateNode>.VisitExtension(HtmlElementIntermediateNode node)
{
WriteContentNode(node, node.TagName);
}
void IExtensionIntermediateNodeVisitor<HtmlBlockIntermediateNode>.VisitExtension(HtmlBlockIntermediateNode node)
{
WriteContentNode(node, node.Content);
}
void IExtensionIntermediateNodeVisitor<ComponentExtensionNode>.VisitExtension(ComponentExtensionNode node)
{
WriteContentNode(node, node.TagName, node.TypeName);
}
void IExtensionIntermediateNodeVisitor<ComponentAttributeExtensionNode>.VisitExtension(ComponentAttributeExtensionNode node)
{
WriteContentNode(node, node.AttributeName, node.PropertyName);
}
void IExtensionIntermediateNodeVisitor<ComponentChildContentIntermediateNode>.VisitExtension(ComponentChildContentIntermediateNode node)
{
WriteContentNode(node, node.AttributeName);
}
void IExtensionIntermediateNodeVisitor<ComponentTypeArgumentExtensionNode>.VisitExtension(ComponentTypeArgumentExtensionNode node)
{
WriteContentNode(node, node.TypeParameterName);
}
void IExtensionIntermediateNodeVisitor<ComponentTypeInferenceMethodIntermediateNode>.VisitExtension(ComponentTypeInferenceMethodIntermediateNode node)
{
WriteContentNode(node, node.FullTypeName, node.MethodName);
}
void IExtensionIntermediateNodeVisitor<RouteAttributeExtensionNode>.VisitExtension(RouteAttributeExtensionNode node)
{
WriteContentNode(node, node.Template);
}
void IExtensionIntermediateNodeVisitor<RefExtensionNode>.VisitExtension(RefExtensionNode node)
{
WriteContentNode(node, node.IdentifierToken.Content, node.IsComponentCapture ? node.ComponentCaptureTypeName : "Element");
}
}
}