Move FileSystemRazorProject into Razor.Evolution

Add tests
This commit is contained in:
Pranav K 2017-02-24 09:25:12 -08:00
parent c83741f11c
commit 12a502d775
18 changed files with 468 additions and 88 deletions

View File

@ -0,0 +1,69 @@
// 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.Diagnostics;
using System.IO;
using System.Linq;
namespace Microsoft.AspNetCore.Razor.Evolution
{
/// <summary>
/// A <see cref="RazorProject"/> implementation over the physical file system.
/// </summary>
public class FileSystemRazorProject : RazorProject
{
/// <summary>
/// Initializes a new instance of <see cref="FileSystemRazorProject"/>.
/// </summary>
/// <param name="root">The directory to root the file system at.</param>
public FileSystemRazorProject(string root)
{
if (string.IsNullOrEmpty(root))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(root));
}
Root = root.TrimEnd('/', '\\');
}
/// <summary>
/// The root of the file system.
/// </summary>
public string Root { get; }
/// <inheritdoc />
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
{
EnsureValidPath(basePath);
Debug.Assert(basePath.StartsWith("/"));
var absoluteBasePath = Path.Combine(Root, basePath.Substring(1));
var directory = new DirectoryInfo(absoluteBasePath);
if (!directory.Exists)
{
return Enumerable.Empty<RazorProjectItem>();
}
return directory
.EnumerateFiles("*.cshtml", SearchOption.AllDirectories)
.Select(file =>
{
var relativePath = file.FullName.Substring(absoluteBasePath.Length).Replace(Path.DirectorySeparatorChar, '/');
return new FileSystemRazorProjectItem(basePath, relativePath, file);
});
}
/// <inheritdoc />
public override RazorProjectItem GetItem(string path)
{
EnsureValidPath(path);
Debug.Assert(path.StartsWith("/"));
var absolutePath = Path.Combine(Root, path.Substring(1));
return new FileSystemRazorProjectItem("/", path, new FileInfo(absolutePath));
}
}
}

View File

@ -0,0 +1,49 @@
// 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.IO;
namespace Microsoft.AspNetCore.Razor.Evolution
{
/// <summary>
/// An implementation of <see cref="RazorProjectItem"/> using <see cref="FileInfo"/>.
/// </summary>
public class FileSystemRazorProjectItem : RazorProjectItem
{
/// <summary>
/// Initializes a new instance of <see cref="FileSystemRazorProjectItem"/>.
/// </summary>
/// <param name="basePath">The base path.</param>
/// <param name="path">The path.</param>
/// <param name="file">The <see cref="FileInfo"/>.</param>
public FileSystemRazorProjectItem(string basePath, string path, FileInfo file)
{
BasePath = basePath;
Path = path;
File = file;
}
/// <summary>
/// Gets the <see cref="FileInfo"/>.
/// </summary>
public FileInfo File { get; }
/// <inheritdoc />
public override string BasePath { get; }
/// <inheritdoc />
public override string Path { get; }
/// <inheritdoc />
public override bool Exists => File.Exists;
/// <inheritdoc />
public override string Filename => File.Name;
/// <inheritdoc />
public override string PhysicalPath => File.FullName;
/// <inheritdoc />
public override Stream Read() => File.OpenRead();
}
}

View File

@ -8,8 +8,17 @@ using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
namespace Microsoft.AspNetCore.Razor.Evolution
{
/// <summary>
/// Extension methods to <see cref="IRazorEngineBuilder" />.
/// </summary>
public static class RazorEngineBuilderExtensions
{
/// <summary>
/// Adds the specified <see cref="DirectiveDescriptor"/>.
/// </summary>
/// <param name="builder">The <see cref="IRazorEngineBuilder"/>.</param>
/// <param name="directive">The <see cref="DirectiveDescriptor"/> to add.</param>
/// <returns>The <see cref="IRazorEngineBuilder"/>.</returns>
public static IRazorEngineBuilder AddDirective(this IRazorEngineBuilder builder, DirectiveDescriptor directive)
{
if (builder == null)
@ -28,6 +37,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution
return builder;
}
/// <summary>
/// Adds the specified <see cref="IRuntimeTargetExtension"/>.
/// </summary>
/// <param name="builder">The <see cref="IRazorEngineBuilder"/>.</param>
/// <param name="extension">The <see cref="IRuntimeTargetExtension"/> to add.</param>
/// <returns>The <see cref="IRazorEngineBuilder"/>.</returns>
public static IRazorEngineBuilder AddTargetExtension(this IRazorEngineBuilder builder, IRuntimeTargetExtension extension)
{
if (builder == null)
@ -46,6 +61,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution
return builder;
}
/// <summary>
/// Sets the base type for generated types.
/// </summary>
/// <param name="builder">The <see cref="IRazorEngineBuilder"/>.</param>
/// <param name="baseType">The name of the base type.</param>
/// <returns>The <see cref="IRazorEngineBuilder"/>.</returns>
public static IRazorEngineBuilder SetBaseType(this IRazorEngineBuilder builder, string baseType)
{
if (builder == null)
@ -58,17 +79,13 @@ namespace Microsoft.AspNetCore.Razor.Evolution
return builder;
}
public static IRazorEngineBuilder SetClassName(this IRazorEngineBuilder builder, string className)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
ConfigureClass(builder, (document, @class) => @class.Name = className);
return builder;
}
/// <summary>
/// Registers a class configuration delegate that gets invoked during code generation.
/// </summary>
/// <param name="builder">The <see cref="IRazorEngineBuilder"/>.</param>
/// <param name="configureClass"><see cref="Action"/> invoked to configure
/// <see cref="ClassDeclarationIRNode"/> during code generation.</param>
/// <returns>The <see cref="IRazorEngineBuilder"/>.</returns>
public static IRazorEngineBuilder ConfigureClass(
this IRazorEngineBuilder builder,
Action<RazorCodeDocument, ClassDeclarationIRNode> configureClass)
@ -88,6 +105,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution
return builder;
}
/// <summary>
/// Sets the namespace for generated types.
/// </summary>
/// <param name="builder">The <see cref="IRazorEngineBuilder"/>.</param>
/// <param name="namespaceName">The name of the namespace.</param>
/// <returns>The <see cref="IRazorEngineBuilder"/>.</returns>
public static IRazorEngineBuilder SetNamespace(this IRazorEngineBuilder builder, string namespaceName)
{
if (builder == null)

View File

@ -2,7 +2,6 @@
// 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;
@ -35,22 +34,21 @@ namespace RazorPageGenerator
@class.AccessModifier = "internal";
});
builder.Features.Add(new RemovePragamaChecksumFeature());
builder.Features.Add(new RemovePragmaChecksumFeature());
});
var viewDirectories = Directory.EnumerateDirectories(targetProjectDirectory, "Views", SearchOption.AllDirectories);
var razorProject = new FileSystemRazorProject(targetProjectDirectory);
var templateEngine = new RazorTemplateEngine(razorEngine, razorProject);
var fileCount = 0;
foreach (var viewDir in viewDirectories)
{
Console.WriteLine();
Console.WriteLine(" Generating code files for views in {0}", viewDir);
var razorProject = new FileSystemRazorProject(viewDir);
var templateEngine = new RazorTemplateEngine(razorEngine, razorProject);
var cshtmlFiles = razorProject.EnumerateItems("");
var viewDirPath = viewDir.Substring(targetProjectDirectory.Length).Replace('\\', '/');
var cshtmlFiles = razorProject.EnumerateItems(viewDirPath).Cast<FileSystemRazorProjectItem>();
if (!cshtmlFiles.Any())
{
@ -72,69 +70,37 @@ namespace RazorPageGenerator
Console.WriteLine();
}
private static void GenerateCodeFile(RazorTemplateEngine templateEngine, RazorProjectItem projectItem)
private static void GenerateCodeFile(RazorTemplateEngine templateEngine, FileSystemRazorProjectItem projectItem)
{
var cSharpDocument = templateEngine.GenerateCode(projectItem);
var projectItemWrapper = new FileSystemRazorProjectItemWrapper(projectItem);
var cSharpDocument = templateEngine.GenerateCode(projectItemWrapper);
if (cSharpDocument.Diagnostics.Any())
{
var diagnostics = string.Join(Environment.NewLine, cSharpDocument.Diagnostics);
Console.WriteLine($"One or more parse errors encountered. This will not prevent the generator from continuing: {Environment.NewLine}{diagnostics}.");
}
var generatedCodeFilePath = Path.ChangeExtension(
((FileSystemRazorProjectItem)projectItem).FileInfo.FullName,
".Designer.cs");
var generatedCodeFilePath = Path.ChangeExtension(projectItem.PhysicalPath, ".Designer.cs");
File.WriteAllText(generatedCodeFilePath, cSharpDocument.GeneratedCode);
}
private class FileSystemRazorProject : RazorProject
private class FileSystemRazorProjectItemWrapper : RazorProjectItem
{
private readonly string _basePath;
private readonly FileSystemRazorProjectItem _source;
public FileSystemRazorProject(string basePath)
public FileSystemRazorProjectItemWrapper(FileSystemRazorProjectItem item)
{
_basePath = basePath;
_source = item;
}
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
{
return new DirectoryInfo(_basePath)
.EnumerateFiles("*.cshtml", SearchOption.TopDirectoryOnly)
.Select(file => GetItem(basePath, file));
}
public override string BasePath => _source.BasePath;
public override RazorProjectItem GetItem(string path) => throw new NotSupportedException();
private RazorProjectItem GetItem(string basePath, FileInfo file)
{
if (!file.Exists)
{
throw new FileNotFoundException($"{file.FullName} does not exist.");
}
return new FileSystemRazorProjectItem(basePath, file);
}
}
private class FileSystemRazorProjectItem : RazorProjectItem
{
public FileSystemRazorProjectItem(string basePath, FileInfo fileInfo)
{
BasePath = basePath;
Path = fileInfo.Name;
FileInfo = fileInfo;
}
public FileInfo FileInfo { get; }
public override string BasePath { get; }
public override string Path { get; }
public override string Path => _source.Path;
// Mask the full name since we don't want a developer's local file paths to be commited.
public override string PhysicalPath => FileInfo.Name;
public override string PhysicalPath => _source.Filename;
public override bool Exists => true;
public override bool Exists => _source.Exists;
public override Stream Read()
{
@ -144,8 +110,8 @@ namespace RazorPageGenerator
private string ProcessFileIncludes()
{
var basePath = FileInfo.DirectoryName;
var cshtmlContent = File.ReadAllText(FileInfo.FullName);
var basePath = _source.File.DirectoryName;
var cshtmlContent = File.ReadAllText(_source.PhysicalPath);
var startMatch = "<%$ include: ";
var endMatch = " %>";
@ -160,7 +126,7 @@ namespace RazorPageGenerator
var endIndex = cshtmlContent.IndexOf(endMatch, startIndex);
if (endIndex == -1)
{
throw new InvalidOperationException($"Invalid include file format in {FileInfo.FullName}. Usage example: <%$ include: ErrorPage.js %>");
throw new InvalidOperationException($"Invalid include file format in {_source.PhysicalPath}. Usage example: <%$ include: ErrorPage.js %>");
}
var includeFileName = cshtmlContent.Substring(startIndex + startMatch.Length, endIndex - (startIndex + startMatch.Length));
Console.WriteLine(" Inlining file {0}", includeFileName);

View File

@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
namespace RazorPageGenerator
{
class RemovePragamaChecksumFeature : RazorIRPassBase, IRazorIROptimizationPass
class RemovePragmaChecksumFeature : RazorIRPassBase, IRazorIROptimizationPass
{
public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
{

View File

@ -0,0 +1,58 @@
// 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.IO;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution
{
public class FileSystemRazorProjectItemTest
{
private static string TestFolder { get; } =
Path.Combine(TestProject.GetProjectDirectory(), "TestFiles", "FileSystemRazorProject");
[Fact]
public void FileSystemRazorProjectItem_SetsProperties()
{
// Arrange
var fileInfo = new FileInfo(Path.Combine(TestFolder, "Home.cshtml"));
// Act
var projectItem = new FileSystemRazorProjectItem("/Views", "/Home.cshtml", fileInfo);
// Assert
Assert.Equal("/Home.cshtml", projectItem.Path);
Assert.Equal("/Views", projectItem.BasePath);
Assert.True(projectItem.Exists);
Assert.Equal("Home.cshtml", projectItem.Filename);
Assert.Equal(fileInfo.FullName, projectItem.PhysicalPath);
}
[Fact]
public void Exists_ReturnsFalseWhenFileDoesNotExist()
{
// Arrange
var fileInfo = new FileInfo(Path.Combine(TestFolder, "Views", "FileDoesNotExist.cshtml"));
// Act
var projectItem = new FileSystemRazorProjectItem("/Views", "/FileDoesNotExist.cshtml", fileInfo);
// Assert
Assert.False(projectItem.Exists);
}
[Fact]
public void Read_ReturnsReadStream()
{
// Arrange
var fileInfo = new FileInfo(Path.Combine(TestFolder, "Home.cshtml"));
var projectItem = new FileSystemRazorProjectItem("/", "/Home.cshtml", fileInfo);
// Act
var stream = projectItem.Read();
// Assert
Assert.Equal("home-content", new StreamReader(stream).ReadToEnd());
}
}
}

View File

@ -0,0 +1,108 @@
// 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.IO;
using System.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution
{
public class FileSystemRazorProjectTest
{
private static string TestFolder { get; } =
Path.Combine(TestProject.GetProjectDirectory(), "TestFiles", "FileSystemRazorProject");
[Fact]
public void EnumerateItems_DiscoversAllCshtmlFiles()
{
// Arrange
var fileSystemProject = new FileSystemRazorProject(TestFolder);
// Act
var files = fileSystemProject.EnumerateItems("/");
// Assert
Assert.Collection(files.OrderBy(f => f.Path),
file =>
{
Assert.Equal("/Home.cshtml", file.Path);
Assert.Equal("/", file.BasePath);
},
file =>
{
Assert.Equal("/Views/About/About.cshtml", file.Path);
Assert.Equal("/", file.BasePath);
},
file =>
{
Assert.Equal("/Views/Home/Index.cshtml", file.Path);
Assert.Equal("/", file.BasePath);
});
}
[Fact]
public void EnumerateItems_DiscoversAllCshtmlFiles_UnderSpecifiedBasePath()
{
// Arrange
var fileSystemProject = new FileSystemRazorProject(TestFolder);
// Act
var files = fileSystemProject.EnumerateItems("/Views");
// Assert
Assert.Collection(files.OrderBy(f => f.Path),
file =>
{
Assert.Equal("/About/About.cshtml", file.Path);
Assert.Equal("/Views", file.BasePath);
},
file =>
{
Assert.Equal("/Home/Index.cshtml", file.Path);
Assert.Equal("/Views", file.BasePath);
});
}
[Fact]
public void EnumerateItems_ReturnsEmptySequence_WhenBasePathDoesNotExist()
{
// Arrange
var fileSystemProject = new FileSystemRazorProject(TestFolder);
// Act
var files = fileSystemProject.EnumerateItems("/Does-Not-Exist");
// Assert
Assert.Empty(files);
}
[Fact]
public void GetItem_ReturnsFileFromDisk()
{
// Arrange
var path = "/Views/About/About.cshtml";
var fileSystemProject = new FileSystemRazorProject(TestFolder);
// Act
var file = fileSystemProject.GetItem(path);
// Assert
Assert.True(file.Exists);
Assert.Equal(path, file.Path);
}
[Fact]
public void GetItem_ReturnsNotFoundResult()
{
// Arrange
var path = "/NotFound.cshtml";
var fileSystemProject = new FileSystemRazorProject(TestFolder);
// Act
var file = fileSystemProject.GetItem(path);
// Assert
Assert.False(file.Exists);
}
}
}

View File

@ -20,26 +20,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests
[IntializeTestFile]
public abstract class IntegrationTestBase
{
private static readonly string ThisProjectName = typeof(IntializeTestFileAttribute).GetTypeInfo().Assembly.GetName().Name;
private static string FindTestProjectRoot()
{
#if NET452
var currentDirectory = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
#else
var currentDirectory = new DirectoryInfo(AppContext.BaseDirectory);
#endif
while (currentDirectory != null &&
!string.Equals(currentDirectory.Name, ThisProjectName, StringComparison.Ordinal))
{
currentDirectory = currentDirectory.Parent;
}
var normalizedSeparators = currentDirectory.FullName.Replace(Path.DirectorySeparatorChar, '/');
return currentDirectory.FullName;
}
#if GENERATE_BASELINES
private static readonly bool GenerateBaselines = true;
#else
@ -50,8 +30,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests
private static readonly AsyncLocal<string> _filename = new AsyncLocal<string>();
#endif
protected static string TestProjectRoot { get; } = FindTestProjectRoot();
protected static string TestProjectRoot { get; } = TestProject.GetProjectDirectory();
// Used by the test framework to set the 'base' name for test files.
public static string Filename

View File

@ -49,5 +49,63 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests
// Assert
AssertCSharpDocumentMatchesBaseline(cSharpDocument);
}
[Fact]
public void GenerateCodeWithConfigureClass()
{
// Arrange
var filePath = Path.Combine(TestProjectRoot, $"{Filename}.cshtml");
var content = File.ReadAllText(filePath);
var projectItem = new TestRazorProjectItem($"{Filename}.cshtml", "")
{
Content = content,
};
var project = new TestRazorProject(new[] { projectItem });
var razorEngine = RazorEngine.Create(engine =>
{
engine.ConfigureClass((document, @class) =>
{
@class.Name = "MyClass";
@class.AccessModifier = "protected internal";
});
engine.ConfigureClass((document, @class) =>
{
@class.Interfaces = new[] { "global::System.IDisposable" };
@class.BaseType = "CustomBaseType";
});
});
var templateEngine = new RazorTemplateEngine(razorEngine, project);
// Act
var cSharpDocument = templateEngine.GenerateCode(projectItem.Path);
// Assert
AssertCSharpDocumentMatchesBaseline(cSharpDocument);
}
[Fact]
public void GenerateCodeWithSetNamespace()
{
// Arrange
var filePath = Path.Combine(TestProjectRoot, $"{Filename}.cshtml");
var content = File.ReadAllText(filePath);
var projectItem = new TestRazorProjectItem($"{Filename}.cshtml", "")
{
Content = content,
};
var project = new TestRazorProject(new[] { projectItem });
var razorEngine = RazorEngine.Create(engine =>
{
engine.SetNamespace("MyApp.Razor.Views");
});
var templateEngine = new RazorTemplateEngine(razorEngine, project);
// Act
var cSharpDocument = templateEngine.GenerateCode(projectItem.Path);
// Assert
AssertCSharpDocumentMatchesBaseline(cSharpDocument);
}
}
}

View File

@ -0,0 +1,16 @@
#pragma checksum "TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithConfigureClass.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "247d02621904d87ab805e437f33c5d03b9a0de91"
namespace Razor
{
#line hidden
using System;
using System.Threading.Tasks;
protected internal class MyClass : CustomBaseType, global::System.IDisposable
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral("<h1>Hello world!</h1>");
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,16 @@
#pragma checksum "TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithSetNamespace.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "247d02621904d87ab805e437f33c5d03b9a0de91"
namespace MyApp.Razor.Views
{
#line hidden
using System;
using System.Threading.Tasks;
public class Template
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
WriteLiteral("<h1>Hello world!</h1>");
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,32 @@
// 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.IO;
using System.Reflection;
namespace Microsoft.AspNetCore.Razor.Evolution
{
public static class TestProject
{
private static readonly string ThisProjectName = typeof(TestProject).GetTypeInfo().Assembly.GetName().Name;
public static string GetProjectDirectory()
{
#if NET452
var currentDirectory = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
#else
var currentDirectory = new DirectoryInfo(AppContext.BaseDirectory);
#endif
while (currentDirectory != null &&
!string.Equals(currentDirectory.Name, ThisProjectName, StringComparison.Ordinal))
{
currentDirectory = currentDirectory.Parent;
}
return currentDirectory.FullName;
}
}
}