Skip to content
This repository has been archived by the owner on Dec 19, 2018. It is now read-only.

Commit

Permalink
Capture exceptions when trying to parse files and return parse errors.
Browse files Browse the repository at this point in the history
  • Loading branch information
NTaylorMullen committed Aug 11, 2016
1 parent 23a6604 commit 82c9c40
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 20 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.AspNetCore.Razor/RazorResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
69 changes: 49 additions & 20 deletions src/Microsoft.AspNetCore.Razor/RazorTemplateEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -363,26 +368,50 @@ protected internal virtual GeneratorResults GenerateCodeCore(
throw new ArgumentNullException(nameof(input));
}

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);

// 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();

// Collect results and return
return new GeneratorResults(results, codeGeneratorResult, codeGeneratorContext.ChunkTreeBuilder.Root);
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);

// 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();

// 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(
Expand Down
66 changes: 66 additions & 0 deletions test/Microsoft.AspNetCore.Razor.Test/RazorTemplateEngineTest.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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()
{
Expand Down Expand Up @@ -339,5 +387,23 @@ protected internal override GeneratorResults GenerateCodeCore(
return null;
}
}

private class InvalidRazorEngineHost : RazorEngineHost
{
public InvalidRazorEngineHost(RazorCodeLanguage codeLanguage) : base(codeLanguage)
{
}

public override string DefaultClassName
{
get
{
throw new InvalidOperationException("Hello World");
}
set
{
}
}
}
}
}

0 comments on commit 82c9c40

Please sign in to comment.