diff --git a/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs b/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs index 8e2fcb3042..d11b9c58b2 100644 --- a/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs +++ b/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs @@ -27,8 +27,13 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation /// If not null, additional information will be written to this . /// A collection of instances representing any warnings or errors that were encountered. public ICollection CompileFiles(IEnumerable inputPaths, string outputNamespace, TextWriter resultOutput, TextWriter verboseOutput) - => inputPaths.SelectMany( - path => CompileSingleFile(path, outputNamespace, resultOutput, verboseOutput)).ToList(); + => inputPaths.SelectMany(path => + { + using (var reader = File.OpenText(path)) + { + return CompileSingleFile(path, reader, outputNamespace, resultOutput, verboseOutput); + } + }).ToList(); /// /// Writes C# source code representing a Blazor component defined by a Razor file. @@ -38,7 +43,7 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation /// A to which C# source code will be written. /// If not null, additional information will be written to this . /// An enumerable of instances representing any warnings or errors that were encountered. - public IEnumerable CompileSingleFile(string inputFilePath, string outputNamespace, TextWriter resultOutput, TextWriter verboseOutput) + public IEnumerable CompileSingleFile(string inputFilePath, TextReader inputFileReader, string outputNamespace, TextWriter resultOutput, TextWriter verboseOutput) { if (resultOutput == null) { diff --git a/test/Microsoft.Blazor.Build.Test/Microsoft.Blazor.Build.Test.csproj b/test/Microsoft.Blazor.Build.Test/Microsoft.Blazor.Build.Test.csproj index 35f4292050..6ca55b304f 100644 --- a/test/Microsoft.Blazor.Build.Test/Microsoft.Blazor.Build.Test.csproj +++ b/test/Microsoft.Blazor.Build.Test/Microsoft.Blazor.Build.Test.csproj @@ -8,6 +8,7 @@ + diff --git a/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs b/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs new file mode 100644 index 0000000000..f8985451b7 --- /dev/null +++ b/test/Microsoft.Blazor.Build.Test/RazorCompilerTest.cs @@ -0,0 +1,134 @@ +// 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 Microsoft.Blazor.Build.Core.RazorCompilation; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Xunit; + +namespace Microsoft.Blazor.Build.Test +{ + public class RazorCompilerTest + { + [Fact] + public void RejectsInvalidClassName() + { + // Arrange/Act + var result = CompileToCSharp( + "x:\\dir\\subdir\\Filename with spaces.cshtml", + "ignored code", + "ignored namespace"); + + // Assert + Assert.Collection(result.Diagnostics, + item => + { + Assert.Equal(RazorCompilerDiagnostic.DiagnosticType.Error, item.Type); + Assert.StartsWith($"Invalid name 'Filename with spaces'", item.Message); + }); + } + + [Fact] + public void CreatesClassWithCorrectNameAndNamespace() + { + // Arrange/Act + var result = CompileToAssembly( + "x:\\dir\\subdir\\Filename.cshtml", + "{* No code *}", + "MyCompany.MyNamespace"); + + // Assert + Assert.Empty(result.Diagnostics); + Assert.Collection(result.Assembly.GetTypes(), + type => + { + Assert.Equal("Filename", type.Name); + Assert.Equal("MyCompany.MyNamespace", type.Namespace); + }); + } + + private static CompileToAssemblyResult CompileToAssembly(string cshtmlFilename, string cshtmlContent, string outputNamespace) + { + var csharpResult = CompileToCSharp(cshtmlFilename, cshtmlContent, outputNamespace); + if (csharpResult.Diagnostics.Any()) + { + var diagnosticsLog = string.Join(Environment.NewLine, + csharpResult.Diagnostics.Select(d => d.FormatForConsole()).ToArray()); + throw new InvalidOperationException($"Aborting compilation to assembly because RazorCompiler returned nonempty diagnostics: {diagnosticsLog}"); + } + + var syntaxTrees = new[] + { + CSharpSyntaxTree.ParseText(csharpResult.Code) + }; + var references = new[] + { + MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location) + }; + var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + var assemblyName = "TestAssembly" + Guid.NewGuid().ToString("N"); + var compilation = CSharpCompilation.Create(assemblyName, + syntaxTrees, + references, + options); + + using (var peStream = new MemoryStream()) + { + compilation.Emit(peStream); + + return new CompileToAssemblyResult + { + Diagnostics = compilation.GetDiagnostics(), + VerboseLog = csharpResult.VerboseLog, + Assembly = Assembly.Load(peStream.ToArray()) + }; + } + } + + private static CompileToCSharpResult CompileToCSharp(string cshtmlFilename, string cshtmlContent, string outputNamespace) + { + using (var resultStream = new MemoryStream()) + using (var resultWriter = new StreamWriter(resultStream)) + using (var verboseLogStream = new MemoryStream()) + using (var verboseWriter = new StreamWriter(verboseLogStream)) + using (var inputReader = new StringReader(cshtmlContent)) + { + var diagnostics = new RazorCompiler().CompileSingleFile( + cshtmlFilename, + inputReader, + outputNamespace, + resultWriter, + verboseWriter); + + resultWriter.Flush(); + verboseWriter.Flush(); + return new CompileToCSharpResult + { + Code = Encoding.UTF8.GetString(resultStream.ToArray()), + VerboseLog = Encoding.UTF8.GetString(verboseLogStream.ToArray()), + Diagnostics = diagnostics + }; + } + } + + private class CompileToCSharpResult + { + public string Code { get; set; } + public string VerboseLog { get; set; } + public IEnumerable Diagnostics { get; set; } + } + + private class CompileToAssemblyResult + { + public Assembly Assembly { get; set; } + public string VerboseLog { get; set; } + public IEnumerable Diagnostics { get; set; } + } + } +}