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 9, 2014
1 parent 05d8193 commit b67b8da
Show file tree
Hide file tree
Showing 19 changed files with 381 additions and 25 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
Expand Up @@ -17,6 +17,7 @@ public class CSharpCodeVisitor : CodeVisitor<CSharpCodeWriter>
private const string TemplateWriterName = "__razor_template_writer";

private CSharpPaddingBuilder _paddingBuilder;
private CSharpTagHelperCodeRenderer _tagHelperCodeRenderer;

public CSharpCodeVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
: base(writer, context)
Expand All @@ -25,7 +26,22 @@ public CSharpCodeVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
TagHelperRenderer = new CSharpTagHelperCodeRenderer(this, writer, context);
}

public CSharpTagHelperCodeRenderer TagHelperRenderer { get; set; }
public CSharpTagHelperCodeRenderer TagHelperRenderer
{
get
{
return _tagHelperCodeRenderer;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(TagHelperRenderer));
}

_tagHelperCodeRenderer = value;
}
}

protected override void Visit(TagHelperChunk chunk)
{
Expand Down Expand Up @@ -485,7 +501,7 @@ private bool ShouldGenerateInstrumentationForExpressions()
return Context.Host.EnableInstrumentation &&
Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput;
}

private CSharpCodeWriter RenderPreWriteStart()
{
return RenderPreWriteStart(Writer, Context);
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>
{
internal const string InheritsHelper = "__inheritsHelper";
internal const string DesignTimeHelperMethodName = "__RazorDesignTimeHelpers__";

private const string TagHelperDirectiveSyntaxHelper = "__tagHelperDirectiveSyntaxHelper";
private const int DisableVariableNamingWarnings = 219;

public CSharpDesignTimeHelpersVisitor(CSharpCodeWriter writer, CodeBuilderContext context)
: base(writer, context) { }
private readonly 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,29 @@ 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("string", TagHelperDirectiveSyntaxHelper, "null");
}

Writer.WriteStartAssignment(TagHelperDirectiveSyntaxHelper);

// The parsing mechanism for the AddTagHelperChunk (CSharpCodeParser.TagHelperDirective()) removes quotes
// that surround the chunk.LookupText.
_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 @@ -32,6 +32,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"/> used to look up <see cref="TagHelpers.TagHelperDescriptor"/>s.
/// </summary>
public class AddTagHelperChunk : Chunk
{
/// <summary>
/// Text used to look up <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"/> 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);
}
}
}
69 changes: 69 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.AddTagHelperKeyword);
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.AddTagHelperKeyword, (lookupText) =>
{
return new AddTagHelperCodeGenerator(lookupText);
});
}

protected virtual void LayoutDirective()
{
AssertDirective(SyntaxConstants.CSharp.LayoutKeyword);
Expand Down Expand Up @@ -498,5 +507,65 @@ 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. Essentially accepts anything until end of line, comments, invalid code
// etc.
AcceptUntil(CSharpSymbolType.NewLine);

// Pull out the value minus the spaces at the end
var rawValue = Span.GetContent().Value.TrimEnd();
var startsWithQuote = rawValue.StartsWith("\"", StringComparison.OrdinalIgnoreCase);

// If the value starts with a quote then we should generate appropriate C# code to colorize the value.
if (startsWithQuote)
{
// Set up code generation
// The generated chunk of this code generator is picked up by CSharpDesignTimeHelpersVisitor which
// 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('"'));
}

// We expect the directive to be surrounded in quotes.
// The format for taghelper directives are: @directivename "SomeValue"
if (!startsWithQuote ||
!rawValue.EndsWith("\"", StringComparison.OrdinalIgnoreCase))
{
Context.OnError(startLocation,
RazorResources.FormatParseError_DirectiveMustBeSurroundedByQuotes(keyword));
}
}

// Output the span and finish the block
CompleteBlock();
Output(SpanKind.Code);
}
}
}
1 change: 1 addition & 0 deletions src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public partial class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer, C

internal static ISet<string> DefaultKeywords = new HashSet<string>()
{
SyntaxConstants.CSharp.AddTagHelperKeyword,
"if",
"do",
"try",
Expand Down
33 changes: 16 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] ParserBase codeParser,
[NotNull] ParserBase markupParser,
ITagHelperDescriptorResolver tagHelperDescriptorResolver)
{
_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,16 @@ private ParserResults ParseCore(ITextDocument input)
current = rewriter.Rewrite(current);
}

if (_tagHelperDescriptorResolver != null)
{
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 AddTagHelperKeyword = "addtaghelper";
public static readonly string InheritsKeyword = "inherits";
public static readonly string FunctionsKeyword = "functions";
public static readonly string SectionKeyword = "section";
Expand Down
Loading

0 comments on commit b67b8da

Please sign in to comment.