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
|
||||
Span owner = null;
|
||||
foreach (SyntaxTreeNode element in Children)
|
||||
foreach (var element in elements)
|
||||
{
|
||||
var span = element as Span;
|
||||
if (span == null)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Editor;
|
||||
using Microsoft.AspNetCore.Razor.Parser.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
|
|
@ -468,7 +469,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
spanBuilder.ChunkGenerator = new MarkupChunkGenerator();
|
||||
}
|
||||
|
||||
spanBuilder.Kind = SpanKind.Code;
|
||||
ConfigureNonStringAttribute(spanBuilder);
|
||||
|
||||
span = spanBuilder.Build();
|
||||
}
|
||||
}
|
||||
|
|
@ -628,7 +630,7 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
// SyntaxTreeNode reflects that.
|
||||
if (isBoundNonStringAttribute)
|
||||
{
|
||||
builder.Kind = SpanKind.Code;
|
||||
ConfigureNonStringAttribute(builder);
|
||||
}
|
||||
|
||||
return builder.Build();
|
||||
|
|
@ -701,6 +703,18 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
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
|
||||
{
|
||||
public string AttributeName { get; set; }
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Text;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
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 />
|
||||
public override string ToString()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -139,7 +139,27 @@ namespace Microsoft.AspNetCore.Razor.Test.Framework
|
|||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1013,9 +1013,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
new ExpressionBlock(
|
||||
factory.CodeTransition(),
|
||||
factory
|
||||
.Code("DateTime.Now.Year")
|
||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)))))
|
||||
.CSharpCodeMarkup("DateTime.Now.Year")
|
||||
.With(new ExpressionChunkGenerator())))))
|
||||
}))
|
||||
},
|
||||
{
|
||||
|
|
@ -1031,14 +1030,10 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
new MarkupBlock(
|
||||
factory.CodeMarkup(" "),
|
||||
new ExpressionBlock(
|
||||
factory.CSharpCodeMarkup("@"),
|
||||
factory
|
||||
.CodeTransition()
|
||||
.As(SpanKind.Code)
|
||||
.With(new MarkupChunkGenerator()),
|
||||
factory
|
||||
.Code("DateTime.Now.Year")
|
||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)))))
|
||||
.CSharpCodeMarkup("DateTime.Now.Year")
|
||||
.With(new ExpressionChunkGenerator())))))
|
||||
}))
|
||||
},
|
||||
{
|
||||
|
|
@ -1078,14 +1073,9 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
new MarkupBlock(
|
||||
factory.CodeMarkup(" "),
|
||||
new ExpressionBlock(
|
||||
factory
|
||||
.CodeTransition()
|
||||
.As(SpanKind.Code)
|
||||
.With(new MarkupChunkGenerator()),
|
||||
factory
|
||||
.Code("value")
|
||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace))),
|
||||
factory.CSharpCodeMarkup("@"),
|
||||
factory.CSharpCodeMarkup("value")
|
||||
.With(new ExpressionChunkGenerator()))),
|
||||
factory.CodeMarkup(" +"),
|
||||
factory.CodeMarkup(" 2"))),
|
||||
new TagHelperAttributeNode(
|
||||
|
|
@ -1094,33 +1084,26 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
factory.CodeMarkup("(bool)"),
|
||||
new MarkupBlock(
|
||||
new ExpressionBlock(
|
||||
factory.CSharpCodeMarkup("@"),
|
||||
factory
|
||||
.CodeTransition()
|
||||
.As(SpanKind.Code)
|
||||
.With(new MarkupChunkGenerator()),
|
||||
factory
|
||||
.Code("Bag[\"val\"]")
|
||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace))),
|
||||
.CSharpCodeMarkup("Bag[\"val\"]")
|
||||
.With(new ExpressionChunkGenerator()))),
|
||||
factory.CodeMarkup(" ?"),
|
||||
new MarkupBlock(
|
||||
factory.CodeMarkup(" @").Accepts(AcceptedCharacters.None),
|
||||
factory.CodeMarkup(" @")
|
||||
.As(SpanKind.Code),
|
||||
factory.CodeMarkup("@")
|
||||
.With(SpanChunkGenerator.Null)
|
||||
.Accepts(AcceptedCharacters.None)),
|
||||
.As(SpanKind.Code)
|
||||
.With(SpanChunkGenerator.Null)),
|
||||
factory.CodeMarkup("DateTime"),
|
||||
factory.CodeMarkup(" :"),
|
||||
new MarkupBlock(
|
||||
factory.CodeMarkup(" "),
|
||||
new ExpressionBlock(
|
||||
factory.CSharpCodeMarkup("@"),
|
||||
factory
|
||||
.CodeTransition()
|
||||
.As(SpanKind.Code)
|
||||
.With(new MarkupChunkGenerator()),
|
||||
factory
|
||||
.Code("DateTime.Now")
|
||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)))),
|
||||
.CSharpCodeMarkup("DateTime.Now")
|
||||
.With(new ExpressionChunkGenerator())))),
|
||||
HtmlAttributeValueStyle.SingleQuotes)
|
||||
}))
|
||||
},
|
||||
|
|
@ -1196,27 +1179,19 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
"age",
|
||||
new MarkupBlock(
|
||||
new MarkupBlock(
|
||||
factory.CodeMarkup("@"),
|
||||
factory.CodeMarkup("@")
|
||||
.Accepts(AcceptedCharacters.None)
|
||||
.With(new MarkupChunkGenerator()),
|
||||
factory.CodeMarkup("@")
|
||||
.With(SpanChunkGenerator.Null)
|
||||
.Accepts(AcceptedCharacters.None)),
|
||||
.With(SpanChunkGenerator.Null)),
|
||||
new MarkupBlock(
|
||||
factory.EmptyHtml().As(SpanKind.Code),
|
||||
factory.EmptyHtml()
|
||||
.AsCodeMarkup()
|
||||
.As(SpanKind.Code),
|
||||
new ExpressionBlock(
|
||||
factory.CodeTransition()
|
||||
.As(SpanKind.Code)
|
||||
.With(new MarkupChunkGenerator()),
|
||||
factory.MetaCode("(")
|
||||
.Accepts(AcceptedCharacters.None)
|
||||
.As(SpanKind.Code)
|
||||
.With(new MarkupChunkGenerator()),
|
||||
factory.Code("11+1").AsExpression(),
|
||||
factory.MetaCode(")")
|
||||
.Accepts(AcceptedCharacters.None)
|
||||
.As(SpanKind.Code)
|
||||
.With(new MarkupChunkGenerator()))))),
|
||||
factory.CSharpCodeMarkup("@"),
|
||||
factory.CSharpCodeMarkup("("),
|
||||
factory.CSharpCodeMarkup("11+1")
|
||||
.With(new ExpressionChunkGenerator()),
|
||||
factory.CSharpCodeMarkup(")"))))),
|
||||
new TagHelperAttributeNode(
|
||||
"birthday",
|
||||
factory.CodeMarkup("DateTime.Now")),
|
||||
|
|
@ -2237,12 +2212,9 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
new MarkupBlock(
|
||||
factory.CodeMarkup(" "),
|
||||
new ExpressionBlock(
|
||||
factory.CodeTransition()
|
||||
.As(SpanKind.Code)
|
||||
.With(new MarkupChunkGenerator()),
|
||||
factory.Code("true")
|
||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace))),
|
||||
factory.CSharpCodeMarkup("@"),
|
||||
factory.CSharpCodeMarkup("true")
|
||||
.With(new ExpressionChunkGenerator()))),
|
||||
factory.CodeMarkup(" ")),
|
||||
HtmlAttributeValueStyle.SingleQuotes)
|
||||
}
|
||||
|
|
@ -2264,18 +2236,11 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
new MarkupBlock(
|
||||
factory.CodeMarkup(" "),
|
||||
new ExpressionBlock(
|
||||
factory.CodeTransition()
|
||||
.As(SpanKind.Code)
|
||||
.With(new MarkupChunkGenerator()),
|
||||
factory.MetaCode("(")
|
||||
.Accepts(AcceptedCharacters.None)
|
||||
.As(SpanKind.Code)
|
||||
.With(new MarkupChunkGenerator()),
|
||||
factory.Code("true").AsExpression(),
|
||||
factory.MetaCode(")")
|
||||
.Accepts(AcceptedCharacters.None)
|
||||
.As(SpanKind.Code)
|
||||
.With(new MarkupChunkGenerator()))),
|
||||
factory.CSharpCodeMarkup("@"),
|
||||
factory.CSharpCodeMarkup("("),
|
||||
factory.CSharpCodeMarkup("true")
|
||||
.With(new ExpressionChunkGenerator()),
|
||||
factory.CSharpCodeMarkup(")"))),
|
||||
factory.CodeMarkup(" ")),
|
||||
HtmlAttributeValueStyle.SingleQuotes)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Globalization;
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Editor;
|
||||
using Microsoft.AspNetCore.Razor.Parser.Internal;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
|
|
@ -2452,8 +2453,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
new ExpressionBlock(
|
||||
factory.CodeTransition(),
|
||||
factory.Code("DateTime.Now")
|
||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)))))
|
||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline)))))
|
||||
}
|
||||
})),
|
||||
availableDescriptorsColon
|
||||
|
|
@ -2474,8 +2475,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
new ExpressionBlock(
|
||||
factory.CodeTransition(),
|
||||
factory.Code("DateTime.Now")
|
||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)))))
|
||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
|
||||
.Accepts(AcceptedCharacters.AnyExceptNewline)))))
|
||||
}
|
||||
})),
|
||||
availableDescriptorsText
|
||||
|
|
@ -2493,24 +2494,19 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal
|
|||
"bound",
|
||||
new MarkupBlock(
|
||||
new MarkupBlock(
|
||||
factory.CodeMarkup("@"),
|
||||
factory
|
||||
.CodeMarkup("@")
|
||||
.With(new MarkupChunkGenerator())
|
||||
.Accepts(AcceptedCharacters.None),
|
||||
factory
|
||||
.CodeMarkup("@")
|
||||
.With(SpanChunkGenerator.Null)
|
||||
.Accepts(AcceptedCharacters.None)),
|
||||
.With(SpanChunkGenerator.Null)),
|
||||
new MarkupBlock(
|
||||
factory.EmptyHtml().As(SpanKind.Code),
|
||||
factory
|
||||
.EmptyHtml()
|
||||
.As(SpanKind.Code)
|
||||
.AsCodeMarkup(),
|
||||
new ExpressionBlock(
|
||||
factory
|
||||
.CodeTransition()
|
||||
.As(SpanKind.Code)
|
||||
.With(new MarkupChunkGenerator()),
|
||||
factory.Code("DateTime.Now")
|
||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)))))
|
||||
factory.CSharpCodeMarkup("@"),
|
||||
factory.CSharpCodeMarkup("DateTime.Now")
|
||||
.With(new ExpressionChunkGenerator())))))
|
||||
}
|
||||
})),
|
||||
availableDescriptorsText
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Threading;
|
||||
using System.Web.WebPages.TestUtils;
|
||||
using Microsoft.AspNetCore.Razor.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNetCore.Razor.Test.Framework;
|
||||
using Microsoft.AspNetCore.Razor.Test.Utils;
|
||||
|
|
@ -58,7 +59,7 @@ namespace Microsoft.AspNetCore.Razor
|
|||
return new TestParserManager(parser);
|
||||
}
|
||||
|
||||
protected static RazorEngineHost CreateHost()
|
||||
protected static RazorEngineHost CreateHost(ITagHelperDescriptorResolver descriptorResolver = null)
|
||||
{
|
||||
return new RazorEngineHost(new TLanguage())
|
||||
{
|
||||
|
|
@ -71,7 +72,8 @@ namespace Microsoft.AspNetCore.Razor
|
|||
"Template",
|
||||
"DefineSection",
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Web.WebPages.TestUtils;
|
||||
using Microsoft.AspNetCore.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Editor;
|
||||
using Microsoft.AspNetCore.Razor.Parser;
|
||||
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNetCore.Razor.Parser.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using Microsoft.AspNetCore.Razor.Test.CodeGenerators;
|
||||
using Microsoft.AspNetCore.Razor.Test.Framework;
|
||||
using Microsoft.AspNetCore.Razor.Test.Utils;
|
||||
using Microsoft.AspNetCore.Razor.Text;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor
|
||||
|
|
@ -23,6 +27,273 @@ namespace Microsoft.AspNetCore.Razor
|
|||
private static readonly TestFile SimpleCSHTMLDocumentGenerated = TestFile.Create("TestFiles/DesignTime/Simple.txt");
|
||||
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]
|
||||
public void ConstructorRequiresNonNullPhysicalPath()
|
||||
{
|
||||
|
|
@ -898,5 +1169,14 @@ namespace Microsoft.AspNetCore.Razor
|
|||
{
|
||||
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