Added support for @namespace directive in Blazor (dotnet/aspnetcore-tooling#504)
* Added support for @namespace directive in Blazor
\n\nCommit migrated from 7f6c1422dd
This commit is contained in:
parent
ed04f3ebb8
commit
5f462346c6
|
|
@ -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<TModel>";
|
||||
@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";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
/// <summary>
|
||||
/// Specify the base namespace for the page.
|
||||
/// </summary>
|
||||
internal static string NamespaceDirective_Description
|
||||
{
|
||||
get => GetString("NamespaceDirective_Description");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the base namespace for the page.
|
||||
/// </summary>
|
||||
internal static string FormatNamespaceDirective_Description()
|
||||
=> GetString("NamespaceDirective_Description");
|
||||
|
||||
/// <summary>
|
||||
/// The namespace for the page.
|
||||
/// </summary>
|
||||
internal static string NamespaceDirective_NamespaceToken_Description
|
||||
{
|
||||
get => GetString("NamespaceDirective_NamespaceToken_Description");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The namespace for the page.
|
||||
/// </summary>
|
||||
internal static string FormatNamespaceDirective_NamespaceToken_Description()
|
||||
=> GetString("NamespaceDirective_NamespaceToken_Description");
|
||||
|
||||
/// <summary>
|
||||
/// Namespace
|
||||
/// </summary>
|
||||
internal static string NamespaceDirective_NamespaceToken_Name
|
||||
{
|
||||
get => GetString("NamespaceDirective_NamespaceToken_Name");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Namespace
|
||||
/// </summary>
|
||||
internal static string FormatNamespaceDirective_NamespaceToken_Name()
|
||||
=> GetString("NamespaceDirective_NamespaceToken_Name");
|
||||
|
||||
/// <summary>
|
||||
/// The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor cshtml file.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -159,15 +159,6 @@
|
|||
<data name="MvcRazorParser_InvalidPropertyType" xml:space="preserve">
|
||||
<value>Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'.</value>
|
||||
</data>
|
||||
<data name="NamespaceDirective_Description" xml:space="preserve">
|
||||
<value>Specify the base namespace for the page.</value>
|
||||
</data>
|
||||
<data name="NamespaceDirective_NamespaceToken_Description" xml:space="preserve">
|
||||
<value>The namespace for the page.</value>
|
||||
</data>
|
||||
<data name="NamespaceDirective_NamespaceToken_Name" xml:space="preserve">
|
||||
<value>Namespace</value>
|
||||
</data>
|
||||
<data name="PageDirectiveCannotBeImported" xml:space="preserve">
|
||||
<value>The '@{0}' directive specified in {1} file will not be imported. The directive must appear at the top of each Razor cshtml file.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -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<RazorEngine>();
|
||||
|
||||
// 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<RazorEngine>();
|
||||
|
||||
// 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<RazorEngine>();
|
||||
|
||||
// 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<RazorEngine>();
|
||||
|
||||
// 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<RazorEngine>();
|
||||
|
||||
// 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<RazorEngine>();
|
||||
|
||||
// Act
|
||||
pass.Execute(codeDocument, document);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("default", @namespace.Content);
|
||||
Assert.Equal("default", @class.ClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1898,6 +1898,48 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
internal static string FormatRewriter_InsufficientStack()
|
||||
=> GetString("Rewriter_InsufficientStack");
|
||||
|
||||
/// <summary>
|
||||
/// Specify the base namespace for the document.
|
||||
/// </summary>
|
||||
internal static string NamespaceDirective_Description
|
||||
{
|
||||
get => GetString("NamespaceDirective_Description");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the base namespace for the document.
|
||||
/// </summary>
|
||||
internal static string FormatNamespaceDirective_Description()
|
||||
=> GetString("NamespaceDirective_Description");
|
||||
|
||||
/// <summary>
|
||||
/// The namespace for the document.
|
||||
/// </summary>
|
||||
internal static string NamespaceDirective_NamespaceToken_Description
|
||||
{
|
||||
get => GetString("NamespaceDirective_NamespaceToken_Description");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The namespace for the document.
|
||||
/// </summary>
|
||||
internal static string FormatNamespaceDirective_NamespaceToken_Description()
|
||||
=> GetString("NamespaceDirective_NamespaceToken_Description");
|
||||
|
||||
/// <summary>
|
||||
/// Namespace
|
||||
/// </summary>
|
||||
internal static string NamespaceDirective_NamespaceToken_Name
|
||||
{
|
||||
get => GetString("NamespaceDirective_NamespaceToken_Name");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Namespace
|
||||
/// </summary>
|
||||
internal static string FormatNamespaceDirective_NamespaceToken_Name()
|
||||
=> GetString("NamespaceDirective_NamespaceToken_Name");
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -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<TagHelperDescriptor> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
FunctionsDirective.Register(builder);
|
||||
ImplementsDirective.Register(builder);
|
||||
InheritsDirective.Register(builder);
|
||||
NamespaceDirective.Register(builder);
|
||||
|
||||
AddComponentFeatures(builder);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -542,4 +542,13 @@ Instead, wrap the contents of the block in "{{}}":
|
|||
<data name="Rewriter_InsufficientStack" xml:space="preserve">
|
||||
<value>Not enough stack space to continue parsing this document. Razor doesn't support deeply nested elements.</value>
|
||||
</data>
|
||||
<data name="NamespaceDirective_Description" xml:space="preserve">
|
||||
<value>Specify the base namespace for the document.</value>
|
||||
</data>
|
||||
<data name="NamespaceDirective_NamespaceToken_Description" xml:space="preserve">
|
||||
<value>The namespace for the document.</value>
|
||||
</data>
|
||||
<data name="NamespaceDirective_NamespaceToken_Name" xml:space="preserve">
|
||||
<value>Namespace</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -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
|
||||
|
||||
<HeaderComponent Header='head'>
|
||||
</HeaderComponent>
|
||||
<FooterComponent Footer='feet'>
|
||||
</FooterComponent>
|
||||
");
|
||||
|
||||
// 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(@"
|
||||
<Counter />
|
||||
");
|
||||
|
||||
// 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
|
||||
<Counter2 />
|
||||
");
|
||||
|
||||
// Assert
|
||||
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||
CompileToAssembly(generated);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Misc
|
||||
|
|
|
|||
|
|
@ -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<RazorSourceDocument>());
|
||||
|
||||
// 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<RazorSourceDocument>());
|
||||
|
||||
// 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<RazorSourceDocument>());
|
||||
|
||||
// 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<RazorSourceDocument>());
|
||||
|
||||
// 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<RazorSourceDocument>());
|
||||
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<RazorSourceDocument>());
|
||||
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<RazorSourceDocument>());
|
||||
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<RazorSourceDocument>());
|
||||
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<RazorSourceDocument>());
|
||||
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<RazorSourceDocument>());
|
||||
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<RazorSourceDocument>());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
// <auto-generated/>
|
||||
#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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// <auto-generated/>
|
||||
#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
|
||||
|
|
@ -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
|
||||
|
|
@ -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|
|
||||
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// <auto-generated/>
|
||||
#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
|
||||
|
|
@ -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
|
||||
|
|
@ -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|
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// <auto-generated/>
|
||||
#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<New.Test.Counter>(0);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// <auto-generated/>
|
||||
#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<New.Test.Counter2>(0);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// <auto-generated/>
|
||||
#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<Test.HeaderComponent>(0);
|
||||
builder.AddAttribute(1, "Header", "head");
|
||||
builder.CloseComponent();
|
||||
builder.AddMarkupContent(2, "\r\n");
|
||||
builder.OpenComponent<AnotherTest.FooterComponent>(3);
|
||||
builder.AddAttribute(4, "Footer", "feet");
|
||||
builder.CloseComponent();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue