From e5cac9fb7fdcb725958fabe708c33def09d055a6 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 6 Apr 2017 11:12:10 -0700 Subject: [PATCH] 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. --- .../MvcViewDocumentClassifierPass.cs | 2 +- .../NamespaceDirective.cs | 191 ++++++++++++ .../RazorExtensions.cs | 1 + .../Legacy/CSharpCodeParser.cs | 31 +- .../CodeGenerationIntegrationTest.cs | 60 ++++ .../NamespaceDirectiveTest.cs | 278 ++++++++++++++++++ .../PageWithNamespace.cshtml | 3 + .../PageWithNamespace_DesignTime.codegen.cs | 41 +++ .../PageWithNamespace_DesignTime.ir.txt | 52 ++++ .../PageWithNamespace_Runtime.codegen.cs | 35 +++ .../PageWithNamespace_Runtime.ir.txt | 30 ++ .../ViewWithNamespace.cshtml | 2 + .../ViewWithNamespace_DesignTime.codegen.cs | 39 +++ .../ViewWithNamespace_DesignTime.ir.txt | 48 +++ .../ViewWithNamespace_Runtime.codegen.cs | 33 +++ .../ViewWithNamespace_Runtime.ir.txt | 26 ++ .../Legacy/CSharpDirectivesTest.cs | 33 +++ 17 files changed, 898 insertions(+), 7 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/NamespaceDirectiveTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace.cshtml create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_DesignTime.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_DesignTime.ir.txt create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_Runtime.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_Runtime.ir.txt create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace.cshtml create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_DesignTime.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_DesignTime.ir.txt create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_Runtime.codegen.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_Runtime.ir.txt diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs index c2e99715c..4341c8eec 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/MvcViewDocumentClassifierPass.cs @@ -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; diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs new file mode 100644 index 000000000..188b60f6c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs @@ -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); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs index edf39e521..03bea5e65 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs @@ -11,6 +11,7 @@ public static void Register(IRazorEngineBuilder builder) { InjectDirective.Register(builder); ModelDirective.Register(builder); + NamespaceDirective.Register(builder); PageDirective.Register(builder); builder.AddTargetExtension(new InjectDirectiveTargetExtension()); diff --git a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs index 4e638e224..d00d3570f 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Legacy/CSharpCodeParser.cs @@ -110,6 +110,17 @@ protected void MapDirectives(Action handler, params string[] directives) { _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 @@ private void AfterTransition() } 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 @@ private void AfterTransition() } 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 @@ private void SetUpKeywords() 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 @@ protected virtual void RemoveTagHelperDirective() [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)); } diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs index bff7037e1..779a691a3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -159,6 +159,36 @@ public void RazorPagesWithoutModel_Runtime() 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 @@ public void RazorPagesWithoutModel_DesignTime() 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 descriptors = null) diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/NamespaceDirectiveTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/NamespaceDirectiveTest.cs new file mode 100644 index 000000000..292afbd94 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/NamespaceDirectiveTest.cs @@ -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); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace.cshtml new file mode 100644 index 000000000..ecee97de5 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace.cshtml @@ -0,0 +1,3 @@ +@page +@namespace Test.Namespace +

Hi There!

diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_DesignTime.codegen.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_DesignTime.codegen.cs new file mode 100644 index 000000000..c8ba2ad45 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_DesignTime.codegen.cs @@ -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 Html { get; private set; } + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary)PageContext?.ViewData; + public PageWithNamespace_Page Model => ViewData.Model; + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_DesignTime.ir.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_DesignTime.ir.txt new file mode 100644 index 000000000..700750997 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_DesignTime.ir.txt @@ -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 + 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 -

+ RazorIRToken - (38:2,4 [9] PageWithNamespace.cshtml) - Html - Hi There! + RazorIRToken - (47:2,13 [5] PageWithNamespace.cshtml) - Html -

+ RazorIRToken - (52:2,18 [2] PageWithNamespace.cshtml) - Html - \n + InjectDirective - + InjectDirective - + InjectDirective - + InjectDirective - + InjectDirective - + CSharpStatement - + RazorIRToken - - CSharp - public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary)PageContext?.ViewData; + CSharpStatement - + RazorIRToken - - CSharp - public PageWithNamespace_Page Model => ViewData.Model; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_Runtime.codegen.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_Runtime.codegen.cs new file mode 100644 index 000000000..a8d2fad65 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_Runtime.codegen.cs @@ -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("

Hi There!

\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 Html { get; private set; } + public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary)PageContext?.ViewData; + public PageWithNamespace_Page Model => ViewData.Model; + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_Runtime.ir.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_Runtime.ir.txt new file mode 100644 index 000000000..512d0daa1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PageWithNamespace_Runtime.ir.txt @@ -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 -

+ RazorIRToken - (38:2,4 [9] PageWithNamespace.cshtml) - Html - Hi There! + RazorIRToken - (47:2,13 [5] PageWithNamespace.cshtml) - Html -

+ 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 ViewData => (global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary)PageContext?.ViewData; + CSharpStatement - + RazorIRToken - - CSharp - public PageWithNamespace_Page Model => ViewData.Model; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace.cshtml b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace.cshtml new file mode 100644 index 000000000..eaf33e48e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace.cshtml @@ -0,0 +1,2 @@ +@namespace Test.Namespace +

Hi There!

diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_DesignTime.codegen.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_DesignTime.codegen.cs new file mode 100644 index 000000000..32ada4b8f --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_DesignTime.codegen.cs @@ -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 + { + #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 Html { get; private set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_DesignTime.ir.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_DesignTime.ir.txt new file mode 100644 index 000000000..e8cd81f77 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_DesignTime.ir.txt @@ -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 - + 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 + 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 -

+ RazorIRToken - (31:1,4 [9] ViewWithNamespace.cshtml) - Html - Hi There! + RazorIRToken - (40:1,13 [5] ViewWithNamespace.cshtml) - Html -

+ RazorIRToken - (45:1,18 [2] ViewWithNamespace.cshtml) - Html - \n + InjectDirective - + InjectDirective - + InjectDirective - + InjectDirective - + InjectDirective - diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_Runtime.codegen.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_Runtime.codegen.cs new file mode 100644 index 000000000..f467c598d --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_Runtime.codegen.cs @@ -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 + { + #pragma warning disable 1998 + public async override global::System.Threading.Tasks.Task ExecuteAsync() + { + BeginContext(27, 20, true); + WriteLiteral("

Hi There!

\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 Html { get; private set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_Runtime.ir.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_Runtime.ir.txt new file mode 100644 index 000000000..60e9a65d2 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ViewWithNamespace_Runtime.ir.txt @@ -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 - + 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 -

+ RazorIRToken - (31:1,4 [9] ViewWithNamespace.cshtml) - Html - Hi There! + RazorIRToken - (40:1,13 [5] ViewWithNamespace.cshtml) - Html -

+ RazorIRToken - (45:1,18 [2] ViewWithNamespace.cshtml) - Html - \n + CSharpStatement - + RazorIRToken - - CSharp - EndContext(); + InjectDirective - + InjectDirective - + InjectDirective - + InjectDirective - + InjectDirective - diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs index 9bbe9030f..e56b58b48 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Legacy/CSharpDirectivesTest.cs @@ -1017,6 +1017,39 @@ public void OptionalMemberTokens_WithMemberSpecified_IsParsed() .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 descriptors,