Added SourceLocation to inherited chunks \ tag helpers

Updated CompilationResult to support compilation failures from multiple
files.

Fixes #2321
This commit is contained in:
Pranav K 2015-04-08 06:09:38 -07:00
parent f878ca5b15
commit fb451b51e5
19 changed files with 552 additions and 133 deletions

View File

@ -126,22 +126,33 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
private static CodeTree ParseViewFile(RazorTemplateEngine engine,
IFileInfo fileInfo,
string viewStartPath)
string globalImportPath)
{
using (var stream = fileInfo.CreateReadStream())
{
using (var streamReader = new StreamReader(stream))
{
var parseResults = engine.ParseTemplate(streamReader, viewStartPath);
var parseResults = engine.ParseTemplate(streamReader, globalImportPath);
var className = ParserHelpers.SanitizeClassName(fileInfo.Name);
var language = engine.Host.CodeLanguage;
var codeGenerator = language.CreateCodeGenerator(className,
engine.Host.DefaultNamespace,
viewStartPath,
globalImportPath,
engine.Host);
codeGenerator.Visit(parseResults);
return codeGenerator.Context.CodeTreeBuilder.CodeTree;
// Rewrite the location of inherited chunks so they point to the global import file.
var codeTree = codeGenerator.Context.CodeTreeBuilder.CodeTree;
foreach (var chunk in codeTree.Chunks)
{
chunk.Start = new SourceLocation(
globalImportPath,
chunk.Start.AbsoluteIndex,
chunk.Start.LineIndex,
chunk.Start.CharacterIndex);
}
return codeTree;
}
}
}

View File

@ -76,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{
var descriptor = new TagHelperDirectiveDescriptor(
addTagHelperChunk.LookupText,
SourceLocation.Undefined,
chunk.Start,
TagHelperDirectiveType.AddTagHelper);
descriptors.Add(descriptor);
@ -89,7 +89,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{
var descriptor = new TagHelperDirectiveDescriptor(
removeTagHelperChunk.LookupText,
SourceLocation.Undefined,
chunk.Start,
TagHelperDirectiveType.RemoveTagHelper);
descriptors.Add(descriptor);
@ -102,7 +102,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{
var descriptor = new TagHelperDirectiveDescriptor(
tagHelperPrefixDirectiveChunk.Prefix,
SourceLocation.Undefined,
chunk.Start,
TagHelperDirectiveType.TagHelperPrefix);
descriptors.Add(descriptor);

View File

@ -17,24 +17,24 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
/// <summary>
/// Instantiates a new instance of <see cref="CompilationFailedException"/>.
/// </summary>
/// <param name="compilationFailure">The <see cref="ICompilationFailure"/> instance containing
/// <param name="compilationFailures"><see cref="ICompilationFailure"/>s containing
/// details of the compilation failure.</param>
public CompilationFailedException(
[NotNull] ICompilationFailure compilationFailure)
: base(FormatMessage(compilationFailure))
[NotNull] IEnumerable<ICompilationFailure> compilationFailures)
: base(FormatMessage(compilationFailures))
{
CompilationFailures = new[] { compilationFailure };
CompilationFailures = compilationFailures;
}
/// <inheritdoc />
public IEnumerable<ICompilationFailure> CompilationFailures { get; }
private static string FormatMessage(ICompilationFailure compilationFailure)
private static string FormatMessage(IEnumerable<ICompilationFailure> compilationFailures)
{
return Resources.CompilationFailed + Environment.NewLine +
string.Join(
Environment.NewLine,
compilationFailure.Messages.Select(message => message.FormattedMessage));
compilationFailures.SelectMany(f => f.Messages).Select(message => message.FormattedMessage));
}
}
}

View File

@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Framework.Runtime;
using System.Collections.Generic;
using System.IO;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Runtime;
namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
@ -31,10 +33,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
public string CompiledContent { get; protected set; }
/// <summary>
/// Gets the <see cref="ICompilationFailure"/> produced from parsing or compiling the Razor file.
/// Gets the <see cref="ICompilationFailure"/>s produced from parsing or compiling the Razor file.
/// </summary>
/// <remarks>This property is <c>null</c> when compilation succeeded.</remarks>
public ICompilationFailure CompilationFailure { get; private set; }
/// <remarks>This property is <c>null</c> when compilation succeeded. An empty sequence
/// indicates a failed compilation.</remarks>
public IEnumerable<ICompilationFailure> CompilationFailures { get; private set; }
/// <summary>
/// Gets the <see cref="CompilationResult"/>.
@ -43,9 +46,9 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
/// <exception cref="CompilationFailedException">Thrown if compilation failed.</exception>
public CompilationResult EnsureSuccessful()
{
if (CompilationFailure != null)
if (CompilationFailures != null)
{
throw new CompilationFailedException(CompilationFailure);
throw new CompilationFailedException(CompilationFailures);
}
return this;
@ -54,14 +57,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
/// <summary>
/// Creates a <see cref="CompilationResult"/> for a failed compilation.
/// </summary>
/// <param name="compilationFailure">The <see cref="ICompilationFailure"/> produced from parsing or
/// <param name="compilationFailures"><see cref="ICompilationFailure"/>s produced from parsing or
/// compiling the Razor file.</param>
/// <returns>A <see cref="CompilationResult"/> instance for a failed compilation.</returns>
public static CompilationResult Failed([NotNull] ICompilationFailure compilationFailure)
public static CompilationResult Failed([NotNull] IEnumerable<ICompilationFailure> compilationFailures)
{
return new CompilationResult
{
CompilationFailure = compilationFailure
CompilationFailures = compilationFailures
};
}

View File

@ -1,11 +1,14 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Linq;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Razor;
using Microsoft.Framework.Internal;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
@ -16,12 +19,15 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
private readonly ICompilationService _compilationService;
private readonly IMvcRazorHost _razorHost;
private readonly IFileProvider _fileProvider;
public RazorCompilationService(ICompilationService compilationService,
IMvcRazorHost razorHost)
IMvcRazorHost razorHost,
IOptions<RazorViewEngineOptions> viewEngineOptions)
{
_compilationService = compilationService;
_razorHost = razorHost;
_fileProvider = viewEngineOptions.Options.FileProvider;
}
/// <inheritdoc />
@ -30,39 +36,61 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
GeneratorResults results;
using (var inputStream = file.FileInfo.CreateReadStream())
{
results = _razorHost.GenerateCode(
file.RelativePath, inputStream);
results = _razorHost.GenerateCode(file.RelativePath, inputStream);
}
if (!results.Success)
{
var messages = results.ParserErrors
.Select(parseError => new RazorCompilationMessage(parseError, file.RelativePath));
var failure = new RazorCompilationFailure(
file.RelativePath,
ReadFileContentsSafely(file.FileInfo),
messages);
return CompilationResult.Failed(failure);
return GetCompilationFailedResult(file, results.ParserErrors);
}
return _compilationService.Compile(file, results.GeneratedCode);
}
private static string ReadFileContentsSafely(IFileInfo fileInfo)
// Internal for unit testing
internal CompilationResult GetCompilationFailedResult(RelativeFileInfo file, IEnumerable<RazorError> errors)
{
try
// If a SourceLocation does not specify a file path, assume it is produced
// from parsing the current file.
var messageGroups = errors
.GroupBy(razorError =>
razorError.Location.FilePath ?? file.RelativePath,
StringComparer.Ordinal);
var failures = new List<RazorCompilationFailure>();
foreach (var group in messageGroups)
{
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
var filePath = group.Key;
var fileContent = ReadFileContentsSafely(filePath);
var compilationFailure = new RazorCompilationFailure(
filePath,
fileContent,
group.Select(parserError => new RazorCompilationMessage(parserError, filePath)));
failures.Add(compilationFailure);
}
return CompilationResult.Failed(failures);
}
private string ReadFileContentsSafely(string relativePath)
{
var fileInfo = _fileProvider.GetFileInfo(relativePath);
if (fileInfo.Exists)
{
try
{
return reader.ReadToEnd();
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
{
return reader.ReadToEnd();
}
}
catch
{
// Ignore any failures
}
}
catch
{
// Ignore any failures
return null;
}
return null;
}
}
}

View File

@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.Framework.Internal;
using Microsoft.Framework.OptionsModel;
using Microsoft.Framework.Runtime;
using Microsoft.Framework.Runtime.Compilation;
using Microsoft.Framework.Runtime.Roslyn;
@ -34,9 +35,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
private readonly IApplicationEnvironment _environment;
private readonly IAssemblyLoadContext _loader;
private readonly ICompilerOptionsProvider _compilerOptionsProvider;
private readonly IFileProvider _fileProvider;
private readonly Lazy<List<MetadataReference>> _applicationReferences;
private readonly string _classPrefix;
/// <summary>
@ -50,30 +50,28 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
IAssemblyLoadContextAccessor loaderAccessor,
ILibraryManager libraryManager,
ICompilerOptionsProvider compilerOptionsProvider,
IMvcRazorHost host)
IMvcRazorHost host,
IOptions<RazorViewEngineOptions> optionsAccessor)
{
_environment = environment;
_loader = loaderAccessor.GetLoadContext(typeof(RoslynCompilationService).GetTypeInfo().Assembly);
_libraryManager = libraryManager;
_applicationReferences = new Lazy<List<MetadataReference>>(GetApplicationReferences);
_compilerOptionsProvider = compilerOptionsProvider;
_fileProvider = optionsAccessor.Options.FileProvider;
_classPrefix = host.MainClassNamePrefix;
}
/// <inheritdoc />
public CompilationResult Compile([NotNull] RelativeFileInfo fileInfo, [NotNull] string compilationContent)
{
// The path passed to SyntaxTreeGenerator.Generate is used by the compiler to generate symbols (pdb) that
// map to the source file. If a file does not exist on a physical file system, PhysicalPath will be null.
// This prevents files that exist in a non-physical file system from being debugged.
var path = fileInfo.FileInfo.PhysicalPath ?? fileInfo.RelativePath;
var assemblyName = Path.GetRandomFileName();
var compilationSettings = _compilerOptionsProvider.GetCompilationSettings(_environment);
var syntaxTree = SyntaxTreeGenerator.Generate(compilationContent,
path,
assemblyName,
compilationSettings);
var references = _applicationReferences.Value;
var assemblyName = Path.GetRandomFileName();
var compilationOptions = compilationSettings.CompilationOptions
.WithOutputKind(OutputKind.DynamicallyLinkedLibrary);
@ -99,15 +97,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
if (!result.Success)
{
var failures = result.Diagnostics.Where(IsError);
var compilationFailure = new RoslynCompilationFailure(failures)
{
CompiledContent = compilationContent,
SourceFileContent = ReadFileContentsSafely(fileInfo.FileInfo),
SourceFilePath = fileInfo.RelativePath
};
return CompilationResult.Failed(compilationFailure);
return GetCompilationFailedResult(
fileInfo.RelativePath,
compilationContent,
assemblyName,
result.Diagnostics);
}
Assembly assembly;
@ -131,6 +125,56 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
}
}
// Internal for unit testing
internal CompilationResult GetCompilationFailedResult(
string relativePath,
string compilationContent,
string assemblyName,
IEnumerable<Diagnostic> diagnostics)
{
var diagnosticGroups = diagnostics
.Where(IsError)
.GroupBy(diagnostic => GetFilePath(relativePath, diagnostic), StringComparer.Ordinal);
var failures = new List<ICompilationFailure>();
foreach (var group in diagnosticGroups)
{
var sourceFilePath = group.Key;
string sourceFileContent;
if (string.Equals(assemblyName, sourceFilePath, StringComparison.Ordinal))
{
// The error is in the generated code and does not have a mapping line pragma
sourceFileContent = compilationContent;
sourceFilePath = Resources.GeneratedCodeFileName;
}
else
{
sourceFileContent = ReadFileContentsSafely(_fileProvider, sourceFilePath);
}
var compilationFailure = new RoslynCompilationFailure(group)
{
CompiledContent = compilationContent,
SourceFileContent = sourceFileContent,
SourceFilePath = sourceFilePath
};
failures.Add(compilationFailure);
}
return CompilationResult.Failed(failures);
}
private static string GetFilePath(string relativePath, Diagnostic diagnostic)
{
if (diagnostic.Location == Location.None)
{
return relativePath;
}
return diagnostic.Location.GetMappedLineSpan().Path;
}
private List<MetadataReference> GetApplicationReferences()
{
var references = new List<MetadataReference>();
@ -222,20 +266,25 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
return diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error;
}
private static string ReadFileContentsSafely(IFileInfo fileInfo)
private static string ReadFileContentsSafely(IFileProvider fileProvider, string filePath)
{
try
var fileInfo = fileProvider.GetFileInfo(filePath);
if (fileInfo.Exists)
{
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
try
{
return reader.ReadToEnd();
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
{
return reader.ReadToEnd();
}
}
catch
{
// Ignore any failures
}
}
catch
{
// Ignore any failures
return null;
}
return null;
}
}
}

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Precompilation
isEnabledByDefault: true);
var location = error.Location;
if (location == SourceLocation.Undefined)
if (location.Equals(SourceLocation.Undefined))
{
location = SourceLocation.Zero;
}

View File

@ -394,6 +394,22 @@ namespace Microsoft.AspNet.Mvc.Razor
return string.Format(CultureInfo.CurrentCulture, GetString("RazorFileInfoCollection_ResourceCouldNotBeFound"), p0, p1);
}
/// <summary>
/// Generated Code
/// </summary>
internal static string GeneratedCodeFileName
{
get { return GetString("GeneratedCodeFileName"); }
}
/// <summary>
/// Generated Code
/// </summary>
internal static string FormatGeneratedCodeFileName()
{
return GetString("GeneratedCodeFileName");
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -189,4 +189,7 @@
<data name="RazorFileInfoCollection_ResourceCouldNotBeFound" xml:space="preserve">
<value>The resource '{0}' specified by '{1}' could not be found.</value>
</data>
<data name="GeneratedCodeFileName" xml:space="preserve">
<value>Generated Code</value>
</data>
</root>

View File

@ -47,5 +47,26 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Contains($"/Views/ErrorPageMiddleware/{action}.cshtml", content);
Assert.Contains(expected, content);
}
[Fact]
public async Task CompilationFailuresFromGlobalImportAreListed()
{
// Arrange
var expectedMessage = "The type or namespace name &#x27;NamespaceDoesNotExist&#x27; could not be found ("
+ "are you missing a using directive or an assembly reference?)";
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
var expectedMediaType = MediaTypeHeaderValue.Parse("text/html");
// Act
var response = await client.GetAsync("http://localhost/ErrorFromGlobalImport");
// Assert
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
Assert.Equal(expectedMediaType, response.Content.Headers.ContentType);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains(@"Views\ErrorFromGlobalImport\_GlobalImport.cshtml", content);
Assert.Contains(expectedMessage, content);
}
}
}

View File

@ -38,33 +38,72 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
var codeTrees = utility.GetInheritedCodeTrees(@"Views\home\Index.cshtml");
// Assert
Assert.Equal(2, codeTrees.Count);
var viewStartChunks = codeTrees[0].Chunks;
Assert.Equal(3, viewStartChunks.Count);
Assert.Collection(codeTrees,
codeTree =>
{
var globalImportPath = @"Views\home\_GlobalImport.cshtml";
Assert.Collection(codeTree.Chunks,
chunk =>
{
Assert.IsType<LiteralChunk>(chunk);
Assert.Equal(globalImportPath, chunk.Start.FilePath);
},
chunk =>
{
var usingChunk = Assert.IsType<UsingChunk>(chunk);
Assert.Equal("MyNamespace", usingChunk.Namespace);
Assert.Equal(globalImportPath, chunk.Start.FilePath);
},
chunk =>
{
Assert.IsType<LiteralChunk>(chunk);
Assert.Equal(globalImportPath, chunk.Start.FilePath);
});
},
codeTree =>
{
var globalImportPath = @"Views\_GlobalImport.cshtml";
Assert.Collection(codeTree.Chunks,
chunk =>
{
Assert.IsType<LiteralChunk>(chunk);
Assert.Equal(globalImportPath, chunk.Start.FilePath);
},
chunk =>
{
var injectChunk = Assert.IsType<InjectChunk>(chunk);
Assert.Equal("MyHelper<TModel>", injectChunk.TypeName);
Assert.Equal("Helper", injectChunk.MemberName);
Assert.Equal(globalImportPath, chunk.Start.FilePath);
},
chunk =>
{
Assert.IsType<LiteralChunk>(chunk);
Assert.Equal(globalImportPath, chunk.Start.FilePath);
},
chunk =>
{
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunk);
Assert.Equal("MyBaseType", setBaseTypeChunk.TypeName);
Assert.Equal(globalImportPath, chunk.Start.FilePath);
Assert.IsType<LiteralChunk>(viewStartChunks[0]);
var usingChunk = Assert.IsType<UsingChunk>(viewStartChunks[1]);
Assert.Equal("MyNamespace", usingChunk.Namespace);
Assert.IsType<LiteralChunk>(viewStartChunks[2]);
viewStartChunks = codeTrees[1].Chunks;
Assert.Equal(7, viewStartChunks.Count);
Assert.IsType<LiteralChunk>(viewStartChunks[0]);
var injectChunk = Assert.IsType<InjectChunk>(viewStartChunks[1]);
Assert.Equal("MyHelper<TModel>", injectChunk.TypeName);
Assert.Equal("Helper", injectChunk.MemberName);
Assert.IsType<LiteralChunk>(viewStartChunks[2]);
var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(viewStartChunks[3]);
Assert.Equal("MyBaseType", setBaseTypeChunk.TypeName);
Assert.IsType<LiteralChunk>(viewStartChunks[4]);
Assert.IsType<StatementChunk>(viewStartChunks[5]);
Assert.IsType<LiteralChunk>(viewStartChunks[6]);
},
chunk =>
{
Assert.IsType<LiteralChunk>(chunk);
Assert.Equal(globalImportPath, chunk.Start.FilePath);
},
chunk =>
{
Assert.IsType<StatementChunk>(chunk);
Assert.Equal(globalImportPath, chunk.Start.FilePath);
},
chunk =>
{
Assert.IsType<LiteralChunk>(chunk);
Assert.Equal(globalImportPath, chunk.Start.FilePath);
});
});
}
[Fact]

View File

@ -172,7 +172,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var actual = descriptors[i];
Assert.Equal(expected.DirectiveText, actual.DirectiveText, StringComparer.Ordinal);
Assert.Equal(expected.Location, actual.Location);
Assert.Equal(SourceLocation.Zero, actual.Location);
Assert.Equal(expected.DirectiveType, actual.DirectiveType);
}
}

View File

@ -14,14 +14,15 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
{
// Arrange
var compilationFailure = Mock.Of<ICompilationFailure>();
var result = CompilationResult.Failed(compilationFailure);
var failures = new[] { compilationFailure };
var result = CompilationResult.Failed(failures);
// Act and Assert
Assert.Null(result.CompiledType);
Assert.Same(compilationFailure, result.CompilationFailure);
Assert.Same(failures, result.CompilationFailures);
var exception = Assert.Throws<CompilationFailedException>(() => result.EnsureSuccessful());
Assert.Collection(exception.CompilationFailures,
failure => Assert.Same(compilationFailure, failure));
var failure = Assert.Single(exception.CompilationFailures);
Assert.Same(compilationFailure, failure);
}
}
}

View File

@ -8,6 +8,7 @@ using Microsoft.AspNet.Razor;
using Microsoft.AspNet.Razor.Generator.Compiler;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.Framework.OptionsModel;
using Moq;
using Xunit;
@ -36,7 +37,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
compiler.Setup(c => c.Compile(relativeFileInfo, It.IsAny<string>()))
.Returns(CompilationResult.Successful(typeof(RazorCompilationServiceTest)));
var razorService = new RazorCompilationService(compiler.Object, host.Object);
var razorService = new RazorCompilationService(compiler.Object, host.Object, GetOptions());
// Act
razorService.Compile(relativeFileInfo);
@ -68,15 +69,19 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
var compiler = new Mock<ICompilationService>(MockBehavior.Strict);
var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml");
var razorService = new RazorCompilationService(compiler.Object, host.Object);
var razorService = new RazorCompilationService(compiler.Object, host.Object, GetOptions());
// Act
var result = razorService.Compile(relativeFileInfo);
// Assert
Assert.NotNull(result.CompilationFailure);
var message = Assert.Single(result.CompilationFailure.Messages);
Assert.Equal("some message", message.Message);
Assert.NotNull(result.CompilationFailures);
Assert.Collection(result.CompilationFailures,
failure =>
{
var message = Assert.Single(failure.Messages);
Assert.Equal("some message", message.Message);
});
host.Verify();
}
@ -105,7 +110,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
compiler.Setup(c => c.Compile(relativeFileInfo, code))
.Returns(compilationResult)
.Verifiable();
var razorService = new RazorCompilationService(compiler.Object, host.Object);
var razorService = new RazorCompilationService(compiler.Object, host.Object, GetOptions());
// Act
var result = razorService.Compile(relativeFileInfo);
@ -115,6 +120,86 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
compiler.Verify();
}
[Fact]
public void GetCompilationFailedResult_ReturnsCompilationResult_WithGroupedMessages()
{
// Arrange
var viewPath = @"views/index.razor";
var globalImportPath = @"views/global.import.cshtml";
var host = Mock.Of<IMvcRazorHost>();
var fileProvider = new TestFileProvider();
var file = fileProvider.AddFile(viewPath, "View Content");
fileProvider.AddFile(globalImportPath, "Global Import Content");
var relativeFileInfo = new RelativeFileInfo(file, viewPath);
var razorService = new RazorCompilationService(
Mock.Of<ICompilationService>(),
Mock.Of<IMvcRazorHost>(),
GetOptions(fileProvider));
var errors = new[]
{
new RazorError("message-1", new SourceLocation(1, 2, 17)),
new RazorError("message-2", new SourceLocation(viewPath, 1, 4, 6), 7),
new RazorError { Message = "message-3" },
new RazorError("message-4", new SourceLocation(globalImportPath, 1, 3, 8), 4),
};
// Act
var result = razorService.GetCompilationFailedResult(relativeFileInfo, errors);
// Assert
Assert.NotNull(result.CompilationFailures);
Assert.Collection(result.CompilationFailures,
failure =>
{
Assert.Equal(viewPath, failure.SourceFilePath);
Assert.Equal("View Content", failure.SourceFileContent);
Assert.Collection(failure.Messages,
message =>
{
Assert.Equal(errors[0].Message, message.Message);
Assert.Equal(viewPath, message.SourceFilePath);
Assert.Equal(3, message.StartLine);
Assert.Equal(17, message.StartColumn);
Assert.Equal(3, message.EndLine);
Assert.Equal(18, message.EndColumn);
},
message =>
{
Assert.Equal(errors[1].Message, message.Message);
Assert.Equal(viewPath, message.SourceFilePath);
Assert.Equal(5, message.StartLine);
Assert.Equal(6, message.StartColumn);
Assert.Equal(5, message.EndLine);
Assert.Equal(13, message.EndColumn);
},
message =>
{
Assert.Equal(errors[2].Message, message.Message);
Assert.Equal(viewPath, message.SourceFilePath);
Assert.Equal(0, message.StartLine);
Assert.Equal(-1, message.StartColumn);
Assert.Equal(0, message.EndLine);
Assert.Equal(0, message.EndColumn);
});
},
failure =>
{
Assert.Equal(globalImportPath, failure.SourceFilePath);
Assert.Equal("Global Import Content", failure.SourceFileContent);
Assert.Collection(failure.Messages,
message =>
{
Assert.Equal(errors[3].Message, message.Message);
Assert.Equal(globalImportPath, message.SourceFilePath);
Assert.Equal(4, message.StartLine);
Assert.Equal(8, message.StartColumn);
Assert.Equal(4, message.EndLine);
Assert.Equal(12, message.EndColumn);
});
});
}
private static GeneratorResults GetGeneratorResult()
{
return new GeneratorResults(
@ -124,5 +209,18 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
new CodeBuilderResult("", new LineMapping[0]),
new CodeTree());
}
private static IOptions<RazorViewEngineOptions> GetOptions(IFileProvider fileProvider = null)
{
var razorViewEngineOptions = new RazorViewEngineOptions
{
FileProvider = fileProvider ?? new TestFileProvider()
};
var options = new Mock<IOptions<RazorViewEngineOptions>>();
options.SetupGet(o => o.Options)
.Returns(razorViewEngineOptions);
return options.Object;
}
}
}

View File

@ -6,6 +6,9 @@ using System.IO;
using System.Reflection;
using System.Runtime.Versioning;
using Microsoft.AspNet.FileProviders;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Framework.OptionsModel;
using Microsoft.Framework.Runtime;
using Microsoft.Framework.Runtime.Compilation;
using Moq;
@ -38,7 +41,8 @@ public class MyTestType {}";
accessor,
libraryManager,
compilerOptionsProvider.Object,
mvcRazorHost.Object);
mvcRazorHost.Object,
GetOptions());
var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" },
"some-relative-path");
@ -52,11 +56,14 @@ public class MyTestType {}";
}
[Fact]
public void Compile_ReturnsCompilationFailureWithRelativePath()
public void Compile_ReturnsCompilationFailureWithPathsFromLinePragmas()
{
// Arrange
var viewPath = "some-relative-path";
var fileContent = "test file content";
var content = @"this should fail";
var content = $@"
#line 1 ""{viewPath}""
this should fail";
var applicationEnvironment = GetApplicationEnvironment();
var accessor = GetLoadContextAccessor();
var libraryManager = GetLibraryManager();
@ -67,17 +74,15 @@ public class MyTestType {}";
applicationEnvironment.Configuration))
.Returns(new CompilerOptions());
var mvcRazorHost = Mock.Of<IMvcRazorHost>();
var fileProvider = new TestFileProvider();
var fileInfo = fileProvider.AddFile(viewPath, fileContent);
var compilationService = new RoslynCompilationService(applicationEnvironment,
accessor,
libraryManager,
compilerOptionsProvider.Object,
mvcRazorHost);
var fileInfo = new TestFileInfo
{
Content = fileContent,
PhysicalPath = "physical path"
};
mvcRazorHost,
GetOptions(fileProvider));
var relativeFileInfo = new RelativeFileInfo(fileInfo, "some-relative-path");
// Act
@ -86,12 +91,13 @@ public class MyTestType {}";
// Assert
Assert.IsType<CompilationResult>(result);
Assert.Null(result.CompiledType);
Assert.Equal(relativeFileInfo.RelativePath, result.CompilationFailure.SourceFilePath);
Assert.Equal(fileContent, result.CompilationFailure.SourceFileContent);
var compilationFailure = Assert.Single(result.CompilationFailures);
Assert.Equal(relativeFileInfo.RelativePath, compilationFailure.SourceFilePath);
Assert.Equal(fileContent, compilationFailure.SourceFileContent);
}
[Fact]
public void Compile_ReturnsApplicationRelativePath_IfPhyicalPathIsNotSpecified()
public void Compile_ReturnsGeneratedCodePath_IfLinePragmaIsNotAvailable()
{
// Arrange
var fileContent = "file content";
@ -111,7 +117,8 @@ public class MyTestType {}";
accessor,
libraryManager,
compilerOptionsProvider.Object,
mvcRazorHost);
mvcRazorHost,
GetOptions());
var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { Content = fileContent },
"some-relative-path");
@ -121,15 +128,20 @@ public class MyTestType {}";
// Assert
Assert.IsType<CompilationResult>(result);
Assert.Null(result.CompiledType);
Assert.Equal("some-relative-path", result.CompilationFailure.SourceFilePath);
Assert.Equal(fileContent, result.CompilationFailure.SourceFileContent);
var compilationFailure = Assert.Single(result.CompilationFailures);
Assert.Equal("Generated Code", compilationFailure.SourceFilePath);
Assert.Equal(content, compilationFailure.SourceFileContent);
}
[Fact]
public void Compile_DoesNotThrow_IfFileCannotBeRead()
{
// Arrange
var content = @"this should fail";
var path = "some-relative-path";
var content = $@"
#line 1 ""{path}""
this should fail";
var applicationEnvironment = GetApplicationEnvironment();
var accessor = GetLoadContextAccessor();
var libraryManager = GetLibraryManager();
@ -141,15 +153,20 @@ public class MyTestType {}";
.Returns(new CompilerOptions());
var mvcRazorHost = Mock.Of<IMvcRazorHost>();
var mockFileInfo = new Mock<IFileInfo>();
mockFileInfo.Setup(f => f.CreateReadStream())
.Throws(new Exception());
var fileProvider = new TestFileProvider();
fileProvider.AddFile(path, mockFileInfo.Object);
var compilationService = new RoslynCompilationService(applicationEnvironment,
accessor,
libraryManager,
compilerOptionsProvider.Object,
mvcRazorHost);
var mockFileInfo = new Mock<IFileInfo>();
mockFileInfo.Setup(f => f.CreateReadStream())
.Throws(new Exception());
var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, "some-relative-path");
mvcRazorHost,
GetOptions(fileProvider));
var relativeFileInfo = new RelativeFileInfo(mockFileInfo.Object, path);
// Act
var result = compilationService.Compile(relativeFileInfo, content);
@ -157,8 +174,9 @@ public class MyTestType {}";
// Assert
Assert.IsType<CompilationResult>(result);
Assert.Null(result.CompiledType);
Assert.Equal("some-relative-path", result.CompilationFailure.SourceFilePath);
Assert.Null(result.CompilationFailure.SourceFileContent);
var compilationFailure = Assert.Single(result.CompilationFailures);
Assert.Equal(path, compilationFailure.SourceFilePath);
Assert.Null(compilationFailure.SourceFileContent);
}
[Fact]
@ -189,7 +207,8 @@ public class MyNonCustomDefinedClass {}
accessor,
libraryManager,
compilerOptionsProvider.Object,
mvcRazorHost.Object);
mvcRazorHost.Object,
GetOptions());
var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" },
"some-relative-path");
@ -225,7 +244,8 @@ public class NotRazorPrefixType {}";
accessor,
libraryManager,
compilerOptionsProvider.Object,
mvcRazorHost.Object);
mvcRazorHost.Object,
GetOptions());
var relativeFileInfo = new RelativeFileInfo(new TestFileInfo { PhysicalPath = "SomePath" },
"some-relative-path");
@ -238,6 +258,113 @@ public class NotRazorPrefixType {}";
Assert.Equal("RazorPrefixType", result.CompiledType.Name);
}
[Fact]
public void GetCompilationFailedResult_ReturnsCompilationResult_WithGroupedMessages()
{
// Arrange
var viewPath = "Views/Home/Index";
var generatedCodeFileName = "Generated Code";
var fileProvider = new TestFileProvider();
fileProvider.AddFile(viewPath, "view-content");
var options = new Mock<IOptions<RazorViewEngineOptions>>();
options.SetupGet(o => o.Options)
.Returns(new RazorViewEngineOptions
{
FileProvider = fileProvider
});
var compilationService = new RoslynCompilationService(
GetApplicationEnvironment(),
GetLoadContextAccessor(),
GetLibraryManager(),
Mock.Of<ICompilerOptionsProvider>(),
Mock.Of<IMvcRazorHost>(),
options.Object);
var assemblyName = "random-assembly-name";
var diagnostics = new[]
{
Diagnostic.Create(
GetDiagnosticDescriptor("message-1"),
Location.Create(
viewPath,
new TextSpan(10, 5),
new LinePositionSpan(new LinePosition(10, 1), new LinePosition(10, 2)))),
Diagnostic.Create(
GetDiagnosticDescriptor("message-2"),
Location.Create(
assemblyName,
new TextSpan(1, 6),
new LinePositionSpan(new LinePosition(1, 2), new LinePosition(3, 4)))),
Diagnostic.Create(
GetDiagnosticDescriptor("message-3"),
Location.Create(
viewPath,
new TextSpan(40, 50),
new LinePositionSpan(new LinePosition(30, 5), new LinePosition(40, 12)))),
};
// Act
var compilationResult = compilationService.GetCompilationFailedResult(
viewPath,
"compilation-content",
assemblyName,
diagnostics);
// Assert
Assert.Collection(compilationResult.CompilationFailures,
failure =>
{
Assert.Equal(viewPath, failure.SourceFilePath);
Assert.Equal("view-content", failure.SourceFileContent);
Assert.Collection(failure.Messages,
message =>
{
Assert.Equal("message-1", message.Message);
Assert.Equal(viewPath, message.SourceFilePath);
Assert.Equal(11, message.StartLine);
Assert.Equal(2, message.StartColumn);
Assert.Equal(11, message.EndLine);
Assert.Equal(3, message.EndColumn);
},
message =>
{
Assert.Equal("message-3", message.Message);
Assert.Equal(viewPath, message.SourceFilePath);
Assert.Equal(31, message.StartLine);
Assert.Equal(6, message.StartColumn);
Assert.Equal(41, message.EndLine);
Assert.Equal(13, message.EndColumn);
});
},
failure =>
{
Assert.Equal(generatedCodeFileName, failure.SourceFilePath);
Assert.Equal("compilation-content", failure.SourceFileContent);
Assert.Collection(failure.Messages,
message =>
{
Assert.Equal("message-2", message.Message);
Assert.Equal(assemblyName, message.SourceFilePath);
Assert.Equal(2, message.StartLine);
Assert.Equal(3, message.StartColumn);
Assert.Equal(4, message.EndLine);
Assert.Equal(5, message.EndColumn);
});
});
}
private static DiagnosticDescriptor GetDiagnosticDescriptor(string messageFormat)
{
return new DiagnosticDescriptor(
id: "someid",
title: "sometitle",
messageFormat: messageFormat,
category: "some-category",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);
}
private static ILibraryManager GetLibraryManager()
{
var fileReference = new Mock<IMetadataFileReference>();
@ -285,5 +412,18 @@ public class NotRazorPrefixType {}";
return applicationEnvironment.Object;
}
private static IOptions<RazorViewEngineOptions> GetOptions(IFileProvider fileProvider = null)
{
var razorViewEngineOptions = new RazorViewEngineOptions
{
FileProvider = fileProvider ?? new TestFileProvider()
};
var options = new Mock<IOptions<RazorViewEngineOptions>>();
options.SetupGet(o => o.Options)
.Returns(razorViewEngineOptions);
return options.Object;
}
}
}

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.Razor
throw new NotSupportedException();
}
public void AddFile(string path, string contents)
public TestFileInfo AddFile(string path, string contents)
{
var fileInfo = new TestFileInfo
{
@ -32,6 +32,8 @@ namespace Microsoft.AspNet.Mvc.Razor
};
AddFile(path, fileInfo);
return fileInfo;
}
public void AddFile(string path, IFileInfo contents)

View File

@ -18,5 +18,11 @@ namespace ErrorPageMiddlewareWebSite
{
return View();
}
[HttpGet("/ErrorFromGlobalImport")]
public IActionResult GlobalImportError()
{
return View("~/Views/ErrorFromGlobalImport/Index");
}
}
}

View File

@ -0,0 +1 @@
<h1>Hello world!</h1>

View File

@ -0,0 +1 @@
@using NamespaceDoesNotExist