Have `@page` understand malformed directives.

- The `@page` directive will now look for malformed directives and treat a view as a Razor page based on the existence of the malformed directive.
- Updated the `PageDirective` type to contain a reference to its responsible directive node.
- Added unit tests to validate malformed directives were picked up accordingly.
- Updated code generation test baselines to reflect the new malformed directive understanding.

#1448
This commit is contained in:
N. Taylor Mullen 2017-07-03 16:15:50 -07:00
parent 647ce7515d
commit a78202e937
7 changed files with 76 additions and 38 deletions

View File

@ -2,6 +2,7 @@
// 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 System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
@ -20,13 +21,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; builder.Usage = DirectiveUsage.FileScopedSinglyOccurring;
}); });
private PageDirective(string routeTemplate) private PageDirective(string routeTemplate, IntermediateNode directiveNode)
{ {
RouteTemplate = routeTemplate; RouteTemplate = routeTemplate;
DirectiveNode = directiveNode;
} }
public string RouteTemplate { get; } public string RouteTemplate { get; }
public IntermediateNode DirectiveNode { get; }
public static IRazorEngineBuilder Register(IRazorEngineBuilder builder) public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
{ {
builder.AddDirective(Directive); builder.AddDirective(Directive);
@ -41,25 +45,27 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
visitor.Visit(documentNode.Children[i]); visitor.Visit(documentNode.Children[i]);
} }
if (visitor.DirectiveNode == null) if (visitor.DirectiveTokens == null)
{ {
pageDirective = null; pageDirective = null;
return false; return false;
} }
var tokens = visitor.DirectiveNode.Tokens.ToList(); var tokens = visitor.DirectiveTokens.ToList();
string routeTemplate = null; string routeTemplate = null;
if (tokens.Count > 0) if (tokens.Count > 0)
{ {
routeTemplate = TrimQuotes(tokens[0].Content); routeTemplate = TrimQuotes(tokens[0].Content);
} }
pageDirective = new PageDirective(routeTemplate); pageDirective = new PageDirective(routeTemplate, visitor.DirectiveNode);
return true; return true;
} }
private static string TrimQuotes(string content) private static string TrimQuotes(string content)
{ {
// Tokens aren't captured if they're malformed. Therefore, this method will
// always be called with a valid token content.
Debug.Assert(content.Length >= 2); Debug.Assert(content.Length >= 2);
Debug.Assert(content.StartsWith("\"", StringComparison.Ordinal)); Debug.Assert(content.StartsWith("\"", StringComparison.Ordinal));
Debug.Assert(content.EndsWith("\"", StringComparison.Ordinal)); Debug.Assert(content.EndsWith("\"", StringComparison.Ordinal));
@ -69,13 +75,25 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
private class Visitor : IntermediateNodeWalker private class Visitor : IntermediateNodeWalker
{ {
public DirectiveIntermediateNode DirectiveNode { get; private set; } public IntermediateNode DirectiveNode { get; private set; }
public IEnumerable<DirectiveTokenIntermediateNode> DirectiveTokens { get; private set; }
public override void VisitDirective(DirectiveIntermediateNode node) public override void VisitDirective(DirectiveIntermediateNode node)
{ {
if (node.Descriptor == Directive) if (node.Descriptor == Directive)
{ {
DirectiveNode = node; DirectiveNode = node;
DirectiveTokens = node.Tokens;
}
}
public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node)
{
if (DirectiveTokens == null && node.Descriptor == Directive)
{
DirectiveNode = node;
DirectiveTokens = node.Tokens;
} }
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System.Diagnostics;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate; using Microsoft.AspNetCore.Razor.Language.Intermediate;
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{ {
return PageDirective.TryGetPageDirective(documentNode, out var directive); return PageDirective.TryGetPageDirective(documentNode, out var pageDirective);
} }
protected override void OnDocumentStructureCreated( protected override void OnDocumentStructureCreated(
@ -49,26 +49,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
private void EnsureValidPageDirective(RazorCodeDocument codeDocument) private void EnsureValidPageDirective(RazorCodeDocument codeDocument)
{ {
var document = codeDocument.GetDocumentIntermediateNode(); var document = codeDocument.GetDocumentIntermediateNode();
var visitor = new Visitor(); PageDirective.TryGetPageDirective(document, out var pageDirective);
visitor.VisitDocument(document);
if (visitor.DirectiveNode.IsImported()) Debug.Assert(pageDirective != null);
if (pageDirective.DirectiveNode.IsImported())
{ {
visitor.DirectiveNode.Diagnostics.Add( pageDirective.DirectiveNode.Diagnostics.Add(
RazorExtensionsDiagnosticFactory.CreatePageDirective_CannotBeImported(visitor.DirectiveNode.Source.Value)); RazorExtensionsDiagnosticFactory.CreatePageDirective_CannotBeImported(pageDirective.DirectiveNode.Source.Value));
}
}
private class Visitor : IntermediateNodeWalker
{
public DirectiveIntermediateNode DirectiveNode { get; private set; }
public override void VisitDirective(DirectiveIntermediateNode node)
{
if (node.Descriptor == PageDirective.Directive)
{
DirectiveNode = node;
}
} }
} }
} }

View File

@ -10,6 +10,25 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{ {
public class PageDirectiveTest public class PageDirectiveTest
{ {
[Fact]
public void TryGetPageDirective_ReturnsTrue_IfPageIsMalformed()
{
// Arrange
var content = "@page \"some-route-template\" Invalid";
var sourceDocument = RazorSourceDocument.Create(content, "file");
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var engine = CreateEngine();
var irDocument = CreateIRDocument(engine, codeDocument);
// Act
var result = PageDirective.TryGetPageDirective(irDocument, out var pageDirective);
// Assert
Assert.True(result);
Assert.Equal("some-route-template", pageDirective.RouteTemplate);
Assert.NotNull(pageDirective.DirectiveNode);
}
[Fact] [Fact]
public void TryGetPageDirective_ReturnsTrue_IfPageIsImported() public void TryGetPageDirective_ReturnsTrue_IfPageIsImported()
{ {
@ -48,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
} }
[Fact] [Fact]
public void TryGetPageDirective_ReturnsFalse_IfPageDoesStartWithDirective() public void TryGetPageDirective_ReturnsTrue_IfPageDoesStartWithDirective()
{ {
// Arrange // Arrange
var content = "Hello @page"; var content = "Hello @page";
@ -61,8 +80,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
var result = PageDirective.TryGetPageDirective(irDocument, out var pageDirective); var result = PageDirective.TryGetPageDirective(irDocument, out var pageDirective);
// Assert // Assert
Assert.False(result); Assert.True(result);
Assert.Null(pageDirective); Assert.Null(pageDirective.RouteTemplate);
Assert.NotNull(pageDirective.DirectiveNode);
} }
[Fact] [Fact]

View File

@ -1,6 +1,6 @@
// <auto-generated/> // <auto-generated/>
#pragma warning disable 1591 #pragma warning disable 1591
[assembly:global::Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute(null, typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml))] [assembly:global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute(null, typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml), null)]
namespace AspNetCore namespace AspNetCore
{ {
#line hidden #line hidden
@ -12,7 +12,7 @@ namespace AspNetCore
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic> public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml : global::Microsoft.AspNetCore.Mvc.RazorPages.Page
{ {
#pragma warning disable 219 #pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() { private void __RazorDirectiveTokenHelpers__() {
@ -35,7 +35,9 @@ namespace AspNetCore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml> Html { get; private set; }
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml>)PageContext?.ViewData;
public TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml Model => ViewData.Model;
} }
} }
#pragma warning restore 1591 #pragma warning restore 1591

View File

@ -1,6 +1,6 @@
Document - Document -
CSharpCode - CSharpCode -
IntermediateToken - - CSharp - [assembly:global::Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute(null, typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml))] IntermediateToken - - CSharp - [assembly:global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute(null, typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml), null)]
NamespaceDeclaration - - AspNetCore NamespaceDeclaration - - AspNetCore
UsingDirective - - TModel = global::System.Object UsingDirective - - TModel = global::System.Object
UsingDirective - (1:0,1 [12] ) - System UsingDirective - (1:0,1 [12] ) - System
@ -10,7 +10,7 @@ Document -
UsingDirective - (102:4,1 [30] ) - Microsoft.AspNetCore.Mvc UsingDirective - (102:4,1 [30] ) - Microsoft.AspNetCore.Mvc
UsingDirective - (135:5,1 [40] ) - Microsoft.AspNetCore.Mvc.Rendering UsingDirective - (135:5,1 [40] ) - Microsoft.AspNetCore.Mvc.Rendering
UsingDirective - (178:6,1 [43] ) - Microsoft.AspNetCore.Mvc.ViewFeatures UsingDirective - (178:6,1 [43] ) - Microsoft.AspNetCore.Mvc.ViewFeatures
ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic> - ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml - global::Microsoft.AspNetCore.Mvc.RazorPages.Page -
DesignTimeDirective - DesignTimeDirective -
DirectiveToken - (231:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel> DirectiveToken - (231:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel>
DirectiveToken - (294:7,71 [4] ) - Html DirectiveToken - (294:7,71 [4] ) - Html
@ -47,3 +47,7 @@ Document -
Inject - Inject -
Inject - Inject -
Inject - Inject -
CSharpCode -
IntermediateToken - - CSharp - public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml>)PageContext?.ViewData;
CSharpCode -
IntermediateToken - - CSharp - public TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml Model => ViewData.Model;

View File

@ -1,7 +1,7 @@
#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "5a9ff8440150c6746e4a8ba63bc633ea84930405" #pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MalformedPageDirective.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "5a9ff8440150c6746e4a8ba63bc633ea84930405"
// <auto-generated/> // <auto-generated/>
#pragma warning disable 1591 #pragma warning disable 1591
[assembly:global::Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute(null, typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml))] [assembly:global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute(null, typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml), null)]
namespace AspNetCore namespace AspNetCore
{ {
#line hidden #line hidden
@ -12,7 +12,7 @@ namespace AspNetCore
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic> public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml : global::Microsoft.AspNetCore.Mvc.RazorPages.Page
{ {
#pragma warning disable 1998 #pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync() public async override global::System.Threading.Tasks.Task ExecuteAsync()
@ -31,7 +31,9 @@ namespace AspNetCore
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; } public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml> Html { get; private set; }
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml>)PageContext?.ViewData;
public TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml Model => ViewData.Model;
} }
} }
#pragma warning restore 1591 #pragma warning restore 1591

View File

@ -1,6 +1,6 @@
Document - Document -
CSharpCode - CSharpCode -
IntermediateToken - - CSharp - [assembly:global::Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute(null, typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml))] IntermediateToken - - CSharp - [assembly:global::Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute(null, typeof(AspNetCore.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml), null)]
NamespaceDeclaration - - AspNetCore NamespaceDeclaration - - AspNetCore
UsingDirective - (1:0,1 [14] ) - System UsingDirective - (1:0,1 [14] ) - System
UsingDirective - (16:1,1 [34] ) - System.Collections.Generic UsingDirective - (16:1,1 [34] ) - System.Collections.Generic
@ -9,7 +9,7 @@ Document -
UsingDirective - (102:4,1 [32] ) - Microsoft.AspNetCore.Mvc UsingDirective - (102:4,1 [32] ) - Microsoft.AspNetCore.Mvc
UsingDirective - (135:5,1 [42] ) - Microsoft.AspNetCore.Mvc.Rendering UsingDirective - (135:5,1 [42] ) - Microsoft.AspNetCore.Mvc.Rendering
UsingDirective - (178:6,1 [45] ) - Microsoft.AspNetCore.Mvc.ViewFeatures UsingDirective - (178:6,1 [45] ) - Microsoft.AspNetCore.Mvc.ViewFeatures
ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic> - ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml - global::Microsoft.AspNetCore.Mvc.RazorPages.Page -
MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync
MalformedDirective - (0:0,0 [6] MalformedPageDirective.cshtml) - page MalformedDirective - (0:0,0 [6] MalformedPageDirective.cshtml) - page
CSharpCode - CSharpCode -
@ -30,3 +30,7 @@ Document -
Inject - Inject -
Inject - Inject -
Inject - Inject -
CSharpCode -
IntermediateToken - - CSharp - public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml>)PageContext?.ViewData;
CSharpCode -
IntermediateToken - - CSharp - public TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MalformedPageDirective_cshtml Model => ViewData.Model;