Support importing components with @using directives (dotnet/aspnetcore-tooling#276)

* Support importing components with @using directives

* Suppress taghelper directive completion in component documents

* feedback

* More feedback

* Update tests

* Update CodeAnalysis.Razor tests

* Flow filekind

* Changes

* More code gen tests

* More tests

* fix

* Added more tests

* Made stuff internal

* Filter out temporary tag helper descriptors

* update

* Do the needful
\n\nCommit migrated from 343f37748e
This commit is contained in:
Ajay Bhargav Baaskaran 2019-03-22 10:29:38 -07:00 committed by GitHub
parent 91a383ad3b
commit 92d931c229
24 changed files with 610 additions and 156 deletions

View File

@ -8,7 +8,6 @@ Document -
UsingDirective - (102:4,1 [37] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public - AspNetCore_d3c3d6059615673cb46fc4974164d61eabadb890 - Microsoft.AspNetCore.Components.ComponentBase - IDisposable
DesignTimeDirective -
DirectiveToken - (14:0,14 [36] ) - "*, Microsoft.AspNetCore.Components"
DirectiveToken - (12:0,12 [11] BasicComponent.cshtml) - IDisposable
CSharpCode -
IntermediateToken - - CSharp - #pragma warning disable 0414

View File

@ -457,6 +457,30 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
context.CodeWriter.Write(");");
context.CodeWriter.WriteLine();
}
// We want to generate something that references the Component type to avoid
// the "usings directive is unnecessary" message.
// Looks like:
// __o = typeof(SomeNamespace.SomeComponent);
using (context.CodeWriter.BuildLinePragma(node.Source.Value))
{
context.CodeWriter.Write(DesignTimeVariable);
context.CodeWriter.Write(" = ");
context.CodeWriter.Write("typeof(");
context.CodeWriter.Write(node.TagName);
if (node.Component.IsGenericTypedComponent())
{
context.CodeWriter.Write("<");
var typeArgumentCount = node.Component.GetTypeParameters().Count();
for (var i = 1; i < typeArgumentCount; i++)
{
context.CodeWriter.Write(",");
}
context.CodeWriter.Write(">");
}
context.CodeWriter.Write(");");
context.CodeWriter.WriteLine();
}
}
public override void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeIntermediateNode node)

View File

@ -12,9 +12,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal static class ComponentDiagnosticFactory
{
public static readonly RazorDiagnosticDescriptor CodeBlockInAttribute =
new RazorDiagnosticDescriptor(
"BL9979",
private const string DiagnosticPrefix = "RZ";
public static readonly RazorDiagnosticDescriptor UnsupportedTagHelperDirective = new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}9978",
() =>
"The directives @addTagHelper, @removeTagHelper and @tagHelperPrefix are not valid in a component document." +
"Use '@using <namespace>' directive instead.",
RazorDiagnosticSeverity.Error);
public static RazorDiagnostic Create_UnsupportedTagHelperDirective(SourceSpan? source)
{
return RazorDiagnostic.Create(UnsupportedTagHelperDirective, source ?? SourceSpan.Undefined);
}
public static readonly RazorDiagnosticDescriptor CodeBlockInAttribute = new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}9979",
() =>
"Code blocks delimited by '@{...}' like '@{{ {0} }}' for attributes are no longer supported " +
"These features have been changed to use attribute syntax. " +
@ -31,7 +44,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
public static readonly RazorDiagnosticDescriptor UnclosedTag = new RazorDiagnosticDescriptor(
"BL9980",
$"{DiagnosticPrefix}9980",
() => "Unclosed tag '{0}' with no matching end tag.",
RazorDiagnosticSeverity.Error);
@ -41,7 +54,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
public static readonly RazorDiagnosticDescriptor UnexpectedClosingTag = new RazorDiagnosticDescriptor(
"BL9981",
$"{DiagnosticPrefix}9981",
() => "Unexpected closing tag '{0}' with no matching start tag.",
RazorDiagnosticSeverity.Error);
@ -51,7 +64,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
public static readonly RazorDiagnosticDescriptor UnexpectedClosingTagForVoidElement = new RazorDiagnosticDescriptor(
"BL9983",
$"{DiagnosticPrefix}9983",
() => "Unexpected closing tag '{0}'. The element '{0}' is a void element, and should be used without a closing tag.",
RazorDiagnosticSeverity.Error);
@ -61,7 +74,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
public static readonly RazorDiagnosticDescriptor InvalidHtmlContent = new RazorDiagnosticDescriptor(
"BL9984",
$"{DiagnosticPrefix}9984",
() => "Found invalid HTML content. Text '{0}'",
RazorDiagnosticSeverity.Error);
@ -71,7 +84,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
public static readonly RazorDiagnosticDescriptor MultipleComponents = new RazorDiagnosticDescriptor(
"BL9985",
$"{DiagnosticPrefix}9985",
() => "Multiple components use the tag '{0}'. Components: {1}",
RazorDiagnosticSeverity.Error);
@ -81,7 +94,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
public static readonly RazorDiagnosticDescriptor UnsupportedComplexContent = new RazorDiagnosticDescriptor(
"BL9986",
$"{DiagnosticPrefix}9986",
() => "Component attributes do not support complex content (mixed C# and markup). Attribute: '{0}', text '{1}'",
RazorDiagnosticSeverity.Error);
@ -93,7 +106,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor PageDirective_CannotBeImported =
new RazorDiagnosticDescriptor(
"BL9987",
$"{DiagnosticPrefix}9987",
() => ComponentResources.PageDirectiveCannotBeImported,
RazorDiagnosticSeverity.Error);
@ -107,7 +120,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor PageDirective_MustSpecifyRoute =
new RazorDiagnosticDescriptor(
"BL9988",
$"{DiagnosticPrefix}9988",
() => "The @page directive must specify a route template. The route template must be enclosed in quotes and begin with the '/' character.",
RazorDiagnosticSeverity.Error);
@ -119,7 +132,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor BindAttribute_Duplicates =
new RazorDiagnosticDescriptor(
"BL9989",
$"{DiagnosticPrefix}9989",
() => "The attribute '{0}' was matched by multiple bind attributes. Duplicates:{1}",
RazorDiagnosticSeverity.Error);
@ -135,7 +148,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor EventHandler_Duplicates =
new RazorDiagnosticDescriptor(
"BL9990",
$"{DiagnosticPrefix}9990",
() => "The attribute '{0}' was matched by multiple event handlers attributes. Duplicates:{1}",
RazorDiagnosticSeverity.Error);
@ -151,7 +164,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor BindAttribute_InvalidSyntax =
new RazorDiagnosticDescriptor(
"BL9991",
$"{DiagnosticPrefix}9991",
() => "The attribute names could not be inferred from bind attribute '{0}'. Bind attributes should be of the form" +
"'bind', 'bind-value' or 'bind-value-change'",
RazorDiagnosticSeverity.Error);
@ -166,7 +179,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
}
public static readonly RazorDiagnosticDescriptor DisallowedScriptTag = new RazorDiagnosticDescriptor(
"BL9992",
$"{DiagnosticPrefix}9992",
() => "Script tags should not be placed inside components because they cannot be updated dynamically. To fix this, move the script tag to the 'index.html' file or another static location. For more information see https://go.microsoft.com/fwlink/?linkid=872131",
RazorDiagnosticSeverity.Error);
@ -180,7 +193,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor TemplateInvalidLocation =
new RazorDiagnosticDescriptor(
"BL9994",
$"{DiagnosticPrefix}9994",
() => "Razor templates cannot be used in attributes.",
RazorDiagnosticSeverity.Error);
@ -191,7 +204,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor ChildContentSetByAttributeAndBody =
new RazorDiagnosticDescriptor(
"BL9995",
$"{DiagnosticPrefix}9995",
() => "The child content property '{0}' is set by both the attribute and the element contents.",
RazorDiagnosticSeverity.Error);
@ -202,7 +215,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor ChildContentMixedWithExplicitChildContent =
new RazorDiagnosticDescriptor(
"BL9996",
$"{DiagnosticPrefix}9996",
() => "Unrecognized child content inside component '{0}'. The component '{0}' accepts child content through the " +
"following top-level items: {1}.",
RazorDiagnosticSeverity.Error);
@ -215,7 +228,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor ChildContentHasInvalidAttribute =
new RazorDiagnosticDescriptor(
"BL9997",
$"{DiagnosticPrefix}9997",
() => "Unrecognized attribute '{0}' on child content element '{1}'.",
RazorDiagnosticSeverity.Error);
@ -226,7 +239,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor ChildContentHasInvalidParameter =
new RazorDiagnosticDescriptor(
"BL9998",
$"{DiagnosticPrefix}9998",
() => "Invalid parameter name. The parameter name attribute '{0}' on child content element '{1}' can only include literal text.",
RazorDiagnosticSeverity.Error);
@ -237,7 +250,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor ChildContentRepeatedParameterName =
new RazorDiagnosticDescriptor(
"BL9999",
$"{DiagnosticPrefix}9999",
() => "The child content element '{0}' of component '{1}' uses the same parameter name ('{2}') as enclosing child content " +
"element '{3}' of component '{4}'. Specify the parameter name like: '<{0} Context=\"another_name\"> to resolve the ambiguity",
RazorDiagnosticSeverity.Error);
@ -265,7 +278,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor GenericComponentMissingTypeArgument =
new RazorDiagnosticDescriptor(
"BL10000",
$"{DiagnosticPrefix}10000",
() => "The component '{0}' is missing required type arguments. Specify the missing types using the attributes: {1}.",
RazorDiagnosticSeverity.Error);
@ -282,7 +295,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor GenericComponentTypeInferenceUnderspecified =
new RazorDiagnosticDescriptor(
"BL10001",
$"{DiagnosticPrefix}10001",
() => "The type of component '{0}' cannot be inferred based on the values provided. Consider specifying the type arguments " +
"directly using the following attributes: {1}.",
RazorDiagnosticSeverity.Error);
@ -300,7 +313,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly RazorDiagnosticDescriptor ChildContentHasInvalidParameterOnComponent =
new RazorDiagnosticDescriptor(
"BL10002",
$"{DiagnosticPrefix}10002",
() => "Invalid parameter name. The parameter name attribute '{0}' on component '{1}' can only include literal text.",
RazorDiagnosticSeverity.Error);

View File

@ -14,8 +14,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{
public static readonly string ComponentDocumentKind = "component.1.0";
private static readonly object BuildRenderTreeBaseCallAnnotation = new object();
private static readonly char[] PathSeparators = new char[] { '/', '\\' };
private static readonly char[] NamespaceSeparators = new char[] { '.' };
/// <summary>
/// The fallback value of the root namespace. Only used if the fallback root namespace
@ -57,13 +55,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
ClassDeclarationIntermediateNode @class,
MethodDeclarationIntermediateNode method)
{
var options = codeDocument.GetDocumentIntermediateNode().Options;
if (!TryComputeNamespaceAndClass(
options,
codeDocument.Source.FilePath,
codeDocument.Source.RelativePath,
out var computedNamespace,
out var computedClass))
if (!codeDocument.TryComputeNamespaceAndClass(out var computedNamespace, out var computedClass))
{
// If we can't compute a nice namespace (no relative path) then just generate something
// mangled.
@ -74,7 +66,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
if (MangleClassNames)
{
computedClass = "__" + computedClass;
computedClass = ComponentMetadata.MangleClassName(computedClass);
}
@namespace.Content = computedNamespace;
@ -121,59 +113,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
method.Children.Insert(0, callBase);
}
// 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.
//
// However all kinds of thing are possible in tools. We shouldn't barf here if the document isn't
// set up correctly.
private bool TryComputeNamespaceAndClass(
RazorCodeGenerationOptions options,
string filePath,
string relativePath,
out string @namespace,
out string @class)
{
if (filePath == null || relativePath == null || filePath.Length <= relativePath.Length)
{
@namespace = null;
@class = null;
return false;
}
var rootNamespace = options.RootNamespace;
if (string.IsNullOrEmpty(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);
builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[0]));
for (var i = 1; i < segments.Length; i++)
{
builder.Append('.');
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++)
{
builder.Append('.');
builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i]));
}
@namespace = builder.ToString();
@class = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(relativePath));
return true;
}
internal static bool IsBuildRenderTreeBaseCall(CSharpCodeIntermediateNode node)
=> node.Annotations[BuildRenderTreeBaseCallAnnotation] != null;
}

View File

@ -40,28 +40,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
var imports = new List<RazorProjectItem>()
{
new VirtualProjectItem(DefaultUsingImportContent),
new VirtualProjectItem(@"@addTagHelper ""*, Microsoft.AspNetCore.Components"""),
};
// Try and infer a namespace from the project directory. We don't yet have the ability to pass
// the namespace through from the project.
if (projectItem.PhysicalPath != null && projectItem.FilePath != null)
{
// Avoiding the path-specific APIs here, we want to handle all styles of paths
// on all platforms
var trimLength = projectItem.FilePath.Length + (projectItem.FilePath.StartsWith("/") ? 0 : 1);
if (projectItem.PhysicalPath.Length > trimLength)
{
var baseDirectory = projectItem.PhysicalPath.Substring(0, projectItem.PhysicalPath.Length - trimLength);
var lastSlash = baseDirectory.LastIndexOfAny(PathSeparators);
var baseNamespace = lastSlash == -1 ? baseDirectory : baseDirectory.Substring(lastSlash + 1);
if (!string.IsNullOrEmpty(baseNamespace))
{
imports.Add(new VirtualProjectItem($@"@addTagHelper ""*, {baseNamespace}"""));
}
}
}
// We add hierarchical imports second so any default directive imports can be overridden.
imports.AddRange(GetHierarchicalImports(ProjectEngine.FileSystem, projectItem));

View File

@ -1,11 +1,15 @@
// 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.Components
{
// Metadata used for Components interactions with the tag helper system
internal static class ComponentMetadata
{
private static readonly string MangledClassNamePrefix = "__generated__";
// There's a bug in the 15.7 preview 1 Razor that prevents 'Kind' from being serialized
// this affects both tooling and build. For now our workaround is to ignore 'Kind' and
// use our own metadata entry to denote non-Component tag helpers.
@ -13,6 +17,35 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public static readonly string ImportsFileName = "_Imports.razor";
public static bool IsComponentTagHelperKind(string tagHelperKind)
{
return tagHelperKind == Component.TagHelperKind ||
tagHelperKind == ChildContent.TagHelperKind ||
tagHelperKind == EventHandler.TagHelperKind ||
tagHelperKind == Bind.TagHelperKind ||
tagHelperKind == Ref.TagHelperKind;
}
public static string MangleClassName(string className)
{
if (string.IsNullOrEmpty(className))
{
return string.Empty;
}
return MangledClassNamePrefix + className;
}
public static bool IsMangledClass(string className)
{
if (string.IsNullOrEmpty(className))
{
return false;
}
return className.StartsWith(MangledClassNamePrefix, StringComparison.Ordinal);
}
public static class Bind
{
public static readonly string RuntimeName = "Components.None";
@ -68,6 +101,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
public readonly static string GenericTypedKey = "Components.GenericTyped";
public readonly static string TypeParameterKey = "Components.TypeParameter";
public readonly static string NameMatchKey = "Components.NameMatch";
public readonly static string FullyQualifiedNameMatch = "Components.FullyQualifiedNameMatch";
}
public static class EventHandler

View File

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
@ -152,6 +151,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
string.Equals(ComponentMetadata.Ref.TagHelperKind, kind);
}
/// <summary>
/// Gets whether the component matches a tag with a fully qualified name.
/// </summary>
/// <param name="tagHelper">The <see cref="TagHelperDescriptor"/>.</param>
public static bool IsComponentFullyQualifiedNameMatch(this TagHelperDescriptor tagHelper)
{
if (tagHelper == null)
{
throw new ArgumentNullException(nameof(tagHelper));
}
return
tagHelper.Metadata.TryGetValue(ComponentMetadata.Component.NameMatchKey, out var matchType) &&
string.Equals(ComponentMetadata.Component.FullyQualifiedNameMatch, matchType);
}
public static string GetEventArgsType(this TagHelperDescriptor tagHelper)
{
if (tagHelper == null)

View File

@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
internal class DefaultRazorParserOptions : RazorParserOptions
{
public DefaultRazorParserOptions(DirectiveDescriptor[] directives, bool designTime, bool parseLeadingDirectives, RazorLanguageVersion version)
public DefaultRazorParserOptions(DirectiveDescriptor[] directives, bool designTime, bool parseLeadingDirectives, RazorLanguageVersion version, string fileKind)
{
if (directives == null)
{
@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Razor.Language
ParseLeadingDirectives = parseLeadingDirectives;
Version = version;
FeatureFlags = RazorParserFeatureFlags.Create(Version);
FileKind = fileKind;
}
public override bool DesignTime { get; }
@ -30,6 +31,8 @@ namespace Microsoft.AspNetCore.Razor.Language
public override RazorLanguageVersion Version { get; }
internal override string FileKind { get; }
internal override RazorParserFeatureFlags FeatureFlags { get; }
}
}

View File

@ -23,10 +23,11 @@ namespace Microsoft.AspNetCore.Razor.Language
FileKind = fileKind;
}
public DefaultRazorParserOptionsBuilder(bool designTime, RazorLanguageVersion version)
public DefaultRazorParserOptionsBuilder(bool designTime, RazorLanguageVersion version, string fileKind)
{
_designTime = designTime;
LanguageVersion = version;
FileKind = fileKind;
}
public override RazorConfiguration Configuration { get; }
@ -43,7 +44,7 @@ namespace Microsoft.AspNetCore.Razor.Language
public override RazorParserOptions Build()
{
return new DefaultRazorParserOptions(Directives.ToArray(), DesignTime, ParseLeadingDirectives, LanguageVersion);
return new DefaultRazorParserOptions(Directives.ToArray(), DesignTime, ParseLeadingDirectives, LanguageVersion, FileKind);
}
public override void SetDesignTime(bool designTime)

View File

@ -11,12 +11,14 @@ namespace Microsoft.AspNetCore.Razor.Language
{
private readonly bool _designTime;
private readonly RazorLanguageVersion _version;
private readonly string _fileKind;
private IConfigureRazorParserOptionsFeature[] _configureOptions;
public DefaultRazorParserOptionsFeature(bool designTime, RazorLanguageVersion version)
public DefaultRazorParserOptionsFeature(bool designTime, RazorLanguageVersion version, string fileKind)
{
_designTime = designTime;
_version = version;
_fileKind = fileKind;
}
protected override void OnInitialized()
@ -26,7 +28,7 @@ namespace Microsoft.AspNetCore.Razor.Language
public RazorParserOptions GetOptions()
{
var builder = new DefaultRazorParserOptionsBuilder(_designTime, _version);
var builder = new DefaultRazorParserOptionsBuilder(_designTime, _version, _fileKind);
for (var i = 0; i < _configureOptions.Length; i++)
{
_configureOptions[i].Configure(builder);

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Components;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.AspNetCore.Razor.Language.Syntax;
@ -11,6 +12,8 @@ namespace Microsoft.AspNetCore.Razor.Language
{
internal class DefaultRazorTagHelperBinderPhase : RazorEnginePhaseBase, IRazorTagHelperBinderPhase
{
private static readonly char[] NamespaceSeparators = new char[] { '.' };
protected override void ExecuteCore(RazorCodeDocument codeDocument)
{
var syntaxTree = codeDocument.GetSyntaxTree();
@ -33,20 +36,31 @@ namespace Microsoft.AspNetCore.Razor.Language
//
// The imports come logically before the main razor file and are in the order they
// should be processed.
var visitor = new DirectiveVisitor(descriptors);
DirectiveVisitor visitor = null;
if (FileKinds.IsComponent(codeDocument.GetFileKind()))
{
codeDocument.TryComputeNamespaceAndClass(out var currentNamespace, out var _);
visitor = new ComponentDirectiveVisitor(codeDocument.Source.FilePath, descriptors, currentNamespace);
}
else
{
visitor = new TagHelperDirectiveVisitor(descriptors);
}
var imports = codeDocument.GetImportSyntaxTrees();
if (imports != null)
{
for (var i = 0; i < imports.Count; i++)
{
var import = imports[i];
visitor.Visit(import.Root);
visitor.Visit(import);
}
}
visitor.Visit(syntaxTree.Root);
visitor.Visit(syntaxTree);
// This will always be null for a component document.
var tagHelperPrefix = visitor.TagHelperPrefix;
descriptors = visitor.Matches.ToArray();
var context = TagHelperDocumentContext.Create(tagHelperPrefix, descriptors);
@ -86,20 +100,38 @@ namespace Microsoft.AspNetCore.Razor.Language
return string.Equals(descriptor.Name, typePattern, StringComparison.Ordinal);
}
internal class DirectiveVisitor : SyntaxRewriter
internal abstract class DirectiveVisitor : SyntaxWalker
{
public abstract HashSet<TagHelperDescriptor> Matches { get; }
public abstract string TagHelperPrefix { get; }
public abstract void Visit(RazorSyntaxTree tree);
}
internal class TagHelperDirectiveVisitor : DirectiveVisitor
{
private IReadOnlyList<TagHelperDescriptor> _tagHelpers;
private string _tagHelperPrefix;
public DirectiveVisitor(IReadOnlyList<TagHelperDescriptor> tagHelpers)
public TagHelperDirectiveVisitor(IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
_tagHelpers = tagHelpers;
// We don't want to consider components in a view document.
_tagHelpers = tagHelpers.Where(t => !ComponentMetadata.IsComponentTagHelperKind(t.Kind)).ToList();
}
public string TagHelperPrefix { get; private set; }
public override string TagHelperPrefix => _tagHelperPrefix;
public HashSet<TagHelperDescriptor> Matches { get; } = new HashSet<TagHelperDescriptor>();
public override HashSet<TagHelperDescriptor> Matches { get; } = new HashSet<TagHelperDescriptor>();
public override SyntaxNode VisitRazorDirective(RazorDirectiveSyntax node)
public override void Visit(RazorSyntaxTree tree)
{
Visit(tree.Root);
}
public override void VisitRazorDirective(RazorDirectiveSyntax node)
{
var descendantLiterals = node.DescendantNodes();
foreach (var child in descendantLiterals)
@ -167,12 +199,10 @@ namespace Microsoft.AspNetCore.Razor.Language
if (!string.IsNullOrEmpty(tagHelperPrefix.DirectiveText))
{
// We only expect to see a single one of these per file, but that's enforced at another level.
TagHelperPrefix = tagHelperPrefix.DirectiveText;
_tagHelperPrefix = tagHelperPrefix.DirectiveText;
}
}
}
return base.VisitRazorDirective(node);
}
private bool AssemblyContainsTagHelpers(string assemblyName, IReadOnlyList<TagHelperDescriptor> tagHelpers)
@ -188,5 +218,231 @@ namespace Microsoft.AspNetCore.Razor.Language
return false;
}
}
internal class ComponentDirectiveVisitor : DirectiveVisitor
{
private IReadOnlyList<TagHelperDescriptor> _tagHelpers;
private string _filePath;
private RazorSourceDocument _source;
public ComponentDirectiveVisitor(string filePath, IReadOnlyList<TagHelperDescriptor> tagHelpers, string currentNamespace)
{
_filePath = filePath;
// We don't want to consider non-component tag helpers in a component document.
_tagHelpers = tagHelpers.Where(t => ComponentMetadata.IsComponentTagHelperKind(t.Kind) && !IsTagHelperFromMangledClass(t)).ToList();
for (var i = 0; i < _tagHelpers.Count; i++)
{
var tagHelper = _tagHelpers[i];
if (tagHelper.IsComponentFullyQualifiedNameMatch())
{
// If the component descriptor matches for a fully qualified name, using directives shouldn't matter.
Matches.Add(tagHelper);
continue;
}
var typeName = tagHelper.GetTypeName();
if (tagHelper.IsChildContentTagHelper())
{
// If this is a child content tag helper, we want to add it if it's original type is in scope.
// E.g, if the type name is `Test.MyComponent.ChildContent`, we want to add it if `Test.MyComponent` is in scope.
TrySplitNamespaceAndType(typeName, out typeName, out var _);
}
if (currentNamespace != null && IsTypeInScope(typeName, currentNamespace))
{
// Also, if the type is already in scope of the document's namespace, using isn't necessary.
Matches.Add(tagHelper);
}
}
}
public override HashSet<TagHelperDescriptor> Matches { get; } = new HashSet<TagHelperDescriptor>();
// There is no support for tag helper prefix in component documents.
public override string TagHelperPrefix => null;
public override void Visit(RazorSyntaxTree tree)
{
_source = tree.Source;
Visit(tree.Root);
}
public override void VisitRazorDirective(RazorDirectiveSyntax node)
{
var descendantLiterals = node.DescendantNodes();
foreach (var child in descendantLiterals)
{
if (!(child is CSharpStatementLiteralSyntax literal))
{
continue;
}
var context = literal.GetSpanContext();
if (context == null)
{
// We can't find a chunk generator.
continue;
}
else if (context.ChunkGenerator is AddTagHelperChunkGenerator addTagHelper)
{
// Make sure this node exists in the file we're parsing and not in its imports.
if (_filePath.Equals(_source.FilePath, StringComparison.Ordinal))
{
addTagHelper.Diagnostics.Add(
ComponentDiagnosticFactory.Create_UnsupportedTagHelperDirective(node.GetSourceSpan(_source)));
}
}
else if (context.ChunkGenerator is RemoveTagHelperChunkGenerator removeTagHelper)
{
// Make sure this node exists in the file we're parsing and not in its imports.
if (_filePath.Equals(_source.FilePath, StringComparison.Ordinal))
{
removeTagHelper.Diagnostics.Add(
ComponentDiagnosticFactory.Create_UnsupportedTagHelperDirective(node.GetSourceSpan(_source)));
}
}
else if (context.ChunkGenerator is TagHelperPrefixDirectiveChunkGenerator tagHelperPrefix)
{
// Make sure this node exists in the file we're parsing and not in its imports.
if (_filePath.Equals(_source.FilePath, StringComparison.Ordinal))
{
tagHelperPrefix.Diagnostics.Add(
ComponentDiagnosticFactory.Create_UnsupportedTagHelperDirective(node.GetSourceSpan(_source)));
}
}
else if (context.ChunkGenerator is AddImportChunkGenerator usingStatement && !usingStatement.IsStatic)
{
// Get the namespace from the using statement.
var @namespace = usingStatement.ParsedNamespace;
if (@namespace.Contains('='))
{
// We don't support usings with alias.
continue;
}
for (var i = 0; i < _tagHelpers.Count; i++)
{
var tagHelper = _tagHelpers[i];
if (tagHelper.IsComponentFullyQualifiedNameMatch())
{
// We've already added these to our list of matches.
continue;
}
var typeName = tagHelper.GetTypeName();
if (typeName != null && IsTypeInNamespace(typeName, @namespace))
{
// If the type is at the top-level or if the type's namespace matches the using's namespace, add it.
Matches.Add(tagHelper);
}
}
}
}
}
internal static bool IsTypeInNamespace(string typeName, string @namespace)
{
if (!TrySplitNamespaceAndType(typeName, out var typeNamespace, out var _) || typeNamespace == string.Empty)
{
// Either the typeName is not the full type name or this type is at the top level.
return true;
}
return typeNamespace.Equals(@namespace, StringComparison.Ordinal);
}
// Check if the given type is already in scope given the namespace of the current document.
// E.g,
// If the namespace of the document is `MyComponents.Components.Shared`,
// then the types `MyComponents.FooComponent`, `MyComponents.Components.BarComponent`, `MyComponents.Components.Shared.BazComponent` are all in scope.
// Whereas `MyComponents.SomethingElse.OtherComponent` is not in scope.
internal static bool IsTypeInScope(string typeName, string currentNamespace)
{
if (!TrySplitNamespaceAndType(typeName, out var typeNamespace, out var _) || typeNamespace == string.Empty)
{
// Either the typeName is not the full type name or this type is at the top level.
return true;
}
var typeNamespaceSegments = typeNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries);
var currentNamespaceSegments = currentNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries);
if (typeNamespaceSegments.Length > currentNamespaceSegments.Length)
{
return false;
}
for (var i = 0; i < typeNamespaceSegments.Length; i++)
{
if (!typeNamespaceSegments[i].Equals(currentNamespaceSegments[i], StringComparison.Ordinal))
{
return false;
}
}
return true;
}
// We need to filter out the duplicate tag helper descriptors that come from the
// open file in the editor. We mangle the class name for its generated code, so using that here to filter these out.
internal static bool IsTagHelperFromMangledClass(TagHelperDescriptor tagHelper)
{
if (!TrySplitNamespaceAndType(tagHelper.GetTypeName(), out var _, out var className))
{
return false;
}
return ComponentMetadata.IsMangledClass(className);
}
// Internal for testing.
internal static bool TrySplitNamespaceAndType(string fullTypeName, out string @namespace, out string typeName)
{
@namespace = string.Empty;
typeName = string.Empty;
if (string.IsNullOrEmpty(fullTypeName))
{
return false;
}
var nestingLevel = 0;
var splitLocation = -1;
for (var i = fullTypeName.Length - 1; i >= 0; i--)
{
var c = fullTypeName[i];
if (c == Type.Delimiter && nestingLevel == 0)
{
splitLocation = i;
break;
}
else if (c == '>')
{
nestingLevel++;
}
else if (c == '<')
{
nestingLevel--;
}
}
if (splitLocation == -1)
{
typeName = fullTypeName;
return true;
}
@namespace = fullTypeName.Substring(0, splitLocation);
var typeNameStartLocation = splitLocation + 1;
if (typeNameStartLocation < fullTypeName.Length)
{
typeName = fullTypeName.Substring(typeNameStartLocation, fullTypeName.Length - typeNameStartLocation);
}
return true;
}
}
}
}

View File

@ -7,13 +7,19 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
internal class AddImportChunkGenerator : SpanChunkGenerator
{
public AddImportChunkGenerator(string ns)
public AddImportChunkGenerator(string usingContent, string parsedNamespace, bool isStatic)
{
Namespace = ns;
Namespace = usingContent;
ParsedNamespace = parsedNamespace;
IsStatic = isStatic;
}
public string Namespace { get; }
public string ParsedNamespace { get; }
public bool IsStatic { get; }
public override string ToString()
{
return "Import:" + Namespace + ";";

View File

@ -2066,11 +2066,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
var directiveBuilder = pooledResult.Builder;
Assert(CSharpKeyword.Using);
AcceptAndMoveNext();
var isStatic = false;
var nonNamespaceTokenCount = TokenBuilder.Count;
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
var start = CurrentStart;
if (At(SyntaxKind.Identifier))
{
// non-static using
nonNamespaceTokenCount = TokenBuilder.Count;
TryParseNamespaceOrTypeName(directiveBuilder);
var whitespace = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
if (At(SyntaxKind.Assign))
@ -2094,15 +2097,24 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
else if (At(CSharpKeyword.Static))
{
// static using
isStatic = true;
AcceptAndMoveNext();
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
nonNamespaceTokenCount = TokenBuilder.Count;
TryParseNamespaceOrTypeName(directiveBuilder);
}
var usingStatementTokens = TokenBuilder.ToList().Nodes;
var usingContentTokens = usingStatementTokens.Skip(1);
var parsedNamespaceTokens = usingStatementTokens
.Skip(nonNamespaceTokenCount)
.Where(s => s.Kind != SyntaxKind.CSharpComment && s.Kind != SyntaxKind.Whitespace && s.Kind != SyntaxKind.NewLine);
SpanContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.AnyExceptNewline;
SpanContext.ChunkGenerator = new AddImportChunkGenerator(new LocationTagged<string>(
string.Concat(TokenBuilder.ToList().Nodes.Skip(1).Select(s => s.Content)),
start));
SpanContext.ChunkGenerator = new AddImportChunkGenerator(
string.Concat(usingContentTokens.Select(s => s.Content)),
string.Concat(parsedNamespaceTokens.Select(s => s.Content)),
isStatic);
// Optional ";"
if (EnsureCurrent())

View File

@ -3,12 +3,17 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language
{
public static class RazorCodeDocumentExtensions
{
private static readonly char[] PathSeparators = new char[] { '/', '\\' };
private static readonly char[] NamespaceSeparators = new char[] { '.' };
public static TagHelperDocumentContext GetTagHelperContext(this RazorCodeDocument document)
{
if (document == null)
@ -189,6 +194,71 @@ namespace Microsoft.AspNetCore.Razor.Language
document.Items[typeof(FileKinds)] = fileKind;
}
// 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.
//
// 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)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
var filePath = document.Source.FilePath;
var relativePath = document.Source.RelativePath;
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))
{
@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);
builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[0]));
for (var i = 1; i < segments.Length; i++)
{
builder.Append('.');
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++)
{
builder.Append('.');
builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[i]));
}
@namespace = builder.ToString();
@class = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(relativePath));
return true;
}
private static string NormalizePath(string path)
{
path = path.Replace('\\', '/');
return path;
}
private class ImportSyntaxTreesHolder
{
public ImportSyntaxTreesHolder(IReadOnlyList<RazorSyntaxTree> syntaxTrees)

View File

@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Razor.Language
private static void AddDefaultRuntimeFeatures(RazorConfiguration configuration, ICollection<IRazorEngineFeature> features)
{
// Configure options
features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: configuration.LanguageVersion));
features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: configuration.LanguageVersion, fileKind: null));
features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false));
// Intermediate Node Passes
@ -124,7 +124,7 @@ namespace Microsoft.AspNetCore.Razor.Language
private static void AddDefaultDesignTimeFeatures(RazorConfiguration configuration, ICollection<IRazorEngineFeature> features)
{
// Configure options
features.Add(new DefaultRazorParserOptionsFeature(designTime: true, version: configuration.LanguageVersion));
features.Add(new DefaultRazorParserOptionsFeature(designTime: true, version: configuration.LanguageVersion, fileKind: null));
features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: true));
features.Add(new SuppressChecksumOptionsFeature());

View File

@ -14,17 +14,23 @@ namespace Microsoft.AspNetCore.Razor.Language
Array.Empty<DirectiveDescriptor>(),
designTime: false,
parseLeadingDirectives: false,
version: RazorLanguageVersion.Latest);
version: RazorLanguageVersion.Latest,
fileKind: null);
}
public static RazorParserOptions Create(Action<RazorParserOptionsBuilder> configure)
{
return Create(configure, fileKind: null);
}
public static RazorParserOptions Create(Action<RazorParserOptionsBuilder> configure, string fileKind)
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
var builder = new DefaultRazorParserOptionsBuilder(designTime: false, version: RazorLanguageVersion.Latest);
var builder = new DefaultRazorParserOptionsBuilder(designTime: false, version: RazorLanguageVersion.Latest, fileKind);
configure(builder);
var options = builder.Build();
@ -32,13 +38,18 @@ namespace Microsoft.AspNetCore.Razor.Language
}
public static RazorParserOptions CreateDesignTime(Action<RazorParserOptionsBuilder> configure)
{
return CreateDesignTime(configure, fileKind: null);
}
public static RazorParserOptions CreateDesignTime(Action<RazorParserOptionsBuilder> configure, string fileKind)
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
var builder = new DefaultRazorParserOptionsBuilder(designTime: true, version: RazorLanguageVersion.Latest);
var builder = new DefaultRazorParserOptionsBuilder(designTime: true, version: RazorLanguageVersion.Latest, fileKind);
configure(builder);
var options = builder.Build();
@ -61,6 +72,8 @@ namespace Microsoft.AspNetCore.Razor.Language
public virtual RazorLanguageVersion Version { get; } = RazorLanguageVersion.Latest;
internal virtual string FileKind { get; }
internal virtual RazorParserFeatureFlags FeatureFlags { get; }
}
}

View File

@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Legacy options features
//
// These features are obsolete as of 2.1. Our code will resolve this but not invoke them.
features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorLanguageVersion.Version_2_0));
features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorLanguageVersion.Version_2_0, fileKind: null));
features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false));
// Syntax Tree passes

View File

@ -445,6 +445,11 @@ namespace Microsoft.CodeAnalysis.Razor
attribute.SetPropertyName(valueAttribute.GetPropertyName());
});
if (tagHelper.IsComponentFullyQualifiedNameMatch())
{
builder.Metadata[ComponentMetadata.Component.NameMatchKey] = ComponentMetadata.Component.FullyQualifiedNameMatch;
}
results.Add(builder.Build());
}
}

View File

@ -63,13 +63,20 @@ namespace Microsoft.CodeAnalysis.Razor
for (var i = 0; i < types.Count; i++)
{
var type = types[i];
var descriptor = CreateDescriptor(symbols, type);
context.Results.Add(descriptor);
foreach (var childContent in descriptor.GetChildContentProperties())
// Components have very simple matching rules.
// 1. The type name (short) matches the tag name.
// 2. The fully qualified name matches the tag name.
var shortNameMatchingDescriptor = CreateShortNameMatchingDescriptor(symbols, type);
context.Results.Add(shortNameMatchingDescriptor);
var fullyQualifiedNameMatchingDescriptor = CreateFullyQualifiedNameMatchingDescriptor(symbols, type);
context.Results.Add(fullyQualifiedNameMatchingDescriptor);
foreach (var childContent in shortNameMatchingDescriptor.GetChildContentProperties())
{
// Synthesize a separate tag helper for each child content property that's declared.
context.Results.Add(CreateChildContentDescriptor(symbols, descriptor, childContent));
context.Results.Add(CreateChildContentDescriptor(symbols, shortNameMatchingDescriptor, childContent));
context.Results.Add(CreateChildContentDescriptor(symbols, fullyQualifiedNameMatchingDescriptor, childContent));
}
}
}
@ -81,7 +88,26 @@ namespace Microsoft.CodeAnalysis.Razor
return compilation.WithOptions(newCompilationOptions);
}
private TagHelperDescriptor CreateDescriptor(ComponentSymbols symbols, INamedTypeSymbol type)
private TagHelperDescriptor CreateShortNameMatchingDescriptor(ComponentSymbols symbols, INamedTypeSymbol type)
{
var builder = CreateDescriptorBuilder(symbols, type);
builder.TagMatchingRule(r => r.TagName = type.Name);
return builder.Build();
}
private TagHelperDescriptor CreateFullyQualifiedNameMatchingDescriptor(ComponentSymbols symbols, INamedTypeSymbol type)
{
var builder = CreateDescriptorBuilder(symbols, type);
var containingNamespace = type.ContainingNamespace.ToDisplayString();
var fullName = $"{containingNamespace}.{type.Name}";
builder.TagMatchingRule(r => r.TagName = fullName);
builder.Metadata[ComponentMetadata.Component.NameMatchKey] = ComponentMetadata.Component.FullyQualifiedNameMatch;
return builder.Build();
}
private TagHelperDescriptorBuilder CreateDescriptorBuilder(ComponentSymbols symbols, INamedTypeSymbol type)
{
var typeName = type.ToDisplayString(FullNameTypeDisplayFormat);
var assemblyName = type.ContainingAssembly.Identity.Name;
@ -113,9 +139,6 @@ namespace Microsoft.CodeAnalysis.Razor
builder.Documentation = xml;
}
// Components have very simple matching rules. The type name (short) matches the tag name.
builder.TagMatchingRule(r => r.TagName = type.Name);
foreach (var property in GetProperties(symbols, type))
{
if (property.kind == PropertyKind.Ignored)
@ -135,8 +158,7 @@ namespace Microsoft.CodeAnalysis.Razor
CreateContextParameter(builder, childContentName: null);
}
var descriptor = builder.Build();
return descriptor;
return builder;
}
private void CreateProperty(TagHelperDescriptorBuilder builder, IPropertySymbol property, PropertyKind kind)
@ -270,6 +292,11 @@ namespace Microsoft.CodeAnalysis.Razor
CreateContextParameter(builder, attribute.Name);
}
if (component.IsComponentFullyQualifiedNameMatch())
{
builder.Metadata[ComponentMetadata.Component.NameMatchKey] = ComponentMetadata.Component.FullyQualifiedNameMatch;
}
var descriptor = builder.Build();
return descriptor;

View File

@ -1,18 +1,18 @@
// 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.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.AspNetCore.Razor.Language.Components;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Extensions.DependencyModel;
using Xunit;
namespace Microsoft.CodeAnalysis.Razor
{
public abstract class BaseTagHelperDescriptorProviderTest
public abstract class TagHelperDescriptorProviderTestBase
{
static BaseTagHelperDescriptorProviderTest()
static TagHelperDescriptorProviderTestBase()
{
BaseCompilation = TestCompilation.Create(typeof(ComponentTagHelperDescriptorProviderTest).Assembly);
CSharpParseOptions = new CSharpParseOptions(LanguageVersion.CSharp7_3);
@ -39,5 +39,28 @@ namespace Microsoft.CodeAnalysis.Razor
.ToArray();
}
protected static TagHelperDescriptor[] AssertAndExcludeFullyQualifiedNameMatchComponents(
TagHelperDescriptor[] components,
int expectedCount)
{
var componentLookup = new Dictionary<string, List<TagHelperDescriptor>>();
var fullyQualifiedNameMatchComponents = components.Where(c => c.IsComponentFullyQualifiedNameMatch()).ToArray();
Assert.Equal(expectedCount, fullyQualifiedNameMatchComponents.Length);
var shortNameMatchComponents = components.Where(c => !c.IsComponentFullyQualifiedNameMatch()).ToArray();
// For every fully qualified name component, we want to make sure we have a corresponding short name component.
foreach (var fullNameComponent in fullyQualifiedNameMatchComponents)
{
Assert.Contains(shortNameMatchComponents, component =>
{
return component.Name == fullNameComponent.Name &&
component.Kind == fullNameComponent.Kind &&
component.BoundAttributes.SequenceEqual(fullNameComponent.BoundAttributes, BoundAttributeDescriptorComparer.Default);
});
}
return shortNameMatchComponents;
}
}
}

View File

@ -8,7 +8,7 @@ using Xunit;
namespace Microsoft.CodeAnalysis.Razor
{
public class BindTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
public class BindTagHelperDescriptorProviderTest : TagHelperDescriptorProviderTestBase
{
[Fact]
public void Execute_FindsBindTagHelperOnComponentType_Delegate_CreatesDescriptor()
@ -55,6 +55,7 @@ namespace Test
// Assert
var matches = GetBindTagHelpers(context);
matches = AssertAndExcludeFullyQualifiedNameMatchComponents(matches, expectedCount: 1);
var bind = Assert.Single(matches);
// These are features Bind Tags Helpers don't use. Verifying them once here and
@ -170,6 +171,7 @@ namespace Test
// Assert
var matches = GetBindTagHelpers(context);
matches = AssertAndExcludeFullyQualifiedNameMatchComponents(matches, expectedCount: 1);
var bind = Assert.Single(matches);
// These are features Bind Tags Helpers don't use. Verifying them once here and
@ -283,6 +285,7 @@ namespace Test
// Assert
var matches = GetBindTagHelpers(context);
matches = AssertAndExcludeFullyQualifiedNameMatchComponents(matches, expectedCount: 0);
Assert.Empty(matches);
}
@ -314,6 +317,7 @@ namespace Test
// Assert
var matches = GetBindTagHelpers(context);
matches = AssertAndExcludeFullyQualifiedNameMatchComponents(matches, expectedCount: 0);
var bind = Assert.Single(matches);
// These are features Bind Tags Helpers don't use. Verifying them once here and
@ -448,6 +452,7 @@ namespace Test
// Assert
var matches = GetBindTagHelpers(context);
matches = AssertAndExcludeFullyQualifiedNameMatchComponents(matches, expectedCount: 0);
var bind = Assert.Single(matches);
Assert.Equal("myprop", bind.Metadata[ComponentMetadata.Bind.ValueAttribute]);
@ -502,6 +507,7 @@ namespace Test
// Assert
var matches = GetBindTagHelpers(context);
matches = AssertAndExcludeFullyQualifiedNameMatchComponents(matches, expectedCount: 0);
var bind = Assert.Single(matches);
Assert.Equal("myprop", bind.Metadata[ComponentMetadata.Bind.ValueAttribute]);
@ -557,6 +563,7 @@ namespace Test
// Assert
var matches = GetBindTagHelpers(context);
matches = AssertAndExcludeFullyQualifiedNameMatchComponents(matches, expectedCount: 0);
var bind = Assert.Single(matches);
Assert.Equal("myprop", bind.Metadata[ComponentMetadata.Bind.ValueAttribute]);
@ -624,6 +631,7 @@ namespace Test
// Assert
var matches = GetBindTagHelpers(context);
matches = AssertAndExcludeFullyQualifiedNameMatchComponents(matches, expectedCount: 0);
var bind = Assert.Single(matches);
Assert.Equal("myprop", bind.Metadata[ComponentMetadata.Bind.ValueAttribute]);

View File

@ -8,7 +8,7 @@ using Xunit;
namespace Microsoft.CodeAnalysis.Razor
{
public class ComponentTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
public class ComponentTagHelperDescriptorProviderTest : TagHelperDescriptorProviderTestBase
{
[Fact]
public void Execute_FindsIComponentType_CreatesDescriptor()
@ -45,6 +45,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
// These are features Components don't use. Verifying them once here and
@ -159,6 +160,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
Assert.Equal("TestAssembly", component.AssemblyName);
@ -222,6 +224,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
Assert.Equal("TestAssembly", component.AssemblyName);
@ -263,6 +266,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
Assert.Equal("TestAssembly", component.AssemblyName);
@ -315,6 +319,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
Assert.Equal("TestAssembly", component.AssemblyName);
@ -361,6 +366,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
Assert.Equal("TestAssembly", component.AssemblyName);
@ -424,6 +430,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
Assert.Equal("TestAssembly", component.AssemblyName);
@ -498,6 +505,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
Assert.Equal("TestAssembly", component.AssemblyName);
@ -547,6 +555,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
Assert.Equal("TestAssembly", component.AssemblyName);
@ -608,6 +617,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
Assert.Equal("TestAssembly", component.AssemblyName);
@ -657,6 +667,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
Assert.Equal("TestAssembly", component.AssemblyName);
@ -711,6 +722,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
Assert.Equal("TestAssembly", component.AssemblyName);
@ -773,6 +785,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
var component = Assert.Single(components, c => c.IsComponentTagHelper());
Assert.Equal("TestAssembly", component.AssemblyName);
@ -829,6 +842,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
var component = Assert.Single(components, c => c.IsComponentTagHelper());
Assert.Equal("TestAssembly", component.AssemblyName);
@ -903,6 +917,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
var component = Assert.Single(components, c => c.IsComponentTagHelper());
Assert.Equal("TestAssembly", component.AssemblyName);
@ -974,6 +989,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
var component = Assert.Single(components, c => c.IsComponentTagHelper());
Assert.Equal("TestAssembly", component.AssemblyName);
@ -1055,6 +1071,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
var component = Assert.Single(components, c => c.IsComponentTagHelper());
Assert.Equal("TestAssembly", component.AssemblyName);
@ -1136,6 +1153,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
var component = Assert.Single(components, c => c.IsComponentTagHelper());
Assert.Equal("TestAssembly", component.AssemblyName);
@ -1221,6 +1239,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 2);
var component = Assert.Single(components, c => c.IsComponentTagHelper());
Assert.Equal("TestAssembly", component.AssemblyName);
@ -1306,6 +1325,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 4);
var component = Assert.Single(components, c => c.IsComponentTagHelper());
Assert.Equal("TestAssembly", component.AssemblyName);
@ -1397,6 +1417,7 @@ namespace Test
// Assert
var components = ExcludeBuiltInComponents(context);
components = AssertAndExcludeFullyQualifiedNameMatchComponents(components, expectedCount: 1);
var component = Assert.Single(components);
Assert.Equal("TestAssembly", component.AssemblyName);

View File

@ -9,7 +9,7 @@ using Xunit;
namespace Microsoft.CodeAnalysis.Razor
{
public class EventHandlerTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
public class EventHandlerTagHelperDescriptorProviderTest : TagHelperDescriptorProviderTestBase
{
[Fact]
public void Execute_EventHandler_CreatesDescriptor()

View File

@ -8,7 +8,7 @@ using Xunit;
namespace Microsoft.CodeAnalysis.Razor
{
public class RefTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
public class RefTagHelperDescriptorProviderTest : TagHelperDescriptorProviderTestBase
{
[Fact]
public void Execute_CreatesDescriptor()