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

Commit

Permalink
Add @addtaghelper directive.
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
NTaylorMullen committed Oct 2, 2014
1 parent 5e074f7 commit 1e7f546
Show file tree
Hide file tree
Showing 17 changed files with 317 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public override CodeBuilderResult Build()

new CSharpHelperVisitor(csharpCodeVisitor, writer, Context).Accept(Tree.Chunks);
new CSharpTypeMemberVisitor(csharpCodeVisitor, writer, Context).Accept(Tree.Chunks);
new CSharpDesignTimeHelpersVisitor(writer, Context).AcceptTree(Tree);
new CSharpDesignTimeHelpersVisitor(csharpCodeVisitor, writer, Context).AcceptTree(Tree);
new CSharpTagHelperFieldDeclarationVisitor(writer, Context).Accept(Tree.Chunks);

BuildConstructor(writer);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
// 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.Diagnostics;
using System.Globalization;

namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
{
public class CSharpDesignTimeHelpersVisitor : CodeVisitor<CSharpCodeWriter>
{
private const string TagHelperDirectiveSyntaxHelper = "__tagHelperDirectiveSyntaxHelper";
internal const string InheritsHelper = "__inheritsHelper";
internal const string DesignTimeHelperMethodName = "__RazorDesignTimeHelpers__";
private static readonly string ObjectTypeString = typeof(object).ToString();

private const int DisableVariableNamingWarnings = 219;

public CSharpDesignTimeHelpersVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
: base(writer, context) { }
private CSharpCodeVisitor _csharpCodeVisitor;
private bool _initializedTagHelperDirectiveSyntaxHelper;

public CSharpDesignTimeHelpersVisitor([NotNull] CSharpCodeVisitor csharpCodeVisitor,
[NotNull] CSharpCodeWriter writer,
[NotNull] CodeBuilderContext context)

: base(writer, context)
{
_csharpCodeVisitor = csharpCodeVisitor;
}

public void AcceptTree(CodeTree tree)
{
Expand Down Expand Up @@ -43,5 +57,27 @@ protected override void Visit(SetBaseTypeChunk chunk)
}
}
}

protected override void Visit(AddTagHelperChunk chunk)
{
// We should always be in design time mode because of the calling AcceptTree method verification.
Debug.Assert(Context.Host.DesignTimeMode);

if (!_initializedTagHelperDirectiveSyntaxHelper)
{
_initializedTagHelperDirectiveSyntaxHelper = true;
Writer.WriteVariableDeclaration(ObjectTypeString, TagHelperDirectiveSyntaxHelper, "null");
}

Writer.WriteStartAssignment(TagHelperDirectiveSyntaxHelper);

_csharpCodeVisitor.CreateExpressionCodeMapping(
string.Format(
CultureInfo.InvariantCulture,
"\"{0}\"", chunk.LookupText),
chunk);

Writer.WriteLine(";");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public virtual void Accept(Chunk chunk)
{
Visit((TagHelperChunk)chunk);
}
else if(chunk is AddTagHelperChunk)
{
Visit((AddTagHelperChunk)chunk);
}
else if(chunk is SetLayoutChunk)
{
Visit((SetLayoutChunk)chunk);
Expand Down Expand Up @@ -115,6 +119,7 @@ public virtual void Accept(Chunk chunk)
protected abstract void Visit(ExpressionChunk chunk);
protected abstract void Visit(StatementChunk chunk);
protected abstract void Visit(TagHelperChunk chunk);
protected abstract void Visit(AddTagHelperChunk chunk);
protected abstract void Visit(UsingChunk chunk);
protected abstract void Visit(ChunkBlock chunk);
protected abstract void Visit(DynamicCodeAttributeChunk chunk);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ protected override void Visit(DynamicCodeAttributeChunk chunk)
protected override void Visit(TagHelperChunk chunk)
{
}
protected override void Visit(AddTagHelperChunk chunk)
{
}
protected override void Visit(LiteralCodeAttributeChunk chunk)
{
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 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"/> that is used to lookup <see cref="TagHelpers.TagHelperDescriptor"/>s.
/// </summary>
public class AddTagHelperChunk : Chunk
{
/// <summary>
/// Arbitrary text used to lookup <see cref="TagHelpers.TagHelperDescriptor"/>s.
/// </summary>
public string LookupText { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ public void AddChunk(Chunk chunk, SyntaxTreeNode association, bool topLevel = fa
}
}

public void AddAddTagHelperChunk(string lookupText, SyntaxTreeNode association)
{
AddChunk(new AddTagHelperChunk
{
LookupText = lookupText
}, association);
}

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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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"/> that is responsible for generating <see cref="Compiler.AddTagHelperChunk"/>s.
/// </summary>
public class AddTagHelperCodeGenerator : SpanCodeGenerator
{
/// <summary>
/// Instantiates a new <see cref="AddTagHelperCodeGenerator"/>.
/// </summary>
/// <param name="lookupText">
/// Arbitrary text used to lookup <see cref="TagHelpers.TagHelperDescriptor"/>s.
/// </param>
public AddTagHelperCodeGenerator(string lookupText)
{
LookupText = lookupText;
}

/// <summary>
/// Arbitrary text used to lookup <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);
}
}
}
59 changes: 59 additions & 0 deletions src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Directives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public partial class CSharpCodeParser
{
private void SetupDirectives()
{
MapDirectives(AddTagHelperDirective, SyntaxConstants.CSharp.AddTagHelper);
MapDirectives(InheritsDirective, SyntaxConstants.CSharp.InheritsKeyword);
MapDirectives(FunctionsDirective, SyntaxConstants.CSharp.FunctionsKeyword);
MapDirectives(SectionDirective, SyntaxConstants.CSharp.SectionKeyword);
Expand All @@ -27,6 +28,14 @@ private void SetupDirectives()
MapDirectives(SessionStateDirective, SyntaxConstants.CSharp.SessionStateKeyword);
}

protected virtual void AddTagHelperDirective()
{
TagHelperDirective(SyntaxConstants.CSharp.AddTagHelper, (lookupText) =>
{
return new AddTagHelperCodeGenerator(lookupText);
});
}

protected virtual void LayoutDirective()
{
AssertDirective(SyntaxConstants.CSharp.LayoutKeyword);
Expand Down Expand Up @@ -498,5 +507,55 @@ protected void BaseTypeDirective(string noTypeNameError, Func<string, SpanCodeGe
CompleteBlock();
Output(SpanKind.Code);
}

private void TagHelperDirective(string keyword, Func<string, SpanCodeGenerator> codeGeneratorBuilder)
{
AssertDirective(keyword);

// Accept the directive name
AcceptAndMoveNext();

// Set the block type
Context.CurrentBlock.Type = BlockType.Directive;

var foundWhitespace = At(CSharpSymbolType.WhiteSpace);
AcceptWhile(CSharpSymbolType.WhiteSpace);
// If we found whitespace then any content placed within the whitespace MAY cause a destructive change
// to the document. We can't accept it.
Output(SpanKind.MetaCode, foundWhitespace ? AcceptedCharacters.None : AcceptedCharacters.Any);

if (EndOfFile || At(CSharpSymbolType.NewLine))
{
Context.OnError(CurrentLocation, RazorResources.FormatParseError_DirectiveMustHaveValue(keyword));
}
else
{
// Need to grab the current location before we accept until the end of the line.
var startLocation = CurrentLocation;

// Parse to the end of the line
AcceptUntil(CSharpSymbolType.NewLine);

// Pull out the value minus the spaces at the end
var rawValue = Span.GetContent().Value.TrimEnd();

// We expect the directive to be surrounded in quotes.
if (!rawValue.StartsWith("\"", StringComparison.OrdinalIgnoreCase) ||
!rawValue.EndsWith("\"", StringComparison.OrdinalIgnoreCase))
{
Context.OnError(startLocation,
RazorResources.FormatParseError_DirectiveMustBeSurroundedByQuotes(keyword));
}
else
{
// Set up code generation
Span.CodeGenerator = codeGeneratorBuilder(rawValue.Trim('"'));
}
}

// Output the span and finish the block
CompleteBlock();
Output(SpanKind.Code);
}
}
}
30 changes: 13 additions & 17 deletions src/Microsoft.AspNet.Razor/Parser/RazorParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,16 @@ namespace Microsoft.AspNet.Razor.Parser
{
public class RazorParser
{
public RazorParser(ParserBase codeParser, ParserBase markupParser)
{
if (codeParser == null)
{
throw new ArgumentNullException("codeParser");
}
if (markupParser == null)
{
throw new ArgumentNullException("markupParser");
}
private ITagHelperDescriptorResolver _tagHelperDescriptorResolver;

public RazorParser([NotNull] ITagHelperDescriptorResolver tagHelperDescriptorResolver,
[NotNull] ParserBase codeParser,
[NotNull] ParserBase markupParser)
{
_tagHelperDescriptorResolver = tagHelperDescriptorResolver;
MarkupParser = markupParser;
CodeParser = codeParser;

// TODO: As part of https://github.com/aspnet/Razor/issues/111 and
// https://github.com/aspnet/Razor/issues/112 pull the provider from some sort of tag helper locator
// object.
var provider = new TagHelperDescriptorProvider(Enumerable.Empty<TagHelperDescriptor>());

Optimizers = new List<ISyntaxTreeRewriter>()
{
// TODO: Modify the below WhiteSpaceRewriter & ConditionalAttributeCollapser to handle
Expand All @@ -45,8 +36,6 @@ public RazorParser(ParserBase codeParser, ParserBase markupParser)
new WhiteSpaceRewriter(MarkupParser.BuildSpan),
// Collapse conditional attributes where the entire value is literal
new ConditionalAttributeCollapser(MarkupParser.BuildSpan),
// Enables tag helpers
new TagHelperParseTreeRewriter(provider),
};
}

Expand Down Expand Up @@ -153,6 +142,13 @@ private ParserResults ParseCore(ITextDocument input)
current = rewriter.Rewrite(current);
}

var tagHelperRegistrationVisitor = new TagHelperRegistrationVisitor(_tagHelperDescriptorResolver);
var tagHelperProvider = tagHelperRegistrationVisitor.CreateProvider(current);

var tagHelperParseTreeRewriter = new TagHelperParseTreeRewriter(tagHelperProvider);
// Rewrite the document to utilize tag helpers
current = tagHelperParseTreeRewriter.Rewrite(current);

// Link the leaf nodes into a chain
Span prev = null;
foreach (Span node in current.Flatten())
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.AspNet.Razor/Parser/SyntaxConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static class SyntaxConstants
public static class CSharp
{
public static readonly int UsingKeywordLength = 5;
public static readonly string AddTagHelper = "addtaghelper";
public static readonly string InheritsKeyword = "inherits";
public static readonly string FunctionsKeyword = "functions";
public static readonly string SectionKeyword = "section";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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.Collections.Generic;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.TagHelpers;

namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
{
public class TagHelperRegistrationVisitor : ParserVisitor
{
private HashSet<TagHelperDescriptor> _descriptors;
private ITagHelperDescriptorResolver _descriptorResolver;

public TagHelperRegistrationVisitor(ITagHelperDescriptorResolver descriptorResolver)
{
_descriptorResolver = descriptorResolver;
}

public TagHelperDescriptorProvider CreateProvider(Block root)
{
_descriptors = new HashSet<TagHelperDescriptor>(TagHelperDescriptorComparer.Default);

// This will recurse through the syntax tree.
VisitBlock(root);

return new TagHelperDescriptorProvider(_descriptors);
}

public override void VisitSpan(Span span)
{
// We're only interested in spans with AddTagHelperCodeGenerator's.
if (span.CodeGenerator is AddTagHelperCodeGenerator)
{
var addGenerator = (AddTagHelperCodeGenerator)span.CodeGenerator;

// Lookup all the descriptors associated with the "LookupText".
var descriptors = _descriptorResolver.Resolve(addGenerator.LookupText);

// Add all the found descriptors to our HashSet. Duplicates are handled by the HashSet.
foreach (var descriptor in descriptors)
{
_descriptors.Add(descriptor);
}
}
}
}
}
Loading

0 comments on commit 1e7f546

Please sign in to comment.