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
{
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)
{
return irDocument;
}
if (!IsMatch(codeDocument, irDocument))
{
return irDocument;
}
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);
irDocument.Children.Clear();
@ -39,7 +52,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
var method = new RazorMethodDeclarationIRNode()
{
//AccessModifier = "public",
// Modifiers = new List<string>() { "async" },
// Modifiers = new List<string>() { "async" },
//Name = "Execute",
//ReturnType = "Task",
};
@ -62,7 +75,20 @@ namespace Microsoft.AspNetCore.Razor.Evolution
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
@ -84,7 +110,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
_document.Insert(0, node);
}
public override void VisitUsingStatement(UsingStatementIRNode node)
{
_namespace.AddAfter<UsingStatementIRNode>(node);

View File

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

View File

@ -226,7 +226,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
Assert.NotNull(irDocument);
// 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;
}
}

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.
using System;
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
using Xunit;
using static Microsoft.AspNetCore.Razor.Evolution.Intermediate.RazorIRAssert;
namespace Microsoft.AspNetCore.Razor.Evolution
{
public class DefaultDocumentClassifierTest
public class DocumentClassifierPassBaseTest
{
[Fact]
public void Execute_IgnoresDocumentsWithDocumentKind()
public void Execute_HasDocumentKind_IgnoresDocument()
{
// Arrange
var irDocument = new DocumentIRNode()
@ -19,7 +20,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
DocumentKind = "ignore",
};
var pass = new DefaultDocumentClassifier();
var pass = new TestDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
@ -31,19 +32,39 @@ namespace Microsoft.AspNetCore.Razor.Evolution
}
[Fact]
public void Execute_CreatesClassStructure()
public void Execute_NoMatch_IgnoresDocument()
{
// Arrange
var irDocument = new DocumentIRNode();
var pass = new DefaultDocumentClassifier();
pass.Engine = RazorEngine.CreateEmpty(b =>{ });
var pass = new TestDocumentClassifierPass()
{
Engine = RazorEngine.CreateEmpty(b => { }),
ShouldMatch = false,
};
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument);
// 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 @class = SingleChild<ClassDeclarationIRNode>(@namespace);
@ -60,7 +81,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
var builder = RazorIRBuilder.Create(irDocument);
builder.Add(new ChecksumIRNode());
var pass = new DefaultDocumentClassifier();
var pass = new TestDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
@ -82,7 +103,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
var builder = RazorIRBuilder.Create(irDocument);
builder.Add(new UsingStatementIRNode());
var pass = new DefaultDocumentClassifier();
var pass = new TestDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
@ -105,7 +126,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
var builder = RazorIRBuilder.Create(irDocument);
builder.Add(new DeclareTagHelperFieldsIRNode());
var pass = new DefaultDocumentClassifier();
var pass = new TestDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
@ -130,7 +151,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
builder.Add(new HtmlContentIRNode());
builder.Add(new CSharpStatementIRNode());
var pass = new DefaultDocumentClassifier();
var pass = new TestDocumentClassifierPass();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
@ -145,5 +166,66 @@ namespace Microsoft.AspNetCore.Razor.Evolution
n => Assert.IsType<HtmlContentIRNode>(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<HtmlNodeOptimizationPass>(feature),
feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature),
feature => Assert.IsType<DefaultDocumentClassifier>(feature),
feature => Assert.IsType<DefaultDocumentClassifierPass>(feature),
feature => Assert.IsType<DefaultDirectiveIRPass>(feature),
feature => Assert.IsType<RazorPreallocatedTagHelperAttributeOptimizationPass>(feature));
}
@ -162,7 +162,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
feature => Assert.IsType<DefaultDirectiveSyntaxTreePass>(feature),
feature => Assert.IsType<HtmlNodeOptimizationPass>(feature),
feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature),
feature => Assert.IsType<DefaultDocumentClassifier>(feature),
feature => Assert.IsType<DefaultDocumentClassifierPass>(feature),
feature => Assert.IsType<DefaultDirectiveIRPass>(feature),
feature => Assert.IsType<RazorEngine.ConfigureDesignTimeOptions>(feature),
feature => Assert.IsType<RazorDesignTimeIRPass>(feature));