Skip to content
This repository was archived by the owner on Dec 19, 2018. It is now read-only.

Commit 859b75b

Browse files
author
N. Taylor Mullen
committed
Add @addtaghelper directive.
- Also added some infrastructure pieces for it such as the ITagHelperDescriptorResolver and the default implementation TagHelperDescriptorResolver which will be filled out in a later commit. - Reworked some extensibility points to allow accessibility of the descriptor resolvers per offline discussions. #111
1 parent 6a99ea3 commit 859b75b

17 files changed

+317
-23
lines changed

src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public override CodeBuilderResult Build()
4545

4646
new CSharpHelperVisitor(csharpCodeVisitor, writer, Context).Accept(Tree.Chunks);
4747
new CSharpTypeMemberVisitor(csharpCodeVisitor, writer, Context).Accept(Tree.Chunks);
48-
new CSharpDesignTimeHelpersVisitor(writer, Context).AcceptTree(Tree);
48+
new CSharpDesignTimeHelpersVisitor(csharpCodeVisitor, writer, Context).AcceptTree(Tree);
4949
new CSharpPropertyVisitor(writer, Context).Accept(Tree.Chunks);
5050

5151
BuildConstructor(writer);

src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpDesignTimeHelpersVisitor.cs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
11
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System.Diagnostics;
5+
using System.Globalization;
6+
47
namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
58
{
69
public class CSharpDesignTimeHelpersVisitor : CodeVisitor<CSharpCodeWriter>
710
{
11+
private const string TagHelperDirectiveSyntaxHelper = "__tagHelperDirectiveSyntaxHelper";
812
internal const string InheritsHelper = "__inheritsHelper";
913
internal const string DesignTimeHelperMethodName = "__RazorDesignTimeHelpers__";
14+
private static readonly string ObjectTypeString = typeof(object).ToString();
1015

1116
private const int DisableVariableNamingWarnings = 219;
1217

13-
public CSharpDesignTimeHelpersVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
14-
: base(writer, context) { }
18+
private CSharpCodeVisitor _csharpCodeVisitor;
19+
private bool _initializedTagHelperDirectiveSyntaxHelper;
20+
21+
public CSharpDesignTimeHelpersVisitor([NotNull] CSharpCodeVisitor csharpCodeVisitor,
22+
[NotNull] CSharpCodeWriter writer,
23+
[NotNull] CodeBuilderContext context)
24+
25+
: base(writer, context)
26+
{
27+
_csharpCodeVisitor = csharpCodeVisitor;
28+
}
1529

1630
public void AcceptTree(CodeTree tree)
1731
{
@@ -43,5 +57,27 @@ protected override void Visit(SetBaseTypeChunk chunk)
4357
}
4458
}
4559
}
60+
61+
protected override void Visit(AddTagHelperChunk chunk)
62+
{
63+
// We should always be in design time mode because of the calling AcceptTree method verification.
64+
Debug.Assert(Context.Host.DesignTimeMode);
65+
66+
if (!_initializedTagHelperDirectiveSyntaxHelper)
67+
{
68+
_initializedTagHelperDirectiveSyntaxHelper = true;
69+
Writer.WriteVariableDeclaration(ObjectTypeString, TagHelperDirectiveSyntaxHelper, "null");
70+
}
71+
72+
Writer.WriteStartAssignment(TagHelperDirectiveSyntaxHelper);
73+
74+
_csharpCodeVisitor.CreateExpressionCodeMapping(
75+
string.Format(
76+
CultureInfo.InvariantCulture,
77+
"\"{0}\"", chunk.LookupText),
78+
chunk);
79+
80+
Writer.WriteLine(";");
81+
}
4682
}
4783
}

src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/ChunkVisitor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ public virtual void Accept(Chunk chunk)
5757
{
5858
Visit((TagHelperChunk)chunk);
5959
}
60+
else if(chunk is AddTagHelperChunk)
61+
{
62+
Visit((AddTagHelperChunk)chunk);
63+
}
6064
else if(chunk is SetLayoutChunk)
6165
{
6266
Visit((SetLayoutChunk)chunk);
@@ -115,6 +119,7 @@ public virtual void Accept(Chunk chunk)
115119
protected abstract void Visit(ExpressionChunk chunk);
116120
protected abstract void Visit(StatementChunk chunk);
117121
protected abstract void Visit(TagHelperChunk chunk);
122+
protected abstract void Visit(AddTagHelperChunk chunk);
118123
protected abstract void Visit(UsingChunk chunk);
119124
protected abstract void Visit(ChunkBlock chunk);
120125
protected abstract void Visit(DynamicCodeAttributeChunk chunk);

src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CodeVisitor.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ protected override void Visit(DynamicCodeAttributeChunk chunk)
3434
protected override void Visit(TagHelperChunk chunk)
3535
{
3636
}
37+
protected override void Visit(AddTagHelperChunk chunk)
38+
{
39+
}
3740
protected override void Visit(LiteralCodeAttributeChunk chunk)
3841
{
3942
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNet.Razor.Generator.Compiler
5+
{
6+
/// <summary>
7+
/// A <see cref="Chunk"/> that is used to lookup <see cref="TagHelpers.TagHelperDescriptor"/>s.
8+
/// </summary>
9+
public class AddTagHelperChunk : Chunk
10+
{
11+
/// <summary>
12+
/// Arbitrary text used to lookup <see cref="TagHelpers.TagHelperDescriptor"/>s.
13+
/// </summary>
14+
public string LookupText { get; set; }
15+
}
16+
}

src/Microsoft.AspNet.Razor/Generator/Compiler/CodeTree/CodeTreeBuilder.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ public void AddChunk(Chunk chunk, SyntaxTreeNode association, bool topLevel = fa
3838
}
3939
}
4040

41+
public void AddAddTagHelperChunk(string lookupText, SyntaxTreeNode association)
42+
{
43+
AddChunk(new AddTagHelperChunk
44+
{
45+
LookupText = lookupText
46+
}, association);
47+
}
48+
4149
public void AddLiteralChunk(string literal, SyntaxTreeNode association)
4250
{
4351
if (_lastChunk is LiteralChunk)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
5+
6+
namespace Microsoft.AspNet.Razor.Generator
7+
{
8+
/// <summary>
9+
/// A <see cref="SpanCodeGenerator"/> that is responsible for generating <see cref="Compiler.AddTagHelperChunk"/>s.
10+
/// </summary>
11+
public class AddTagHelperCodeGenerator : SpanCodeGenerator
12+
{
13+
/// <summary>
14+
/// Instantiates a new <see cref="AddTagHelperCodeGenerator"/>.
15+
/// </summary>
16+
/// <param name="lookupText">
17+
/// Arbitrary text used to lookup <see cref="TagHelpers.TagHelperDescriptor"/>s.
18+
/// </param>
19+
public AddTagHelperCodeGenerator(string lookupText)
20+
{
21+
LookupText = lookupText;
22+
}
23+
24+
/// <summary>
25+
/// Arbitrary text used to lookup <see cref="TagHelpers.TagHelperDescriptor"/>s.
26+
/// </summary>
27+
public string LookupText { get; private set; }
28+
29+
/// <summary>
30+
/// Generates a <see cref="Compiler.AddTagHelperChunk"/>.
31+
/// </summary>
32+
/// <param name="target">
33+
/// The <see cref="Span"/> responsible for this <see cref="AddTagHelperCodeGenerator"/>.
34+
/// </param>
35+
/// <param name="context">A <see cref="CodeGeneratorContext"/> instance that contains information about
36+
/// the current code generation process.</param>
37+
public override void GenerateCode(Span target, CodeGeneratorContext context)
38+
{
39+
context.CodeTreeBuilder.AddAddTagHelperChunk(LookupText, target);
40+
}
41+
}
42+
}

src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Directives.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public partial class CSharpCodeParser
1919
{
2020
private void SetupDirectives()
2121
{
22+
MapDirectives(AddTagHelperDirective, SyntaxConstants.CSharp.AddTagHelper);
2223
MapDirectives(InheritsDirective, SyntaxConstants.CSharp.InheritsKeyword);
2324
MapDirectives(FunctionsDirective, SyntaxConstants.CSharp.FunctionsKeyword);
2425
MapDirectives(SectionDirective, SyntaxConstants.CSharp.SectionKeyword);
@@ -27,6 +28,14 @@ private void SetupDirectives()
2728
MapDirectives(SessionStateDirective, SyntaxConstants.CSharp.SessionStateKeyword);
2829
}
2930

31+
protected virtual void AddTagHelperDirective()
32+
{
33+
TagHelperDirective(SyntaxConstants.CSharp.AddTagHelper, (lookupText) =>
34+
{
35+
return new AddTagHelperCodeGenerator(lookupText);
36+
});
37+
}
38+
3039
protected virtual void LayoutDirective()
3140
{
3241
AssertDirective(SyntaxConstants.CSharp.LayoutKeyword);
@@ -498,5 +507,55 @@ protected void BaseTypeDirective(string noTypeNameError, Func<string, SpanCodeGe
498507
CompleteBlock();
499508
Output(SpanKind.Code);
500509
}
510+
511+
private void TagHelperDirective(string keyword, Func<string, SpanCodeGenerator> codeGeneratorBuilder)
512+
{
513+
AssertDirective(keyword);
514+
515+
// Accept the directive name
516+
AcceptAndMoveNext();
517+
518+
// Set the block type
519+
Context.CurrentBlock.Type = BlockType.Directive;
520+
521+
var foundWhitespace = At(CSharpSymbolType.WhiteSpace);
522+
AcceptWhile(CSharpSymbolType.WhiteSpace);
523+
// If we found whitespace then any content placed within the whitespace MAY cause a destructive change
524+
// to the document. We can't accept it.
525+
Output(SpanKind.MetaCode, foundWhitespace ? AcceptedCharacters.None : AcceptedCharacters.Any);
526+
527+
if (EndOfFile || At(CSharpSymbolType.NewLine))
528+
{
529+
Context.OnError(CurrentLocation, RazorResources.FormatParseError_DirectiveMustHaveValue(keyword));
530+
}
531+
else
532+
{
533+
// Need to grab the current location before we accept until the end of the line.
534+
var startLocation = CurrentLocation;
535+
536+
// Parse to the end of the line
537+
AcceptUntil(CSharpSymbolType.NewLine);
538+
539+
// Pull out the value minus the spaces at the end
540+
var rawValue = Span.GetContent().Value.TrimEnd();
541+
542+
// We expect the directive to be surrounded in quotes.
543+
if (!rawValue.StartsWith("\"", StringComparison.OrdinalIgnoreCase) ||
544+
!rawValue.EndsWith("\"", StringComparison.OrdinalIgnoreCase))
545+
{
546+
Context.OnError(startLocation,
547+
RazorResources.FormatParseError_DirectiveMustBeSurroundedByQuotes(keyword));
548+
}
549+
else
550+
{
551+
// Set up code generation
552+
Span.CodeGenerator = codeGeneratorBuilder(rawValue.Trim('"'));
553+
}
554+
}
555+
556+
// Output the span and finish the block
557+
CompleteBlock();
558+
Output(SpanKind.Code);
559+
}
501560
}
502561
}

src/Microsoft.AspNet.Razor/Parser/RazorParser.cs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,16 @@ namespace Microsoft.AspNet.Razor.Parser
1717
{
1818
public class RazorParser
1919
{
20-
public RazorParser(ParserBase codeParser, ParserBase markupParser)
21-
{
22-
if (codeParser == null)
23-
{
24-
throw new ArgumentNullException("codeParser");
25-
}
26-
if (markupParser == null)
27-
{
28-
throw new ArgumentNullException("markupParser");
29-
}
20+
private ITagHelperDescriptorResolver _tagHelperDescriptorResolver;
3021

22+
public RazorParser([NotNull] ITagHelperDescriptorResolver tagHelperDescriptorResolver,
23+
[NotNull] ParserBase codeParser,
24+
[NotNull] ParserBase markupParser)
25+
{
26+
_tagHelperDescriptorResolver = tagHelperDescriptorResolver;
3127
MarkupParser = markupParser;
3228
CodeParser = codeParser;
3329

34-
// TODO: As part of https://github.com/aspnet/Razor/issues/111 and
35-
// https://github.com/aspnet/Razor/issues/112 pull the provider from some sort of tag helper locator
36-
// object.
37-
var provider = new TagHelperDescriptorProvider(Enumerable.Empty<TagHelperDescriptor>());
38-
3930
Optimizers = new List<ISyntaxTreeRewriter>()
4031
{
4132
// TODO: Modify the below WhiteSpaceRewriter & ConditionalAttributeCollapser to handle
@@ -45,8 +36,6 @@ public RazorParser(ParserBase codeParser, ParserBase markupParser)
4536
new WhiteSpaceRewriter(MarkupParser.BuildSpan),
4637
// Collapse conditional attributes where the entire value is literal
4738
new ConditionalAttributeCollapser(MarkupParser.BuildSpan),
48-
// Enables tag helpers
49-
new TagHelperParseTreeRewriter(provider),
5039
};
5140
}
5241

@@ -154,6 +143,13 @@ private ParserResults ParseCore(ITextDocument input)
154143
current = rewriter.Rewrite(current);
155144
}
156145

146+
var tagHelperRegistrationVisitor = new TagHelperRegistrationVisitor(_tagHelperDescriptorResolver);
147+
var tagHelperProvider = tagHelperRegistrationVisitor.CreateProvider(current);
148+
149+
var tagHelperParseTreeRewriter = new TagHelperParseTreeRewriter(tagHelperProvider);
150+
// Rewrite the document to utilize tag helpers
151+
current = tagHelperParseTreeRewriter.Rewrite(current);
152+
157153
// Link the leaf nodes into a chain
158154
Span prev = null;
159155
foreach (Span node in current.Flatten())

src/Microsoft.AspNet.Razor/Parser/SyntaxConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public static class SyntaxConstants
1818
public static class CSharp
1919
{
2020
public static readonly int UsingKeywordLength = 5;
21+
public static readonly string AddTagHelper = "addtaghelper";
2122
public static readonly string InheritsKeyword = "inherits";
2223
public static readonly string FunctionsKeyword = "functions";
2324
public static readonly string SectionKeyword = "section";

0 commit comments

Comments
 (0)