Implement @namespace

This change adds support for @namespace, and introduces a set of
changes that are needed to support @namespace in the parser.

@namespace and @class have always been treated as reserved words by Razor,
with the intent that someday they would be allowed as directives.

This changes makes that possible.

You will still get an error about @namespace being a reserved word if you
don't have the directive.
This commit is contained in:
Ryan Nowak 2017-04-06 11:12:10 -07:00
parent b4b4a19549
commit e5cac9fb7f
17 changed files with 898 additions and 7 deletions

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
public class MvcViewDocumentClassifierPass : DocumentClassifierPassBase
{
public readonly string MvcViewDocumentKind = "mvc.1.0.view";
public static readonly string MvcViewDocumentKind = "mvc.1.0.view";
protected override string DocumentKind => MvcViewDocumentKind;

View File

@ -0,0 +1,191 @@
// 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.Linq;
using System.Text;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
public static class NamespaceDirective
{
private static readonly char[] Separators = new char[] { '\\', '/' };
public static readonly DirectiveDescriptor Directive = DirectiveDescriptorBuilder.Create("namespace").AddNamespace().Build();
public static void Register(IRazorEngineBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException();
}
builder.AddDirective(Directive);
builder.Features.Add(new Pass());
}
// internal for testing
internal class Pass : RazorIRPassBase, IRazorDirectiveClassifierPass
{
public override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
{
if (irDocument.DocumentKind != RazorPageDocumentClassifierPass.RazorPageDocumentKind &&
irDocument.DocumentKind != MvcViewDocumentClassifierPass.MvcViewDocumentKind)
{
// Not a page. Skip.
return;
}
var visitor = new Visitor();
visitor.Visit(irDocument);
var directive = visitor.LastNamespaceDirective;
if (directive == null)
{
// No namespace set. Skip.
return;
}
var @namespace = visitor.FirstNamespace;
if (@namespace == null)
{
// No namespace node. Skip.
return;
}
if (TryComputeNamespace(codeDocument.Source.FileName, directive, out var computedNamespace))
{
// Beautify the class name since we're using a hierarchy for namespaces.
var @class = visitor.FirstClass;
if (@class != null && irDocument.DocumentKind == RazorPageDocumentClassifierPass.RazorPageDocumentKind)
{
@class.Name = Path.GetFileNameWithoutExtension(codeDocument.Source.FileName) + "_Page";
}
else if (@class != null && irDocument.DocumentKind == MvcViewDocumentClassifierPass.MvcViewDocumentKind)
{
@class.Name = Path.GetFileNameWithoutExtension(codeDocument.Source.FileName) + "_View";
}
}
@namespace.Content = computedNamespace;
}
}
// internal for testing.
//
// This code does a best-effort attempt to compute a namespace 'suffix' - the path difference between
// where the @namespace directive appears and where the current document is on disk.
//
// In the event that these two source either don't have filenames set or don't follow a coherent hierarchy,
// we will just use the namespace verbatim.
internal static bool TryComputeNamespace(string source, DirectiveIRNode directive, out string @namespace)
{
var directiveSource = NormalizeDirectory(directive.Source?.FilePath);
var baseNamespace = directive.Tokens.First().Content;
if (string.IsNullOrEmpty(source) || directiveSource == null)
{
// No sources, can't compute a suffix.
@namespace = baseNamespace;
return false;
}
// We're specifically using OrdinalIgnoreCase here because Razor treats all paths as case-insensitive.
if (!source.StartsWith(directiveSource, StringComparison.OrdinalIgnoreCase) ||
source.Length <= directiveSource.Length)
{
// The imports are not from the directory hierarchy, can't compute a suffix.
@namespace = baseNamespace;
return false;
}
// OK so that this point we know that the 'imports' file containing this directive is in the directory
// hierarchy of this soure file. This is the case where we can append a suffix to the baseNamespace.
//
// Everything so far has just been defensiveness on our part.
var builder = new StringBuilder(baseNamespace);
var segments = source.Substring(directiveSource.Length).Split(Separators);
// Skip the last segment because it's the filename.
for (var i = 0; i < segments.Length - 1; i++)
{
builder.Append('.');
builder.Append(segments[i]);
}
@namespace = builder.ToString();
return true;
}
// We want to normalize the path of the file containing the '@namespace' directive to just the containing
// directory with a trailing separator.
//
// Not using Path.GetDirectoryName here because it doesn't meet these requirements, and we want to handle
// both 'view engine' style paths and absolute paths.
//
// We also don't normalize the separators here. We expect that all documents are using a consistent style of path.
//
// If we can't normalize the path, we just return null so it will be ignored.
private static string NormalizeDirectory(string path)
{
if (string.IsNullOrEmpty(path))
{
return null;
}
var lastSeparator = path.LastIndexOfAny(Separators);
if (lastSeparator == -1)
{
return null;
}
// Includes the separator
return path.Substring(0, lastSeparator + 1);
}
private class Visitor : RazorIRNodeWalker
{
public ClassDeclarationIRNode FirstClass { get; private set; }
public NamespaceDeclarationIRNode FirstNamespace { get; private set; }
// We want the last one, so get them all and then .
public DirectiveIRNode LastNamespaceDirective { get; private set; }
public override void VisitNamespace(NamespaceDeclarationIRNode node)
{
if (FirstNamespace == null)
{
FirstNamespace = node;
}
base.VisitNamespace(node);
}
public override void VisitClass(ClassDeclarationIRNode node)
{
if (FirstClass == null)
{
FirstClass = node;
}
base.VisitClass(node);
}
public override void VisitDirective(DirectiveIRNode node)
{
if (node.Descriptor == Directive)
{
LastNamespaceDirective = node;
}
base.VisitDirective(node);
}
}
}
}

View File

@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
InjectDirective.Register(builder);
ModelDirective.Register(builder);
NamespaceDirective.Register(builder);
PageDirective.Register(builder);
builder.AddTargetExtension(new InjectDirectiveTargetExtension());

View File

@ -110,6 +110,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
_directiveParsers.Add(directive, handler);
Keywords.Add(directive);
// These C# keywords are reserved for use in directives. It's an error to use them outside of
// a directive. This code removes the error generation if the directive *is* registered.
if (string.Equals(directive, "class", StringComparison.OrdinalIgnoreCase))
{
_keywordParsers.Remove(CSharpKeyword.Class);
}
else if (string.Equals(directive, "namespace", StringComparison.OrdinalIgnoreCase))
{
_keywordParsers.Remove(CSharpKeyword.Namespace);
}
}
}
@ -266,8 +277,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
else if (CurrentSymbol.Type == CSharpSymbolType.Identifier)
{
Action handler;
if (TryGetDirectiveHandler(CurrentSymbol.Content, out handler))
if (TryGetDirectiveHandler(CurrentSymbol.Content, out var handler))
{
Span.ChunkGenerator = SpanChunkGenerator.Null;
handler();
@ -295,8 +305,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
else if (CurrentSymbol.Type == CSharpSymbolType.Keyword)
{
KeywordBlock(topLevel: true);
return;
if (TryGetDirectiveHandler(CurrentSymbol.Content, out var handler))
{
Span.ChunkGenerator = SpanChunkGenerator.Null;
handler();
return;
}
else
{
KeywordBlock(topLevel: true);
return;
}
}
else if (CurrentSymbol.Type == CSharpSymbolType.LeftBrace)
{
@ -737,7 +756,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
MapKeywords(TryStatement, CSharpKeyword.Try);
MapKeywords(UsingKeyword, CSharpKeyword.Using);
MapKeywords(DoStatement, CSharpKeyword.Do);
MapKeywords(ReservedDirective, CSharpKeyword.Namespace, CSharpKeyword.Class);
MapKeywords(ReservedDirective, CSharpKeyword.Class, CSharpKeyword.Namespace);
}
protected virtual void ReservedDirective(bool topLevel)
@ -1748,7 +1767,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
[Conditional("DEBUG")]
protected void AssertDirective(string directive)
{
Assert(CSharpSymbolType.Identifier);
Debug.Assert(CurrentSymbol.Type == CSharpSymbolType.Identifier || CurrentSymbol.Type == CSharpSymbolType.Keyword);
Debug.Assert(string.Equals(CurrentSymbol.Content, directive, StringComparison.Ordinal));
}

View File

@ -159,6 +159,36 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.IntegrationTests
AssertIRMatchesBaseline(document.GetIRDocument());
AssertCSharpDocumentMatchesBaseline(document.GetCSharpDocument());
}
[Fact]
public void PageWithNamespace_Runtime()
{
// Arrange
var engine = CreateRuntimeEngine();
var document = CreateCodeDocument();
// Act
engine.Process(document);
// Assert
AssertIRMatchesBaseline(document.GetIRDocument());
AssertCSharpDocumentMatchesBaseline(document.GetCSharpDocument());
}
[Fact]
public void ViewWithNamespace_Runtime()
{
// Arrange
var engine = CreateRuntimeEngine();
var document = CreateCodeDocument();
// Act
engine.Process(document);
// Assert
AssertIRMatchesBaseline(document.GetIRDocument());
AssertCSharpDocumentMatchesBaseline(document.GetCSharpDocument());
}
#endregion
#region DesignTime
@ -311,6 +341,36 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.IntegrationTests
AssertIRMatchesBaseline(document.GetIRDocument());
AssertCSharpDocumentMatchesBaseline(document.GetCSharpDocument());
}
[Fact]
public void PageWithNamespace_DesignTime()
{
// Arrange
var engine = CreateDesignTimeEngine();
var document = CreateCodeDocument();
// Act
engine.Process(document);
// Assert
AssertIRMatchesBaseline(document.GetIRDocument());
AssertCSharpDocumentMatchesBaseline(document.GetCSharpDocument());
}
[Fact]
public void ViewWithNamespace_DesignTime()
{
// Arrange
var engine = CreateDesignTimeEngine();
var document = CreateCodeDocument();
// Act
engine.Process(document);
// Assert
AssertIRMatchesBaseline(document.GetIRDocument());
AssertCSharpDocumentMatchesBaseline(document.GetCSharpDocument());
}
#endregion
protected RazorEngine CreateDesignTimeEngine(IEnumerable<TagHelperDescriptor> descriptors = null)

View File

@ -0,0 +1,278 @@
// 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.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
public class NamespaceDirectiveTest
{
// When we don't have a relationship between the source file and the imports file
// we will just use the namespace on the node directly.
[Theory]
[InlineData((string)null, (string)null)]
[InlineData("", "")]
[InlineData(null, "/foo/bar")]
[InlineData("/foo/baz", "/foo/bar/bleh")]
[InlineData("/foo.cshtml", "/foo/bar.cshtml")]
[InlineData("c:\\foo.cshtml", "d:\\foo\\bar.cshtml")]
[InlineData("c:\\foo\\bar\\bleh.cshtml", "c:\\foo\\baz\\bleh.cshtml")]
public void TryComputeNamespace_ForNonRelatedFiles_UsesNamespaceVerbatim(string source, string imports)
{
// Arrange
var node = new DirectiveIRNode()
{
Descriptor = NamespaceDirective.Directive,
Source = new SourceSpan(imports, 0, 0, 0, 0),
};
node.Children.Add(new DirectiveTokenIRNode() { Content = "Base" });
node.Children[0].Parent = node;
// Act
var computed = NamespaceDirective.TryComputeNamespace(source, node, out var @namespace);
// Assert
Assert.False(computed);
Assert.Equal("Base", @namespace);
}
[Theory]
[InlineData("/foo.cshtml", "/_ViewImports.cshtml", "Base")]
[InlineData("/foo/bar.cshtml", "/_ViewImports.cshtml", "Base.foo")]
[InlineData("/foo/bar/baz.cshtml", "/_ViewImports.cshtml", "Base.foo.bar")]
[InlineData("/foo/bar/baz.cshtml", "/foo/_ViewImports.cshtml", "Base.bar")]
[InlineData("/Foo/bar/baz.cshtml", "/foo/_ViewImports.cshtml", "Base.bar")]
[InlineData("c:\\foo.cshtml", "c:\\_ViewImports.cshtml", "Base")]
[InlineData("c:\\foo\\bar.cshtml", "c:\\_ViewImports.cshtml", "Base.foo")]
[InlineData("c:\\foo\\bar\\baz.cshtml", "c:\\_ViewImports.cshtml", "Base.foo.bar")]
[InlineData("c:\\foo\\bar\\baz.cshtml", "c:\\foo\\_ViewImports.cshtml", "Base.bar")]
[InlineData("c:\\Foo\\bar\\baz.cshtml", "c:\\foo\\_ViewImports.cshtml", "Base.bar")]
public void TryComputeNamespace_ForRelatedFiles_ComputesNamespaceWithSuffix(string source, string imports, string expected)
{
// Arrange
var node = new DirectiveIRNode()
{
Descriptor = NamespaceDirective.Directive,
Source = new SourceSpan(imports, 0, 0, 0, 0),
};
node.Children.Add(new DirectiveTokenIRNode() { Content = "Base" });
node.Children[0].Parent = node;
// Act
var computed = NamespaceDirective.TryComputeNamespace(source, node, out var @namespace);
// Assert
Assert.True(computed);
Assert.Equal(expected, @namespace);
}
// This is the case where a _ViewImports sets the namespace.
[Fact]
public void Pass_SetsNamespaceAndClassName_ComputedFromImports()
{
// Arrange
var builder = RazorIRBuilder.Document();
builder.Push(new DirectiveIRNode()
{
Descriptor = NamespaceDirective.Directive,
Source = new SourceSpan("/Account/_ViewImports.cshtml", 0, 0, 0, 0),
});
builder.Add(new DirectiveTokenIRNode() { Content = "WebApplication.Account" });
builder.Pop();
var @namespace = new NamespaceDeclarationIRNode() { Content = "default" };
builder.Push(@namespace);
var @class = new ClassDeclarationIRNode() { Name = "default" };
builder.Add(@class);
var irDocument = (DocumentIRNode)builder.Build();
irDocument.DocumentKind = RazorPageDocumentClassifierPass.RazorPageDocumentKind;
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("ignored", "/Account/Manage/AddUser.cshtml"));
var pass = new NamespaceDirective.Pass();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Assert.Equal("WebApplication.Account.Manage", @namespace.Content);
Assert.Equal("AddUser_Page", @class.Name);
}
// This is the case where the source file sets the namespace.
[Fact]
public void Pass_SetsNamespaceAndClassName_ComputedFromSource()
{
// Arrange
var builder = RazorIRBuilder.Document();
// This will be ignored.
builder.Push(new DirectiveIRNode()
{
Descriptor = NamespaceDirective.Directive,
Source = new SourceSpan("/Account/_ViewImports.cshtml", 0, 0, 0, 0),
});
builder.Add(new DirectiveTokenIRNode() { Content = "ignored" });
builder.Pop();
// This will be used.
builder.Push(new DirectiveIRNode()
{
Descriptor = NamespaceDirective.Directive,
Source = new SourceSpan("/Account/Manage/AddUser.cshtml", 0, 0, 0, 0),
});
builder.Add(new DirectiveTokenIRNode() { Content = "WebApplication.Account.Manage" });
builder.Pop();
var @namespace = new NamespaceDeclarationIRNode() { Content = "default" };
builder.Push(@namespace);
var @class = new ClassDeclarationIRNode() { Name = "default" };
builder.Add(@class);
var irDocument = (DocumentIRNode)builder.Build();
irDocument.DocumentKind = RazorPageDocumentClassifierPass.RazorPageDocumentKind;
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("ignored", "/Account/Manage/AddUser.cshtml"));
var pass = new NamespaceDirective.Pass();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Assert.Equal("WebApplication.Account.Manage", @namespace.Content);
Assert.Equal("AddUser_Page", @class.Name);
}
// This is the case where the source file sets the namespace.
[Fact]
public void Pass_SetsNamespaceAndClassName_ComputedFromSource_ForView()
{
// Arrange
var builder = RazorIRBuilder.Document();
// This will be ignored.
builder.Push(new DirectiveIRNode()
{
Descriptor = NamespaceDirective.Directive,
Source = new SourceSpan("/Account/_ViewImports.cshtml", 0, 0, 0, 0),
});
builder.Add(new DirectiveTokenIRNode() { Content = "ignored" });
builder.Pop();
// This will be used.
builder.Push(new DirectiveIRNode()
{
Descriptor = NamespaceDirective.Directive,
Source = new SourceSpan("/Account/Manage/AddUser.cshtml", 0, 0, 0, 0),
});
builder.Add(new DirectiveTokenIRNode() { Content = "WebApplication.Account.Manage" });
builder.Pop();
var @namespace = new NamespaceDeclarationIRNode() { Content = "default" };
builder.Push(@namespace);
var @class = new ClassDeclarationIRNode() { Name = "default" };
builder.Add(@class);
var irDocument = (DocumentIRNode)builder.Build();
irDocument.DocumentKind = MvcViewDocumentClassifierPass.MvcViewDocumentKind;
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("ignored", "/Account/Manage/AddUser.cshtml"));
var pass = new NamespaceDirective.Pass();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Assert.Equal("WebApplication.Account.Manage", @namespace.Content);
Assert.Equal("AddUser_View", @class.Name);
}
// This handles an error case where we can't determine the relationship between the
// imports and the source.
[Fact]
public void Pass_SetsNamespaceButNotClassName_VerbatimFromImports()
{
// Arrange
var builder = RazorIRBuilder.Document();
builder.Push(new DirectiveIRNode()
{
Descriptor = NamespaceDirective.Directive,
Source = new SourceSpan(null, 0, 0, 0, 0),
});
builder.Add(new DirectiveTokenIRNode() { Content = "WebApplication.Account" });
builder.Pop();
var @namespace = new NamespaceDeclarationIRNode() { Content = "default" };
builder.Push(@namespace);
var @class = new ClassDeclarationIRNode() { Name = "default" };
builder.Add(@class);
var irDocument = (DocumentIRNode)builder.Build();
irDocument.DocumentKind = RazorPageDocumentClassifierPass.RazorPageDocumentKind;
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("ignored", "/Account/Manage/AddUser.cshtml"));
var pass = new NamespaceDirective.Pass();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Assert.Equal("WebApplication.Account", @namespace.Content);
Assert.Equal("default", @class.Name);
}
[Fact]
public void Pass_DoesNothing_ForUnknownDocumentKind()
{
// Arrange
var builder = RazorIRBuilder.Document();
builder.Push(new DirectiveIRNode()
{
Descriptor = NamespaceDirective.Directive,
Source = new SourceSpan(null, 0, 0, 0, 0),
});
builder.Add(new DirectiveTokenIRNode() { Content = "WebApplication.Account" });
builder.Pop();
var @namespace = new NamespaceDeclarationIRNode() { Content = "default" };
builder.Push(@namespace);
var @class = new ClassDeclarationIRNode() { Name = "default" };
builder.Add(@class);
var irDocument = (DocumentIRNode)builder.Build();
irDocument.DocumentKind = null;
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("ignored", "/Account/Manage/AddUser.cshtml"));
var pass = new NamespaceDirective.Pass();
pass.Engine = RazorEngine.CreateEmpty(b => { });
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Assert.Equal("default", @namespace.Content);
Assert.Equal("default", @class.Name);
}
}
}

View File

@ -0,0 +1,3 @@
@page
@namespace Test.Namespace
<h1>Hi There!</h1>

View File

@ -0,0 +1,41 @@
namespace Test.Namespace
{
#line hidden
using TModel = PageWithNamespace_Page;
using System;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
public class PageWithNamespace_Page : global::Microsoft.AspNetCore.Mvc.RazorPages.Page
{
#pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() {
((System.Action)(() => {
global::System.Object __typeHelper = nameof(Test.Namespace);
}
))();
}
#pragma warning restore 219
private static System.Object __o = null;
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
}
#pragma warning restore 1998
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_PageWithNamespace_cshtml> Html { get; private set; }
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<PageWithNamespace_Page> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<PageWithNamespace_Page>)PageContext?.ViewData;
public PageWithNamespace_Page Model => ViewData.Model;
}
}

View File

@ -0,0 +1,52 @@
Document -
Checksum -
NamespaceDeclaration - - Test.Namespace
UsingStatement - - TModel = PageWithNamespace_Page
UsingStatement - (1:0,1 [12] ) - System
UsingStatement - - System.Threading.Tasks
UsingStatement - (16:1,1 [17] ) - System.Linq
UsingStatement - (36:2,1 [32] ) - System.Collections.Generic
UsingStatement - (71:3,1 [30] ) - Microsoft.AspNetCore.Mvc
UsingStatement - (104:4,1 [40] ) - Microsoft.AspNetCore.Mvc.Rendering
UsingStatement - (147:5,1 [43] ) - Microsoft.AspNetCore.Mvc.ViewFeatures
ClassDeclaration - - public - PageWithNamespace_Page - global::Microsoft.AspNetCore.Mvc.RazorPages.Page -
DirectiveTokenHelper -
CSharpStatement -
RazorIRToken - - CSharp - #pragma warning disable 219
CSharpStatement -
RazorIRToken - - CSharp - private void __RazorDirectiveTokenHelpers__() {
DirectiveToken - (200:6,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel>
DirectiveToken - (263:6,71 [4] ) - Html
DirectiveToken - (277:7,8 [54] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper
DirectiveToken - (332:7,63 [4] ) - Json
DirectiveToken - (346:8,8 [53] ) - global::Microsoft.AspNetCore.Mvc.IViewComponentHelper
DirectiveToken - (400:8,62 [9] ) - Component
DirectiveToken - (419:9,8 [43] ) - global::Microsoft.AspNetCore.Mvc.IUrlHelper
DirectiveToken - (463:9,52 [3] ) - Url
DirectiveToken - (476:10,8 [70] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider
DirectiveToken - (547:10,79 [23] ) - ModelExpressionProvider
DirectiveToken - (586:11,14 [96] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor
DirectiveToken - (698:12,14 [87] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor
DirectiveToken - (801:13,14 [87] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor
DirectiveToken - (18:1,11 [14] PageWithNamespace.cshtml) - Test.Namespace
CSharpStatement -
RazorIRToken - - CSharp - }
CSharpStatement -
RazorIRToken - - CSharp - #pragma warning restore 219
CSharpStatement -
RazorIRToken - - CSharp - private static System.Object __o = null;
RazorMethodDeclaration - - public - async, override - global::System.Threading.Tasks.Task - ExecuteAsync
HtmlContent - (34:2,0 [20] PageWithNamespace.cshtml)
RazorIRToken - (34:2,0 [4] PageWithNamespace.cshtml) - Html - <h1>
RazorIRToken - (38:2,4 [9] PageWithNamespace.cshtml) - Html - Hi There!
RazorIRToken - (47:2,13 [5] PageWithNamespace.cshtml) - Html - </h1>
RazorIRToken - (52:2,18 [2] PageWithNamespace.cshtml) - Html - \n
InjectDirective -
InjectDirective -
InjectDirective -
InjectDirective -
InjectDirective -
CSharpStatement -
RazorIRToken - - CSharp - public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<PageWithNamespace_Page> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<PageWithNamespace_Page>)PageContext?.ViewData;
CSharpStatement -
RazorIRToken - - CSharp - public PageWithNamespace_Page Model => ViewData.Model;

View File

@ -0,0 +1,35 @@
#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "b205857d3dad47cb3f0c1d7775ae251b306ab830"
namespace Test.Namespace
{
#line hidden
using System;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
public class PageWithNamespace_Page : global::Microsoft.AspNetCore.Mvc.RazorPages.Page
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
BeginContext(34, 20, true);
WriteLiteral("<h1>Hi There!</h1>\r\n");
EndContext();
}
#pragma warning restore 1998
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TestFiles_IntegrationTests_CodeGenerationIntegrationTest_PageWithNamespace_cshtml> Html { get; private set; }
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<PageWithNamespace_Page> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<PageWithNamespace_Page>)PageContext?.ViewData;
public PageWithNamespace_Page Model => ViewData.Model;
}
}

View File

@ -0,0 +1,30 @@
Document -
Checksum -
NamespaceDeclaration - - Test.Namespace
UsingStatement - (1:0,1 [14] ) - System
UsingStatement - - System.Threading.Tasks
UsingStatement - (16:1,1 [19] ) - System.Linq
UsingStatement - (36:2,1 [34] ) - System.Collections.Generic
UsingStatement - (71:3,1 [32] ) - Microsoft.AspNetCore.Mvc
UsingStatement - (104:4,1 [42] ) - Microsoft.AspNetCore.Mvc.Rendering
UsingStatement - (147:5,1 [45] ) - Microsoft.AspNetCore.Mvc.ViewFeatures
ClassDeclaration - - public - PageWithNamespace_Page - global::Microsoft.AspNetCore.Mvc.RazorPages.Page -
RazorMethodDeclaration - - public - async, override - global::System.Threading.Tasks.Task - ExecuteAsync
CSharpStatement -
RazorIRToken - - CSharp - BeginContext(34, 20, true);
HtmlContent - (34:2,0 [20] PageWithNamespace.cshtml)
RazorIRToken - (34:2,0 [4] PageWithNamespace.cshtml) - Html - <h1>
RazorIRToken - (38:2,4 [9] PageWithNamespace.cshtml) - Html - Hi There!
RazorIRToken - (47:2,13 [5] PageWithNamespace.cshtml) - Html - </h1>
RazorIRToken - (52:2,18 [2] PageWithNamespace.cshtml) - Html - \n
CSharpStatement -
RazorIRToken - - CSharp - EndContext();
InjectDirective -
InjectDirective -
InjectDirective -
InjectDirective -
InjectDirective -
CSharpStatement -
RazorIRToken - - CSharp - public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<PageWithNamespace_Page> ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<PageWithNamespace_Page>)PageContext?.ViewData;
CSharpStatement -
RazorIRToken - - CSharp - public PageWithNamespace_Page Model => ViewData.Model;

View File

@ -0,0 +1,2 @@
@namespace Test.Namespace
<h1>Hi There!</h1>

View File

@ -0,0 +1,39 @@
namespace Test.Namespace
{
#line hidden
using TModel = global::System.Object;
using System;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
public class ViewWithNamespace_View : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
{
#pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() {
((System.Action)(() => {
global::System.Object __typeHelper = nameof(Test.Namespace);
}
))();
}
#pragma warning restore 219
private static System.Object __o = null;
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
}
#pragma warning restore 1998
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; }
}
}

View File

@ -0,0 +1,48 @@
Document -
Checksum -
NamespaceDeclaration - - Test.Namespace
UsingStatement - - TModel = global::System.Object
UsingStatement - (1:0,1 [12] ) - System
UsingStatement - - System.Threading.Tasks
UsingStatement - (16:1,1 [17] ) - System.Linq
UsingStatement - (36:2,1 [32] ) - System.Collections.Generic
UsingStatement - (71:3,1 [30] ) - Microsoft.AspNetCore.Mvc
UsingStatement - (104:4,1 [40] ) - Microsoft.AspNetCore.Mvc.Rendering
UsingStatement - (147:5,1 [43] ) - Microsoft.AspNetCore.Mvc.ViewFeatures
ClassDeclaration - - public - ViewWithNamespace_View - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic> -
DirectiveTokenHelper -
CSharpStatement -
RazorIRToken - - CSharp - #pragma warning disable 219
CSharpStatement -
RazorIRToken - - CSharp - private void __RazorDirectiveTokenHelpers__() {
DirectiveToken - (200:6,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel>
DirectiveToken - (263:6,71 [4] ) - Html
DirectiveToken - (277:7,8 [54] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper
DirectiveToken - (332:7,63 [4] ) - Json
DirectiveToken - (346:8,8 [53] ) - global::Microsoft.AspNetCore.Mvc.IViewComponentHelper
DirectiveToken - (400:8,62 [9] ) - Component
DirectiveToken - (419:9,8 [43] ) - global::Microsoft.AspNetCore.Mvc.IUrlHelper
DirectiveToken - (463:9,52 [3] ) - Url
DirectiveToken - (476:10,8 [70] ) - global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider
DirectiveToken - (547:10,79 [23] ) - ModelExpressionProvider
DirectiveToken - (586:11,14 [96] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor
DirectiveToken - (698:12,14 [87] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor
DirectiveToken - (801:13,14 [87] ) - Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor
DirectiveToken - (11:0,11 [14] ViewWithNamespace.cshtml) - Test.Namespace
CSharpStatement -
RazorIRToken - - CSharp - }
CSharpStatement -
RazorIRToken - - CSharp - #pragma warning restore 219
CSharpStatement -
RazorIRToken - - CSharp - private static System.Object __o = null;
RazorMethodDeclaration - - public - async, override - global::System.Threading.Tasks.Task - ExecuteAsync
HtmlContent - (27:1,0 [20] ViewWithNamespace.cshtml)
RazorIRToken - (27:1,0 [4] ViewWithNamespace.cshtml) - Html - <h1>
RazorIRToken - (31:1,4 [9] ViewWithNamespace.cshtml) - Html - Hi There!
RazorIRToken - (40:1,13 [5] ViewWithNamespace.cshtml) - Html - </h1>
RazorIRToken - (45:1,18 [2] ViewWithNamespace.cshtml) - Html - \n
InjectDirective -
InjectDirective -
InjectDirective -
InjectDirective -
InjectDirective -

View File

@ -0,0 +1,33 @@
#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "2893acf42354a0bc8b6a2698f5d2e4fab0e59dbe"
namespace Test.Namespace
{
#line hidden
using System;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
public class ViewWithNamespace_View : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
{
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
BeginContext(27, 20, true);
WriteLiteral("<h1>Hi There!</h1>\r\n");
EndContext();
}
#pragma warning restore 1998
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; }
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<dynamic> Html { get; private set; }
}
}

View File

@ -0,0 +1,26 @@
Document -
Checksum -
NamespaceDeclaration - - Test.Namespace
UsingStatement - (1:0,1 [14] ) - System
UsingStatement - - System.Threading.Tasks
UsingStatement - (16:1,1 [19] ) - System.Linq
UsingStatement - (36:2,1 [34] ) - System.Collections.Generic
UsingStatement - (71:3,1 [32] ) - Microsoft.AspNetCore.Mvc
UsingStatement - (104:4,1 [42] ) - Microsoft.AspNetCore.Mvc.Rendering
UsingStatement - (147:5,1 [45] ) - Microsoft.AspNetCore.Mvc.ViewFeatures
ClassDeclaration - - public - ViewWithNamespace_View - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic> -
RazorMethodDeclaration - - public - async, override - global::System.Threading.Tasks.Task - ExecuteAsync
CSharpStatement -
RazorIRToken - - CSharp - BeginContext(27, 20, true);
HtmlContent - (27:1,0 [20] ViewWithNamespace.cshtml)
RazorIRToken - (27:1,0 [4] ViewWithNamespace.cshtml) - Html - <h1>
RazorIRToken - (31:1,4 [9] ViewWithNamespace.cshtml) - Html - Hi There!
RazorIRToken - (40:1,13 [5] ViewWithNamespace.cshtml) - Html - </h1>
RazorIRToken - (45:1,18 [2] ViewWithNamespace.cshtml) - Html - \n
CSharpStatement -
RazorIRToken - - CSharp - EndContext();
InjectDirective -
InjectDirective -
InjectDirective -
InjectDirective -
InjectDirective -

View File

@ -1017,6 +1017,39 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
.With(new DirectiveTokenChunkGenerator(descriptor.Tokens.First()))));
}
[Fact]
public void Directives_CanUseReservedWord_Class()
{
// Arrange
var descriptor = DirectiveDescriptorBuilder.Create("class").Build();
// Act & Assert
ParseCodeBlockTest(
"@class",
new[] { descriptor },
new DirectiveBlock(
new DirectiveChunkGenerator(descriptor),
Factory.CodeTransition(),
Factory.MetaCode("class").Accepts(AcceptedCharacters.None)));
}
[Fact]
public void Directives_CanUseReservedWord_Namespace()
{
// Arrange
var descriptor = DirectiveDescriptorBuilder.Create("namespace").Build();
// Act & Assert
ParseCodeBlockTest(
"@namespace",
new[] { descriptor },
new DirectiveBlock(
new DirectiveChunkGenerator(descriptor),
Factory.CodeTransition(),
Factory.MetaCode("namespace").Accepts(AcceptedCharacters.None)));
}
internal virtual void ParseCodeBlockTest(
string document,
IEnumerable<DirectiveDescriptor> descriptors,