Propogate additional compilation data from RoslynCompilationService
* Update CompilationFailedException to include path of file being compiled * Pass in path being compiled to Rolsyn. * Adding doc comments for compilation pieces Fixes #869
This commit is contained in:
parent
d95b85d057
commit
5e010597cd
|
|
@ -4,38 +4,70 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception thrown when accessing the result of a failed compilation.
|
||||
/// </summary>
|
||||
public class CompilationFailedException : Exception
|
||||
{
|
||||
public CompilationFailedException(IEnumerable<CompilationMessage> messages, string generatedCode)
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="CompilationFailedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The path of the Razor source file that was compiled.</param>
|
||||
/// <param name="fileContent">The contents of the Razor source file.</param>
|
||||
/// <param name="compiledContent">The generated C# content that was compiled.</param>
|
||||
/// <param name="messages">A sequence of <see cref="CompilationMessage"/> encountered
|
||||
/// during compilation.</param>
|
||||
public CompilationFailedException(
|
||||
[NotNull] string filePath,
|
||||
[NotNull] string fileContent,
|
||||
[NotNull] string compiledContent,
|
||||
[NotNull] IEnumerable<CompilationMessage> messages)
|
||||
: base(FormatMessage(messages))
|
||||
{
|
||||
FilePath = filePath;
|
||||
FileContent = fileContent;
|
||||
CompiledContent = compiledContent;
|
||||
Messages = messages.ToList();
|
||||
GeneratedCode = generatedCode;
|
||||
}
|
||||
|
||||
public string GeneratedCode { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the path of the Razor source file that produced the compilation failure.
|
||||
/// </summary>
|
||||
public string FilePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a sequence of <see cref="CompilationMessage"/> instances encountered during compilation.
|
||||
/// </summary>
|
||||
public IEnumerable<CompilationMessage> Messages { get; private set; }
|
||||
|
||||
public string CompilationSource
|
||||
{
|
||||
get { return GeneratedCode; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the content of the Razor source file.
|
||||
/// </summary>
|
||||
public string FileContent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the generated C# content that was compiled.
|
||||
/// </summary>
|
||||
public string CompiledContent { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Compilation Failed:" + FormatMessage(Messages);
|
||||
return Resources.FormatCompilationFailed(FilePath) +
|
||||
Environment.NewLine +
|
||||
FormatMessage(Messages);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatMessage(IEnumerable<CompilationMessage> messages)
|
||||
{
|
||||
return String.Join(Environment.NewLine, messages);
|
||||
return string.Join(Environment.NewLine, messages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,30 @@
|
|||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a message encountered during compilation.
|
||||
/// </summary>
|
||||
public class CompilationMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="CompilationMessage"/> with the specified message.
|
||||
/// </summary>
|
||||
/// <param name="message">A message <see cref="string"/> produced from compilation.</param>
|
||||
public CompilationMessage(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a message produced from compilation.
|
||||
/// </summary>
|
||||
public string Message { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string"/> representation of this instance of <see cref="CompilationMessage"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="string"/> representing this <see cref="CompilationMessage"/> instance.</returns>
|
||||
/// <remarks>Returns same value as <see cref="Message"/>.</remarks>
|
||||
public override string ToString()
|
||||
{
|
||||
return Message;
|
||||
|
|
|
|||
|
|
@ -3,46 +3,145 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the result of compilation.
|
||||
/// </summary>
|
||||
public class CompilationResult
|
||||
{
|
||||
private readonly Type _type;
|
||||
private Type _type;
|
||||
|
||||
private CompilationResult(string generatedCode, Type type, IEnumerable<CompilationMessage> messages)
|
||||
private CompilationResult()
|
||||
{
|
||||
_type = type;
|
||||
GeneratedCode = generatedCode;
|
||||
Messages = messages.ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<CompilationMessage> Messages { get; private set; }
|
||||
|
||||
public string GeneratedCode { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the path of the Razor file that was compiled.
|
||||
/// </summary>
|
||||
public string FilePath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (File != null)
|
||||
{
|
||||
return File.PhysicalPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a sequence of <see cref="CompilationMessage"/> instances encountered during compilation.
|
||||
/// </summary>
|
||||
public IEnumerable<CompilationMessage> Messages { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets additional information from compilation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In the event of a compilation failure, values from this dictionary are copied to the
|
||||
/// <see cref="Exception.Data"/> property of the <see cref="Exception"/> thrown.
|
||||
/// </remarks>
|
||||
public IDictionary<string, object> AdditionalInfo
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the generated C# content that was compiled.
|
||||
/// </summary>
|
||||
public string CompiledContent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type produced as a result of compilation.
|
||||
/// </summary>
|
||||
/// <exception cref="CompilationFailedException">An error occured during compilation.</exception>
|
||||
public Type CompiledType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_type == null)
|
||||
{
|
||||
throw new CompilationFailedException(Messages, GeneratedCode);
|
||||
throw CreateCompilationFailedException();
|
||||
}
|
||||
|
||||
return _type;
|
||||
}
|
||||
}
|
||||
|
||||
public static CompilationResult Failed(string generatedCode, IEnumerable<CompilationMessage> messages)
|
||||
private IFileInfo File { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="CompilationResult"/> that represents a failure in compilation.
|
||||
/// </summary>
|
||||
/// <param name="fileInfo">The <see cref="IFileInfo"/> for the Razor file that was compiled.</param>
|
||||
/// <param name="compilationContent">The generated C# content to be compiled.</param>
|
||||
/// <param name="messages">The sequence of failure messages encountered during compilation.</param>
|
||||
/// <param name="additionalInfo">Additional info about the compilation.</param>
|
||||
/// <returns>A CompilationResult instance representing a failure.</returns>
|
||||
public static CompilationResult Failed([NotNull] IFileInfo file,
|
||||
[NotNull] string compilationContent,
|
||||
[NotNull] IEnumerable<CompilationMessage> messages,
|
||||
IDictionary<string, object> additionalInfo)
|
||||
{
|
||||
return new CompilationResult(generatedCode, type: null, messages: messages);
|
||||
return new CompilationResult
|
||||
{
|
||||
File = file,
|
||||
CompiledContent = compilationContent,
|
||||
Messages = messages,
|
||||
AdditionalInfo = additionalInfo
|
||||
};
|
||||
}
|
||||
|
||||
public static CompilationResult Successful(string generatedCode, Type type)
|
||||
/// <summary>
|
||||
/// Creates a <see cref="CompilationResult"/> that represents a success in compilation.
|
||||
/// </summary>
|
||||
/// <param name="type">The compiled type.</param>
|
||||
/// <returns>A CompilationResult instance representing a success.</returns>
|
||||
public static CompilationResult Successful([NotNull] Type type)
|
||||
{
|
||||
return new CompilationResult(generatedCode, type, Enumerable.Empty<CompilationMessage>());
|
||||
return new CompilationResult
|
||||
{
|
||||
_type = type
|
||||
};
|
||||
}
|
||||
|
||||
private CompilationFailedException CreateCompilationFailedException()
|
||||
{
|
||||
var fileContent = ReadContent(File);
|
||||
var exception = new CompilationFailedException(FilePath, fileContent, CompiledContent, Messages);
|
||||
if (AdditionalInfo != null)
|
||||
{
|
||||
foreach (var item in AdditionalInfo)
|
||||
{
|
||||
exception.Data.Add(item.Key, item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return exception;
|
||||
}
|
||||
|
||||
private static string ReadContent(IFileInfo file)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = file.CreateReadStream())
|
||||
{
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Don't throw if reading the file fails.
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
return result;
|
||||
}
|
||||
|
||||
return CompilationResult.Successful(generatedCode: null, type: compiledType);
|
||||
return CompilationResult.Successful(compiledType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,23 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for compilation of a Razor page.
|
||||
/// </summary>
|
||||
public interface ICompilationService
|
||||
{
|
||||
CompilationResult Compile(string content);
|
||||
/// <summary>
|
||||
/// Compiles content and returns the result of compilation.
|
||||
/// </summary>
|
||||
/// <param name="fileInfo">The <see cref="IFileInfo"/> for the Razor file that was compiled.</param>
|
||||
/// <param name="compilationContent">The generated C# content to be compiled.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="CompilationResult"/> representing the result of compilation.
|
||||
/// </returns>
|
||||
CompilationResult Compile(IFileInfo fileInfo, string compilationContent);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,15 +7,22 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Emit;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Framework.Runtime;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
||||
{
|
||||
/// <summary>
|
||||
/// A type that uses Roslyn to compile C# content.
|
||||
/// </summary>
|
||||
public class RoslynCompilationService : ICompilationService
|
||||
{
|
||||
public static readonly string CompilationResultDiagnosticsKey = "Diagnostics";
|
||||
private static readonly ConcurrentDictionary<string, MetadataReference> _metadataFileCache =
|
||||
new ConcurrentDictionary<string, MetadataReference>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
|
|
@ -23,6 +30,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
private readonly IApplicationEnvironment _environment;
|
||||
private readonly IAssemblyLoaderEngine _loader;
|
||||
|
||||
/// <summary>
|
||||
/// Initalizes a new instance of the <see cref="RoslynCompilationService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="environment">The environment for the executing application.</param>
|
||||
/// <param name="loaderEngine">The loader used to load compiled assemblies.</param>
|
||||
/// <param name="libraryManager">The library manager that provides export and reference information.</param>
|
||||
public RoslynCompilationService(IApplicationEnvironment environment,
|
||||
IAssemblyLoaderEngine loaderEngine,
|
||||
ILibraryManager libraryManager)
|
||||
|
|
@ -32,9 +45,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
public CompilationResult Compile(string content)
|
||||
/// <inheritdoc />
|
||||
public CompilationResult Compile(IFileInfo fileInfo, string compilationContent)
|
||||
{
|
||||
var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(content) };
|
||||
var sourceText = SourceText.From(compilationContent, Encoding.UTF8);
|
||||
var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(sourceText, path: fileInfo.PhysicalPath) };
|
||||
var targetFramework = _environment.TargetFramework;
|
||||
|
||||
var references = GetApplicationReferences();
|
||||
|
|
@ -70,7 +85,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
.Select(d => GetCompilationMessage(formatter, d))
|
||||
.ToList();
|
||||
|
||||
return CompilationResult.Failed(content, messages);
|
||||
var additionalInfo = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ CompilationResultDiagnosticsKey, result.Diagnostics }
|
||||
};
|
||||
return CompilationResult.Failed(fileInfo, compilationContent, messages, additionalInfo);
|
||||
}
|
||||
|
||||
Assembly assembly;
|
||||
|
|
@ -89,7 +108,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
var type = assembly.GetExportedTypes()
|
||||
.First();
|
||||
|
||||
return CompilationResult.Successful(string.Empty, type);
|
||||
return CompilationResult.Successful(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -135,7 +154,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation
|
|||
});
|
||||
}
|
||||
|
||||
private CompilationMessage GetCompilationMessage(DiagnosticFormatter formatter, Diagnostic diagnostic)
|
||||
private static CompilationMessage GetCompilationMessage(DiagnosticFormatter formatter, Diagnostic diagnostic)
|
||||
{
|
||||
return new CompilationMessage(formatter.Format(diagnostic));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,22 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
return GetString("ArgumentCannotBeNullOrEmpty");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compilation for '{0}' failed:
|
||||
/// </summary>
|
||||
internal static string CompilationFailed
|
||||
{
|
||||
get { return GetString("CompilationFailed"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compilation for '{0}' failed:
|
||||
/// </summary>
|
||||
internal static string FormatCompilationFailed(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("CompilationFailed"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The layout view '{0}' could not be located.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
private readonly string _appRoot;
|
||||
|
||||
public RazorCompilationService(IApplicationEnvironment environment,
|
||||
ICompilationService compilationService,
|
||||
ICompilationService compilationService,
|
||||
IMvcRazorHost razorHost)
|
||||
{
|
||||
_environment = environment;
|
||||
|
|
@ -49,10 +49,10 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
if (!results.Success)
|
||||
{
|
||||
var messages = results.ParserErrors.Select(e => new CompilationMessage(e.Message));
|
||||
throw new CompilationFailedException(messages, results.GeneratedCode);
|
||||
return CompilationResult.Failed(file, results.GeneratedCode, messages, additionalInfo: null);
|
||||
}
|
||||
|
||||
return _baseCompilationService.Compile(results.GeneratedCode);
|
||||
return _baseCompilationService.Compile(file, results.GeneratedCode);
|
||||
}
|
||||
|
||||
private static string EnsureTrailingSlash([NotNull]string path)
|
||||
|
|
|
|||
|
|
@ -120,6 +120,9 @@
|
|||
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
||||
<value>The value cannot be null or empty.</value>
|
||||
</data>
|
||||
<data name="CompilationFailed" xml:space="preserve">
|
||||
<value>Compilation for '{0}' failed:</value>
|
||||
</data>
|
||||
<data name="LayoutCannotBeLocated" xml:space="preserve">
|
||||
<value>The layout view '{0}' could not be located.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.FileSystems;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||
{
|
||||
public class CompilationResultTest
|
||||
{
|
||||
[Fact]
|
||||
public void FailedResult_ThrowsWhenAccessingCompiledType()
|
||||
{
|
||||
// Arrange
|
||||
var expected =
|
||||
@"Compilation for 'myfile' failed:
|
||||
hello
|
||||
world";
|
||||
var originalContent = "Original file content";
|
||||
var fileInfo = new Mock<IFileInfo>();
|
||||
fileInfo.SetupGet(f => f.PhysicalPath)
|
||||
.Returns("myfile");
|
||||
var contentBytes = Encoding.UTF8.GetBytes(originalContent);
|
||||
fileInfo.Setup(f => f.CreateReadStream())
|
||||
.Returns(new MemoryStream(contentBytes));
|
||||
var messages = new[]
|
||||
{
|
||||
new CompilationMessage("hello"),
|
||||
new CompilationMessage("world")
|
||||
};
|
||||
var additionalInfo = new Dictionary<string, object>
|
||||
{
|
||||
{ "key", "value" }
|
||||
};
|
||||
var result = CompilationResult.Failed(fileInfo.Object,
|
||||
"<h1>hello world</h1>",
|
||||
messages,
|
||||
additionalInfo);
|
||||
|
||||
// Act and Assert
|
||||
var ex = Assert.Throws<CompilationFailedException>(() => result.CompiledType);
|
||||
Assert.Equal(expected, ex.Message);
|
||||
Assert.Equal("value", ex.Data["key"]);
|
||||
Assert.Equal(originalContent, ex.FileContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
<Content Include="project.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Compilation\CompilationResultTest.cs" />
|
||||
<Compile Include="BufferEntryCollectionTest.cs" />
|
||||
<Compile Include="MvcRazorCodeParserTest.cs" />
|
||||
<Compile Include="RazorCompilationServiceTest.cs" />
|
||||
|
|
|
|||
|
|
@ -27,14 +27,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
host.Setup(h => h.GenerateCode(@"views\index\home.cshtml", It.IsAny<Stream>()))
|
||||
.Returns(new GeneratorResults(new Block(new BlockBuilder { Type = BlockType.Comment }), new RazorError[0], new CodeBuilderResult("", new LineMapping[0])))
|
||||
.Verifiable();
|
||||
var compiler = new Mock<ICompilationService>();
|
||||
compiler.Setup(c => c.Compile(It.IsAny<string>()))
|
||||
.Returns(CompilationResult.Successful("", typeof(RazorCompilationServiceTest)));
|
||||
|
||||
var razorService = new RazorCompilationService(env.Object, compiler.Object, host.Object);
|
||||
var fileInfo = new Mock<IFileInfo>();
|
||||
fileInfo.Setup(f => f.PhysicalPath).Returns(viewPath);
|
||||
fileInfo.Setup(f => f.CreateReadStream()).Returns(Stream.Null);
|
||||
var compiler = new Mock<ICompilationService>();
|
||||
compiler.Setup(c => c.Compile(fileInfo.Object, It.IsAny<string>()))
|
||||
.Returns(CompilationResult.Successful(typeof(RazorCompilationServiceTest)));
|
||||
var razorService = new RazorCompilationService(env.Object, compiler.Object, host.Object);
|
||||
|
||||
// Act
|
||||
razorService.CompileCore(fileInfo.Object);
|
||||
|
|
|
|||
Loading…
Reference in New Issue