Implement a simple base for document classifiers

This should allow us to de-dupe a lot of code in MVC.
This commit is contained in:
Ryan Nowak 2017-02-09 15:22:33 -08:00
parent eaadfb70eb
commit ea778b9b6d
7 changed files with 206 additions and 23 deletions

View File

@ -0,0 +1,19 @@
// 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.AspNetCore.Razor.Evolution.Intermediate;
namespace Microsoft.AspNetCore.Razor.Evolution
{
internal class DefaultDocumentClassifierPass : DocumentClassifierPassBase
{
public override int Order => RazorIRPass.DefaultDocumentClassifierOrder;
protected override string DocumentKind => "default";
protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
{
return true;
}
}
}

View File

@ -6,22 +6,35 @@ using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
namespace Microsoft.AspNetCore.Razor.Evolution namespace Microsoft.AspNetCore.Razor.Evolution
{ {
internal class DefaultDocumentClassifier : RazorIRPassBase public abstract class DocumentClassifierPassBase : RazorIRPassBase
{ {
public override int Order => RazorIRPass.DefaultDocumentClassifierOrder; protected abstract string DocumentKind { get; }
public static string DocumentKind = "default"; public override int Order => RazorIRPass.DocumentClassifierOrder;
public override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument) public sealed override DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
{ {
if (irDocument.DocumentKind != null) if (irDocument.DocumentKind != null)
{ {
return irDocument; return irDocument;
} }
if (!IsMatch(codeDocument, irDocument))
{
return irDocument;
}
irDocument.DocumentKind = DocumentKind; irDocument.DocumentKind = DocumentKind;
// Rewrite a use default namespace and class declaration. Rewrite(codeDocument, irDocument);
return irDocument;
}
private void Rewrite(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
{
// Rewrite the document from a flat structure to use a sensible default structure,
// a namespace and class declaration with a single 'razor' method.
var children = new List<RazorIRNode>(irDocument.Children); var children = new List<RazorIRNode>(irDocument.Children);
irDocument.Children.Clear(); irDocument.Children.Clear();
@ -39,7 +52,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
var method = new RazorMethodDeclarationIRNode() var method = new RazorMethodDeclarationIRNode()
{ {
//AccessModifier = "public", //AccessModifier = "public",
// Modifiers = new List<string>() { "async" }, // Modifiers = new List<string>() { "async" },
//Name = "Execute", //Name = "Execute",
//ReturnType = "Task", //ReturnType = "Task",
}; };
@ -62,7 +75,20 @@ namespace Microsoft.AspNetCore.Razor.Evolution
visitor.Visit(children[i]); visitor.Visit(children[i]);
} }
return irDocument; // Note that this is called at the *end* of rewriting so that user code can see the tree
// and look at its content to make a decision.
OnDocumentStructureCreated(codeDocument, @namespace, @class, method);
}
protected abstract bool IsMatch(RazorCodeDocument codeDocument, DocumentIRNode irDocument);
protected virtual void OnDocumentStructureCreated(
RazorCodeDocument codeDocument,
NamespaceDeclarationIRNode @namespace,
ClassDeclarationIRNode @class,
RazorMethodDeclarationIRNode @method)
{
// Intentionally empty.
} }
private class Visitor : RazorIRNodeVisitor private class Visitor : RazorIRNodeVisitor

View File

@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
builder.Features.Add(new TagHelperBinderSyntaxTreePass()); builder.Features.Add(new TagHelperBinderSyntaxTreePass());
// IR Passes // IR Passes
builder.Features.Add(new DefaultDocumentClassifier()); builder.Features.Add(new DefaultDocumentClassifierPass());
builder.Features.Add(new DefaultDirectiveIRPass()); builder.Features.Add(new DefaultDirectiveIRPass());
} }

View File

@ -226,7 +226,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
Assert.NotNull(irDocument); Assert.NotNull(irDocument);
// These tests depend on the document->namespace->class structure. // These tests depend on the document->namespace->class structure.
irDocument = new DefaultDocumentClassifier() { Engine = engine, }.Execute(codeDocument, irDocument); irDocument = new DefaultDocumentClassifierPass() { Engine = engine, }.Execute(codeDocument, irDocument);
return irDocument; return irDocument;
} }
} }

View File

@ -0,0 +1,56 @@
// 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.AspNetCore.Razor.Evolution.Intermediate;
using Xunit;
using static Microsoft.AspNetCore.Razor.Evolution.Intermediate.RazorIRAssert;
namespace Microsoft.AspNetCore.Razor.Evolution
{
// We're purposely lean on tests here because the functionality is well covered by
// integration tests, and is mostly implemented by the base class.
public class DefaultDocumentClassifierPassTest
{
[Fact]
public void Execute_IgnoresDocumentsWithDocumentKind()
{
// Arrange
var irDocument = new DocumentIRNode()
{
DocumentKind = "ignore",
};
var pass = new DefaultDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument);
// Assert
Assert.Equal("ignore", irDocument.DocumentKind);
NoChildren(irDocument);
}
[Fact]
public void Execute_CreatesClassStructure()
{
// Arrange
var irDocument = new DocumentIRNode();
var pass = new DefaultDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b =>{ });
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument);
// Assert
Assert.Equal("default", irDocument.DocumentKind);
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
var @class = SingleChild<ClassDeclarationIRNode>(@namespace);
var method = SingleChild<RazorMethodDeclarationIRNode>(@class);
NoChildren(method);
}
}
}

View File

@ -2,16 +2,17 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Evolution.Intermediate; using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
using Xunit; using Xunit;
using static Microsoft.AspNetCore.Razor.Evolution.Intermediate.RazorIRAssert; using static Microsoft.AspNetCore.Razor.Evolution.Intermediate.RazorIRAssert;
namespace Microsoft.AspNetCore.Razor.Evolution namespace Microsoft.AspNetCore.Razor.Evolution
{ {
public class DefaultDocumentClassifierTest public class DocumentClassifierPassBaseTest
{ {
[Fact] [Fact]
public void Execute_IgnoresDocumentsWithDocumentKind() public void Execute_HasDocumentKind_IgnoresDocument()
{ {
// Arrange // Arrange
var irDocument = new DocumentIRNode() var irDocument = new DocumentIRNode()
@ -19,7 +20,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
DocumentKind = "ignore", DocumentKind = "ignore",
}; };
var pass = new DefaultDocumentClassifier(); var pass = new TestDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b => { }); pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act // Act
@ -31,19 +32,39 @@ namespace Microsoft.AspNetCore.Razor.Evolution
} }
[Fact] [Fact]
public void Execute_CreatesClassStructure() public void Execute_NoMatch_IgnoresDocument()
{ {
// Arrange // Arrange
var irDocument = new DocumentIRNode(); var irDocument = new DocumentIRNode();
var pass = new DefaultDocumentClassifier(); var pass = new TestDocumentClassifierPass()
pass.Engine = RazorEngine.CreateEmpty(b =>{ }); {
Engine = RazorEngine.CreateEmpty(b => { }),
ShouldMatch = false,
};
// Act // Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument); pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument);
// Assert // Assert
Assert.Equal(DefaultDocumentClassifier.DocumentKind, irDocument.DocumentKind); Assert.Null(irDocument.DocumentKind);
NoChildren(irDocument);
}
[Fact]
public void Execute_Match_SetsDocumentType_AndCreatesStructure()
{
// Arrange
var irDocument = new DocumentIRNode();
var pass = new TestDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument);
// Assert
Assert.Equal("test", irDocument.DocumentKind);
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument); var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
var @class = SingleChild<ClassDeclarationIRNode>(@namespace); var @class = SingleChild<ClassDeclarationIRNode>(@namespace);
@ -60,7 +81,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
var builder = RazorIRBuilder.Create(irDocument); var builder = RazorIRBuilder.Create(irDocument);
builder.Add(new ChecksumIRNode()); builder.Add(new ChecksumIRNode());
var pass = new DefaultDocumentClassifier(); var pass = new TestDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b => { }); pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act // Act
@ -82,7 +103,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
var builder = RazorIRBuilder.Create(irDocument); var builder = RazorIRBuilder.Create(irDocument);
builder.Add(new UsingStatementIRNode()); builder.Add(new UsingStatementIRNode());
var pass = new DefaultDocumentClassifier(); var pass = new TestDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b => { }); pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act // Act
@ -105,7 +126,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
var builder = RazorIRBuilder.Create(irDocument); var builder = RazorIRBuilder.Create(irDocument);
builder.Add(new DeclareTagHelperFieldsIRNode()); builder.Add(new DeclareTagHelperFieldsIRNode());
var pass = new DefaultDocumentClassifier(); var pass = new TestDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b => { }); pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act // Act
@ -130,7 +151,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
builder.Add(new HtmlContentIRNode()); builder.Add(new HtmlContentIRNode());
builder.Add(new CSharpStatementIRNode()); builder.Add(new CSharpStatementIRNode());
var pass = new DefaultDocumentClassifier(); var pass = new TestDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b => { }); pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act // Act
@ -145,5 +166,66 @@ namespace Microsoft.AspNetCore.Razor.Evolution
n => Assert.IsType<HtmlContentIRNode>(n), n => Assert.IsType<HtmlContentIRNode>(n),
n => Assert.IsType<CSharpStatementIRNode>(n)); n => Assert.IsType<CSharpStatementIRNode>(n));
} }
[Fact]
public void Execute_CanInitializeDefaults()
{
// Arrange
var irDocument = new DocumentIRNode();
var builder = RazorIRBuilder.Create(irDocument);
builder.Add(new HtmlContentIRNode());
builder.Add(new CSharpStatementIRNode());
var pass = new TestDocumentClassifierPass()
{
Engine = RazorEngine.CreateEmpty(b => { }),
Namespace = "TestNamespace",
Class = "TestClass",
Method = "TestMethod",
};
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIRNode>(irDocument);
Assert.Equal("TestNamespace", @namespace.Content);
var @class = SingleChild<ClassDeclarationIRNode>(@namespace);
Assert.Equal("TestClass", @class.Name);
var method = SingleChild<RazorMethodDeclarationIRNode>(@class);
Assert.Equal("TestMethod", method.Name);
}
private class TestDocumentClassifierPass : DocumentClassifierPassBase
{
public bool ShouldMatch { get; set; } = true;
public string Namespace { get; set; }
public string Class { get; set; }
public string Method { get; set; }
protected override string DocumentKind => "test";
protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
{
return ShouldMatch;
}
protected override void OnDocumentStructureCreated(
RazorCodeDocument codeDocument,
NamespaceDeclarationIRNode @namespace,
ClassDeclarationIRNode @class,
RazorMethodDeclarationIRNode method)
{
@namespace.Content = Namespace;
@class.Name = Class;
@method.Name = Method;
}
}
} }
} }

View File

@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
feature => Assert.IsType<DefaultDirectiveSyntaxTreePass>(feature), feature => Assert.IsType<DefaultDirectiveSyntaxTreePass>(feature),
feature => Assert.IsType<HtmlNodeOptimizationPass>(feature), feature => Assert.IsType<HtmlNodeOptimizationPass>(feature),
feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature), feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature),
feature => Assert.IsType<DefaultDocumentClassifier>(feature), feature => Assert.IsType<DefaultDocumentClassifierPass>(feature),
feature => Assert.IsType<DefaultDirectiveIRPass>(feature), feature => Assert.IsType<DefaultDirectiveIRPass>(feature),
feature => Assert.IsType<RazorPreallocatedTagHelperAttributeOptimizationPass>(feature)); feature => Assert.IsType<RazorPreallocatedTagHelperAttributeOptimizationPass>(feature));
} }
@ -162,7 +162,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
feature => Assert.IsType<DefaultDirectiveSyntaxTreePass>(feature), feature => Assert.IsType<DefaultDirectiveSyntaxTreePass>(feature),
feature => Assert.IsType<HtmlNodeOptimizationPass>(feature), feature => Assert.IsType<HtmlNodeOptimizationPass>(feature),
feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature), feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature),
feature => Assert.IsType<DefaultDocumentClassifier>(feature), feature => Assert.IsType<DefaultDocumentClassifierPass>(feature),
feature => Assert.IsType<DefaultDirectiveIRPass>(feature), feature => Assert.IsType<DefaultDirectiveIRPass>(feature),
feature => Assert.IsType<RazorEngine.ConfigureDesignTimeOptions>(feature), feature => Assert.IsType<RazorEngine.ConfigureDesignTimeOptions>(feature),
feature => Assert.IsType<RazorDesignTimeIRPass>(feature)); feature => Assert.IsType<RazorDesignTimeIRPass>(feature));