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

Commit 51f5221

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 7399531 commit 51f5221

19 files changed

+381
-25
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
@@ -56,7 +56,7 @@ public override CodeBuilderResult Build()
5656

5757
new CSharpHelperVisitor(csharpCodeVisitor, writer, Context).Accept(Tree.Chunks);
5858
new CSharpTypeMemberVisitor(csharpCodeVisitor, writer, Context).Accept(Tree.Chunks);
59-
new CSharpDesignTimeHelpersVisitor(writer, Context).AcceptTree(Tree);
59+
new CSharpDesignTimeHelpersVisitor(csharpCodeVisitor, writer, Context).AcceptTree(Tree);
6060
new CSharpTagHelperFieldDeclarationVisitor(writer, Context).Accept(Tree.Chunks);
6161

6262
BuildConstructor(writer);

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class CSharpCodeVisitor : CodeVisitor<CSharpCodeWriter>
1717
private const string TemplateWriterName = "__razor_template_writer";
1818

1919
private CSharpPaddingBuilder _paddingBuilder;
20+
private CSharpTagHelperCodeRenderer _tagHelperCodeRenderer;
2021

2122
public CSharpCodeVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
2223
: base(writer, context)
@@ -25,7 +26,22 @@ public CSharpCodeVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
2526
TagHelperRenderer = new CSharpTagHelperCodeRenderer(this, writer, context);
2627
}
2728

28-
public CSharpTagHelperCodeRenderer TagHelperRenderer { get; set; }
29+
public CSharpTagHelperCodeRenderer TagHelperRenderer
30+
{
31+
get
32+
{
33+
return _tagHelperCodeRenderer;
34+
}
35+
set
36+
{
37+
if (value == null)
38+
{
39+
throw new ArgumentNullException(nameof(TagHelperRenderer));
40+
}
41+
42+
_tagHelperCodeRenderer = value;
43+
}
44+
}
2945

3046
protected override void Visit(TagHelperChunk chunk)
3147
{
@@ -472,7 +488,7 @@ private bool ShouldGenerateInstrumentationForExpressions()
472488
return Context.Host.EnableInstrumentation &&
473489
Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput;
474490
}
475-
491+
476492
private CSharpCodeWriter RenderPreWriteStart()
477493
{
478494
return RenderPreWriteStart(Writer, Context);

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

Lines changed: 40 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
{
811
internal const string InheritsHelper = "__inheritsHelper";
912
internal const string DesignTimeHelperMethodName = "__RazorDesignTimeHelpers__";
1013

14+
private const string TagHelperDirectiveSyntaxHelper = "__tagHelperDirectiveSyntaxHelper";
1115
private const int DisableVariableNamingWarnings = 219;
1216

13-
public CSharpDesignTimeHelpersVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
14-
: base(writer, context) { }
17+
private readonly CSharpCodeVisitor _csharpCodeVisitor;
18+
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,29 @@ 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("string", TagHelperDirectiveSyntaxHelper, "null");
70+
}
71+
72+
Writer.WriteStartAssignment(TagHelperDirectiveSyntaxHelper);
73+
74+
// The parsing mechanism for the AddTagHelperChunk (CSharpCodeParser.TagHelperDirective()) removes quotes
75+
// that surround the chunk.LookupText.
76+
_csharpCodeVisitor.CreateExpressionCodeMapping(
77+
string.Format(
78+
CultureInfo.InvariantCulture,
79+
"\"{0}\"", chunk.LookupText),
80+
chunk);
81+
82+
Writer.WriteLine(";");
83+
}
4684
}
4785
}

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
@@ -32,6 +32,9 @@ protected override void Visit(DynamicCodeAttributeChunk chunk)
3232
protected override void Visit(TagHelperChunk chunk)
3333
{
3434
}
35+
protected override void Visit(AddTagHelperChunk chunk)
36+
{
37+
}
3538
protected override void Visit(LiteralCodeAttributeChunk chunk)
3639
{
3740
}
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"/> used to look up <see cref="TagHelpers.TagHelperDescriptor"/>s.
8+
/// </summary>
9+
public class AddTagHelperChunk : Chunk
10+
{
11+
/// <summary>
12+
/// Text used to look up <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
@@ -39,6 +39,14 @@ public void AddChunk(Chunk chunk, SyntaxTreeNode association, bool topLevel = fa
3939
}
4040
}
4141

42+
public void AddAddTagHelperChunk(string lookupText, SyntaxTreeNode association)
43+
{
44+
AddChunk(new AddTagHelperChunk
45+
{
46+
LookupText = lookupText
47+
}, association);
48+
}
49+
4250
public void AddLiteralChunk(string literal, SyntaxTreeNode association)
4351
{
4452
// If the previous chunk was also a LiteralChunk, append the content of the current node to the previous one.
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"/> 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+
/// Text used to look up <see cref="TagHelpers.TagHelperDescriptor"/>s.
18+
/// </param>
19+
public AddTagHelperCodeGenerator(string lookupText)
20+
{
21+
LookupText = lookupText;
22+
}
23+
24+
/// <summary>
25+
/// Text used to look up <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: 69 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.AddTagHelperKeyword);
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.AddTagHelperKeyword, (lookupText) =>
34+
{
35+
return new AddTagHelperCodeGenerator(lookupText);
36+
});
37+
}
38+
3039
protected virtual void LayoutDirective()
3140
{
3241
AssertDirective(SyntaxConstants.CSharp.LayoutKeyword);
@@ -498,5 +507,65 @@ 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+
524+
// If we found whitespace then any content placed within the whitespace MAY cause a destructive change
525+
// to the document. We can't accept it.
526+
Output(SpanKind.MetaCode, foundWhitespace ? AcceptedCharacters.None : AcceptedCharacters.Any);
527+
528+
if (EndOfFile || At(CSharpSymbolType.NewLine))
529+
{
530+
Context.OnError(CurrentLocation, RazorResources.FormatParseError_DirectiveMustHaveValue(keyword));
531+
}
532+
else
533+
{
534+
// Need to grab the current location before we accept until the end of the line.
535+
var startLocation = CurrentLocation;
536+
537+
// Parse to the end of the line. Essentially accepts anything until end of line, comments, invalid code
538+
// etc.
539+
AcceptUntil(CSharpSymbolType.NewLine);
540+
541+
// Pull out the value minus the spaces at the end
542+
var rawValue = Span.GetContent().Value.TrimEnd();
543+
var startsWithQuote = rawValue.StartsWith("\"", StringComparison.OrdinalIgnoreCase);
544+
545+
// If the value starts with a quote then we should generate appropriate C# code to colorize the value.
546+
if (startsWithQuote)
547+
{
548+
// Set up code generation
549+
// The generated chunk of this code generator is picked up by CSharpDesignTimeHelpersVisitor which
550+
// renders the C# to colorize the user provided value. We trim the quotes around the user's value
551+
// so when we render the code we can project the users value into double quotes to not invoke C#
552+
// IntelliSense.
553+
Span.CodeGenerator = codeGeneratorBuilder(rawValue.Trim('"'));
554+
}
555+
556+
// We expect the directive to be surrounded in quotes.
557+
// The format for taghelper directives are: @directivename "SomeValue"
558+
if (!startsWithQuote ||
559+
!rawValue.EndsWith("\"", StringComparison.OrdinalIgnoreCase))
560+
{
561+
Context.OnError(startLocation,
562+
RazorResources.FormatParseError_DirectiveMustBeSurroundedByQuotes(keyword));
563+
}
564+
}
565+
566+
// Output the span and finish the block
567+
CompleteBlock();
568+
Output(SpanKind.Code);
569+
}
501570
}
502571
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public partial class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer, C
1919

2020
internal static ISet<string> DefaultKeywords = new HashSet<string>()
2121
{
22+
SyntaxConstants.CSharp.AddTagHelperKeyword,
2223
"if",
2324
"do",
2425
"try",

0 commit comments

Comments
 (0)