Add @tagHelperPrefix inheritance support.

- Updated some naming bits that were outdated (referring to ViewStart).
- Added a TagHelperDirective merging mechanic to ensure user file descriptors and inherited descriptors come together correctly.

#2110
This commit is contained in:
N. Taylor Mullen 2015-03-03 21:31:58 -08:00
parent f05520602f
commit f49d52b5fc
1 changed files with 90 additions and 33 deletions

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{ {
/// <summary> /// <summary>
/// A subtype of <see cref="RazorParser"/> that <see cref="MvcRazorHost"/> uses to support inheritance of tag /// A subtype of <see cref="RazorParser"/> that <see cref="MvcRazorHost"/> uses to support inheritance of tag
/// helpers from <c>_ViewStart</c> files. /// helpers from <c>_GlobalImport</c> files.
/// </summary> /// </summary>
public class MvcRazorParser : RazorParser public class MvcRazorParser : RazorParser
{ {
@ -27,16 +27,19 @@ namespace Microsoft.AspNet.Mvc.Razor
/// </summary> /// </summary>
/// <param name="parser">The <see cref="RazorParser"/> to copy properties from.</param> /// <param name="parser">The <see cref="RazorParser"/> to copy properties from.</param>
/// <param name="inheritedCodeTrees">The <see cref="IReadOnlyList{CodeTree}"/>s that are inherited /// <param name="inheritedCodeTrees">The <see cref="IReadOnlyList{CodeTree}"/>s that are inherited
/// from parsed pages from _ViewStart files.</param> /// from parsed pages from _GlobalImport files.</param>
/// <param name="defaultInheritedChunks">The <see cref="IReadOnlyList{Chunk}"/> inherited by /// <param name="defaultInheritedChunks">The <see cref="IReadOnlyList{Chunk}"/> inherited by
/// default by all Razor pages in the application.</param> /// default by all Razor pages in the application.</param>
public MvcRazorParser([NotNull] RazorParser parser, public MvcRazorParser(
[NotNull] IReadOnlyList<CodeTree> inheritedCodeTrees, [NotNull] RazorParser parser,
[NotNull] IReadOnlyList<Chunk> defaultInheritedChunks) [NotNull] IReadOnlyList<CodeTree> inheritedCodeTrees,
[NotNull] IReadOnlyList<Chunk> defaultInheritedChunks)
: base(parser) : base(parser)
{ {
// Construct tag helper descriptors from @addTagHelper and @removeTagHelper chunks // Construct tag helper descriptors from @addTagHelper, @removeTagHelper and @tagHelperPrefix chunks
_globalImportDirectiveDescriptors = GetTagHelperDirectiveDescriptors(inheritedCodeTrees, defaultInheritedChunks); _globalImportDirectiveDescriptors = GetTagHelperDirectiveDescriptors(
inheritedCodeTrees,
defaultInheritedChunks);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -44,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Razor
[NotNull] Block documentRoot, [NotNull] Block documentRoot,
[NotNull] ParserErrorSink errorSink) [NotNull] ParserErrorSink errorSink)
{ {
var visitor = new GlobalImportAddRemoveTagHelperVisitor( var visitor = new GlobalImportTagHelperDirectiveSpanVisitor(
TagHelperDescriptorResolver, TagHelperDescriptorResolver,
_globalImportDirectiveDescriptors, _globalImportDirectiveDescriptors,
errorSink); errorSink);
@ -58,43 +61,63 @@ namespace Microsoft.AspNet.Mvc.Razor
var descriptors = new List<TagHelperDirectiveDescriptor>(); var descriptors = new List<TagHelperDirectiveDescriptor>();
// For tag helpers, the @removeTagHelper only applies tag helpers that were added prior to it. // For tag helpers, the @removeTagHelper only applies tag helpers that were added prior to it.
// Consequently we must visit tag helpers outside-in - furthest _ViewStart first and nearest one last. This // Consequently we must visit tag helpers outside-in - furthest _GlobalImport first and nearest one last.
// is different from the behavior of chunk merging where we visit the nearest one first and ignore chunks // This is different from the behavior of chunk merging where we visit the nearest one first and ignore
// that were previously visited. // chunks that were previously visited.
var chunksFromViewStarts = inheritedCodeTrees.Reverse() var chunksFromGlobalImports = inheritedCodeTrees
.SelectMany(tree => tree.Chunks); .Reverse()
var chunksInOrder = defaultInheritedChunks.Concat(chunksFromViewStarts); .SelectMany(tree => tree.Chunks);
var chunksInOrder = defaultInheritedChunks.Concat(chunksFromGlobalImports);
foreach (var chunk in chunksInOrder) foreach (var chunk in chunksInOrder)
{ {
var addHelperChunk = chunk as AddTagHelperChunk; // All TagHelperDirectiveDescriptors created here have undefined source locations because the source
if (addHelperChunk != null) // that created them is not in the same file.
var addTagHelperChunk = chunk as AddTagHelperChunk;
if (addTagHelperChunk != null)
{ {
var descriptor = new TagHelperDirectiveDescriptor(addHelperChunk.LookupText, var descriptor = new TagHelperDirectiveDescriptor(
SourceLocation.Undefined, addTagHelperChunk.LookupText,
TagHelperDirectiveType.AddTagHelper); SourceLocation.Undefined,
TagHelperDirectiveType.AddTagHelper);
descriptors.Add(descriptor); descriptors.Add(descriptor);
continue;
} }
else
var removeTagHelperChunk = chunk as RemoveTagHelperChunk;
if (removeTagHelperChunk != null)
{ {
var removeHelperChunk = chunk as RemoveTagHelperChunk; var descriptor = new TagHelperDirectiveDescriptor(
if (removeHelperChunk != null) removeTagHelperChunk.LookupText,
{ SourceLocation.Undefined,
var descriptor = new TagHelperDirectiveDescriptor(removeHelperChunk.LookupText, TagHelperDirectiveType.RemoveTagHelper);
SourceLocation.Undefined,
TagHelperDirectiveType.RemoveTagHelper); descriptors.Add(descriptor);
descriptors.Add(descriptor);
} continue;
}
var tagHelperPrefixDirectiveChunk = chunk as TagHelperPrefixDirectiveChunk;
if (tagHelperPrefixDirectiveChunk != null)
{
var descriptor = new TagHelperDirectiveDescriptor(
tagHelperPrefixDirectiveChunk.Prefix,
SourceLocation.Undefined,
TagHelperDirectiveType.TagHelperPrefix);
descriptors.Add(descriptor);
} }
} }
return descriptors; return descriptors;
} }
private class GlobalImportAddRemoveTagHelperVisitor : TagHelperDirectiveSpanVisitor private class GlobalImportTagHelperDirectiveSpanVisitor : TagHelperDirectiveSpanVisitor
{ {
private readonly IEnumerable<TagHelperDirectiveDescriptor> _globalImportDirectiveDescriptors; private readonly IEnumerable<TagHelperDirectiveDescriptor> _globalImportDirectiveDescriptors;
public GlobalImportAddRemoveTagHelperVisitor( public GlobalImportTagHelperDirectiveSpanVisitor(
ITagHelperDescriptorResolver descriptorResolver, ITagHelperDescriptorResolver descriptorResolver,
IEnumerable<TagHelperDirectiveDescriptor> globalImportDirectiveDescriptors, IEnumerable<TagHelperDirectiveDescriptor> globalImportDirectiveDescriptors,
ParserErrorSink errorSink) ParserErrorSink errorSink)
@ -107,9 +130,43 @@ namespace Microsoft.AspNet.Mvc.Razor
IEnumerable<TagHelperDirectiveDescriptor> descriptors, IEnumerable<TagHelperDirectiveDescriptor> descriptors,
ParserErrorSink errorSink) ParserErrorSink errorSink)
{ {
return base.GetTagHelperDescriptorResolutionContext( var directivesToImport = MergeDirectiveDescriptors(descriptors, _globalImportDirectiveDescriptors);
_globalImportDirectiveDescriptors.Concat(descriptors),
errorSink); return base.GetTagHelperDescriptorResolutionContext(directivesToImport, errorSink);
}
private static IEnumerable<TagHelperDirectiveDescriptor> MergeDirectiveDescriptors(
IEnumerable<TagHelperDirectiveDescriptor> descriptors,
IEnumerable<TagHelperDirectiveDescriptor> inheritedDescriptors)
{
var mergedDescriptors = new List<TagHelperDirectiveDescriptor>();
TagHelperDirectiveDescriptor prefixDirectiveDescriptor = null;
foreach (var descriptor in inheritedDescriptors)
{
if (descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix)
{
// Always take the latest @tagHelperPrefix descriptor. Can only have 1 per page.
prefixDirectiveDescriptor = descriptor;
}
else
{
mergedDescriptors.Add(descriptor);
}
}
// We need to see if the provided descriptors contain a @tagHelperPrefix directive. If so, it
// takes precedence and overrides any provided by the inheritedDescriptors. If not we need to add the
// inherited @tagHelperPrefix directive back into the merged list.
if (prefixDirectiveDescriptor != null &&
!descriptors.Any(descriptor => descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix))
{
mergedDescriptors.Add(prefixDirectiveDescriptor);
}
mergedDescriptors.AddRange(descriptors);
return mergedDescriptors;
} }
} }
} }