Add @removetaghelper directive.

- Added parsing recognition of the @removetaghelper directive.
- Added TagHelperDescriptor handling: @removetaghelper will cause the system to ignore TagHelpers that are added via @addtaghelper.
- Added Chunk generation, this involved building a CodeGenerator (soon to be named ChunkGenerator) that pulled out the stringified @removetaghelper lookup text which it then uses to construct a RemoveTagHelperChunk.
- Modified CodeVisitors to now understand RemoveTagHelperChunk's.
- Added code generation, this involved creating a dummy string in order to give the @removetaghelper "..." coloring.

#112
This commit is contained in:
N. Taylor Mullen 2014-10-21 12:09:02 -07:00
parent 0dba621965
commit 30221f7ce0
12 changed files with 197 additions and 114 deletions

View File

@ -59,6 +59,16 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
}
protected override void Visit(AddTagHelperChunk chunk)
{
VisitAddOrRemoveTagHelperChunk(chunk.LookupText, chunk);
}
protected override void Visit(RemoveTagHelperChunk chunk)
{
VisitAddOrRemoveTagHelperChunk(chunk.LookupText, chunk);
}
private void VisitAddOrRemoveTagHelperChunk(string lookupText, Chunk chunk)
{
// We should always be in design time mode because of the calling AcceptTree method verification.
Debug.Assert(Context.Host.DesignTimeMode);
@ -71,12 +81,12 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
Writer.WriteStartAssignment(TagHelperDirectiveSyntaxHelper);
// The parsing mechanism for the AddTagHelperChunk (CSharpCodeParser.TagHelperDirective()) removes quotes
// that surround the chunk.LookupText.
// The parsing mechanism for the add/remove TagHelper chunk (CSharpCodeParser.TagHelperDirective())
// removes quotes that surround the lookupText.
_csharpCodeVisitor.CreateExpressionCodeMapping(
string.Format(
CultureInfo.InvariantCulture,
"\"{0}\"", chunk.LookupText),
"\"{0}\"", lookupText),
chunk);
Writer.WriteLine(";");

View File

@ -61,6 +61,10 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler
{
Visit((AddTagHelperChunk)chunk);
}
else if (chunk is RemoveTagHelperChunk)
{
Visit((RemoveTagHelperChunk)chunk);
}
else if(chunk is SetLayoutChunk)
{
Visit((SetLayoutChunk)chunk);
@ -120,6 +124,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler
protected abstract void Visit(StatementChunk chunk);
protected abstract void Visit(TagHelperChunk chunk);
protected abstract void Visit(AddTagHelperChunk chunk);
protected abstract void Visit(RemoveTagHelperChunk chunk);
protected abstract void Visit(UsingChunk chunk);
protected abstract void Visit(ChunkBlock chunk);
protected abstract void Visit(DynamicCodeAttributeChunk chunk);

View File

@ -35,6 +35,9 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler
protected override void Visit(AddTagHelperChunk chunk)
{
}
protected override void Visit(RemoveTagHelperChunk chunk)
{
}
protected override void Visit(LiteralCodeAttributeChunk chunk)
{
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Razor.Generator.Compiler
{
/// <summary>
/// A <see cref="Chunk"/> used to look up <see cref="TagHelpers.TagHelperDescriptor"/>s that should be ignored
/// within the Razor page.
/// </summary>
public class RemoveTagHelperChunk : Chunk
{
/// <summary>
/// Text used to look up <see cref="TagHelpers.TagHelperDescriptor"/>s that should be ignored within the Razor
/// page.
/// </summary>
public string LookupText { get; set; }
}
}

View File

@ -45,6 +45,14 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler
}, association, topLevel: true);
}
public void AddRemoveTagHelperChunk(string lookupText, SyntaxTreeNode association)
{
AddChunk(new RemoveTagHelperChunk
{
LookupText = lookupText
}, association, topLevel: true);
}
public void AddLiteralChunk(string literal, SyntaxTreeNode association)
{
// If the previous chunk was also a LiteralChunk, append the content of the current node to the previous one.

View File

@ -0,0 +1,60 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
/// <summary>
/// A <see cref="SpanCodeGenerator"/> responsible for generating <see cref="Compiler.AddTagHelperChunk"/>s and
/// <see cref="Compiler.RemoveTagHelperChunk"/>s.
/// </summary>
public class AddOrRemoveTagHelperCodeGenerator : SpanCodeGenerator
{
/// <summary>
/// Instantiates a new <see cref="AddOrRemoveTagHelperCodeGenerator"/>.
/// </summary>
/// <param name="lookupText">
/// Text used to look up <see cref="TagHelpers.TagHelperDescriptor"/>s that should be added or removed.
/// </param>
public AddOrRemoveTagHelperCodeGenerator(bool removeTagHelperDescriptors, string lookupText)
{
RemoveTagHelperDescriptors = removeTagHelperDescriptors;
LookupText = lookupText;
}
/// <summary>
/// Gets the text used to look up <see cref="TagHelpers.TagHelperDescriptor"/>s that should be added to or
/// removed from the Razor page.
/// </summary>
public string LookupText { get; }
/// <summary>
/// Whether we want to remove <see cref="TagHelpers.TagHelperDescriptor"/>s from the Razor page.
/// </summary>
/// <remarks>If <c>true</c> <see cref="GenerateCode"/> generates <see cref="Compiler.AddTagHelperChunk"/>s,
/// <see cref="Compiler.RemoveTagHelperChunk"/>s otherwise.</remarks>
public bool RemoveTagHelperDescriptors { get; }
/// <summary>
/// Generates <see cref="Compiler.AddTagHelperChunk"/>s if <see cref="RemoveTagHelperDescriptors"/> is
/// <c>true</c>, otherwise <see cref="Compiler.RemoveTagHelperChunk"/>s are generated.
/// </summary>
/// <param name="target">
/// The <see cref="Span"/> responsible for this <see cref="AddOrRemoveTagHelperCodeGenerator"/>.
/// </param>
/// <param name="context">A <see cref="CodeGeneratorContext"/> instance that contains information about
/// the current code generation process.</param>
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
if (RemoveTagHelperDescriptors)
{
context.CodeTreeBuilder.AddRemoveTagHelperChunk(LookupText, target);
}
else
{
context.CodeTreeBuilder.AddAddTagHelperChunk(LookupText, target);
}
}
}
}

View File

@ -1,42 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
/// <summary>
/// A <see cref="SpanCodeGenerator"/> responsible for generating <see cref="Compiler.AddTagHelperChunk"/>s.
/// </summary>
public class AddTagHelperCodeGenerator : SpanCodeGenerator
{
/// <summary>
/// Instantiates a new <see cref="AddTagHelperCodeGenerator"/>.
/// </summary>
/// <param name="lookupText">
/// Text used to look up <see cref="TagHelpers.TagHelperDescriptor"/>s.
/// </param>
public AddTagHelperCodeGenerator(string lookupText)
{
LookupText = lookupText;
}
/// <summary>
/// Text used to look up <see cref="TagHelpers.TagHelperDescriptor"/>s.
/// </summary>
public string LookupText { get; private set; }
/// <summary>
/// Generates a <see cref="Compiler.AddTagHelperChunk"/>.
/// </summary>
/// <param name="target">
/// The <see cref="Span"/> responsible for this <see cref="AddTagHelperCodeGenerator"/>.
/// </param>
/// <param name="context">A <see cref="CodeGeneratorContext"/> instance that contains information about
/// the current code generation process.</param>
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
context.CodeTreeBuilder.AddAddTagHelperChunk(LookupText, target);
}
}
}

View File

@ -20,6 +20,7 @@ namespace Microsoft.AspNet.Razor.Parser
private void SetupDirectives()
{
MapDirectives(AddTagHelperDirective, SyntaxConstants.CSharp.AddTagHelperKeyword);
MapDirectives(RemoveTagHelperDirective, SyntaxConstants.CSharp.RemoveTagHelperKeyword);
MapDirectives(InheritsDirective, SyntaxConstants.CSharp.InheritsKeyword);
MapDirectives(FunctionsDirective, SyntaxConstants.CSharp.FunctionsKeyword);
MapDirectives(SectionDirective, SyntaxConstants.CSharp.SectionKeyword);
@ -30,10 +31,12 @@ namespace Microsoft.AspNet.Razor.Parser
protected virtual void AddTagHelperDirective()
{
TagHelperDirective(SyntaxConstants.CSharp.AddTagHelperKeyword, (lookupText) =>
{
return new AddTagHelperCodeGenerator(lookupText);
});
TagHelperDirective(SyntaxConstants.CSharp.AddTagHelperKeyword, removeTagHelperDescriptors: false);
}
protected virtual void RemoveTagHelperDirective()
{
TagHelperDirective(SyntaxConstants.CSharp.RemoveTagHelperKeyword, removeTagHelperDescriptors: true);
}
protected virtual void LayoutDirective()
@ -508,7 +511,7 @@ namespace Microsoft.AspNet.Razor.Parser
Output(SpanKind.Code);
}
private void TagHelperDirective(string keyword, Func<string, SpanCodeGenerator> codeGeneratorBuilder)
private void TagHelperDirective(string keyword, bool removeTagHelperDescriptors)
{
AssertDirective(keyword);
@ -550,7 +553,8 @@ namespace Microsoft.AspNet.Razor.Parser
// renders the C# to colorize the user provided value. We trim the quotes around the user's value
// so when we render the code we can project the users value into double quotes to not invoke C#
// IntelliSense.
Span.CodeGenerator = codeGeneratorBuilder(rawValue.Trim('"'));
Span.CodeGenerator =
new AddOrRemoveTagHelperCodeGenerator(removeTagHelperDescriptors, rawValue.Trim('"'));
}
// We expect the directive to be surrounded in quotes.

View File

@ -210,8 +210,8 @@ namespace Microsoft.AspNet.Razor.Parser
/// <returns></returns>
protected virtual IEnumerable<TagHelperDescriptor> GetTagHelperDescriptors([NotNull] Block documentRoot)
{
var tagHelperRegistrationVisitor = new TagHelperRegistrationVisitor(TagHelperDescriptorResolver);
return tagHelperRegistrationVisitor.GetDescriptors(documentRoot);
var addOrRemoveTagHelperSpanVisitor = new AddOrRemoveTagHelperSpanVisitor(TagHelperDescriptorResolver);
return addOrRemoveTagHelperSpanVisitor.GetDescriptors(documentRoot);
}
private static IEnumerable<ISyntaxTreeRewriter> GetDefaultRewriters(ParserBase markupParser)

View File

@ -19,6 +19,7 @@ namespace Microsoft.AspNet.Razor.Parser
{
public static readonly int UsingKeywordLength = 5;
public static readonly string AddTagHelperKeyword = "addtaghelper";
public static readonly string RemoveTagHelperKeyword = "removetaghelper";
public static readonly string InheritsKeyword = "inherits";
public static readonly string FunctionsKeyword = "functions";
public static readonly string SectionKeyword = "section";

View File

@ -0,0 +1,77 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.TagHelpers;
namespace Microsoft.AspNet.Razor.Parser.TagHelpers
{
/// <summary>
/// A <see cref="ParserVisitor"/> that generates <see cref="TagHelperDescriptor"/>s from
/// tag helper code generators in a Razor document.
/// </summary>
public class AddOrRemoveTagHelperSpanVisitor : ParserVisitor
{
private readonly ITagHelperDescriptorResolver _descriptorResolver;
private List<TagHelperDescriptor> _descriptors;
public AddOrRemoveTagHelperSpanVisitor(ITagHelperDescriptorResolver descriptorResolver)
{
_descriptorResolver = descriptorResolver;
}
public IEnumerable<TagHelperDescriptor> GetDescriptors([NotNull] Block root)
{
_descriptors = new List<TagHelperDescriptor>();
// This will recurse through the syntax tree.
VisitBlock(root);
return _descriptors;
}
public override void VisitSpan(Span span)
{
// We're only interested in spans with an AddOrRemoveTagHelperCodeGenerator.
if (span.CodeGenerator is AddOrRemoveTagHelperCodeGenerator)
{
var codeGenerator = (AddOrRemoveTagHelperCodeGenerator)span.CodeGenerator;
if (_descriptorResolver == null)
{
var directive = codeGenerator.RemoveTagHelperDescriptors ?
SyntaxConstants.CSharp.RemoveTagHelperKeyword :
SyntaxConstants.CSharp.AddTagHelperKeyword;
throw new InvalidOperationException(
RazorResources.FormatTagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver(
directive, typeof(ITagHelperDescriptorResolver).FullName, typeof(RazorParser).FullName));
}
// Look up all the descriptors associated with the "LookupText".
var descriptors = _descriptorResolver.Resolve(codeGenerator.LookupText);
if (codeGenerator.RemoveTagHelperDescriptors)
{
var evaluatedDescriptors =
new HashSet<TagHelperDescriptor>(descriptors, TagHelperDescriptorComparer.Default);
// We remove all found descriptors from the descriptor list to ignore the associated TagHelpers on the
// Razor page.
_descriptors.RemoveAll(descriptor => evaluatedDescriptors.Contains(descriptor));
}
else
{
// Add all the found descriptors to our list.
_descriptors.AddRange(descriptors);
}
}
}
}
}

View File

@ -1,61 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.TagHelpers;
namespace Microsoft.AspNet.Razor.Parser.TagHelpers
{
/// <summary>
/// A <see cref="ParserVisitor"/> that generates <see cref="TagHelperDescriptor"/>s from
/// tag helper code generators in a Razor document.
/// </summary>
public class TagHelperRegistrationVisitor : ParserVisitor
{
private readonly ITagHelperDescriptorResolver _descriptorResolver;
private List<TagHelperDescriptor> _descriptors;
public TagHelperRegistrationVisitor(ITagHelperDescriptorResolver descriptorResolver)
{
_descriptorResolver = descriptorResolver;
}
public IEnumerable<TagHelperDescriptor> GetDescriptors([NotNull] Block root)
{
_descriptors = new List<TagHelperDescriptor>();
// This will recurse through the syntax tree.
VisitBlock(root);
return _descriptors;
}
public override void VisitSpan(Span span)
{
// We're only interested in spans with an AddTagHelperCodeGenerator.
if (span.CodeGenerator is AddTagHelperCodeGenerator)
{
if (_descriptorResolver == null)
{
throw new InvalidOperationException(
RazorResources.FormatTagHelpers_CannotUseDirectiveWithNoTagHelperDescriptorResolver(
SyntaxConstants.CSharp.AddTagHelperKeyword,
nameof(ITagHelperDescriptorResolver),
nameof(RazorParser)));
}
var addGenerator = (AddTagHelperCodeGenerator)span.CodeGenerator;
// Look up all the descriptors associated with the "LookupText".
var descriptors = _descriptorResolver.Resolve(addGenerator.LookupText);
// Add all the found descriptors to our HashSet.
_descriptors.AddRange(descriptors);
}
}
}
}