From 5b3f05bdc1f58225f2f8dab851262b22dc859317 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Thu, 11 Jan 2018 11:51:27 +0000 Subject: [PATCH] Begin emitting C# classes for Razor components --- .../Core/RazorCompilation/RazorCompiler.cs | 87 ++++++++++++++++--- .../RazorCompilerException.cs | 26 ++++++ .../targets/RazorCompilation.targets | 7 +- 3 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompilerException.cs diff --git a/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs b/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs index 4bb29643db..8e2fcb3042 100644 --- a/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs +++ b/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs @@ -1,30 +1,91 @@ // 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.Linq; +using System.Text.RegularExpressions; namespace Microsoft.Blazor.Build.Core.RazorCompilation { + /// + /// Provides facilities for transforming Razor files into Blazor component classes. + /// public class RazorCompiler { - public ICollection CompileFiles(IEnumerable inputPaths, string outputNamespace, TextWriter resultOutput, TextWriter verboseOutput) - { - var diagnostics = new List(); + // TODO: Relax this to allow for whatever C# allows + private static Regex ClassNameRegex + = new Regex("^[a-z][a-z0-9_]*$", RegexOptions.IgnoreCase); - foreach (var inputFilePath in inputPaths) + /// + /// Writes C# source code representing Blazor components defined by Razor files. + /// + /// Paths to the input files. + /// The namespace for the generated classes. + /// A to which C# source code will be written. + /// 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(); + + /// + /// Writes C# source code representing a Blazor component defined by a Razor file. + /// + /// The path to the input file. + /// The namespace for the generated class. + /// 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) + { + if (resultOutput == null) { - verboseOutput?.WriteLine($"Compiling {inputFilePath}..."); - resultOutput.WriteLine($"// TODO: Compile {inputFilePath}"); - diagnostics.Add(new RazorCompilerDiagnostic( - RazorCompilerDiagnostic.DiagnosticType.Warning, - inputFilePath, - 1, - 1, - "Compiler not implemented")); + throw new ArgumentNullException(nameof(resultOutput)); } - return diagnostics; + try + { + verboseOutput?.WriteLine($"Compiling {inputFilePath}..."); + var className = GetClassName(inputFilePath); + + resultOutput.WriteLine($"namespace {outputNamespace} {{"); + resultOutput.WriteLine($"public class {className}"); + resultOutput.WriteLine("{"); + resultOutput.WriteLine("}"); + resultOutput.WriteLine("}"); + resultOutput.WriteLine(); + + return Enumerable.Empty(); + } + catch (RazorCompilerException ex) + { + return new[] { ex.ToDiagnostic(inputFilePath) }; + } + catch (Exception ex) + { + return new[] + { + new RazorCompilerDiagnostic( + RazorCompilerDiagnostic.DiagnosticType.Error, + inputFilePath, + 1, + 1, + $"Unexpected exception: {ex.Message}{Environment.NewLine}{ex.StackTrace}") + }; + } + } + + private static string GetClassName(string inputFilePath) + { + var basename = Path.GetFileNameWithoutExtension(inputFilePath); + if (!ClassNameRegex.IsMatch(basename)) + { + throw new RazorCompilerException($"Invalid name '{basename}'. The name must be valid for a C# class name."); + } + + return basename; } } } diff --git a/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompilerException.cs b/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompilerException.cs new file mode 100644 index 0000000000..27508c5985 --- /dev/null +++ b/src/Microsoft.Blazor.Build/Core/RazorCompilation/RazorCompilerException.cs @@ -0,0 +1,26 @@ +// 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; + +namespace Microsoft.Blazor.Build.Core.RazorCompilation +{ + /// + /// Represents a fatal error during the transformation of a Blazor component from + /// Razor source code to C# source code. + /// + internal class RazorCompilerException : Exception + { + public RazorCompilerException(string message): base(message) + { + } + + public RazorCompilerDiagnostic ToDiagnostic(string sourceFilePath) + => new RazorCompilerDiagnostic( + RazorCompilerDiagnostic.DiagnosticType.Error, + sourceFilePath, + line: 1, // Later it might be necessary to take line/col constructor args, but not needed yet + column: 1, + message: Message); + } +} diff --git a/src/Microsoft.Blazor.Build/targets/RazorCompilation.targets b/src/Microsoft.Blazor.Build/targets/RazorCompilation.targets index 8682a2a1b2..290fc0e884 100644 --- a/src/Microsoft.Blazor.Build/targets/RazorCompilation.targets +++ b/src/Microsoft.Blazor.Build/targets/RazorCompilation.targets @@ -1,9 +1,4 @@ - - - - + BlazorComponents