Capture exceptions when trying to parse files and return parse errors.
#808
This commit is contained in:
parent
23a6604b9a
commit
82c9c40709
|
|
@ -1562,6 +1562,22 @@ namespace Microsoft.AspNetCore.Razor
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("ParseError_IncompleteQuotesAroundDirective"), p0);
|
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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -428,4 +428,7 @@ Instead, wrap the contents of the block in "{{}}":
|
||||||
<data name="ParseError_IncompleteQuotesAroundDirective" xml:space="preserve">
|
<data name="ParseError_IncompleteQuotesAroundDirective" xml:space="preserve">
|
||||||
<value>Optional quote around the directive '{0}' is missing the corresponding opening or closing quote.</value>
|
<value>Optional quote around the directive '{0}' is missing the corresponding opening or closing quote.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FatalException" xml:space="preserve">
|
||||||
|
<value>A fatal exception occurred when trying to parse '{0}':{1}{2}</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -2,14 +2,19 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Microsoft.AspNetCore.Razor.Chunks;
|
||||||
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
||||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||||
|
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||||
using Microsoft.AspNetCore.Razor.Parser;
|
using Microsoft.AspNetCore.Razor.Parser;
|
||||||
|
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||||
using Microsoft.AspNetCore.Razor.Text;
|
using Microsoft.AspNetCore.Razor.Text;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor
|
namespace Microsoft.AspNetCore.Razor
|
||||||
|
|
@ -363,26 +368,50 @@ namespace Microsoft.AspNetCore.Razor
|
||||||
throw new ArgumentNullException(nameof(input));
|
throw new ArgumentNullException(nameof(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
className = (className ?? Host.DefaultClassName) ?? DefaultClassName;
|
try
|
||||||
rootNamespace = (rootNamespace ?? Host.DefaultNamespace) ?? DefaultNamespace;
|
{
|
||||||
|
className = (className ?? Host.DefaultClassName) ?? DefaultClassName;
|
||||||
|
rootNamespace = (rootNamespace ?? Host.DefaultNamespace) ?? DefaultNamespace;
|
||||||
|
|
||||||
// Run the parser
|
// Run the parser
|
||||||
var parser = CreateParser(sourceFileName);
|
var parser = CreateParser(sourceFileName);
|
||||||
Debug.Assert(parser != null);
|
Debug.Assert(parser != null);
|
||||||
var results = parser.Parse(input);
|
var results = parser.Parse(input);
|
||||||
|
|
||||||
// Generate code
|
// Generate code
|
||||||
var chunkGenerator = CreateChunkGenerator(className, rootNamespace, sourceFileName);
|
var chunkGenerator = CreateChunkGenerator(className, rootNamespace, sourceFileName);
|
||||||
chunkGenerator.DesignTimeMode = Host.DesignTimeMode;
|
chunkGenerator.DesignTimeMode = Host.DesignTimeMode;
|
||||||
chunkGenerator.Visit(results);
|
chunkGenerator.Visit(results);
|
||||||
|
|
||||||
var codeGeneratorContext = new CodeGeneratorContext(chunkGenerator.Context, results.ErrorSink);
|
var codeGeneratorContext = new CodeGeneratorContext(chunkGenerator.Context, results.ErrorSink);
|
||||||
codeGeneratorContext.Checksum = checksum;
|
codeGeneratorContext.Checksum = checksum;
|
||||||
var codeGenerator = CreateCodeGenerator(codeGeneratorContext);
|
var codeGenerator = CreateCodeGenerator(codeGeneratorContext);
|
||||||
var codeGeneratorResult = codeGenerator.Generate();
|
var codeGeneratorResult = codeGenerator.Generate();
|
||||||
|
|
||||||
// Collect results and return
|
// Collect results and return
|
||||||
return new GeneratorResults(results, codeGeneratorResult, codeGeneratorContext.ChunkTreeBuilder.Root);
|
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(
|
protected internal virtual RazorChunkGenerator CreateChunkGenerator(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -18,6 +19,53 @@ namespace Microsoft.AspNetCore.Razor
|
||||||
{
|
{
|
||||||
public class RazorTemplateEngineTest
|
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]
|
[Fact]
|
||||||
public void ConstructorInitializesHost()
|
public void ConstructorInitializesHost()
|
||||||
{
|
{
|
||||||
|
|
@ -339,5 +387,23 @@ namespace Microsoft.AspNetCore.Razor
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class InvalidRazorEngineHost : RazorEngineHost
|
||||||
|
{
|
||||||
|
public InvalidRazorEngineHost(RazorCodeLanguage codeLanguage) : base(codeLanguage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string DefaultClassName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Hello World");
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue