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

Commit 0dac4a0

Browse files
author
N. Taylor Mullen
committed
Create TagHelper specific C# code Generation.
- Added TagHelperChunk generation. - Added CSharp visitors to understand TagHelperChunks and render corresponding C# code. - Refactored some code in the CSharpCodeVisitor so it could be utilized in other classes. - Added a CSharpFieldDeclarationVisitor to render declaration pieces for TagHelper's - Added metadata to represent specific TagHelper code generation constructs. #72
1 parent dc3a520 commit 0dac4a0

23 files changed

+1160
-109
lines changed

src/Microsoft.AspNet.Razor/Common/HashCodeCombiner.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System.Collections;
5+
using System.Collections.Generic;
56

67
namespace Microsoft.Internal.Web.Utils
78
{
@@ -46,6 +47,11 @@ public HashCodeCombiner Add(object o)
4647
return this;
4748
}
4849

50+
public HashCodeCombiner Add<TValue>(TValue value, IEqualityComparer<TValue> comparer)
51+
{
52+
return Add(comparer.GetHashCode(value));
53+
}
54+
4955
public static HashCodeCombiner Start()
5056
{
5157
return new HashCodeCombiner();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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;
54
using System.Collections.Generic;
65
using System.Linq;
76
using System.Threading.Tasks;
@@ -56,6 +55,7 @@ public override CodeBuilderResult Build()
5655
new CSharpHelperVisitor(writer, Context).Accept(Tree.Chunks);
5756
new CSharpTypeMemberVisitor(writer, Context).Accept(Tree.Chunks);
5857
new CSharpDesignTimeHelpersVisitor(writer, Context).AcceptTree(Tree);
58+
new CSharpTagHelperFieldDeclarationVisitor(writer, Context).Accept(Tree.Chunks);
5959

6060
BuildConstructor(writer);
6161

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
1212
{
1313
public class CSharpCodeWriter : CodeWriter
1414
{
15+
private const string InstanceMethodFormat = "{0}.{1}";
16+
1517
public CSharpCodeWriter()
1618
{
1719
LineMappingManager = new LineMappingManager();
@@ -208,7 +210,7 @@ public CSharpCodeWriter WriteStartMethodInvocation(string methodName)
208210
return WriteStartMethodInvocation(methodName, new string[0]);
209211
}
210212

211-
public CSharpCodeWriter WriteStartMethodInvocation(string methodName, string[] genericArguments)
213+
public CSharpCodeWriter WriteStartMethodInvocation(string methodName, params string[] genericArguments)
212214
{
213215
Write(methodName);
214216

@@ -235,6 +237,33 @@ public CSharpCodeWriter WriteEndMethodInvocation(bool endLine)
235237
return this;
236238
}
237239

240+
// Writes a method invocation for the given instance name.
241+
public CSharpCodeWriter WriteInstanceMethodInvocation([NotNull] string instanceName,
242+
[NotNull] string methodName,
243+
params string[] parameters)
244+
{
245+
return WriteInstanceMethodInvocation(instanceName, methodName, endLine: true, parameters: parameters);
246+
}
247+
248+
// Writes a method invocation for the given instance name.
249+
public CSharpCodeWriter WriteInstanceMethodInvocation([NotNull] string instanceName,
250+
[NotNull] string methodName,
251+
bool endLine,
252+
params string[] parameters)
253+
{
254+
return WriteMethodInvocation(
255+
string.Format(CultureInfo.InvariantCulture, InstanceMethodFormat, instanceName, methodName),
256+
endLine,
257+
parameters);
258+
}
259+
260+
public CSharpCodeWriter WriteStartInstanceMethodInvocation([NotNull] string instanceName,
261+
[NotNull] string methodName)
262+
{
263+
return WriteStartMethodInvocation(
264+
string.Format(CultureInfo.InvariantCulture, InstanceMethodFormat, instanceName, methodName));
265+
}
266+
238267
public CSharpCodeWriter WriteMethodInvocation(string methodName, params string[] parameters)
239268
{
240269
return WriteMethodInvocation(methodName, endLine: true, parameters: parameters);

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

Lines changed: 486 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
88
using Microsoft.AspNet.Razor.Text;
9+
using Microsoft.AspNet.Razor.TagHelpers;
910

1011
namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
1112
{
@@ -16,11 +17,23 @@ public class CSharpCodeVisitor : CodeVisitor<CSharpCodeWriter>
1617
private const string TemplateWriterName = "__razor_template_writer";
1718

1819
private CSharpPaddingBuilder _paddingBuilder;
20+
private CSharpTagHelperCodeRenderer _tagHelperCodeRenderer;
1921

2022
public CSharpCodeVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
2123
: base(writer, context)
2224
{
2325
_paddingBuilder = new CSharpPaddingBuilder(context.Host);
26+
_tagHelperCodeRenderer = new CSharpTagHelperCodeRenderer(this, writer, context);
27+
}
28+
29+
protected override void Visit(TagHelperChunk chunk)
30+
{
31+
_tagHelperCodeRenderer.RenderTagHelper(chunk);
32+
}
33+
34+
protected override void Visit(ChunkBlock chunk)
35+
{
36+
Accept(chunk.Children);
2437
}
2538

2639
protected override void Visit(SetLayoutChunk chunk)
@@ -72,21 +85,12 @@ protected override void Visit(ResolveUrlChunk chunk)
7285
{
7386
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
7487
{
75-
if (!String.IsNullOrEmpty(Context.TargetWriterName))
76-
{
77-
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteLiteralToMethodName)
78-
.Write(Context.TargetWriterName)
79-
.WriteParameterSeparator();
80-
}
81-
else
82-
{
83-
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteLiteralMethodName);
84-
}
88+
RenderPreWriteStart();
8589
}
8690

8791
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.ResolveUrlMethodName)
88-
.WriteStringLiteral(chunk.Url)
89-
.WriteEndMethodInvocation(endLine: false);
92+
.WriteStringLiteral(chunk.Url)
93+
.WriteEndMethodInvocation(endLine: false);
9094

9195
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
9296
{
@@ -114,19 +118,17 @@ protected override void Visit(LiteralChunk chunk)
114118

115119
if (!String.IsNullOrEmpty(chunk.Text) && !Context.Host.DesignTimeMode)
116120
{
117-
if (!String.IsNullOrEmpty(Context.TargetWriterName))
121+
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
118122
{
119-
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteLiteralToMethodName)
120-
.Write(Context.TargetWriterName)
121-
.WriteParameterSeparator();
123+
RenderPreWriteStart();
122124
}
123-
else
125+
126+
Writer.WriteStringLiteral(chunk.Text);
127+
128+
if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
124129
{
125-
Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteLiteralMethodName);
130+
Writer.WriteEndMethodInvocation();
126131
}
127-
128-
Writer.WriteStringLiteral(chunk.Text)
129-
.WriteEndMethodInvocation();
130132
}
131133

132134
if (Context.Host.EnableInstrumentation)
@@ -187,10 +189,10 @@ protected override void Visit(DynamicCodeAttributeChunk chunk)
187189

188190
Writer.WriteParameterSeparator()
189191
.Write(chunk.Start.AbsoluteIndex.ToString(CultureInfo.CurrentCulture))
190-
.WriteEndMethodInvocation(false)
192+
.WriteEndMethodInvocation(endLine: false)
191193
.WriteParameterSeparator()
192-
.WriteBooleanLiteral(false)
193-
.WriteEndMethodInvocation(false);
194+
.WriteBooleanLiteral(value: false)
195+
.WriteEndMethodInvocation(endLine: false);
194196
}
195197
else
196198
{
@@ -469,5 +471,26 @@ private bool ShouldGenerateInstrumentationForExpressions()
469471
return Context.Host.EnableInstrumentation &&
470472
Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput;
471473
}
474+
475+
private CSharpCodeWriter RenderPreWriteStart()
476+
{
477+
return RenderPreWriteStart(Writer, Context);
478+
}
479+
480+
public static CSharpCodeWriter RenderPreWriteStart(CSharpCodeWriter writer, CodeBuilderContext context)
481+
{
482+
if (!string.IsNullOrEmpty(context.TargetWriterName))
483+
{
484+
writer.WriteStartMethodInvocation(context.Host.GeneratedClassContext.WriteLiteralToMethodName)
485+
.Write(context.TargetWriterName)
486+
.WriteParameterSeparator();
487+
}
488+
else
489+
{
490+
writer.WriteStartMethodInvocation(context.Host.GeneratedClassContext.WriteLiteralMethodName);
491+
}
492+
493+
return writer;
494+
}
472495
}
473496
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 System;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
8+
namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
9+
{
10+
public class CSharpTagHelperFieldDeclarationVisitor : CodeVisitor<CSharpCodeWriter>
11+
{
12+
private readonly HashSet<string> _declaredTagHelpers;
13+
private readonly GeneratedTagHelperContext _tagHelperContext;
14+
private bool _foundTagHelpers;
15+
16+
public CSharpTagHelperFieldDeclarationVisitor([NotNull] CSharpCodeWriter writer,
17+
[NotNull] CodeBuilderContext context)
18+
: base(writer, context)
19+
{
20+
_declaredTagHelpers = new HashSet<string>(StringComparer.Ordinal);
21+
_tagHelperContext = Context.Host.GeneratedClassContext.GeneratedTagHelperContext;
22+
}
23+
24+
protected override void Visit(TagHelperChunk chunk)
25+
{
26+
// We only want to setup tag helper manager fields if there are tag helpers, and only once
27+
if (!_foundTagHelpers)
28+
{
29+
_foundTagHelpers = true;
30+
31+
Writer.WriteLineHiddenDirective();
32+
33+
WritePrivateField(typeof(TextWriter).FullName,
34+
CSharpTagHelperCodeRenderer.StringValueBufferVariableName,
35+
value: null);
36+
37+
WritePrivateField(_tagHelperContext.ExecutionContextTypeName,
38+
CSharpTagHelperCodeRenderer.ExecutionContextVariableName,
39+
value: null);
40+
41+
WritePrivateField(_tagHelperContext.RunnerTypeName,
42+
CSharpTagHelperCodeRenderer.RunnerVariableName,
43+
"new " + _tagHelperContext.RunnerTypeName + "()");
44+
45+
WritePrivateField(_tagHelperContext.ScopeManagerTypeName,
46+
CSharpTagHelperCodeRenderer.ScopeManagerVariableName,
47+
"new " + _tagHelperContext.ScopeManagerTypeName + "()");
48+
}
49+
50+
foreach (var descriptor in chunk.Descriptors)
51+
{
52+
if (!_declaredTagHelpers.Contains(descriptor.TagHelperName))
53+
{
54+
_declaredTagHelpers.Add(descriptor.TagHelperName);
55+
56+
WritePrivateField(descriptor.TagHelperName,
57+
CSharpTagHelperCodeRenderer.GetVariableName(descriptor),
58+
value: null);
59+
}
60+
}
61+
62+
// We need to dive deeper to ensure we pick up any nested tag helpers.
63+
Accept(chunk.Children);
64+
}
65+
66+
public override void Accept(Chunk chunk)
67+
{
68+
var chunkBlock = chunk as ChunkBlock;
69+
70+
// If we're any ChunkBlock other than TagHelperChunk then we want to dive into its Children
71+
// to search for more TagHelperChunk chunks. This if-statement enables us to not override
72+
// each of the special ChunkBlock types and then dive into their children.
73+
if (chunkBlock != null && !(chunkBlock is TagHelperChunk))
74+
{
75+
Accept(chunkBlock.Children);
76+
}
77+
else
78+
{
79+
// If we're a TagHelperChunk or any other non ChunkBlock we ".Accept" it. This ensures
80+
// that our overriden Visit(TagHelperChunk) method gets called and is not skipped over.
81+
// If we're a non ChunkBlock or a TagHelperChunk then we want to just invoke the Visit
82+
// method for that given chunk (base.Accept indirectly calls the Visit method).
83+
base.Accept(chunk);
84+
}
85+
}
86+
87+
private void WritePrivateField(string type, string name, string value)
88+
{
89+
Writer.Write("private ")
90+
.WriteVariableDeclaration(type, name, value);
91+
}
92+
}
93+
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
99
{
1010
public class CSharpUsingVisitor : CodeVisitor<CSharpCodeWriter>
1111
{
12+
private const string TagHelpersRuntimeNamespace = "Microsoft.AspNet.Razor.Runtime.TagHelpers";
13+
14+
private bool _foundTagHelpers;
15+
1216
public CSharpUsingVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
1317
: base(writer, context)
1418
{
@@ -46,5 +50,20 @@ protected override void Visit(UsingChunk chunk)
4650
Writer.WriteLine(";");
4751
}
4852
}
53+
54+
protected override void Visit(TagHelperChunk chunk)
55+
{
56+
if (!_foundTagHelpers)
57+
{
58+
_foundTagHelpers = true;
59+
60+
if (!ImportedUsings.Contains(TagHelpersRuntimeNamespace))
61+
{
62+
// If we find TagHelpers then we need to add the TagHelper runtime namespace to our list of usings.
63+
Writer.WriteUsing(TagHelpersRuntimeNamespace);
64+
ImportedUsings.Add(TagHelpersRuntimeNamespace);
65+
}
66+
}
67+
}
4968
}
5069
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ public virtual void Accept(Chunk chunk)
5353
{
5454
Visit((StatementChunk)chunk);
5555
}
56+
else if(chunk is TagHelperChunk)
57+
{
58+
Visit((TagHelperChunk)chunk);
59+
}
5660
else if(chunk is SetLayoutChunk)
5761
{
5862
Visit((SetLayoutChunk)chunk);
@@ -110,6 +114,7 @@ public virtual void Accept(Chunk chunk)
110114
protected abstract void Visit(LiteralChunk chunk);
111115
protected abstract void Visit(ExpressionChunk chunk);
112116
protected abstract void Visit(StatementChunk chunk);
117+
protected abstract void Visit(TagHelperChunk chunk);
113118
protected abstract void Visit(UsingChunk chunk);
114119
protected abstract void Visit(ChunkBlock chunk);
115120
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
@@ -29,6 +29,9 @@ protected override void Visit(ChunkBlock chunk)
2929
protected override void Visit(DynamicCodeAttributeChunk chunk)
3030
{
3131
}
32+
protected override void Visit(TagHelperChunk chunk)
33+
{
34+
}
3235
protected override void Visit(LiteralCodeAttributeChunk chunk)
3336
{
3437
}

0 commit comments

Comments
 (0)