diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/MvcViewDocumentClassifierPass.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/MvcViewDocumentClassifierPass.cs index 975cf90666..7ab07afc8b 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/MvcViewDocumentClassifierPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/MvcViewDocumentClassifierPass.cs @@ -23,10 +23,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method); - @namespace.Content = "AspNetCore"; + if (!codeDocument.TryComputeNamespace(fallbackToRootNamespace: false, out var namespaceName)) + { + @namespace.Content = "AspNetCore"; + } + else + { + @namespace.Content = namespaceName; + } - var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath; - if (string.IsNullOrEmpty(filePath)) + if (!TryComputeClassName(codeDocument, out var className)) { // It's possible for a Razor document to not have a file path. // Eg. When we try to generate code for an in memory document like default imports. @@ -35,8 +41,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions } else { - @class.ClassName = GetClassNameFromPath(filePath); + @class.ClassName = className; } + @class.BaseType = "global::Microsoft.AspNetCore.Mvc.Razor.RazorPage"; @class.Modifiers.Clear(); @@ -50,6 +57,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions method.ReturnType = $"global::{typeof(System.Threading.Tasks.Task).FullName}"; } + private bool TryComputeClassName(RazorCodeDocument codeDocument, out string className) + { + var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath; + if (string.IsNullOrEmpty(filePath)) + { + className = null; + return false; + } + + className = GetClassNameFromPath(filePath); + return true; + } + private static string GetClassNameFromPath(string path) { const string cshtmlExtension = ".cshtml"; diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/NamespaceDirective.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/NamespaceDirective.cs deleted file mode 100644 index 1a809c0629..0000000000 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/NamespaceDirective.cs +++ /dev/null @@ -1,204 +0,0 @@ -// 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 = DirectiveDescriptor.CreateDirective( - "namespace", - DirectiveKind.SingleLine, - builder => - { - builder.AddNamespaceToken( - Resources.NamespaceDirective_NamespaceToken_Name, - Resources.NamespaceDirective_NamespaceToken_Description); - builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; - builder.Description = Resources.NamespaceDirective_Description; - }); - - public static void Register(RazorProjectEngineBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(); - } - - builder.AddDirective(Directive); - builder.Features.Add(new Pass()); - } - - // internal for testing - internal class Pass : IntermediateNodePassBase, IRazorDirectiveClassifierPass - { - protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) - { - if (documentNode.DocumentKind != RazorPageDocumentClassifierPass.RazorPageDocumentKind && - documentNode.DocumentKind != MvcViewDocumentClassifierPass.MvcViewDocumentKind) - { - // Not a page. Skip. - return; - } - - var visitor = new Visitor(); - visitor.Visit(documentNode); - - var directive = visitor.LastNamespaceDirective; - if (directive == null) - { - // No namespace set. Skip. - return; - } - - var @namespace = visitor.FirstNamespace; - if (@namespace == null) - { - // No namespace node. Skip. - return; - } - - @namespace.Content = GetNamespace(codeDocument.Source.FilePath, directive); - } - } - - // 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 string GetNamespace(string source, DirectiveIntermediateNode directive) - { - var directiveSource = NormalizeDirectory(directive.Source?.FilePath); - - var baseNamespace = directive.Tokens.FirstOrDefault()?.Content; - if (string.IsNullOrEmpty(baseNamespace)) - { - // The namespace directive was incomplete. - return string.Empty; - } - - if (string.IsNullOrEmpty(source) || directiveSource == null) - { - // No sources, can't compute a suffix. - return baseNamespace; - } - - // 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. - return baseNamespace; - } - - // 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(CSharpIdentifier.SanitizeIdentifier(segments[i])); - } - - return builder.ToString(); - } - - // 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 : IntermediateNodeWalker - { - public ClassDeclarationIntermediateNode FirstClass { get; private set; } - - public NamespaceDeclarationIntermediateNode FirstNamespace { get; private set; } - - // We want the last one, so get them all and then . - public DirectiveIntermediateNode LastNamespaceDirective { get; private set; } - - public override void VisitNamespaceDeclaration(NamespaceDeclarationIntermediateNode node) - { - if (FirstNamespace == null) - { - FirstNamespace = node; - } - - base.VisitNamespaceDeclaration(node); - } - - public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node) - { - if (FirstClass == null) - { - FirstClass = node; - } - - base.VisitClassDeclaration(node); - } - - public override void VisitDirective(DirectiveIntermediateNode node) - { - if (node.Directive == Directive) - { - LastNamespaceDirective = node; - } - - base.VisitDirective(node); - } - } - - #region Obsolete - [Obsolete("This method is obsolete and will be removed in a future version.")] - public static void Register(IRazorEngineBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(); - } - - builder.AddDirective(Directive); - builder.Features.Add(new Pass()); - } - #endregion - } -} diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Properties/Resources.Designer.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Properties/Resources.Designer.cs index dfa9a59ca9..18566dc27e 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Properties/Resources.Designer.cs +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Properties/Resources.Designer.cs @@ -206,48 +206,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions internal static string FormatMvcRazorParser_InvalidPropertyType(object p0, object p1, object p2) => string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorParser_InvalidPropertyType"), p0, p1, p2); - /// - /// Specify the base namespace for the page. - /// - internal static string NamespaceDirective_Description - { - get => GetString("NamespaceDirective_Description"); - } - - /// - /// Specify the base namespace for the page. - /// - internal static string FormatNamespaceDirective_Description() - => GetString("NamespaceDirective_Description"); - - /// - /// The namespace for the page. - /// - internal static string NamespaceDirective_NamespaceToken_Description - { - get => GetString("NamespaceDirective_NamespaceToken_Description"); - } - - /// - /// The namespace for the page. - /// - internal static string FormatNamespaceDirective_NamespaceToken_Description() - => GetString("NamespaceDirective_NamespaceToken_Description"); - - /// - /// Namespace - /// - internal static string NamespaceDirective_NamespaceToken_Name - { - get => GetString("NamespaceDirective_NamespaceToken_Name"); - } - - /// - /// Namespace - /// - internal static string FormatNamespaceDirective_NamespaceToken_Name() - => GetString("NamespaceDirective_NamespaceToken_Name"); - /// /// The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor cshtml file. /// diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorExtensions.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorExtensions.cs index 26e641d7aa..867586db5c 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorExtensions.cs @@ -19,7 +19,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions InjectDirective.Register(builder); ModelDirective.Register(builder); - NamespaceDirective.Register(builder); PageDirective.Register(builder); SectionDirective.Register(builder); diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorPageDocumentClassifierPass.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorPageDocumentClassifierPass.cs index 96a05cfc22..567754cd66 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorPageDocumentClassifierPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/RazorPageDocumentClassifierPass.cs @@ -48,12 +48,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { base.OnDocumentStructureCreated(codeDocument, @namespace, @class, method); - @namespace.Content = "AspNetCore"; + if (!codeDocument.TryComputeNamespace(fallbackToRootNamespace: false, out var namespaceName)) + { + @namespace.Content = "AspNetCore"; + } + else + { + @namespace.Content = namespaceName; + } - @class.BaseType = "global::Microsoft.AspNetCore.Mvc.RazorPages.Page"; - - var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath; - if (string.IsNullOrEmpty(filePath)) + if (!TryComputeClassName(codeDocument, out var className)) { // It's possible for a Razor document to not have a file path. // Eg. When we try to generate code for an in memory document like default imports. @@ -62,9 +66,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions } else { - @class.ClassName = GetClassNameFromPath(filePath); + @class.ClassName = className; } + @class.BaseType = "global::Microsoft.AspNetCore.Mvc.RazorPages.Page"; @class.Modifiers.Clear(); @class.Modifiers.Add("public"); @@ -143,6 +148,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions } } + private bool TryComputeClassName(RazorCodeDocument codeDocument, out string className) + { + var filePath = codeDocument.Source.RelativePath ?? codeDocument.Source.FilePath; + if (string.IsNullOrEmpty(filePath)) + { + className = null; + return false; + } + + className = GetClassNameFromPath(filePath); + return true; + } + private static string GetClassNameFromPath(string path) { const string cshtmlExtension = ".cshtml"; diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Resources.resx b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Resources.resx index 474537197e..9a0b9cfdc4 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Resources.resx +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/Resources.resx @@ -159,15 +159,6 @@ Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'. - - Specify the base namespace for the page. - - - The namespace for the page. - - - Namespace - The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor cshtml file. diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/NamespaceDirectiveTest.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/NamespaceDirectiveTest.cs deleted file mode 100644 index d744eec9fb..0000000000 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/NamespaceDirectiveTest.cs +++ /dev/null @@ -1,352 +0,0 @@ -// 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 Moq; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Razor.Extensions -{ - public class NamespaceDirectiveTest - { - [Fact] - public void GetNamespace_IncompleteDirective_UsesEmptyNamespace() - { - // Arrange - var source = "c:\\foo\\bar\\bleh.cshtml"; - var imports = "c:\\foo\\baz\\bleh.cshtml"; - var node = new DirectiveIntermediateNode() - { - Directive = NamespaceDirective.Directive, - Source = new SourceSpan(imports, 0, 0, 0, 0), - }; - - // Act - var @namespace = NamespaceDirective.GetNamespace(source, node); - - // Assert - Assert.Equal(string.Empty, @namespace); - } - - [Fact] - public void GetNamespace_EmptyDirective_UsesEmptyNamespace() - { - // Arrange - var source = "c:\\foo\\bar\\bleh.cshtml"; - var imports = "c:\\foo\\baz\\bleh.cshtml"; - var node = new DirectiveIntermediateNode() - { - Directive = NamespaceDirective.Directive, - Source = new SourceSpan(imports, 0, 0, 0, 0), - }; - node.Children.Add(new DirectiveTokenIntermediateNode() { Content = string.Empty }); - - // Act - var @namespace = NamespaceDirective.GetNamespace(source, node); - - // Assert - Assert.Equal(string.Empty, @namespace); - } - - // 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 GetNamespace_ForNonRelatedFiles_UsesNamespaceVerbatim(string source, string imports) - { - // Arrange - var node = new DirectiveIntermediateNode() - { - Directive = NamespaceDirective.Directive, - Source = new SourceSpan(imports, 0, 0, 0, 0), - }; - - node.Children.Add(new DirectiveTokenIntermediateNode() { Content = "Base" }); - - // Act - var @namespace = NamespaceDirective.GetNamespace(source, node); - - // Assert - 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 GetNamespace_ForRelatedFiles_ComputesNamespaceWithSuffix(string source, string imports, string expected) - { - // Arrange - var node = new DirectiveIntermediateNode() - { - Directive = NamespaceDirective.Directive, - Source = new SourceSpan(imports, 0, 0, 0, 0), - }; - - node.Children.Add(new DirectiveTokenIntermediateNode() { Content = "Base" }); - - // Act - var @namespace = NamespaceDirective.GetNamespace(source, node); - - // Assert - Assert.Equal(expected, @namespace); - } - - // This is the case where a _ViewImports sets the namespace. - [Fact] - public void Pass_SetsNamespace_ComputedFromImports() - { - // Arrange - var document = new DocumentIntermediateNode(); - var builder = IntermediateNodeBuilder.Create(document); - - builder.Push(new DirectiveIntermediateNode() - { - Directive = NamespaceDirective.Directive, - Source = new SourceSpan("/Account/_ViewImports.cshtml", 0, 0, 0, 0), - }); - builder.Add(new DirectiveTokenIntermediateNode() { Content = "WebApplication.Account" }); - builder.Pop(); - - var @namespace = new NamespaceDeclarationIntermediateNode() { Content = "default" }; - builder.Push(@namespace); - - var @class = new ClassDeclarationIntermediateNode() { ClassName = "default" }; - builder.Add(@class); - - document.DocumentKind = RazorPageDocumentClassifierPass.RazorPageDocumentKind; - - var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("ignored", "/Account/Manage/AddUser.cshtml")); - - var pass = new NamespaceDirective.Pass(); - pass.Engine = Mock.Of(); - - // Act - pass.Execute(codeDocument, document); - - // Assert - Assert.Equal("WebApplication.Account.Manage", @namespace.Content); - Assert.Equal("default", @class.ClassName); - } - - // This is the case where the source file sets the namespace. - [Fact] - public void Pass_SetsNamespace_ComputedFromSource() - { - // Arrange - var document = new DocumentIntermediateNode(); - var builder = IntermediateNodeBuilder.Create(document); - - // This will be ignored. - builder.Push(new DirectiveIntermediateNode() - { - Directive = NamespaceDirective.Directive, - Source = new SourceSpan("/Account/_ViewImports.cshtml", 0, 0, 0, 0), - }); - builder.Add(new DirectiveTokenIntermediateNode() { Content = "ignored" }); - builder.Pop(); - - // This will be used. - builder.Push(new DirectiveIntermediateNode() - { - Directive = NamespaceDirective.Directive, - Source = new SourceSpan("/Account/Manage/AddUser.cshtml", 0, 0, 0, 0), - }); - builder.Add(new DirectiveTokenIntermediateNode() { Content = "WebApplication.Account.Manage" }); - builder.Pop(); - - var @namespace = new NamespaceDeclarationIntermediateNode() { Content = "default" }; - builder.Push(@namespace); - - var @class = new ClassDeclarationIntermediateNode() { ClassName = "default" }; - builder.Add(@class); - - document.DocumentKind = RazorPageDocumentClassifierPass.RazorPageDocumentKind; - - var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("ignored", "/Account/Manage/AddUser.cshtml")); - - var pass = new NamespaceDirective.Pass(); - pass.Engine = Mock.Of(); - - // Act - pass.Execute(codeDocument, document); - - // Assert - Assert.Equal("WebApplication.Account.Manage", @namespace.Content); - Assert.Equal("default", @class.ClassName); - } - - // Handles cases where invalid characters appears in FileNames. Note that we don't sanitize the part of - // the namespace that you put in an import, just the file-based-suffix. Garbage in, garbage out. - [Fact] - public void Pass_SetsNamespace_SanitizesClassAndNamespace() - { - // Arrange - var document = new DocumentIntermediateNode(); - var builder = IntermediateNodeBuilder.Create(document); - - builder.Push(new DirectiveIntermediateNode() - { - Directive = NamespaceDirective.Directive, - Source = new SourceSpan("/Account/_ViewImports.cshtml", 0, 0, 0, 0), - }); - builder.Add(new DirectiveTokenIntermediateNode() { Content = "WebApplication.Account" }); - builder.Pop(); - - var @namespace = new NamespaceDeclarationIntermediateNode() { Content = "default" }; - builder.Push(@namespace); - - var @class = new ClassDeclarationIntermediateNode() { ClassName = "default" }; - builder.Add(@class); - - document.DocumentKind = RazorPageDocumentClassifierPass.RazorPageDocumentKind; - - var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("ignored", "/Account/Manage-Info/Add+User.cshtml")); - - var pass = new NamespaceDirective.Pass(); - pass.Engine = Mock.Of(); - - // Act - pass.Execute(codeDocument, document); - - // Assert - Assert.Equal("WebApplication.Account.Manage_Info", @namespace.Content); - Assert.Equal("default", @class.ClassName); - } - - // This is the case where the source file sets the namespace. - [Fact] - public void Pass_SetsNamespace_ComputedFromSource_ForView() - { - // Arrange - var document = new DocumentIntermediateNode(); - var builder = IntermediateNodeBuilder.Create(document); - - // This will be ignored. - builder.Push(new DirectiveIntermediateNode() - { - Directive = NamespaceDirective.Directive, - Source = new SourceSpan("/Account/_ViewImports.cshtml", 0, 0, 0, 0), - }); - builder.Add(new DirectiveTokenIntermediateNode() { Content = "ignored" }); - builder.Pop(); - - // This will be used. - builder.Push(new DirectiveIntermediateNode() - { - Directive = NamespaceDirective.Directive, - Source = new SourceSpan("/Account/Manage/AddUser.cshtml", 0, 0, 0, 0), - }); - builder.Add(new DirectiveTokenIntermediateNode() { Content = "WebApplication.Account.Manage" }); - builder.Pop(); - - var @namespace = new NamespaceDeclarationIntermediateNode() { Content = "default" }; - builder.Push(@namespace); - - var @class = new ClassDeclarationIntermediateNode() { ClassName = "default" }; - builder.Add(@class); - - document.DocumentKind = MvcViewDocumentClassifierPass.MvcViewDocumentKind; - - var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("ignored", "/Account/Manage/AddUser.cshtml")); - - var pass = new NamespaceDirective.Pass(); - pass.Engine = Mock.Of(); - - // Act - pass.Execute(codeDocument, document); - - // Assert - Assert.Equal("WebApplication.Account.Manage", @namespace.Content); - Assert.Equal("default", @class.ClassName); - } - - // This handles an error case where we can't determine the relationship between the - // imports and the source. - [Fact] - public void Pass_SetsNamespace_VerbatimFromImports() - { - // Arrange - var document = new DocumentIntermediateNode(); - var builder = IntermediateNodeBuilder.Create(document); - - builder.Push(new DirectiveIntermediateNode() - { - Directive = NamespaceDirective.Directive, - Source = new SourceSpan(null, 0, 0, 0, 0), - }); - builder.Add(new DirectiveTokenIntermediateNode() { Content = "WebApplication.Account" }); - builder.Pop(); - - var @namespace = new NamespaceDeclarationIntermediateNode() { Content = "default" }; - builder.Push(@namespace); - - var @class = new ClassDeclarationIntermediateNode() { ClassName = "default" }; - builder.Add(@class); - - document.DocumentKind = RazorPageDocumentClassifierPass.RazorPageDocumentKind; - - var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("ignored", "/Account/Manage/AddUser.cshtml")); - - var pass = new NamespaceDirective.Pass(); - pass.Engine = Mock.Of(); - - // Act - pass.Execute(codeDocument, document); - - // Assert - Assert.Equal("WebApplication.Account", @namespace.Content); - Assert.Equal("default", @class.ClassName); - } - - [Fact] - public void Pass_DoesNothing_ForUnknownDocumentKind() - { - // Arrange - var document = new DocumentIntermediateNode(); - var builder = IntermediateNodeBuilder.Create(document); - - builder.Push(new DirectiveIntermediateNode() - { - Directive = NamespaceDirective.Directive, - Source = new SourceSpan(null, 0, 0, 0, 0), - }); - builder.Add(new DirectiveTokenIntermediateNode() { Content = "WebApplication.Account" }); - builder.Pop(); - - var @namespace = new NamespaceDeclarationIntermediateNode() { Content = "default" }; - builder.Push(@namespace); - - var @class = new ClassDeclarationIntermediateNode() { ClassName = "default" }; - builder.Add(@class); - - document.DocumentKind = null; - - var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("ignored", "/Account/Manage/AddUser.cshtml")); - - var pass = new NamespaceDirective.Pass(); - pass.Engine = Mock.Of(); - - // Act - pass.Execute(codeDocument, document); - - // Assert - Assert.Equal("default", @namespace.Content); - Assert.Equal("default", @class.ClassName); - } - } -} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDocumentClassifierPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDocumentClassifierPass.cs index 8b5ad5061d..8ff9196e1e 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDocumentClassifierPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDocumentClassifierPass.cs @@ -54,7 +54,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method) { - if (!codeDocument.TryComputeNamespaceAndClass(out var computedNamespace, out var computedClass)) + if (!codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var computedNamespace) || + !TryComputeClassName(codeDocument, out var computedClass)) { // If we can't compute a nice namespace (no relative path) then just generate something // mangled. @@ -117,5 +118,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Components }); } } + + private bool TryComputeClassName(RazorCodeDocument codeDocument, out string className) + { + className = null; + if (codeDocument.Source.FilePath == null || codeDocument.Source.RelativePath == null) + { + return false; + } + + var relativePath = NormalizePath(codeDocument.Source.RelativePath); + className = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(relativePath)); + return true; + } + + private static string NormalizePath(string path) + { + path = path.Replace('\\', '/'); + + return path; + } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperBinderPhase.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperBinderPhase.cs index 03e8bc4314..788b22c597 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperBinderPhase.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperBinderPhase.cs @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Razor.Language if (FileKinds.IsComponent(codeDocument.GetFileKind()) && (parserOptions == null || parserOptions.FeatureFlags.AllowComponentFileKind)) { - codeDocument.TryComputeNamespaceAndClass(out var currentNamespace, out var _); + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var currentNamespace); visitor = new ComponentDirectiveVisitor(codeDocument.Source.FilePath, descriptors, currentNamespace); } else diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/NamespaceDirective.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/NamespaceDirective.cs new file mode 100644 index 0000000000..3d29f63a03 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Extensions/NamespaceDirective.cs @@ -0,0 +1,33 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Language.Extensions +{ + public static class NamespaceDirective + { + public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective( + "namespace", + DirectiveKind.SingleLine, + builder => + { + builder.AddNamespaceToken( + Resources.NamespaceDirective_NamespaceToken_Name, + Resources.NamespaceDirective_NamespaceToken_Description); + builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; + builder.Description = Resources.NamespaceDirective_Description; + }); + + public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.AddDirective(Directive, FileKinds.Legacy, FileKinds.Component, FileKinds.ComponentImport); + return builder; + } + } +} diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Properties/Resources.Designer.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Properties/Resources.Designer.cs index be43f4d896..47cfb574a2 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Properties/Resources.Designer.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Properties/Resources.Designer.cs @@ -1898,6 +1898,48 @@ namespace Microsoft.AspNetCore.Razor.Language internal static string FormatRewriter_InsufficientStack() => GetString("Rewriter_InsufficientStack"); + /// + /// Specify the base namespace for the document. + /// + internal static string NamespaceDirective_Description + { + get => GetString("NamespaceDirective_Description"); + } + + /// + /// Specify the base namespace for the document. + /// + internal static string FormatNamespaceDirective_Description() + => GetString("NamespaceDirective_Description"); + + /// + /// The namespace for the document. + /// + internal static string NamespaceDirective_NamespaceToken_Description + { + get => GetString("NamespaceDirective_NamespaceToken_Description"); + } + + /// + /// The namespace for the document. + /// + internal static string FormatNamespaceDirective_NamespaceToken_Description() + => GetString("NamespaceDirective_NamespaceToken_Description"); + + /// + /// Namespace + /// + internal static string NamespaceDirective_NamespaceToken_Name + { + get => GetString("NamespaceDirective_NamespaceToken_Name"); + } + + /// + /// Namespace + /// + internal static string FormatNamespaceDirective_NamespaceToken_Name() + => GetString("NamespaceDirective_NamespaceToken_Name"); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeDocumentExtensions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeDocumentExtensions.cs index e1cd262573..704a9da482 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeDocumentExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeDocumentExtensions.cs @@ -3,9 +3,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Linq; using System.Text; +using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Microsoft.AspNetCore.Razor.Language.Syntax; namespace Microsoft.AspNetCore.Razor.Language { @@ -195,11 +199,11 @@ namespace Microsoft.AspNetCore.Razor.Language } // In general documents will have a relative path (relative to the project root). - // We can only really compute a nice class/namespace when we know a relative path. + // We can only really compute a nice namespace when we know a relative path. // // However all kinds of thing are possible in tools. We shouldn't barf here if the document isn't // set up correctly. - internal static bool TryComputeNamespaceAndClass(this RazorCodeDocument document, out string @namespace, out string @class) + public static bool TryComputeNamespace(this RazorCodeDocument document, bool fallbackToRootNamespace, out string @namespace) { if (document == null) { @@ -208,28 +212,78 @@ namespace Microsoft.AspNetCore.Razor.Language var filePath = document.Source.FilePath; var relativePath = document.Source.RelativePath; - if (filePath == null || relativePath == null || filePath.Length <= relativePath.Length) + if (filePath == null || relativePath == null || filePath.Length < relativePath.Length) { @namespace = null; - @class = null; return false; } - filePath = NormalizePath(filePath); relativePath = NormalizePath(relativePath); - var options = document.GetCodeGenerationOptions() ?? document.GetDocumentIntermediateNode()?.Options; - var rootNamespace = options?.RootNamespace; - if (string.IsNullOrEmpty(rootNamespace)) + // If the document or it's imports contains a @namespace directive, we want to use that over the root namespace. + var baseNamespace = string.Empty; + var appendSuffix = true; + var lastNamespaceContent = string.Empty; + var lastNamespaceLocation = SourceSpan.Undefined; + var importSyntaxTrees = document.GetImportSyntaxTrees(); + if (importSyntaxTrees != null) { + // ImportSyntaxTrees is usually set. Just being defensive. + foreach (var importSyntaxTree in importSyntaxTrees) + { + if (importSyntaxTree != null && NamespaceVisitor.TryGetLastNamespaceDirective(importSyntaxTree, out var importNamespaceContent, out var importNamespaceLocation)) + { + lastNamespaceContent = importNamespaceContent; + lastNamespaceLocation = importNamespaceLocation; + } + } + } + + var syntaxTree = document.GetSyntaxTree(); + if (syntaxTree != null && NamespaceVisitor.TryGetLastNamespaceDirective(syntaxTree, out var namespaceContent, out var namespaceLocation)) + { + lastNamespaceContent = namespaceContent; + lastNamespaceLocation = namespaceLocation; + } + + // If there are multiple @namespace directives in the heirarchy, + // we want to pick the closest one to the current document. + if (!string.IsNullOrEmpty(lastNamespaceContent)) + { + baseNamespace = lastNamespaceContent; + var directiveLocationDirectory = NormalizeDirectory(lastNamespaceLocation.FilePath); + + // We're specifically using OrdinalIgnoreCase here because Razor treats all paths as case-insensitive. + if (!document.Source.FilePath.StartsWith(directiveLocationDirectory, StringComparison.OrdinalIgnoreCase) || + document.Source.FilePath.Length <= directiveLocationDirectory.Length) + { + // The most relevant directive is not from the directory hierarchy, can't compute a suffix. + appendSuffix = false; + } + else + { + // We know that the document containing the namespace directive is in the current document's heirarchy. + // Let's compute the actual relative path that we'll use to compute the namespace suffix. + relativePath = document.Source.FilePath.Substring(directiveLocationDirectory.Length); + } + } + else if (fallbackToRootNamespace) + { + var options = document.GetCodeGenerationOptions() ?? document.GetDocumentIntermediateNode()?.Options; + baseNamespace = options?.RootNamespace; + appendSuffix = true; + } + + if (string.IsNullOrEmpty(baseNamespace)) + { + // There was no valid @namespace directive and we couldn't compute the RootNamespace. @namespace = null; - @class = null; return false; } var builder = new StringBuilder(); // Sanitize the base namespace, but leave the dots. - var segments = rootNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries); + var segments = baseNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries); builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[0])); for (var i = 1; i < segments.Length; i++) { @@ -237,19 +291,49 @@ namespace Microsoft.AspNetCore.Razor.Language builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i])); } - segments = relativePath.Split(PathSeparators, StringSplitOptions.RemoveEmptyEntries); - - // Skip the last segment because it's the FileName. - for (var i = 0; i < segments.Length - 1; i++) + if (appendSuffix) { - builder.Append('.'); - builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i])); + // If we get here, we already have a base namespace and the relative path that should be used as the namespace suffix. + segments = relativePath.Split(PathSeparators, StringSplitOptions.RemoveEmptyEntries); + + // Skip the last segment because it's the FileName. + for (var i = 0; i < segments.Length - 1; i++) + { + builder.Append('.'); + builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i])); + } } @namespace = builder.ToString(); - @class = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(relativePath)); 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. + string NormalizeDirectory(string path) + { + char[] Separators = new char[] { '\\', '/' }; + 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 static string NormalizePath(string path) @@ -288,5 +372,55 @@ namespace Microsoft.AspNetCore.Razor.Language public IReadOnlyList TagHelpers { get; } } + + private class NamespaceVisitor : SyntaxWalker + { + private readonly RazorSourceDocument _source; + + private NamespaceVisitor(RazorSourceDocument source) + { + _source = source; + } + + public string LastNamespaceContent { get; set; } + + public SourceSpan LastNamespaceLocation { get; set; } + + public static bool TryGetLastNamespaceDirective( + RazorSyntaxTree syntaxTree, + out string namespaceDirectiveContent, + out SourceSpan namespaceDirectiveSpan) + { + var visitor = new NamespaceVisitor(syntaxTree.Source); + visitor.Visit(syntaxTree.Root); + if (string.IsNullOrEmpty(visitor.LastNamespaceContent)) + { + namespaceDirectiveContent = null; + namespaceDirectiveSpan = SourceSpan.Undefined; + return false; + } + + namespaceDirectiveContent = visitor.LastNamespaceContent; + namespaceDirectiveSpan = visitor.LastNamespaceLocation; + return true; + } + + public override void VisitRazorDirective(RazorDirectiveSyntax node) + { + if (node != null && node.DirectiveDescriptor == NamespaceDirective.Directive) + { + var directiveContent = node.Body?.GetContent(); + + // In practice, this should never be null and always start with 'namespace'. Just being defensive here. + if (directiveContent != null && directiveContent.StartsWith(NamespaceDirective.Directive.Directive)) + { + LastNamespaceContent = directiveContent.Substring(NamespaceDirective.Directive.Directive.Length).Trim(); + LastNamespaceLocation = node.GetSourceSpan(_source); + } + } + + base.VisitRazorDirective(node); + } + } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngine.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngine.cs index cdc6b9ff4a..7155a3c2e7 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngine.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngine.cs @@ -117,6 +117,7 @@ namespace Microsoft.AspNetCore.Razor.Language FunctionsDirective.Register(builder); ImplementsDirective.Register(builder); InheritsDirective.Register(builder); + NamespaceDirective.Register(builder); AddComponentFeatures(builder); } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Resources.resx b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Resources.resx index d7f21d9466..ef92859644 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Resources.resx +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Resources.resx @@ -542,4 +542,13 @@ Instead, wrap the contents of the block in "{{}}": Not enough stack space to continue parsing this document. Razor doesn't support deeply nested elements. + + Specify the base namespace for the document. + + + The namespace for the document. + + + Namespace + \ No newline at end of file diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs index 7cbc74266f..923c8bcabe 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs @@ -1570,6 +1570,50 @@ namespace AnotherTest CompileToAssembly(generated); } + [Fact] + public void Component_WithNamespaceDirective() + { + // Arrange + AdditionalSyntaxTrees.Add(Parse(@" +using System; +using Microsoft.AspNetCore.Components; + +namespace Test +{ + public class HeaderComponent : ComponentBase + { + [Parameter] + string Header { get; set; } + } +} + +namespace AnotherTest +{ + public class FooterComponent : ComponentBase + { + [Parameter] + string Footer { get; set; } + } +} +")); + + // Act + var generated = CompileToCSharp(@" +@using Test +@namespace AnotherTest + + + + + +"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + #endregion #region EventCallback @@ -3582,6 +3626,75 @@ namespace Test AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); CompileToAssembly(generated, throwOnFailure: false); } + + [Fact] + public void Component_NamespaceDirective_InImports() + { + // Arrange + var importContent = @" +@using System.Text +@using System.Reflection +@namespace New.Test +"; + var importItem = CreateProjectItem("_Imports.razor", importContent, FileKinds.ComponentImport); + ImportItems.Add(importItem); + AdditionalSyntaxTrees.Add(Parse(@" +using Microsoft.AspNetCore.Components; + +namespace New.Test +{ + public class Counter : ComponentBase + { + public int Count { get; set; } + } +} +")); + + // Act + var generated = CompileToCSharp(@" + +"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } + + [Fact] + public void Component_NamespaceDirective_OverrideImports() + { + // Arrange + var importContent = @" +@using System.Text +@using System.Reflection +@namespace Import.Test +"; + var importItem = CreateProjectItem("_Imports.razor", importContent, FileKinds.ComponentImport); + ImportItems.Add(importItem); + AdditionalSyntaxTrees.Add(Parse(@" +using Microsoft.AspNetCore.Components; + +namespace New.Test +{ + public class Counter2 : ComponentBase + { + public int Count { get; set; } + } +} +")); + + // Act + var generated = CompileToCSharp("Pages/Counter.razor", @" +@namespace New.Test + +"); + + // Assert + AssertDocumentNodeMatchesBaseline(generated.CodeDocument); + AssertCSharpDocumentMatchesBaseline(generated.CodeDocument); + CompileToAssembly(generated); + } #endregion #region Misc diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/RazorCodeDocumentExtensionsTest.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/RazorCodeDocumentExtensionsTest.cs index 9cf25fbeed..2840823a17 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/RazorCodeDocumentExtensionsTest.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/RazorCodeDocumentExtensionsTest.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; +using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; using Xunit; @@ -228,67 +230,63 @@ namespace Microsoft.AspNetCore.Razor.Language } [Fact] - public void TryComputeNamespaceAndClass_RootNamespaceNotSet_ReturnsNull() + public void TryComputeNamespace_RootNamespaceNotSet_ReturnsNull() { // Arrange var sourceDocument = TestRazorSourceDocument.Create(filePath: "C:\\Hello\\Test.cshtml", relativePath: "Test.cshtml"); var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty()); // Act - codeDocument.TryComputeNamespaceAndClass(out var @namespace, out var @class); + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); // Assert Assert.Null(@namespace); - Assert.Null(@class); } [Fact] - public void TryComputeNamespaceAndClass_RelativePathNull_ReturnsNull() + public void TryComputeNamespace_RelativePathNull_ReturnsNull() { // Arrange var sourceDocument = TestRazorSourceDocument.Create(filePath: "C:\\Hello\\Test.cshtml", relativePath: null); var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty()); // Act - codeDocument.TryComputeNamespaceAndClass(out var @namespace, out var @class); + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); // Assert Assert.Null(@namespace); - Assert.Null(@class); } [Fact] - public void TryComputeNamespaceAndClass_FilePathNull_ReturnsNull() + public void TryComputeNamespace_FilePathNull_ReturnsNull() { // Arrange var sourceDocument = TestRazorSourceDocument.Create(filePath: null, relativePath: "Test.cshtml"); var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty()); // Act - codeDocument.TryComputeNamespaceAndClass(out var @namespace, out var @class); + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); // Assert Assert.Null(@namespace); - Assert.Null(@class); } [Fact] - public void TryComputeNamespaceAndClass_RelativePathLongerThanFilePath_ReturnsNull() + public void TryComputeNamespace_RelativePathLongerThanFilePath_ReturnsNull() { // Arrange var sourceDocument = TestRazorSourceDocument.Create(filePath: "C:\\Hello\\Test.cshtml", relativePath: "Some\\invalid\\relative\\path\\Test.cshtml"); var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty()); // Act - codeDocument.TryComputeNamespaceAndClass(out var @namespace, out var @class); + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); // Assert Assert.Null(@namespace); - Assert.Null(@class); } [Fact] - public void TryComputeNamespaceAndClass_ComputesNamespaceAndClass() + public void TryComputeNamespace_ComputesNamespace() { // Arrange var sourceDocument = TestRazorSourceDocument.Create(filePath: "C:\\Hello\\Components\\Test.cshtml", relativePath: "\\Components\\Test.cshtml"); @@ -299,15 +297,14 @@ namespace Microsoft.AspNetCore.Razor.Language })); // Act - codeDocument.TryComputeNamespaceAndClass(out var @namespace, out var @class); + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); // Assert Assert.Equal("Hello.Components", @namespace); - Assert.Equal("Test", @class); } [Fact] - public void TryComputeNamespaceAndClass_UsesIROptions_ComputesNamespaceAndClass() + public void TryComputeNamespace_UsesIROptions_ComputesNamespace() { // Arrange var sourceDocument = TestRazorSourceDocument.Create(filePath: "C:\\Hello\\Components\\Test.cshtml", relativePath: "\\Components\\Test.cshtml"); @@ -322,15 +319,36 @@ namespace Microsoft.AspNetCore.Razor.Language codeDocument.SetDocumentIntermediateNode(documentNode); // Act - codeDocument.TryComputeNamespaceAndClass(out var @namespace, out var @class); + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); // Assert Assert.Equal("Hello.Components", @namespace); - Assert.Equal("Test", @class); } [Fact] - public void TryComputeNamespaceAndClass_PrefersOptionsFromCodeDocument_ComputesNamespaceAndClass() + public void TryComputeNamespace_NoRootNamespaceFallback_ReturnsNull() + { + // Arrange + var sourceDocument = TestRazorSourceDocument.Create(filePath: "C:\\Hello\\Components\\Test.cshtml", relativePath: "\\Components\\Test.cshtml"); + var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty()); + var documentNode = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.Create(c => + { + c.RootNamespace = "Hello"; + }) + }; + codeDocument.SetDocumentIntermediateNode(documentNode); + + // Act + codeDocument.TryComputeNamespace(fallbackToRootNamespace: false, out var @namespace); + + // Assert + Assert.Null(@namespace); + } + + [Fact] + public void TryComputeNamespace_PrefersOptionsFromCodeDocument_ComputesNamespace() { // Arrange var sourceDocument = TestRazorSourceDocument.Create(filePath: "C:\\Hello\\Components\\Test.cshtml", relativePath: "\\Components\\Test.cshtml"); @@ -349,15 +367,14 @@ namespace Microsoft.AspNetCore.Razor.Language codeDocument.SetDocumentIntermediateNode(documentNode); // Act - codeDocument.TryComputeNamespaceAndClass(out var @namespace, out var @class); + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); // Assert Assert.Equal("World.Components", @namespace); - Assert.Equal("Test", @class); } [Fact] - public void TryComputeNamespaceAndClass_SanitizesNamespaceAndClassName() + public void TryComputeNamespace_SanitizesNamespaceName() { // Arrange var sourceDocument = TestRazorSourceDocument.Create(filePath: "C:\\Hello\\Components with space\\Test$name.cshtml", relativePath: "\\Components with space\\Test$name.cshtml"); @@ -372,11 +389,243 @@ namespace Microsoft.AspNetCore.Razor.Language codeDocument.SetDocumentIntermediateNode(documentNode); // Act - codeDocument.TryComputeNamespaceAndClass(out var @namespace, out var @class); + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); // Assert Assert.Equal("Hel_o.World.Components_with_space", @namespace); - Assert.Equal("Test_name", @class); + } + + [Fact] + public void TryComputeNamespace_RespectsNamespaceDirective() + { + // Arrange + var sourceDocument = TestRazorSourceDocument.Create( + content: "@namespace My.Custom.NS", + filePath: "C:\\Hello\\Components\\Test.cshtml", + relativePath: "\\Components\\Test.cshtml"); + var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty()); + codeDocument.SetFileKind(FileKinds.Component); + codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(sourceDocument, RazorParserOptions.Create(options => + { + options.Directives.Add(NamespaceDirective.Directive); + }))); + + var documentNode = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.Create(c => + { + c.RootNamespace = "Hello.World"; + }) + }; + codeDocument.SetDocumentIntermediateNode(documentNode); + + // Act + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); + + // Assert + Assert.Equal("My.Custom.NS", @namespace); + } + + [Fact] + public void TryComputeNamespace_RespectsImportsNamespaceDirective() + { + // Arrange + var sourceDocument = TestRazorSourceDocument.Create( + filePath: "C:\\Hello\\Components\\Test.cshtml", + relativePath: "\\Components\\Test.cshtml"); + var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty()); + codeDocument.SetFileKind(FileKinds.Component); + codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(sourceDocument, RazorParserOptions.Create(options => + { + options.Directives.Add(NamespaceDirective.Directive); + }))); + + var importSourceDocument = TestRazorSourceDocument.Create( + content: "@namespace My.Custom.NS", + filePath: "C:\\Hello\\_Imports.razor", + relativePath: "\\_Imports.razor"); + codeDocument.SetImportSyntaxTrees(new[] + { + RazorSyntaxTree.Parse(importSourceDocument, RazorParserOptions.Create(options => + { + options.Directives.Add(NamespaceDirective.Directive); + })) + }); + + var documentNode = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.Create(c => + { + c.RootNamespace = "Hello.World"; + }) + }; + codeDocument.SetDocumentIntermediateNode(documentNode); + + // Act + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); + + // Assert + Assert.Equal("My.Custom.NS.Components", @namespace); + } + + [Fact] + public void TryComputeNamespace_RespectsImportsNamespaceDirective_SameFolder() + { + // Arrange + var sourceDocument = TestRazorSourceDocument.Create( + filePath: "C:\\Hello\\Components\\Test.cshtml", + relativePath: "\\Components\\Test.cshtml"); + var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty()); + codeDocument.SetFileKind(FileKinds.Component); + codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(sourceDocument, RazorParserOptions.Create(options => + { + options.Directives.Add(NamespaceDirective.Directive); + }))); + + var importSourceDocument = TestRazorSourceDocument.Create( + content: "@namespace My.Custom.NS", + filePath: "C:\\Hello\\Components\\_Imports.razor", + relativePath: "\\Components\\_Imports.razor"); + codeDocument.SetImportSyntaxTrees(new[] + { + RazorSyntaxTree.Parse(importSourceDocument, RazorParserOptions.Create(options => + { + options.Directives.Add(NamespaceDirective.Directive); + })) + }); + + var documentNode = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.Create(c => + { + c.RootNamespace = "Hello.World"; + }) + }; + codeDocument.SetDocumentIntermediateNode(documentNode); + + // Act + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); + + // Assert + Assert.Equal("My.Custom.NS", @namespace); + } + + [Fact] + public void TryComputeNamespace_OverrideImportsNamespaceDirective() + { + // Arrange + var sourceDocument = TestRazorSourceDocument.Create( + content: "@namespace My.Custom.OverrideNS", + filePath: "C:\\Hello\\Components\\Test.cshtml", + relativePath: "\\Components\\Test.cshtml"); + var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty()); + codeDocument.SetFileKind(FileKinds.Component); + codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(sourceDocument, RazorParserOptions.Create(options => + { + options.Directives.Add(NamespaceDirective.Directive); + }))); + + var importSourceDocument = TestRazorSourceDocument.Create( + content: "@namespace My.Custom.NS", + filePath: "C:\\Hello\\_Imports.razor", + relativePath: "\\_Imports.razor"); + codeDocument.SetImportSyntaxTrees(new[] + { + RazorSyntaxTree.Parse(importSourceDocument, RazorParserOptions.Create(options => + { + options.Directives.Add(NamespaceDirective.Directive); + })) + }); + + var documentNode = new DocumentIntermediateNode() + { + Options = RazorCodeGenerationOptions.Create(c => + { + c.RootNamespace = "Hello.World"; + }) + }; + codeDocument.SetDocumentIntermediateNode(documentNode); + + // Act + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); + + // Assert + Assert.Equal("My.Custom.OverrideNS", @namespace); + } + + [Theory] + [InlineData("/", "foo.cshtml", "Base")] + [InlineData("/", "foo/bar.cshtml", "Base.foo")] + [InlineData("/", "foo/bar/baz.cshtml", "Base.foo.bar")] + [InlineData("/foo/", "bar/baz.cshtml", "Base.bar")] + [InlineData("/Foo/", "bar/baz.cshtml", "Base.bar")] + [InlineData("c:\\", "foo.cshtml", "Base")] + [InlineData("c:\\", "foo\\bar.cshtml", "Base.foo")] + [InlineData("c:\\", "foo\\bar\\baz.cshtml", "Base.foo.bar")] + [InlineData("c:\\foo\\", "bar\\baz.cshtml", "Base.bar")] + [InlineData("c:\\Foo\\", "bar\\baz.cshtml", "Base.bar")] + public void TryComputeNamespace_ComputesNamespaceWithSuffix(string basePath, string relativePath, string expectedNamespace) + { + // Arrange + var sourceDocument = TestRazorSourceDocument.Create( + filePath: Path.Combine(basePath, relativePath), + relativePath: relativePath); + var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty()); + codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(sourceDocument, RazorParserOptions.Create(options => + { + options.Directives.Add(NamespaceDirective.Directive); + }))); + + var importRelativePath = "_ViewImports.cshtml"; + var importSourceDocument = TestRazorSourceDocument.Create( + content: "@namespace Base", + filePath: Path.Combine(basePath, importRelativePath), + relativePath: importRelativePath); + codeDocument.SetImportSyntaxTrees(new[] + { + RazorSyntaxTree.Parse(importSourceDocument, RazorParserOptions.Create(options => + { + options.Directives.Add(NamespaceDirective.Directive); + })) + }); + + // Act + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); + + // Assert + Assert.Equal(expectedNamespace, @namespace); + } + + [Fact] + public void TryComputeNamespace_ForNonRelatedFiles_UsesNamespaceVerbatim() + { + // Arrange + var sourceDocument = TestRazorSourceDocument.Create( + filePath: "c:\\foo\\bar\\bleh.cshtml", + relativePath: "bar\\bleh.cshtml"); + var codeDocument = TestRazorCodeDocument.Create(sourceDocument, Array.Empty()); + codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(sourceDocument, RazorParserOptions.Create(options => + { + options.Directives.Add(NamespaceDirective.Directive); + }))); + + var importSourceDocument = TestRazorSourceDocument.Create( + content: "@namespace Base", + filePath: "c:\\foo\\baz\\bleh.cshtml", + relativePath: "baz\\bleh.cshtml"); + codeDocument.SetImportSyntaxTrees(new[] + { + RazorSyntaxTree.Parse(importSourceDocument, RazorParserOptions.Create(options => + { + options.Directives.Add(NamespaceDirective.Directive); + })) + }); + + // Act + codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace); + + // Assert + Assert.Equal("Base", @namespace); } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/RazorProjectEngineTest.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/RazorProjectEngineTest.cs index 9a5673a70b..298be4f46f 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/RazorProjectEngineTest.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/RazorProjectEngineTest.cs @@ -90,7 +90,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Test feature.Directives, directive => Assert.Same(FunctionsDirective.Directive, directive), directive => Assert.Same(ImplementsDirective.Directive, directive), - directive => Assert.Same(InheritsDirective.Directive, directive)); + directive => Assert.Same(InheritsDirective.Directive, directive), + directive => Assert.Same(NamespaceDirective.Directive, directive)); } private static void AssertDefaultTargetExtensions(RazorProjectEngine engine) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_InImports/TestComponent.codegen.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_InImports/TestComponent.codegen.cs new file mode 100644 index 0000000000..c008d053ab --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_InImports/TestComponent.codegen.cs @@ -0,0 +1,51 @@ +// +#pragma warning disable 1591 +namespace New.Test +{ + #line hidden + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Components; +#nullable restore +#line 1 "x:\dir\subdir\Test\_Imports.razor" +using System.Text; + +#line default +#line hidden +#nullable disable +#nullable restore +#line 2 "x:\dir\subdir\Test\_Imports.razor" +using System.Reflection; + +#line default +#line hidden +#nullable disable + public class TestComponent : Microsoft.AspNetCore.Components.ComponentBase + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + } + #pragma warning restore 219 + #pragma warning disable 0414 + private static System.Object __o = null; + #pragma warning restore 0414 + #pragma warning disable 1998 + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) + { + builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Components.RenderFragment)((builder2) => { + } + )); +#nullable restore +#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" +__o = typeof(Counter); + +#line default +#line hidden +#nullable disable + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_InImports/TestComponent.ir.txt b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_InImports/TestComponent.ir.txt new file mode 100644 index 0000000000..a874feb5e5 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_InImports/TestComponent.ir.txt @@ -0,0 +1,22 @@ +Document - + NamespaceDeclaration - - New.Test + UsingDirective - (3:1,1 [12] ) - System + UsingDirective - (18:2,1 [32] ) - System.Collections.Generic + UsingDirective - (53:3,1 [17] ) - System.Linq + UsingDirective - (73:4,1 [28] ) - System.Threading.Tasks + UsingDirective - (104:5,1 [37] ) - Microsoft.AspNetCore.Components + UsingDirective - (1:0,1 [17] x:\dir\subdir\Test\_Imports.razor) - System.Text + UsingDirective - (21:1,1 [23] x:\dir\subdir\Test\_Imports.razor) - System.Reflection + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase - + DesignTimeDirective - + DirectiveToken - (57:2,11 [8] x:\dir\subdir\Test\_Imports.razor) - New.Test + CSharpCode - + IntermediateToken - - CSharp - #pragma warning disable 0414 + CSharpCode - + IntermediateToken - - CSharp - private static System.Object __o = null; + CSharpCode - + IntermediateToken - - CSharp - #pragma warning restore 0414 + MethodDeclaration - - protected override - void - BuildRenderTree + Component - (0:0,0 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Counter + HtmlContent - (11:0,11 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (11:0,11 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.codegen.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.codegen.cs new file mode 100644 index 0000000000..5b2c7bf109 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.codegen.cs @@ -0,0 +1,61 @@ +// +#pragma warning disable 1591 +namespace New.Test +{ + #line hidden + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Components; +#nullable restore +#line 1 "x:\dir\subdir\Test\_Imports.razor" +using System.Text; + +#line default +#line hidden +#nullable disable +#nullable restore +#line 2 "x:\dir\subdir\Test\_Imports.razor" +using System.Reflection; + +#line default +#line hidden +#nullable disable + public class Counter : Microsoft.AspNetCore.Components.ComponentBase + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +#nullable restore +#line 1 "x:\dir\subdir\Test\Pages/Counter.razor" +global::System.Object __typeHelper = nameof(New.Test); + +#line default +#line hidden +#nullable disable + } + ))(); + } + #pragma warning restore 219 + #pragma warning disable 0414 + private static System.Object __o = null; + #pragma warning restore 0414 + #pragma warning disable 1998 + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) + { + builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Components.RenderFragment)((builder2) => { + } + )); +#nullable restore +#line 2 "x:\dir\subdir\Test\Pages/Counter.razor" +__o = typeof(Counter2); + +#line default +#line hidden +#nullable disable + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.ir.txt b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.ir.txt new file mode 100644 index 0000000000..510e28febf --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.ir.txt @@ -0,0 +1,22 @@ +Document - + NamespaceDeclaration - - New.Test + UsingDirective - (3:1,1 [12] ) - System + UsingDirective - (18:2,1 [32] ) - System.Collections.Generic + UsingDirective - (53:3,1 [17] ) - System.Linq + UsingDirective - (73:4,1 [28] ) - System.Threading.Tasks + UsingDirective - (104:5,1 [37] ) - Microsoft.AspNetCore.Components + UsingDirective - (1:0,1 [17] x:\dir\subdir\Test\_Imports.razor) - System.Text + UsingDirective - (21:1,1 [23] x:\dir\subdir\Test\_Imports.razor) - System.Reflection + ClassDeclaration - - public - Counter - Microsoft.AspNetCore.Components.ComponentBase - + DesignTimeDirective - + DirectiveToken - (11:0,11 [8] Counter.razor) - New.Test + CSharpCode - + IntermediateToken - - CSharp - #pragma warning disable 0414 + CSharpCode - + IntermediateToken - - CSharp - private static System.Object __o = null; + CSharpCode - + IntermediateToken - - CSharp - #pragma warning restore 0414 + MethodDeclaration - - protected override - void - BuildRenderTree + Component - (21:1,0 [12] Counter.razor) - Counter2 + HtmlContent - (33:1,12 [2] Counter.razor) + IntermediateToken - (33:1,12 [2] Counter.razor) - Html - \n diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.mappings.txt b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.mappings.txt new file mode 100644 index 0000000000..f0453f843a --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.mappings.txt @@ -0,0 +1,5 @@ +Source Location: (11:0,11 [8] x:\dir\subdir\Test\Pages/Counter.razor) +|New.Test| +Generated Location: (850:31,44 [8] ) +|New.Test| + diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.codegen.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.codegen.cs new file mode 100644 index 0000000000..4862d5ba18 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.codegen.cs @@ -0,0 +1,66 @@ +// +#pragma warning disable 1591 +namespace AnotherTest +{ + #line hidden + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Components; +#nullable restore +#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" +using Test; + +#line default +#line hidden +#nullable disable + public class TestComponent : Microsoft.AspNetCore.Components.ComponentBase + { + #pragma warning disable 219 + private void __RazorDirectiveTokenHelpers__() { + ((System.Action)(() => { +#nullable restore +#line 2 "x:\dir\subdir\Test\TestComponent.cshtml" +global::System.Object __typeHelper = nameof(AnotherTest); + +#line default +#line hidden +#nullable disable + } + ))(); + } + #pragma warning restore 219 + #pragma warning disable 0414 + private static System.Object __o = null; + #pragma warning restore 0414 + #pragma warning disable 1998 + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) + { + __o = ""; + builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Components.RenderFragment)((builder2) => { + } + )); +#nullable restore +#line 4 "x:\dir\subdir\Test\TestComponent.cshtml" +__o = typeof(HeaderComponent); + +#line default +#line hidden +#nullable disable + __o = ""; + builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Components.RenderFragment)((builder2) => { + } + )); +#nullable restore +#line 6 "x:\dir\subdir\Test\TestComponent.cshtml" +__o = typeof(FooterComponent); + +#line default +#line hidden +#nullable disable + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.ir.txt b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.ir.txt new file mode 100644 index 0000000000..156bb74137 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.ir.txt @@ -0,0 +1,34 @@ +Document - + NamespaceDeclaration - - AnotherTest + UsingDirective - (3:1,1 [12] ) - System + UsingDirective - (18:2,1 [32] ) - System.Collections.Generic + UsingDirective - (53:3,1 [17] ) - System.Linq + UsingDirective - (73:4,1 [28] ) - System.Threading.Tasks + UsingDirective - (104:5,1 [37] ) - Microsoft.AspNetCore.Components + UsingDirective - (1:0,1 [10] x:\dir\subdir\Test\TestComponent.cshtml) - Test + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase - + DesignTimeDirective - + DirectiveToken - (24:1,11 [11] x:\dir\subdir\Test\TestComponent.cshtml) - AnotherTest + CSharpCode - + IntermediateToken - - CSharp - #pragma warning disable 0414 + CSharpCode - + IntermediateToken - - CSharp - private static System.Object __o = null; + CSharpCode - + IntermediateToken - - CSharp - #pragma warning restore 0414 + MethodDeclaration - - protected override - void - BuildRenderTree + HtmlContent - (11:0,11 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (11:0,11 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + HtmlContent - (37:2,0 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (37:2,0 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + Component - (39:3,0 [51] x:\dir\subdir\Test\TestComponent.cshtml) - HeaderComponent + ComponentAttribute - (64:3,25 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Header - AttributeStructure.SingleQuotes + HtmlContent - (64:3,25 [4] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (64:3,25 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - head + HtmlContent - (90:4,18 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (90:4,18 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + Component - (92:5,0 [51] x:\dir\subdir\Test\TestComponent.cshtml) - FooterComponent + ComponentAttribute - (117:5,25 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Footer - AttributeStructure.SingleQuotes + HtmlContent - (117:5,25 [4] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (117:5,25 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - feet + HtmlContent - (143:6,18 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (143:6,18 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.mappings.txt b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.mappings.txt new file mode 100644 index 0000000000..084270fa26 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentDesignTimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.mappings.txt @@ -0,0 +1,10 @@ +Source Location: (1:0,1 [10] x:\dir\subdir\Test\TestComponent.cshtml) +|using Test| +Generated Location: (327:12,0 [10] ) +|using Test| + +Source Location: (24:1,11 [11] x:\dir\subdir\Test\TestComponent.cshtml) +|AnotherTest| +Generated Location: (719:24,44 [11] ) +|AnotherTest| + diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_NamespaceDirective_InImports/TestComponent.codegen.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_NamespaceDirective_InImports/TestComponent.codegen.cs new file mode 100644 index 0000000000..83ab17e704 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_NamespaceDirective_InImports/TestComponent.codegen.cs @@ -0,0 +1,36 @@ +// +#pragma warning disable 1591 +namespace New.Test +{ + #line hidden + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Components; +#nullable restore +#line 1 "x:\dir\subdir\Test\_Imports.razor" +using System.Text; + +#line default +#line hidden +#nullable disable +#nullable restore +#line 2 "x:\dir\subdir\Test\_Imports.razor" +using System.Reflection; + +#line default +#line hidden +#nullable disable + public class TestComponent : Microsoft.AspNetCore.Components.ComponentBase + { + #pragma warning disable 1998 + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) + { + builder.OpenComponent(0); + builder.CloseComponent(); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_NamespaceDirective_InImports/TestComponent.ir.txt b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_NamespaceDirective_InImports/TestComponent.ir.txt new file mode 100644 index 0000000000..d7e2828ab0 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_NamespaceDirective_InImports/TestComponent.ir.txt @@ -0,0 +1,12 @@ +Document - + NamespaceDeclaration - - New.Test + UsingDirective - (3:1,1 [14] ) - System + UsingDirective - (18:2,1 [34] ) - System.Collections.Generic + UsingDirective - (53:3,1 [19] ) - System.Linq + UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks + UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components + UsingDirective - (1:0,1 [19] x:\dir\subdir\Test\_Imports.razor) - System.Text + UsingDirective - (21:1,1 [25] x:\dir\subdir\Test\_Imports.razor) - System.Reflection + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase - + MethodDeclaration - - protected override - void - BuildRenderTree + Component - (0:0,0 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Counter diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.codegen.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.codegen.cs new file mode 100644 index 0000000000..ce2bfb5570 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.codegen.cs @@ -0,0 +1,36 @@ +// +#pragma warning disable 1591 +namespace New.Test +{ + #line hidden + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Components; +#nullable restore +#line 1 "x:\dir\subdir\Test\_Imports.razor" +using System.Text; + +#line default +#line hidden +#nullable disable +#nullable restore +#line 2 "x:\dir\subdir\Test\_Imports.razor" +using System.Reflection; + +#line default +#line hidden +#nullable disable + public class Counter : Microsoft.AspNetCore.Components.ComponentBase + { + #pragma warning disable 1998 + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) + { + builder.OpenComponent(0); + builder.CloseComponent(); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.ir.txt b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.ir.txt new file mode 100644 index 0000000000..8a6c4dfbd2 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_NamespaceDirective_OverrideImports/Counter.ir.txt @@ -0,0 +1,12 @@ +Document - + NamespaceDeclaration - - New.Test + UsingDirective - (3:1,1 [14] ) - System + UsingDirective - (18:2,1 [34] ) - System.Collections.Generic + UsingDirective - (53:3,1 [19] ) - System.Linq + UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks + UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components + UsingDirective - (1:0,1 [19] x:\dir\subdir\Test\_Imports.razor) - System.Text + UsingDirective - (21:1,1 [25] x:\dir\subdir\Test\_Imports.razor) - System.Reflection + ClassDeclaration - - public - Counter - Microsoft.AspNetCore.Components.ComponentBase - + MethodDeclaration - - protected override - void - BuildRenderTree + Component - (21:1,0 [12] Counter.razor) - Counter2 diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.codegen.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.codegen.cs new file mode 100644 index 0000000000..b612a0c313 --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.codegen.cs @@ -0,0 +1,34 @@ +// +#pragma warning disable 1591 +namespace AnotherTest +{ + #line hidden + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Components; +#nullable restore +#line 1 "x:\dir\subdir\Test\TestComponent.cshtml" +using Test; + +#line default +#line hidden +#nullable disable + public class TestComponent : Microsoft.AspNetCore.Components.ComponentBase + { + #pragma warning disable 1998 + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder) + { + builder.OpenComponent(0); + builder.AddAttribute(1, "Header", "head"); + builder.CloseComponent(); + builder.AddMarkupContent(2, "\r\n"); + builder.OpenComponent(3); + builder.AddAttribute(4, "Footer", "feet"); + builder.CloseComponent(); + } + #pragma warning restore 1998 + } +} +#pragma warning restore 1591 diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.ir.txt b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.ir.txt new file mode 100644 index 0000000000..1e37dae58c --- /dev/null +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentRuntimeCodeGenerationTest/Component_WithNamespaceDirective/TestComponent.ir.txt @@ -0,0 +1,20 @@ +Document - + NamespaceDeclaration - - AnotherTest + UsingDirective - (3:1,1 [14] ) - System + UsingDirective - (18:2,1 [34] ) - System.Collections.Generic + UsingDirective - (53:3,1 [19] ) - System.Linq + UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks + UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components + UsingDirective - (1:0,1 [12] x:\dir\subdir\Test\TestComponent.cshtml) - Test + ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase - + MethodDeclaration - - protected override - void - BuildRenderTree + Component - (39:3,0 [51] x:\dir\subdir\Test\TestComponent.cshtml) - HeaderComponent + ComponentAttribute - (64:3,25 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Header - AttributeStructure.SingleQuotes + HtmlContent - (64:3,25 [4] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (64:3,25 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - head + HtmlContent - (90:4,18 [2] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (90:4,18 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n + Component - (92:5,0 [51] x:\dir\subdir\Test\TestComponent.cshtml) - FooterComponent + ComponentAttribute - (117:5,25 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Footer - AttributeStructure.SingleQuotes + HtmlContent - (117:5,25 [4] x:\dir\subdir\Test\TestComponent.cshtml) + IntermediateToken - (117:5,25 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - feet