Capture exceptions when trying to parse files and return parse errors.

#808
This commit is contained in:
N. Taylor Mullen 2016-08-04 15:08:02 -07:00
parent 23a6604b9a
commit 82c9c40709
4 changed files with 130 additions and 16 deletions

View File

@ -1562,6 +1562,22 @@ namespace Microsoft.AspNetCore.Razor
return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_IncompleteQuotesAroundDirective"), p0);
}
/// <summary>
/// A fatal exception occurred when trying to parse '{0}':{1}{2}
/// </summary>
internal static string FatalException
{
get { return GetString("FatalException"); }
}
/// <summary>
/// A fatal exception occurred when trying to parse '{0}':{1}{2}
/// </summary>
internal static string FormatFatalException(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("FatalException"), p0, p1, p2);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -428,4 +428,7 @@ Instead, wrap the contents of the block in "{{}}":
<data name="ParseError_IncompleteQuotesAroundDirective" xml:space="preserve">
<value>Optional quote around the directive '{0}' is missing the corresponding opening or closing quote.</value>
</data>
<data name="FatalException" xml:space="preserve">
<value>A fatal exception occurred when trying to parse '{0}':{1}{2}</value>
</data>
</root>

View File

@ -2,14 +2,19 @@
// 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using Microsoft.AspNetCore.Razor.Chunks;
using Microsoft.AspNetCore.Razor.Chunks.Generators;
using Microsoft.AspNetCore.Razor.CodeGenerators;
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
using Microsoft.AspNetCore.Razor.Parser;
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
using Microsoft.AspNetCore.Razor.Text;
namespace Microsoft.AspNetCore.Razor
@ -363,26 +368,50 @@ namespace Microsoft.AspNetCore.Razor
throw new ArgumentNullException(nameof(input));
}
className = (className ?? Host.DefaultClassName) ?? DefaultClassName;
rootNamespace = (rootNamespace ?? Host.DefaultNamespace) ?? DefaultNamespace;
try
{
className = (className ?? Host.DefaultClassName) ?? DefaultClassName;
rootNamespace = (rootNamespace ?? Host.DefaultNamespace) ?? DefaultNamespace;
// Run the parser
var parser = CreateParser(sourceFileName);
Debug.Assert(parser != null);
var results = parser.Parse(input);
// Run the parser
var parser = CreateParser(sourceFileName);
Debug.Assert(parser != null);
var results = parser.Parse(input);
// Generate code
var chunkGenerator = CreateChunkGenerator(className, rootNamespace, sourceFileName);
chunkGenerator.DesignTimeMode = Host.DesignTimeMode;
chunkGenerator.Visit(results);
// Generate code
var chunkGenerator = CreateChunkGenerator(className, rootNamespace, sourceFileName);
chunkGenerator.DesignTimeMode = Host.DesignTimeMode;
chunkGenerator.Visit(results);
var codeGeneratorContext = new CodeGeneratorContext(chunkGenerator.Context, results.ErrorSink);
codeGeneratorContext.Checksum = checksum;
var codeGenerator = CreateCodeGenerator(codeGeneratorContext);
var codeGeneratorResult = codeGenerator.Generate();
var codeGeneratorContext = new CodeGeneratorContext(chunkGenerator.Context, results.ErrorSink);
codeGeneratorContext.Checksum = checksum;
var codeGenerator = CreateCodeGenerator(codeGeneratorContext);
var codeGeneratorResult = codeGenerator.Generate();
// Collect results and return
return new GeneratorResults(results, codeGeneratorResult, codeGeneratorContext.ChunkTreeBuilder.Root);
// Collect results and return
return new GeneratorResults(results, codeGeneratorResult, codeGeneratorContext.ChunkTreeBuilder.Root);
}
// During runtime we want code generation explosions to flow up into the calling code. At design time
// we want to capture these exceptions to prevent IDEs from crashing.
catch (Exception ex) when (Host.DesignTimeMode)
{
var errorSink = new ErrorSink();
errorSink.OnError(
SourceLocation.Undefined,
RazorResources.FormatFatalException(sourceFileName, Environment.NewLine, ex.Message),
length: -1);
var emptyBlock = new BlockBuilder();
emptyBlock.Type = default(BlockType);
return new GeneratorResults(
document: emptyBlock.Build(),
tagHelperDescriptors: Enumerable.Empty<TagHelperDescriptor>(),
errorSink: errorSink,
codeGeneratorResult: new CodeGeneratorResult(
code: string.Empty,
designTimeLineMappings: new List<LineMapping>()),
chunkTree: new ChunkTree());
}
}
protected internal virtual RazorChunkGenerator CreateChunkGenerator(

View File

@ -1,6 +1,7 @@
// 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.Text;
@ -18,6 +19,53 @@ namespace Microsoft.AspNetCore.Razor
{
public class RazorTemplateEngineTest
{
[Fact]
public void InvalidRazorEngineHostReturnsParseErrorsAtDesignTime()
{
// Arrange
var host = new InvalidRazorEngineHost(new CSharpRazorCodeLanguage())
{
DesignTimeMode = true
};
var razorEngine = new RazorTemplateEngine(host);
var input = new StringTextBuffer("<div>Hello @(\"World\")</div>");
var exception = new InvalidOperationException("Hello World");
var expectedError = RazorResources.FormatFatalException("test", Environment.NewLine, exception.Message);
// Act
var result = razorEngine.GenerateCode(input, className: null, rootNamespace: null, sourceFileName: "test");
// Assert
Assert.Empty(result.Document.Children);
Assert.Empty(result.ChunkTree.Children);
Assert.Empty(result.DesignTimeLineMappings);
Assert.Empty(result.GeneratedCode);
var error = Assert.Single(result.ParserErrors);
Assert.Equal(expectedError, error.Message, StringComparer.Ordinal);
Assert.Equal(SourceLocation.Undefined, error.Location);
Assert.Equal(-1, error.Length);
}
[Fact]
public void InvalidRazorEngineHostThrowsAtRuntime()
{
// Arrange
var host = new InvalidRazorEngineHost(new CSharpRazorCodeLanguage())
{
DesignTimeMode = false
};
var razorEngine = new RazorTemplateEngine(host);
var input = new StringTextBuffer("<div>Hello @(\"World\")</div>");
// Act
var thrownException = Assert.Throws<InvalidOperationException>(() =>
razorEngine.GenerateCode(input, className: null, rootNamespace: null, sourceFileName: "test"));
// Assert
Assert.Equal("Hello World", thrownException.Message, StringComparer.Ordinal);
}
[Fact]
public void ConstructorInitializesHost()
{
@ -339,5 +387,23 @@ namespace Microsoft.AspNetCore.Razor
return null;
}
}
private class InvalidRazorEngineHost : RazorEngineHost
{
public InvalidRazorEngineHost(RazorCodeLanguage codeLanguage) : base(codeLanguage)
{
}
public override string DefaultClassName
{
get
{
throw new InvalidOperationException("Hello World");
}
set
{
}
}
}
}
}