Change TagHelpers to work in partial parsing.
- Prior to this change any query for ownership within a TagHelper would never succeed. - TagHelpers structure can be influenced greatly by changes to the tags start/end body; therefore, only allowed modifications to TagHelper attribute values to avoid complexity and enable the more widespread scenario of a user typing C# code in an attribute value. - Updated existing tests to reflect the new edit handlers that were added to TagHelper attributes. - Added partial parsing tests to verify partial parses succeed/fail when expected. #792
This commit is contained in:
parent
82c9c40709
commit
8d7c51bd60
|
|
@ -154,11 +154,13 @@ namespace Microsoft.AspNetCore.Razor.Parser.SyntaxTree
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Span LocateOwner(TextChange change)
|
public virtual Span LocateOwner(TextChange change) => LocateOwner(change, Children);
|
||||||
|
|
||||||
|
protected static Span LocateOwner(TextChange change, IEnumerable<SyntaxTreeNode> elements)
|
||||||
{
|
{
|
||||||
// Ask each child recursively
|
// Ask each child recursively
|
||||||
Span owner = null;
|
Span owner = null;
|
||||||
foreach (SyntaxTreeNode element in Children)
|
foreach (var element in elements)
|
||||||
{
|
{
|
||||||
var span = element as Span;
|
var span = element as Span;
|
||||||
if (span == null)
|
if (span == null)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
||||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||||
|
using Microsoft.AspNetCore.Razor.Editor;
|
||||||
using Microsoft.AspNetCore.Razor.Parser.Internal;
|
using Microsoft.AspNetCore.Razor.Parser.Internal;
|
||||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
|
|
@ -468,7 +469,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
spanBuilder.ChunkGenerator = new MarkupChunkGenerator();
|
spanBuilder.ChunkGenerator = new MarkupChunkGenerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
spanBuilder.Kind = SpanKind.Code;
|
ConfigureNonStringAttribute(spanBuilder);
|
||||||
|
|
||||||
span = spanBuilder.Build();
|
span = spanBuilder.Build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -628,7 +630,7 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
// SyntaxTreeNode reflects that.
|
// SyntaxTreeNode reflects that.
|
||||||
if (isBoundNonStringAttribute)
|
if (isBoundNonStringAttribute)
|
||||||
{
|
{
|
||||||
builder.Kind = SpanKind.Code;
|
ConfigureNonStringAttribute(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.Build();
|
return builder.Build();
|
||||||
|
|
@ -701,6 +703,18 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
htmlSymbol.Type == HtmlSymbolType.SingleQuote;
|
htmlSymbol.Type == HtmlSymbolType.SingleQuote;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ConfigureNonStringAttribute(SpanBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Kind = SpanKind.Code;
|
||||||
|
builder.EditHandler = new ImplicitExpressionEditHandler(
|
||||||
|
builder.EditHandler.Tokenizer,
|
||||||
|
CSharpCodeParser.DefaultKeywords,
|
||||||
|
acceptTrailingDot: true)
|
||||||
|
{
|
||||||
|
AcceptedCharacters = AcceptedCharacters.AnyExceptNewline
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private class TryParseResult
|
private class TryParseResult
|
||||||
{
|
{
|
||||||
public string AttributeName { get; set; }
|
public string AttributeName { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
using Microsoft.AspNetCore.Razor.Text;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers
|
namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers
|
||||||
|
|
@ -123,6 +124,45 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Span LocateOwner(TextChange change)
|
||||||
|
{
|
||||||
|
var oldPosition = change.OldPosition;
|
||||||
|
if (oldPosition < Start.AbsoluteIndex)
|
||||||
|
{
|
||||||
|
// Change occurs prior to the TagHelper.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodyEndLocation = SourceStartTag?.Start.AbsoluteIndex + SourceStartTag?.Length + base.Length;
|
||||||
|
if (oldPosition > bodyEndLocation)
|
||||||
|
{
|
||||||
|
// Change occurs after the TagHelpers body. End tags for TagHelpers cannot claim ownership of changes
|
||||||
|
// because any change to them impacts whether or not a tag is a TagHelper.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startTagEndLocation = Start.AbsoluteIndex + SourceStartTag?.Length;
|
||||||
|
if (oldPosition < startTagEndLocation)
|
||||||
|
{
|
||||||
|
// Change occurs in the start tag.
|
||||||
|
|
||||||
|
var attributeElements = Attributes
|
||||||
|
.Select(attribute => attribute.Value)
|
||||||
|
.Where(value => value != null);
|
||||||
|
|
||||||
|
return LocateOwner(change, attributeElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldPosition < bodyEndLocation)
|
||||||
|
{
|
||||||
|
// Change occurs in the body
|
||||||
|
return base.LocateOwner(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagHelper does not contain a Span that can claim ownership.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,27 @@ namespace Microsoft.AspNetCore.Razor.Test.Framework
|
||||||
|
|
||||||
public static SpanConstructor CodeMarkup(this SpanFactory self, params string[] content)
|
public static SpanConstructor CodeMarkup(this SpanFactory self, params string[] content)
|
||||||
{
|
{
|
||||||
return self.Span(SpanKind.Code, content, markup: true).With(new MarkupChunkGenerator());
|
return self
|
||||||
|
.Span(SpanKind.Code, content, markup: true)
|
||||||
|
.AsCodeMarkup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SpanConstructor CSharpCodeMarkup(this SpanFactory self, string content)
|
||||||
|
{
|
||||||
|
return self.Code(content)
|
||||||
|
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
|
||||||
|
.AsCodeMarkup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SpanConstructor AsCodeMarkup(this SpanConstructor self)
|
||||||
|
{
|
||||||
|
return self
|
||||||
|
.With(new ImplicitExpressionEditHandler(
|
||||||
|
SpanConstructor.TestTokenizer,
|
||||||
|
CSharpCodeParser.DefaultKeywords,
|
||||||
|
acceptTrailingDot: true))
|
||||||
|
.With(new MarkupChunkGenerator())
|
||||||
|
.Accepts(AcceptedCharacters.AnyExceptNewline);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SourceLocation GetLocationAndAdvance(this SourceLocationTracker self, string content)
|
public static SourceLocation GetLocationAndAdvance(this SourceLocationTracker self, string content)
|
||||||
|
|
|
||||||
|
|
@ -1013,9 +1013,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
new ExpressionBlock(
|
new ExpressionBlock(
|
||||||
factory.CodeTransition(),
|
factory.CodeTransition(),
|
||||||
factory
|
factory
|
||||||
.Code("DateTime.Now.Year")
|
.CSharpCodeMarkup("DateTime.Now.Year")
|
||||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
.With(new ExpressionChunkGenerator())))))
|
||||||
.Accepts(AcceptedCharacters.NonWhiteSpace)))))
|
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1031,14 +1030,10 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
factory.CodeMarkup(" "),
|
factory.CodeMarkup(" "),
|
||||||
new ExpressionBlock(
|
new ExpressionBlock(
|
||||||
|
factory.CSharpCodeMarkup("@"),
|
||||||
factory
|
factory
|
||||||
.CodeTransition()
|
.CSharpCodeMarkup("DateTime.Now.Year")
|
||||||
.As(SpanKind.Code)
|
.With(new ExpressionChunkGenerator())))))
|
||||||
.With(new MarkupChunkGenerator()),
|
|
||||||
factory
|
|
||||||
.Code("DateTime.Now.Year")
|
|
||||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
|
||||||
.Accepts(AcceptedCharacters.NonWhiteSpace)))))
|
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1078,14 +1073,9 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
factory.CodeMarkup(" "),
|
factory.CodeMarkup(" "),
|
||||||
new ExpressionBlock(
|
new ExpressionBlock(
|
||||||
factory
|
factory.CSharpCodeMarkup("@"),
|
||||||
.CodeTransition()
|
factory.CSharpCodeMarkup("value")
|
||||||
.As(SpanKind.Code)
|
.With(new ExpressionChunkGenerator()))),
|
||||||
.With(new MarkupChunkGenerator()),
|
|
||||||
factory
|
|
||||||
.Code("value")
|
|
||||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
|
||||||
.Accepts(AcceptedCharacters.NonWhiteSpace))),
|
|
||||||
factory.CodeMarkup(" +"),
|
factory.CodeMarkup(" +"),
|
||||||
factory.CodeMarkup(" 2"))),
|
factory.CodeMarkup(" 2"))),
|
||||||
new TagHelperAttributeNode(
|
new TagHelperAttributeNode(
|
||||||
|
|
@ -1094,33 +1084,26 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
factory.CodeMarkup("(bool)"),
|
factory.CodeMarkup("(bool)"),
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
new ExpressionBlock(
|
new ExpressionBlock(
|
||||||
|
factory.CSharpCodeMarkup("@"),
|
||||||
factory
|
factory
|
||||||
.CodeTransition()
|
.CSharpCodeMarkup("Bag[\"val\"]")
|
||||||
.As(SpanKind.Code)
|
.With(new ExpressionChunkGenerator()))),
|
||||||
.With(new MarkupChunkGenerator()),
|
|
||||||
factory
|
|
||||||
.Code("Bag[\"val\"]")
|
|
||||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
|
||||||
.Accepts(AcceptedCharacters.NonWhiteSpace))),
|
|
||||||
factory.CodeMarkup(" ?"),
|
factory.CodeMarkup(" ?"),
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
factory.CodeMarkup(" @").Accepts(AcceptedCharacters.None),
|
factory.CodeMarkup(" @")
|
||||||
|
.As(SpanKind.Code),
|
||||||
factory.CodeMarkup("@")
|
factory.CodeMarkup("@")
|
||||||
.With(SpanChunkGenerator.Null)
|
.As(SpanKind.Code)
|
||||||
.Accepts(AcceptedCharacters.None)),
|
.With(SpanChunkGenerator.Null)),
|
||||||
factory.CodeMarkup("DateTime"),
|
factory.CodeMarkup("DateTime"),
|
||||||
factory.CodeMarkup(" :"),
|
factory.CodeMarkup(" :"),
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
factory.CodeMarkup(" "),
|
factory.CodeMarkup(" "),
|
||||||
new ExpressionBlock(
|
new ExpressionBlock(
|
||||||
|
factory.CSharpCodeMarkup("@"),
|
||||||
factory
|
factory
|
||||||
.CodeTransition()
|
.CSharpCodeMarkup("DateTime.Now")
|
||||||
.As(SpanKind.Code)
|
.With(new ExpressionChunkGenerator())))),
|
||||||
.With(new MarkupChunkGenerator()),
|
|
||||||
factory
|
|
||||||
.Code("DateTime.Now")
|
|
||||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
|
||||||
.Accepts(AcceptedCharacters.NonWhiteSpace)))),
|
|
||||||
HtmlAttributeValueStyle.SingleQuotes)
|
HtmlAttributeValueStyle.SingleQuotes)
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
|
@ -1196,27 +1179,19 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
"age",
|
"age",
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
|
factory.CodeMarkup("@"),
|
||||||
factory.CodeMarkup("@")
|
factory.CodeMarkup("@")
|
||||||
.Accepts(AcceptedCharacters.None)
|
.With(SpanChunkGenerator.Null)),
|
||||||
.With(new MarkupChunkGenerator()),
|
|
||||||
factory.CodeMarkup("@")
|
|
||||||
.With(SpanChunkGenerator.Null)
|
|
||||||
.Accepts(AcceptedCharacters.None)),
|
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
factory.EmptyHtml().As(SpanKind.Code),
|
factory.EmptyHtml()
|
||||||
|
.AsCodeMarkup()
|
||||||
|
.As(SpanKind.Code),
|
||||||
new ExpressionBlock(
|
new ExpressionBlock(
|
||||||
factory.CodeTransition()
|
factory.CSharpCodeMarkup("@"),
|
||||||
.As(SpanKind.Code)
|
factory.CSharpCodeMarkup("("),
|
||||||
.With(new MarkupChunkGenerator()),
|
factory.CSharpCodeMarkup("11+1")
|
||||||
factory.MetaCode("(")
|
.With(new ExpressionChunkGenerator()),
|
||||||
.Accepts(AcceptedCharacters.None)
|
factory.CSharpCodeMarkup(")"))))),
|
||||||
.As(SpanKind.Code)
|
|
||||||
.With(new MarkupChunkGenerator()),
|
|
||||||
factory.Code("11+1").AsExpression(),
|
|
||||||
factory.MetaCode(")")
|
|
||||||
.Accepts(AcceptedCharacters.None)
|
|
||||||
.As(SpanKind.Code)
|
|
||||||
.With(new MarkupChunkGenerator()))))),
|
|
||||||
new TagHelperAttributeNode(
|
new TagHelperAttributeNode(
|
||||||
"birthday",
|
"birthday",
|
||||||
factory.CodeMarkup("DateTime.Now")),
|
factory.CodeMarkup("DateTime.Now")),
|
||||||
|
|
@ -2237,12 +2212,9 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
factory.CodeMarkup(" "),
|
factory.CodeMarkup(" "),
|
||||||
new ExpressionBlock(
|
new ExpressionBlock(
|
||||||
factory.CodeTransition()
|
factory.CSharpCodeMarkup("@"),
|
||||||
.As(SpanKind.Code)
|
factory.CSharpCodeMarkup("true")
|
||||||
.With(new MarkupChunkGenerator()),
|
.With(new ExpressionChunkGenerator()))),
|
||||||
factory.Code("true")
|
|
||||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
|
||||||
.Accepts(AcceptedCharacters.NonWhiteSpace))),
|
|
||||||
factory.CodeMarkup(" ")),
|
factory.CodeMarkup(" ")),
|
||||||
HtmlAttributeValueStyle.SingleQuotes)
|
HtmlAttributeValueStyle.SingleQuotes)
|
||||||
}
|
}
|
||||||
|
|
@ -2264,18 +2236,11 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
factory.CodeMarkup(" "),
|
factory.CodeMarkup(" "),
|
||||||
new ExpressionBlock(
|
new ExpressionBlock(
|
||||||
factory.CodeTransition()
|
factory.CSharpCodeMarkup("@"),
|
||||||
.As(SpanKind.Code)
|
factory.CSharpCodeMarkup("("),
|
||||||
.With(new MarkupChunkGenerator()),
|
factory.CSharpCodeMarkup("true")
|
||||||
factory.MetaCode("(")
|
.With(new ExpressionChunkGenerator()),
|
||||||
.Accepts(AcceptedCharacters.None)
|
factory.CSharpCodeMarkup(")"))),
|
||||||
.As(SpanKind.Code)
|
|
||||||
.With(new MarkupChunkGenerator()),
|
|
||||||
factory.Code("true").AsExpression(),
|
|
||||||
factory.MetaCode(")")
|
|
||||||
.Accepts(AcceptedCharacters.None)
|
|
||||||
.As(SpanKind.Code)
|
|
||||||
.With(new MarkupChunkGenerator()))),
|
|
||||||
factory.CodeMarkup(" ")),
|
factory.CodeMarkup(" ")),
|
||||||
HtmlAttributeValueStyle.SingleQuotes)
|
HtmlAttributeValueStyle.SingleQuotes)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
||||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||||
|
using Microsoft.AspNetCore.Razor.Editor;
|
||||||
using Microsoft.AspNetCore.Razor.Parser.Internal;
|
using Microsoft.AspNetCore.Razor.Parser.Internal;
|
||||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
|
|
@ -2452,8 +2453,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
new ExpressionBlock(
|
new ExpressionBlock(
|
||||||
factory.CodeTransition(),
|
factory.CodeTransition(),
|
||||||
factory.Code("DateTime.Now")
|
factory.Code("DateTime.Now")
|
||||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
|
||||||
.Accepts(AcceptedCharacters.NonWhiteSpace)))))
|
.Accepts(AcceptedCharacters.AnyExceptNewline)))))
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
availableDescriptorsColon
|
availableDescriptorsColon
|
||||||
|
|
@ -2474,8 +2475,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
new ExpressionBlock(
|
new ExpressionBlock(
|
||||||
factory.CodeTransition(),
|
factory.CodeTransition(),
|
||||||
factory.Code("DateTime.Now")
|
factory.Code("DateTime.Now")
|
||||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
|
||||||
.Accepts(AcceptedCharacters.NonWhiteSpace)))))
|
.Accepts(AcceptedCharacters.AnyExceptNewline)))))
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
availableDescriptorsText
|
availableDescriptorsText
|
||||||
|
|
@ -2493,24 +2494,19 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
||||||
"bound",
|
"bound",
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
|
factory.CodeMarkup("@"),
|
||||||
factory
|
factory
|
||||||
.CodeMarkup("@")
|
.CodeMarkup("@")
|
||||||
.With(new MarkupChunkGenerator())
|
.With(SpanChunkGenerator.Null)),
|
||||||
.Accepts(AcceptedCharacters.None),
|
|
||||||
factory
|
|
||||||
.CodeMarkup("@")
|
|
||||||
.With(SpanChunkGenerator.Null)
|
|
||||||
.Accepts(AcceptedCharacters.None)),
|
|
||||||
new MarkupBlock(
|
new MarkupBlock(
|
||||||
factory.EmptyHtml().As(SpanKind.Code),
|
factory
|
||||||
|
.EmptyHtml()
|
||||||
|
.As(SpanKind.Code)
|
||||||
|
.AsCodeMarkup(),
|
||||||
new ExpressionBlock(
|
new ExpressionBlock(
|
||||||
factory
|
factory.CSharpCodeMarkup("@"),
|
||||||
.CodeTransition()
|
factory.CSharpCodeMarkup("DateTime.Now")
|
||||||
.As(SpanKind.Code)
|
.With(new ExpressionChunkGenerator())))))
|
||||||
.With(new MarkupChunkGenerator()),
|
|
||||||
factory.Code("DateTime.Now")
|
|
||||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
|
||||||
.Accepts(AcceptedCharacters.NonWhiteSpace)))))
|
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
availableDescriptorsText
|
availableDescriptorsText
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Web.WebPages.TestUtils;
|
using System.Web.WebPages.TestUtils;
|
||||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||||
|
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||||
using Microsoft.AspNetCore.Razor.Test.Framework;
|
using Microsoft.AspNetCore.Razor.Test.Framework;
|
||||||
using Microsoft.AspNetCore.Razor.Test.Utils;
|
using Microsoft.AspNetCore.Razor.Test.Utils;
|
||||||
|
|
@ -58,7 +59,7 @@ namespace Microsoft.AspNetCore.Razor
|
||||||
return new TestParserManager(parser);
|
return new TestParserManager(parser);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static RazorEngineHost CreateHost()
|
protected static RazorEngineHost CreateHost(ITagHelperDescriptorResolver descriptorResolver = null)
|
||||||
{
|
{
|
||||||
return new RazorEngineHost(new TLanguage())
|
return new RazorEngineHost(new TLanguage())
|
||||||
{
|
{
|
||||||
|
|
@ -71,7 +72,8 @@ namespace Microsoft.AspNetCore.Razor
|
||||||
"Template",
|
"Template",
|
||||||
"DefineSection",
|
"DefineSection",
|
||||||
new GeneratedTagHelperContext()),
|
new GeneratedTagHelperContext()),
|
||||||
DesignTimeMode = true
|
DesignTimeMode = true,
|
||||||
|
TagHelperDescriptorResolver = descriptorResolver
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,21 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Web.WebPages.TestUtils;
|
using System.Web.WebPages.TestUtils;
|
||||||
|
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||||
using Microsoft.AspNetCore.Razor.Editor;
|
using Microsoft.AspNetCore.Razor.Editor;
|
||||||
using Microsoft.AspNetCore.Razor.Parser;
|
using Microsoft.AspNetCore.Razor.Parser;
|
||||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||||
|
using Microsoft.AspNetCore.Razor.Parser.TagHelpers;
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
using Microsoft.AspNetCore.Razor.Test.CodeGenerators;
|
using Microsoft.AspNetCore.Razor.Test.CodeGenerators;
|
||||||
using Microsoft.AspNetCore.Razor.Test.Framework;
|
using Microsoft.AspNetCore.Razor.Test.Framework;
|
||||||
using Microsoft.AspNetCore.Razor.Test.Utils;
|
using Microsoft.AspNetCore.Razor.Test.Utils;
|
||||||
using Microsoft.AspNetCore.Razor.Text;
|
using Microsoft.AspNetCore.Razor.Text;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Razor
|
namespace Microsoft.AspNetCore.Razor
|
||||||
|
|
@ -23,6 +27,273 @@ namespace Microsoft.AspNetCore.Razor
|
||||||
private static readonly TestFile SimpleCSHTMLDocumentGenerated = TestFile.Create("TestFiles/DesignTime/Simple.txt");
|
private static readonly TestFile SimpleCSHTMLDocumentGenerated = TestFile.Create("TestFiles/DesignTime/Simple.txt");
|
||||||
private const string TestLinePragmaFileName = "C:\\This\\Path\\Is\\Just\\For\\Line\\Pragmas.cshtml";
|
private const string TestLinePragmaFileName = "C:\\This\\Path\\Is\\Just\\For\\Line\\Pragmas.cshtml";
|
||||||
|
|
||||||
|
public static TheoryData TagHelperPartialParseRejectData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var factory = SpanFactory.CreateCsHtml();
|
||||||
|
|
||||||
|
// change, expectedDocument
|
||||||
|
return new TheoryData<TextChange, MarkupBlock>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
CreateInsertionChange("<p></p>", 2, " "),
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock("p"))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CreateInsertionChange("<p></p>", 6, " "),
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock("p"))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CreateInsertionChange("<p some-attr></p>", 12, " "),
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"p",
|
||||||
|
attributes: new List<TagHelperAttributeNode>
|
||||||
|
{
|
||||||
|
new TagHelperAttributeNode(
|
||||||
|
"some-attr",
|
||||||
|
value: null,
|
||||||
|
valueStyle: HtmlAttributeValueStyle.Minimized)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CreateInsertionChange("<p some-attr></p>", 12, "ibute"),
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"p",
|
||||||
|
attributes: new List<TagHelperAttributeNode>
|
||||||
|
{
|
||||||
|
new TagHelperAttributeNode(
|
||||||
|
"some-attribute",
|
||||||
|
value: null,
|
||||||
|
valueStyle: HtmlAttributeValueStyle.Minimized)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CreateInsertionChange("<p some-attr></p>", 2, " before"),
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"p",
|
||||||
|
attributes: new List<TagHelperAttributeNode>
|
||||||
|
{
|
||||||
|
new TagHelperAttributeNode(
|
||||||
|
"before",
|
||||||
|
value: null,
|
||||||
|
valueStyle: HtmlAttributeValueStyle.Minimized),
|
||||||
|
new TagHelperAttributeNode(
|
||||||
|
"some-attr",
|
||||||
|
value: null,
|
||||||
|
valueStyle: HtmlAttributeValueStyle.Minimized)
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TagHelperPartialParseRejectData))]
|
||||||
|
public void TagHelperTagBodiesRejectPartialChanges(TextChange change, MarkupBlock expectedDocument)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptors = new[]
|
||||||
|
{
|
||||||
|
new TagHelperDescriptor
|
||||||
|
{
|
||||||
|
TagName = "p",
|
||||||
|
TypeName = "PTagHelper"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
var descriptorResolver = new Mock<ITagHelperDescriptorResolver>();
|
||||||
|
descriptorResolver
|
||||||
|
.Setup(resolver => resolver.Resolve(It.IsAny<TagHelperDescriptorResolutionContext>()))
|
||||||
|
.Returns(descriptors);
|
||||||
|
var host = CreateHost(descriptorResolver.Object);
|
||||||
|
var parser = new RazorEditorParser(host, @"C:\This\Is\A\Test\Path");
|
||||||
|
|
||||||
|
using (var manager = new TestParserManager(parser))
|
||||||
|
{
|
||||||
|
manager.InitializeWithDocument(change.OldBuffer);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = manager.CheckForStructureChangesAndWait(change);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(PartialParseResult.Rejected, result);
|
||||||
|
Assert.Equal(2, manager.ParseCount);
|
||||||
|
ParserTestBase.EvaluateParseTree(manager.Parser.CurrentParseTree, expectedDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData TagHelperAttributeAcceptData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var factory = SpanFactory.CreateCsHtml();
|
||||||
|
|
||||||
|
// change, expectedDocument, partialParseResult
|
||||||
|
return new TheoryData<TextChange, MarkupBlock, PartialParseResult>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
CreateInsertionChange("<p str-attr='@DateTime'></p>", 22, "."),
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"p",
|
||||||
|
attributes: new List<TagHelperAttributeNode>
|
||||||
|
{
|
||||||
|
new TagHelperAttributeNode(
|
||||||
|
"str-attr",
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupBlock(
|
||||||
|
new ExpressionBlock(
|
||||||
|
factory.CodeTransition(),
|
||||||
|
factory
|
||||||
|
.Code("DateTime.")
|
||||||
|
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||||
|
.Accepts(AcceptedCharacters.NonWhiteSpace)))),
|
||||||
|
HtmlAttributeValueStyle.SingleQuotes)
|
||||||
|
})),
|
||||||
|
PartialParseResult.Accepted | PartialParseResult.Provisional
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CreateInsertionChange("<p obj-attr='DateTime'></p>", 21, "."),
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"p",
|
||||||
|
attributes: new List<TagHelperAttributeNode>
|
||||||
|
{
|
||||||
|
new TagHelperAttributeNode(
|
||||||
|
"obj-attr",
|
||||||
|
factory.CodeMarkup("DateTime."),
|
||||||
|
HtmlAttributeValueStyle.SingleQuotes)
|
||||||
|
})),
|
||||||
|
PartialParseResult.Accepted
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CreateInsertionChange("<p obj-attr='1 + DateTime'></p>", 25, "."),
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"p",
|
||||||
|
attributes: new List<TagHelperAttributeNode>
|
||||||
|
{
|
||||||
|
new TagHelperAttributeNode(
|
||||||
|
"obj-attr",
|
||||||
|
factory.CodeMarkup("1 + DateTime."),
|
||||||
|
HtmlAttributeValueStyle.SingleQuotes)
|
||||||
|
})),
|
||||||
|
PartialParseResult.Accepted
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CreateInsertionChange("<p before-attr str-attr='@DateTime' after-attr></p>", 34, "."),
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"p",
|
||||||
|
attributes: new List<TagHelperAttributeNode>
|
||||||
|
{
|
||||||
|
new TagHelperAttributeNode(
|
||||||
|
"before-attr",
|
||||||
|
value: null,
|
||||||
|
valueStyle: HtmlAttributeValueStyle.Minimized),
|
||||||
|
new TagHelperAttributeNode(
|
||||||
|
"str-attr",
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupBlock(
|
||||||
|
new ExpressionBlock(
|
||||||
|
factory.CodeTransition(),
|
||||||
|
factory
|
||||||
|
.Code("DateTime.")
|
||||||
|
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||||
|
.Accepts(AcceptedCharacters.NonWhiteSpace)))),
|
||||||
|
HtmlAttributeValueStyle.SingleQuotes),
|
||||||
|
new TagHelperAttributeNode(
|
||||||
|
"after-attr",
|
||||||
|
value: null,
|
||||||
|
valueStyle: HtmlAttributeValueStyle.Minimized),
|
||||||
|
})),
|
||||||
|
PartialParseResult.Accepted | PartialParseResult.Provisional
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CreateInsertionChange("<p str-attr='before @DateTime after'></p>", 29, "."),
|
||||||
|
new MarkupBlock(
|
||||||
|
new MarkupTagHelperBlock(
|
||||||
|
"p",
|
||||||
|
attributes: new List<TagHelperAttributeNode>
|
||||||
|
{
|
||||||
|
new TagHelperAttributeNode(
|
||||||
|
"str-attr",
|
||||||
|
new MarkupBlock(
|
||||||
|
factory.Markup("before"),
|
||||||
|
new MarkupBlock(
|
||||||
|
factory.Markup(" "),
|
||||||
|
new ExpressionBlock(
|
||||||
|
factory.CodeTransition(),
|
||||||
|
factory
|
||||||
|
.Code("DateTime.")
|
||||||
|
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||||
|
.Accepts(AcceptedCharacters.NonWhiteSpace))),
|
||||||
|
factory.Markup(" after")),
|
||||||
|
HtmlAttributeValueStyle.SingleQuotes)
|
||||||
|
})),
|
||||||
|
PartialParseResult.Accepted | PartialParseResult.Provisional
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TagHelperAttributeAcceptData))]
|
||||||
|
public void TagHelperAttributesAreLocatedAndAcceptChangesCorrectly(
|
||||||
|
TextChange change,
|
||||||
|
MarkupBlock expectedDocument,
|
||||||
|
PartialParseResult partialParseResult)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var descriptors = new[]
|
||||||
|
{
|
||||||
|
new TagHelperDescriptor
|
||||||
|
{
|
||||||
|
TagName = "p",
|
||||||
|
TypeName = "PTagHelper",
|
||||||
|
Attributes = new[]
|
||||||
|
{
|
||||||
|
new TagHelperAttributeDescriptor
|
||||||
|
{
|
||||||
|
Name = "obj-attr",
|
||||||
|
TypeName = typeof(object).FullName,
|
||||||
|
PropertyName = "ObjectAttribute",
|
||||||
|
},
|
||||||
|
new TagHelperAttributeDescriptor
|
||||||
|
{
|
||||||
|
Name = "str-attr",
|
||||||
|
TypeName = typeof(string).FullName,
|
||||||
|
PropertyName = "StringAttribute",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
var descriptorResolver = new Mock<ITagHelperDescriptorResolver>();
|
||||||
|
descriptorResolver
|
||||||
|
.Setup(resolver => resolver.Resolve(It.IsAny<TagHelperDescriptorResolutionContext>()))
|
||||||
|
.Returns(descriptors);
|
||||||
|
var host = CreateHost(descriptorResolver.Object);
|
||||||
|
var parser = new RazorEditorParser(host, @"C:\This\Is\A\Test\Path");
|
||||||
|
|
||||||
|
using (var manager = new TestParserManager(parser))
|
||||||
|
{
|
||||||
|
manager.InitializeWithDocument(change.OldBuffer);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = manager.CheckForStructureChangesAndWait(change);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(partialParseResult, result);
|
||||||
|
Assert.Equal(1, manager.ParseCount);
|
||||||
|
ParserTestBase.EvaluateParseTree(manager.Parser.CurrentParseTree, expectedDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ConstructorRequiresNonNullPhysicalPath()
|
public void ConstructorRequiresNonNullPhysicalPath()
|
||||||
{
|
{
|
||||||
|
|
@ -898,5 +1169,14 @@ namespace Microsoft.AspNetCore.Razor
|
||||||
{
|
{
|
||||||
return new CodeGenTestHost(new CSharpRazorCodeLanguage()) { DesignTimeMode = true };
|
return new CodeGenTestHost(new CSharpRazorCodeLanguage()) { DesignTimeMode = true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static TextChange CreateInsertionChange(string initialText, int insertionLocation, string insertionText)
|
||||||
|
{
|
||||||
|
var changedText = initialText.Insert(insertionLocation, insertionText);
|
||||||
|
|
||||||
|
var original = new StringTextBuffer(initialText);
|
||||||
|
var changed = new StringTextBuffer(changedText);
|
||||||
|
return new TextChange(insertionLocation, 0, original, insertionText.Length, changed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue