Razor compilation: Support text literals and C# code
This commit is contained in:
parent
3ccdc1d16f
commit
7e40427ffe
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using Microsoft.Blazor.RenderTree;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
||||
{
|
||||
|
|
@ -11,6 +15,8 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
|||
/// </summary>
|
||||
internal class BlazorIntermediateNodeWriter : IntermediateNodeWriter
|
||||
{
|
||||
private const string builderVarName = "builder";
|
||||
|
||||
public override void BeginWriterScope(CodeRenderingContext context, string writer)
|
||||
{
|
||||
throw new System.NotImplementedException(nameof(BeginWriterScope));
|
||||
|
|
@ -23,7 +29,34 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
|||
|
||||
public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeIntermediateNode node)
|
||||
{
|
||||
throw new System.NotImplementedException(nameof(WriteCSharpCode));
|
||||
var isWhitespaceStatement = true;
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
var token = node.Children[i] as IntermediateToken;
|
||||
if (token == null || !string.IsNullOrWhiteSpace(token.Content))
|
||||
{
|
||||
isWhitespaceStatement = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isWhitespaceStatement)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||
{
|
||||
context.CodeWriter.Write(token.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There may be something else inside the statement like an extension node.
|
||||
context.RenderNode(node.Children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteCSharpCodeAttributeValue(CodeRenderingContext context, CSharpCodeAttributeValueIntermediateNode node)
|
||||
|
|
@ -33,7 +66,26 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
|||
|
||||
public override void WriteCSharpExpression(CodeRenderingContext context, CSharpExpressionIntermediateNode node)
|
||||
{
|
||||
throw new System.NotImplementedException(nameof(WriteCSharpExpression));
|
||||
// Render as text node. Later, we'll need to add different handling for expressions
|
||||
// that appear inside elements, e.g., <a href="@url">
|
||||
context.CodeWriter
|
||||
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddText)}");
|
||||
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||
{
|
||||
context.CodeWriter.Write(token.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There may be something else inside the expression like a Template or another extension node.
|
||||
throw new NotImplementedException("Unsupported: CSharpExpression with child node that isn't a CSharp node");
|
||||
}
|
||||
}
|
||||
|
||||
context.CodeWriter
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext context, CSharpExpressionAttributeValueIntermediateNode node)
|
||||
|
|
@ -53,12 +105,26 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
|||
|
||||
public override void WriteHtmlContent(CodeRenderingContext context, HtmlContentIntermediateNode node)
|
||||
{
|
||||
context.CodeWriter.Write("/* HTML content */");
|
||||
context.CodeWriter
|
||||
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddText)}")
|
||||
.WriteStringLiteral(GetContent(node))
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
public override void WriteUsingDirective(CodeRenderingContext context, UsingDirectiveIntermediateNode node)
|
||||
{
|
||||
throw new System.NotImplementedException(nameof(WriteUsingDirective));
|
||||
}
|
||||
|
||||
private static string GetContent(HtmlContentIntermediateNode node)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var htmlTokens = node.Children.OfType<IntermediateToken>().Where(t => t.IsHtml);
|
||||
foreach (var htmlToken in htmlTokens)
|
||||
{
|
||||
builder.Append(htmlToken.Content);
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Extensions;
|
||||
using Microsoft.Blazor.Components;
|
||||
using System.Linq;
|
||||
|
||||
|
|
@ -21,6 +22,8 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
|||
|
||||
_engine = RazorEngine.Create(configure =>
|
||||
{
|
||||
FunctionsDirective.Register(configure);
|
||||
|
||||
configure.SetBaseType(typeof(BlazorComponent).FullName);
|
||||
|
||||
configure.Phases.Remove(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,647 @@
|
|||
// 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 Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
// Copied directly from https://github.com/aspnet/Razor/blob/ff40124594b58b17988d50841175430a4b73d1a9/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/CodeWriterExtensions.cs
|
||||
// (other than the namespace change) because it's internal
|
||||
|
||||
namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
||||
{
|
||||
internal static class CodeWriterExtensions
|
||||
{
|
||||
private const string InstanceMethodFormat = "{0}.{1}";
|
||||
|
||||
private static readonly char[] CStyleStringLiteralEscapeChars =
|
||||
{
|
||||
'\r',
|
||||
'\t',
|
||||
'\"',
|
||||
'\'',
|
||||
'\\',
|
||||
'\0',
|
||||
'\n',
|
||||
'\u2028',
|
||||
'\u2029',
|
||||
};
|
||||
|
||||
public static bool IsAtBeginningOfLine(this CodeWriter writer)
|
||||
{
|
||||
return writer.Length == 0 || writer[writer.Length - 1] == '\n';
|
||||
}
|
||||
|
||||
public static CodeWriter WritePadding(this CodeWriter writer, int offset, SourceSpan? span, CodeRenderingContext context)
|
||||
{
|
||||
if (span == null)
|
||||
{
|
||||
return writer;
|
||||
}
|
||||
|
||||
var basePadding = CalculatePadding();
|
||||
var resolvedPadding = Math.Max(basePadding - offset, 0);
|
||||
|
||||
if (context.Options.IndentWithTabs)
|
||||
{
|
||||
// Avoid writing directly to the StringBuilder here, that will throw off the manual indexing
|
||||
// done by the base class.
|
||||
var tabs = resolvedPadding / context.Options.IndentSize;
|
||||
for (var i = 0; i < tabs; i++)
|
||||
{
|
||||
writer.Write("\t");
|
||||
}
|
||||
|
||||
var spaces = resolvedPadding % context.Options.IndentSize;
|
||||
for (var i = 0; i < spaces; i++)
|
||||
{
|
||||
writer.Write(" ");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < resolvedPadding; i++)
|
||||
{
|
||||
writer.Write(" ");
|
||||
}
|
||||
}
|
||||
|
||||
return writer;
|
||||
|
||||
int CalculatePadding()
|
||||
{
|
||||
var spaceCount = 0;
|
||||
for (var i = span.Value.AbsoluteIndex - 1; i >= 0; i--)
|
||||
{
|
||||
var @char = context.SourceDocument[i];
|
||||
if (@char == '\n' || @char == '\r')
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (@char == '\t')
|
||||
{
|
||||
spaceCount += context.Options.IndentSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
spaceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return spaceCount;
|
||||
}
|
||||
}
|
||||
|
||||
public static CodeWriter WriteVariableDeclaration(this CodeWriter writer, string type, string name, string value)
|
||||
{
|
||||
writer.Write(type).Write(" ").Write(name);
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
writer.Write(" = ").Write(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write(" = null");
|
||||
}
|
||||
|
||||
writer.WriteLine(";");
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
public static CodeWriter WriteBooleanLiteral(this CodeWriter writer, bool value)
|
||||
{
|
||||
return writer.Write(value.ToString().ToLowerInvariant());
|
||||
}
|
||||
|
||||
public static CodeWriter WriteStartAssignment(this CodeWriter writer, string name)
|
||||
{
|
||||
return writer.Write(name).Write(" = ");
|
||||
}
|
||||
|
||||
public static CodeWriter WriteParameterSeparator(this CodeWriter writer)
|
||||
{
|
||||
return writer.Write(", ");
|
||||
}
|
||||
|
||||
public static CodeWriter WriteStartNewObject(this CodeWriter writer, string typeName)
|
||||
{
|
||||
return writer.Write("new ").Write(typeName).Write("(");
|
||||
}
|
||||
|
||||
public static CodeWriter WriteStringLiteral(this CodeWriter writer, string literal)
|
||||
{
|
||||
if (literal.Length >= 256 && literal.Length <= 1500 && literal.IndexOf('\0') == -1)
|
||||
{
|
||||
WriteVerbatimStringLiteral(writer, literal);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteCStyleStringLiteral(writer, literal);
|
||||
}
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
public static CodeWriter WriteUsing(this CodeWriter writer, string name)
|
||||
{
|
||||
return WriteUsing(writer, name, endLine: true);
|
||||
}
|
||||
|
||||
public static CodeWriter WriteUsing(this CodeWriter writer, string name, bool endLine)
|
||||
{
|
||||
writer.Write("using ");
|
||||
writer.Write(name);
|
||||
|
||||
if (endLine)
|
||||
{
|
||||
writer.WriteLine(";");
|
||||
}
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
public static CodeWriter WriteLineNumberDirective(this CodeWriter writer, SourceSpan span)
|
||||
{
|
||||
if (writer.Length >= writer.NewLine.Length && !IsAtBeginningOfLine(writer))
|
||||
{
|
||||
writer.WriteLine();
|
||||
}
|
||||
|
||||
var lineNumberAsString = (span.LineIndex + 1).ToString(CultureInfo.InvariantCulture);
|
||||
return writer.Write("#line ").Write(lineNumberAsString).Write(" \"").Write(span.FilePath).WriteLine("\"");
|
||||
}
|
||||
|
||||
public static CodeWriter WriteStartMethodInvocation(this CodeWriter writer, string methodName)
|
||||
{
|
||||
writer.Write(methodName);
|
||||
|
||||
return writer.Write("(");
|
||||
}
|
||||
|
||||
public static CodeWriter WriteEndMethodInvocation(this CodeWriter writer)
|
||||
{
|
||||
return WriteEndMethodInvocation(writer, endLine: true);
|
||||
}
|
||||
|
||||
public static CodeWriter WriteEndMethodInvocation(this CodeWriter writer, bool endLine)
|
||||
{
|
||||
writer.Write(")");
|
||||
if (endLine)
|
||||
{
|
||||
writer.WriteLine(";");
|
||||
}
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
// Writes a method invocation for the given instance name.
|
||||
public static CodeWriter WriteInstanceMethodInvocation(
|
||||
this CodeWriter writer,
|
||||
string instanceName,
|
||||
string methodName,
|
||||
params string[] parameters)
|
||||
{
|
||||
if (instanceName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instanceName));
|
||||
}
|
||||
|
||||
if (methodName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
}
|
||||
|
||||
return WriteInstanceMethodInvocation(writer, instanceName, methodName, endLine: true, parameters: parameters);
|
||||
}
|
||||
|
||||
// Writes a method invocation for the given instance name.
|
||||
public static CodeWriter WriteInstanceMethodInvocation(
|
||||
this CodeWriter writer,
|
||||
string instanceName,
|
||||
string methodName,
|
||||
bool endLine,
|
||||
params string[] parameters)
|
||||
{
|
||||
if (instanceName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instanceName));
|
||||
}
|
||||
|
||||
if (methodName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
}
|
||||
|
||||
return WriteMethodInvocation(
|
||||
writer,
|
||||
string.Format(CultureInfo.InvariantCulture, InstanceMethodFormat, instanceName, methodName),
|
||||
endLine,
|
||||
parameters);
|
||||
}
|
||||
|
||||
public static CodeWriter WriteStartInstanceMethodInvocation(this CodeWriter writer, string instanceName, string methodName)
|
||||
{
|
||||
if (instanceName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instanceName));
|
||||
}
|
||||
|
||||
if (methodName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
}
|
||||
|
||||
return WriteStartMethodInvocation(
|
||||
writer,
|
||||
string.Format(CultureInfo.InvariantCulture, InstanceMethodFormat, instanceName, methodName));
|
||||
}
|
||||
|
||||
public static CodeWriter WriteField(this CodeWriter writer, IList<string> modifiers, string typeName, string fieldName)
|
||||
{
|
||||
if (modifiers == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modifiers));
|
||||
}
|
||||
|
||||
if (typeName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(typeName));
|
||||
}
|
||||
|
||||
if (fieldName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fieldName));
|
||||
}
|
||||
|
||||
for (var i = 0; i < modifiers.Count; i++)
|
||||
{
|
||||
writer.Write(modifiers[i]);
|
||||
writer.Write(" ");
|
||||
}
|
||||
|
||||
writer.Write(typeName);
|
||||
writer.Write(" ");
|
||||
writer.Write(fieldName);
|
||||
writer.Write(";");
|
||||
writer.WriteLine();
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
public static CodeWriter WriteMethodInvocation(this CodeWriter writer, string methodName, params string[] parameters)
|
||||
{
|
||||
return WriteMethodInvocation(writer, methodName, endLine: true, parameters: parameters);
|
||||
}
|
||||
|
||||
public static CodeWriter WriteMethodInvocation(this CodeWriter writer, string methodName, bool endLine, params string[] parameters)
|
||||
{
|
||||
return
|
||||
WriteStartMethodInvocation(writer, methodName)
|
||||
.Write(string.Join(", ", parameters))
|
||||
.WriteEndMethodInvocation(endLine);
|
||||
}
|
||||
|
||||
public static CodeWriter WriteAutoPropertyDeclaration(this CodeWriter writer, IList<string> modifiers, string typeName, string propertyName)
|
||||
{
|
||||
if (modifiers == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modifiers));
|
||||
}
|
||||
|
||||
if (typeName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(typeName));
|
||||
}
|
||||
|
||||
if (propertyName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(propertyName));
|
||||
}
|
||||
|
||||
for (var i = 0; i < modifiers.Count; i++)
|
||||
{
|
||||
writer.Write(modifiers[i]);
|
||||
writer.Write(" ");
|
||||
}
|
||||
|
||||
writer.Write(typeName);
|
||||
writer.Write(" ");
|
||||
writer.Write(propertyName);
|
||||
writer.Write(" { get; set; }");
|
||||
writer.WriteLine();
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
public static CSharpCodeWritingScope BuildScope(this CodeWriter writer)
|
||||
{
|
||||
return new CSharpCodeWritingScope(writer);
|
||||
}
|
||||
|
||||
public static CSharpCodeWritingScope BuildLambda(this CodeWriter writer, params string[] parameterNames)
|
||||
{
|
||||
return BuildLambda(writer, async: false, parameterNames: parameterNames);
|
||||
}
|
||||
|
||||
public static CSharpCodeWritingScope BuildAsyncLambda(this CodeWriter writer, params string[] parameterNames)
|
||||
{
|
||||
return BuildLambda(writer, async: true, parameterNames: parameterNames);
|
||||
}
|
||||
|
||||
private static CSharpCodeWritingScope BuildLambda(CodeWriter writer, bool async, string[] parameterNames)
|
||||
{
|
||||
if (async)
|
||||
{
|
||||
writer.Write("async");
|
||||
}
|
||||
|
||||
writer.Write("(").Write(string.Join(", ", parameterNames)).Write(") => ");
|
||||
|
||||
var scope = new CSharpCodeWritingScope(writer);
|
||||
|
||||
return scope;
|
||||
}
|
||||
|
||||
public static CSharpCodeWritingScope BuildNamespace(this CodeWriter writer, string name)
|
||||
{
|
||||
writer.Write("namespace ").WriteLine(name);
|
||||
|
||||
return new CSharpCodeWritingScope(writer);
|
||||
}
|
||||
|
||||
public static CSharpCodeWritingScope BuildClassDeclaration(
|
||||
this CodeWriter writer,
|
||||
IList<string> modifiers,
|
||||
string name,
|
||||
string baseType,
|
||||
IEnumerable<string> interfaces)
|
||||
{
|
||||
for (var i = 0; i < modifiers.Count; i++)
|
||||
{
|
||||
writer.Write(modifiers[i]);
|
||||
writer.Write(" ");
|
||||
}
|
||||
|
||||
writer.Write("class ");
|
||||
writer.Write(name);
|
||||
|
||||
var hasBaseType = !string.IsNullOrEmpty(baseType);
|
||||
var hasInterfaces = interfaces != null && interfaces.Count() > 0;
|
||||
|
||||
if (hasBaseType || hasInterfaces)
|
||||
{
|
||||
writer.Write(" : ");
|
||||
|
||||
if (hasBaseType)
|
||||
{
|
||||
writer.Write(baseType);
|
||||
|
||||
if (hasInterfaces)
|
||||
{
|
||||
WriteParameterSeparator(writer);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasInterfaces)
|
||||
{
|
||||
writer.Write(string.Join(", ", interfaces));
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteLine();
|
||||
|
||||
return new CSharpCodeWritingScope(writer);
|
||||
}
|
||||
|
||||
public static CSharpCodeWritingScope BuildMethodDeclaration(
|
||||
this CodeWriter writer,
|
||||
string accessibility,
|
||||
string returnType,
|
||||
string name,
|
||||
IEnumerable<KeyValuePair<string, string>> parameters)
|
||||
{
|
||||
writer.Write(accessibility)
|
||||
.Write(" ")
|
||||
.Write(returnType)
|
||||
.Write(" ")
|
||||
.Write(name)
|
||||
.Write("(")
|
||||
.Write(string.Join(", ", parameters.Select(p => p.Key + " " + p.Value)))
|
||||
.WriteLine(")");
|
||||
|
||||
return new CSharpCodeWritingScope(writer);
|
||||
}
|
||||
|
||||
public static IDisposable BuildLinePragma(this CodeWriter writer, SourceSpan? span)
|
||||
{
|
||||
if (string.IsNullOrEmpty(span?.FilePath))
|
||||
{
|
||||
// Can't build a valid line pragma without a file path.
|
||||
return NullDisposable.Default;
|
||||
}
|
||||
|
||||
return new LinePragmaWriter(writer, span.Value);
|
||||
}
|
||||
|
||||
private static void WriteVerbatimStringLiteral(CodeWriter writer, string literal)
|
||||
{
|
||||
writer.Write("@\"");
|
||||
|
||||
// We need to suppress indenting during the writing of the string's content. A
|
||||
// verbatim string literal could contain newlines that don't get escaped.
|
||||
var indent = writer.CurrentIndent;
|
||||
writer.CurrentIndent = 0;
|
||||
|
||||
// We need to find the index of each '"' (double-quote) to escape it.
|
||||
var start = 0;
|
||||
int end;
|
||||
while ((end = literal.IndexOf('\"', start)) > -1)
|
||||
{
|
||||
writer.Write(literal, start, end - start);
|
||||
|
||||
writer.Write("\"\"");
|
||||
|
||||
start = end + 1;
|
||||
}
|
||||
|
||||
Debug.Assert(end == -1); // We've hit all of the double-quotes.
|
||||
|
||||
// Write the remainder after the last double-quote.
|
||||
writer.Write(literal, start, literal.Length - start);
|
||||
|
||||
writer.Write("\"");
|
||||
|
||||
writer.CurrentIndent = indent;
|
||||
}
|
||||
|
||||
private static void WriteCStyleStringLiteral(CodeWriter writer, string literal)
|
||||
{
|
||||
// From CSharpCodeGenerator.QuoteSnippetStringCStyle in CodeDOM
|
||||
writer.Write("\"");
|
||||
|
||||
// We need to find the index of each escapable character to escape it.
|
||||
var start = 0;
|
||||
int end;
|
||||
while ((end = literal.IndexOfAny(CStyleStringLiteralEscapeChars, start)) > -1)
|
||||
{
|
||||
writer.Write(literal, start, end - start);
|
||||
|
||||
switch (literal[end])
|
||||
{
|
||||
case '\r':
|
||||
writer.Write("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
writer.Write("\\t");
|
||||
break;
|
||||
case '\"':
|
||||
writer.Write("\\\"");
|
||||
break;
|
||||
case '\'':
|
||||
writer.Write("\\\'");
|
||||
break;
|
||||
case '\\':
|
||||
writer.Write("\\\\");
|
||||
break;
|
||||
case '\0':
|
||||
writer.Write("\\\0");
|
||||
break;
|
||||
case '\n':
|
||||
writer.Write("\\n");
|
||||
break;
|
||||
case '\u2028':
|
||||
case '\u2029':
|
||||
writer.Write("\\u");
|
||||
writer.Write(((int)literal[end]).ToString("X4", CultureInfo.InvariantCulture));
|
||||
break;
|
||||
default:
|
||||
Debug.Assert(false, "Unknown escape character.");
|
||||
break;
|
||||
}
|
||||
|
||||
start = end + 1;
|
||||
}
|
||||
|
||||
Debug.Assert(end == -1); // We've hit all of chars that need escaping.
|
||||
|
||||
// Write the remainder after the last escaped char.
|
||||
writer.Write(literal, start, literal.Length - start);
|
||||
|
||||
writer.Write("\"");
|
||||
}
|
||||
|
||||
public struct CSharpCodeWritingScope : IDisposable
|
||||
{
|
||||
private CodeWriter _writer;
|
||||
private bool _autoSpace;
|
||||
private int _tabSize;
|
||||
private int _startIndent;
|
||||
|
||||
public CSharpCodeWritingScope(CodeWriter writer, int tabSize = 4, bool autoSpace = true)
|
||||
{
|
||||
_writer = writer;
|
||||
_autoSpace = true;
|
||||
_tabSize = tabSize;
|
||||
_startIndent = -1; // Set in WriteStartScope
|
||||
|
||||
WriteStartScope();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
WriteEndScope();
|
||||
}
|
||||
|
||||
private void WriteStartScope()
|
||||
{
|
||||
TryAutoSpace(" ");
|
||||
|
||||
_writer.WriteLine("{");
|
||||
_writer.CurrentIndent += _tabSize;
|
||||
_startIndent = _writer.CurrentIndent;
|
||||
}
|
||||
|
||||
private void WriteEndScope()
|
||||
{
|
||||
TryAutoSpace(_writer.NewLine);
|
||||
|
||||
// Ensure the scope hasn't been modified
|
||||
if (_writer.CurrentIndent == _startIndent)
|
||||
{
|
||||
_writer.CurrentIndent -= _tabSize;
|
||||
}
|
||||
|
||||
_writer.WriteLine("}");
|
||||
}
|
||||
|
||||
private void TryAutoSpace(string spaceCharacter)
|
||||
{
|
||||
if (_autoSpace &&
|
||||
_writer.Length > 0 &&
|
||||
!char.IsWhiteSpace(_writer[_writer.Length - 1]))
|
||||
{
|
||||
_writer.Write(spaceCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LinePragmaWriter : IDisposable
|
||||
{
|
||||
private readonly CodeWriter _writer;
|
||||
private readonly int _startIndent;
|
||||
|
||||
public LinePragmaWriter(CodeWriter writer, SourceSpan span)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
_writer = writer;
|
||||
_startIndent = _writer.CurrentIndent;
|
||||
_writer.CurrentIndent = 0;
|
||||
WriteLineNumberDirective(writer, span);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Need to add an additional line at the end IF there wasn't one already written.
|
||||
// This is needed to work with the C# editor's handling of #line ...
|
||||
var endsWithNewline = _writer.Length > 0 && _writer[_writer.Length - 1] == '\n';
|
||||
|
||||
// Always write at least 1 empty line to potentially separate code from pragmas.
|
||||
_writer.WriteLine();
|
||||
|
||||
// Check if the previous empty line wasn't enough to separate code from pragmas.
|
||||
if (!endsWithNewline)
|
||||
{
|
||||
_writer.WriteLine();
|
||||
}
|
||||
|
||||
_writer
|
||||
.WriteLine("#line default")
|
||||
.WriteLine("#line hidden");
|
||||
|
||||
_writer.CurrentIndent = _startIndent;
|
||||
}
|
||||
}
|
||||
|
||||
private class NullDisposable : IDisposable
|
||||
{
|
||||
public static readonly NullDisposable Default = new NullDisposable();
|
||||
|
||||
private NullDisposable()
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,9 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\samples\StandaloneApp\StandaloneApp.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.Blazor.Build\Microsoft.Blazor.Build.csproj" />
|
||||
|
||||
<!-- Shared sources -->
|
||||
<Compile Include="..\shared\AssertNode.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
using Microsoft.Blazor.Build.Core.RazorCompilation;
|
||||
using Microsoft.Blazor.Components;
|
||||
using Microsoft.Blazor.Rendering;
|
||||
using Microsoft.Blazor.RenderTree;
|
||||
using Microsoft.Blazor.Test.Shared;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using System;
|
||||
|
|
@ -65,7 +68,7 @@ namespace Microsoft.Blazor.Build.Test
|
|||
[InlineData("Dir1\\Dir2\\MyFile.cs", "Test.Base.Dir1.Dir2", "MyFile")]
|
||||
public void CreatesClassWithCorrectNameAndNamespace(string relativePath, string expectedNamespace, string expectedClassName)
|
||||
{
|
||||
// Arrange/Acts
|
||||
// Arrange/Act
|
||||
var result = CompileToAssembly(
|
||||
"x:\\dir\\subdir",
|
||||
relativePath,
|
||||
|
|
@ -82,6 +85,63 @@ namespace Microsoft.Blazor.Build.Test
|
|||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRenderPlainText()
|
||||
{
|
||||
// Arrange
|
||||
var treeBuilder = new RenderTreeBuilder(new TestRenderer());
|
||||
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent("Some plain text");
|
||||
component.BuildRenderTree(treeBuilder);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(treeBuilder.GetNodes(),
|
||||
node => AssertNode.Text(node, "Some plain text"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanUseCSharpFunctionsBlock()
|
||||
{
|
||||
// Arrange/Act
|
||||
var component = CompileToComponent(@"
|
||||
@foreach(var item in items) {
|
||||
@item
|
||||
}
|
||||
@functions {
|
||||
string[] items = new[] { ""First"", ""Second"", ""Third"" };
|
||||
}
|
||||
");
|
||||
|
||||
// Assert
|
||||
var nodes = GetRenderTree(component).Where(NotWhitespace);
|
||||
Assert.Collection(nodes,
|
||||
node => AssertNode.Text(node, "First"),
|
||||
node => AssertNode.Text(node, "Second"),
|
||||
node => AssertNode.Text(node, "Third"));
|
||||
}
|
||||
|
||||
private static bool NotWhitespace(RenderTreeNode node)
|
||||
=> node.NodeType != RenderTreeNodeType.Text
|
||||
|| !string.IsNullOrWhiteSpace(node.TextContent);
|
||||
|
||||
private static ArraySegment<RenderTreeNode> GetRenderTree(IComponent component)
|
||||
{
|
||||
var treeBuilder = new RenderTreeBuilder(new TestRenderer());
|
||||
component.BuildRenderTree(treeBuilder);
|
||||
return treeBuilder.GetNodes();
|
||||
}
|
||||
|
||||
private static IComponent CompileToComponent(string cshtmlSource)
|
||||
{
|
||||
var testComponentTypeName = "TestComponent";
|
||||
var testComponentNamespace = "Test";
|
||||
var assemblyResult = CompileToAssembly("c:\\ignored", $"{testComponentTypeName}.cshtml", cshtmlSource, testComponentNamespace);
|
||||
Assert.Empty(assemblyResult.Diagnostics);
|
||||
var testComponentType = assemblyResult.Assembly.GetType($"{testComponentNamespace}.{testComponentTypeName}");
|
||||
return (IComponent)Activator.CreateInstance(testComponentType);
|
||||
}
|
||||
|
||||
private static CompileToAssemblyResult CompileToAssembly(string cshtmlRootPath, string cshtmlRelativePath, string cshtmlContent, string outputNamespace)
|
||||
{
|
||||
var csharpResult = CompileToCSharp(cshtmlRootPath, cshtmlRelativePath, cshtmlContent, outputNamespace);
|
||||
|
|
@ -168,5 +228,11 @@ namespace Microsoft.Blazor.Build.Test
|
|||
public string VerboseLog { get; set; }
|
||||
public IEnumerable<Diagnostic> Diagnostics { get; set; }
|
||||
}
|
||||
|
||||
private class TestRenderer : Renderer
|
||||
{
|
||||
protected override void UpdateDisplay(int componentId, ArraySegment<RenderTreeNode> renderTree)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Blazor\Microsoft.Blazor.csproj" />
|
||||
|
||||
<!-- Shared sources -->
|
||||
<Compile Include="..\shared\AssertNode.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using Microsoft.Blazor.Components;
|
||||
using Microsoft.Blazor.Rendering;
|
||||
using Microsoft.Blazor.RenderTree;
|
||||
using Microsoft.Blazor.Test.Shared;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||
using Microsoft.Blazor.Components;
|
||||
using Microsoft.Blazor.Rendering;
|
||||
using Microsoft.Blazor.RenderTree;
|
||||
using Microsoft.Blazor.Test.Shared;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Blazor.Test
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ using Microsoft.Blazor.Components;
|
|||
using Microsoft.Blazor.RenderTree;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Blazor.Test
|
||||
namespace Microsoft.Blazor.Test.Shared
|
||||
{
|
||||
public static class AssertNode
|
||||
internal static class AssertNode
|
||||
{
|
||||
public static void Text(RenderTreeNode node, string textContent)
|
||||
{
|
||||
Loading…
Reference in New Issue