[Fixes #183] Fix error with double transition in value attribute

This commit is contained in:
Ajay Bhargav Baaskaran 2015-05-01 13:40:41 -07:00
parent d6cb4229a9
commit c680d6b953
9 changed files with 1021 additions and 24 deletions

View File

@ -149,7 +149,7 @@ namespace Microsoft.AspNet.Razor.Parser
var current = CurrentSymbol;
if (At(CSharpSymbolType.StringLiteral) && CurrentSymbol.Content.Length > 0 && CurrentSymbol.Content[0] == SyntaxConstants.TransitionCharacter)
{
Tuple<CSharpSymbol, CSharpSymbol> split = Language.SplitSymbol(CurrentSymbol, 1, CSharpSymbolType.Transition);
var split = Language.SplitSymbol(CurrentSymbol, 1, CSharpSymbolType.Transition);
current = split.Item1;
Context.Source.Position = split.Item2.Start.AbsoluteIndex;
NextToken();

View File

@ -316,7 +316,7 @@ namespace Microsoft.AspNet.Razor.Parser
var matched = RemoveTag(tags, tagName, tagStart);
if (tags.Count == 0 &&
// Note tagName may contain a '!' escape character. This ensures </!text> doesn't match here.
// Note tagName may contain a '!' escape character. This ensures </!text> doesn't match here.
// </!text> tags are treated like any other escaped HTML end tag.
string.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) &&
matched)
@ -491,7 +491,7 @@ namespace Microsoft.AspNet.Razor.Parser
private void AttributePrefix(IEnumerable<HtmlSymbol> whitespace, IEnumerable<HtmlSymbol> nameSymbols)
{
// First, determine if this is a 'data-' attribute (since those can't use conditional attributes)
LocationTagged<string> name = nameSymbols.GetContent(Span.Start);
var name = nameSymbols.GetContent(Span.Start);
var attributeCanBeConditional = !name.Value.StartsWith("data-", StringComparison.OrdinalIgnoreCase);
// Accept the whitespace and name
@ -507,7 +507,7 @@ namespace Microsoft.AspNet.Razor.Parser
}
// We now have the prefix: (i.e. ' foo="')
LocationTagged<string> prefix = Span.GetContent();
var prefix = Span.GetContent();
if (attributeCanBeConditional)
{
@ -521,7 +521,7 @@ namespace Microsoft.AspNet.Razor.Parser
}
// Capture the suffix
LocationTagged<string> suffix = new LocationTagged<string>(string.Empty, CurrentLocation);
var suffix = new LocationTagged<string>(string.Empty, CurrentLocation);
if (quote != HtmlSymbolType.Unknown && At(quote))
{
suffix = CurrentSymbol.GetContent();
@ -554,23 +554,45 @@ namespace Microsoft.AspNet.Razor.Parser
{
var prefixStart = CurrentLocation;
var prefix = ReadWhile(sym => sym.Type == HtmlSymbolType.WhiteSpace || sym.Type == HtmlSymbolType.NewLine);
Accept(prefix);
if (At(HtmlSymbolType.Transition))
{
var valueStart = CurrentLocation;
PutCurrentBack();
// Output the prefix but as a null-span. DynamicAttributeBlockCodeGenerator will render it
Span.CodeGenerator = SpanCodeGenerator.Null;
// Dynamic value, start a new block and set the code generator
using (Context.StartBlock(BlockType.Markup))
if (NextIs(HtmlSymbolType.Transition))
{
Context.CurrentBlock.CodeGenerator =
new DynamicAttributeBlockCodeGenerator(prefix.GetContent(prefixStart), valueStart);
// Wrapping this in a block so that the ConditionalAttributeCollapser doesn't rewrite it.
using (Context.StartBlock(BlockType.Markup))
{
Accept(prefix);
OtherParserBlock();
// Render a single "@" in place of "@@".
Span.CodeGenerator = new LiteralAttributeCodeGenerator(
prefix.GetContent(prefixStart),
new LocationTagged<string>(CurrentSymbol.GetContent(), CurrentLocation));
AcceptAndMoveNext();
Output(SpanKind.Markup, AcceptedCharacters.None);
Span.CodeGenerator = SpanCodeGenerator.Null;
AcceptAndMoveNext();
Output(SpanKind.Markup, AcceptedCharacters.None);
}
}
else
{
Accept(prefix);
var valueStart = CurrentLocation;
PutCurrentBack();
// Output the prefix but as a null-span. DynamicAttributeBlockCodeGenerator will render it
Span.CodeGenerator = SpanCodeGenerator.Null;
// Dynamic value, start a new block and set the code generator
using (Context.StartBlock(BlockType.Markup))
{
Context.CurrentBlock.CodeGenerator =
new DynamicAttributeBlockCodeGenerator(prefix.GetContent(prefixStart), valueStart);
OtherParserBlock();
}
}
}
else if (At(HtmlSymbolType.Text) &&
@ -578,6 +600,8 @@ namespace Microsoft.AspNet.Razor.Parser
CurrentSymbol.Content[0] == '~' &&
NextIs(HtmlSymbolType.ForwardSlash))
{
Accept(prefix);
// Virtual Path value
var valueStart = CurrentLocation;
VirtualPath();
@ -587,6 +611,8 @@ namespace Microsoft.AspNet.Razor.Parser
}
else
{
Accept(prefix);
// Literal value
// 'quote' should be "Unknown" if not quoted and symbols coming from the tokenizer should never have "Unknown" type.
var value = ReadWhile(sym =>
@ -719,7 +745,7 @@ namespace Microsoft.AspNet.Razor.Parser
Tuple<HtmlSymbol, SourceLocation> tag = Tuple.Create(tagName, _lastTagStart);
if (tags.Count == 0 &&
// Note tagName may contain a '!' escape character. This ensures <!text> doesn't match here.
// Note tagName may contain a '!' escape character. This ensures <!text> doesn't match here.
// <!text> tags are treated like any other escaped HTML start tag.
string.Equals(tag.Item1.Content, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase))
{

View File

@ -277,15 +277,14 @@ namespace Microsoft.AspNet.Razor.Test.Framework
// Evaluate the result
var collector = new ErrorCollector();
// Link all the nodes
expectedRoot.LinkNodes();
if (expectedRoot == null)
{
Assert.Null(actualRoot);
}
else
{
// Link all the nodes
expectedRoot.LinkNodes();
Assert.NotNull(actualRoot);
EvaluateSyntaxTreeNode(collector, actualRoot, expectedRoot);
if (collector.Success)
@ -412,7 +411,7 @@ namespace Microsoft.AspNet.Razor.Test.Framework
actual.TagName,
actual.SelfClosing);
}
var expectedAttributes = expected.Attributes.GetEnumerator();
var actualAttributes = actual.Attributes.GetEnumerator();

View File

@ -7,6 +7,7 @@ using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Test.Framework;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
using Xunit;
@ -704,8 +705,8 @@ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
Factory.Markup("<a"),
new MarkupBlock(
new AttributeBlockCodeGenerator(
"href",
new LocationTagged<string>(" href=\"", 183 + Environment.NewLine.Length * 5, 5, 30),
"href",
new LocationTagged<string>(" href=\"", 183 + Environment.NewLine.Length * 5, 5, 30),
new LocationTagged<string>("\"", 246 + Environment.NewLine.Length * 5, 5, 93)),
Factory.Markup(" href=\"").With(SpanCodeGenerator.Null),
new MarkupBlock(
@ -736,6 +737,322 @@ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
Factory.Code(" }").AsStatement().Accepts(AcceptedCharacters.None)));
}
public static TheoryData BlockWithEscapedTransitionData
{
get
{
var factory = CreateDefaultSpanFactory();
var datetimeBlock = new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime.Now")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace));
return new TheoryData<string, Block>
{
{
// Double transition in attribute value
"{<span foo='@@' />}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>("'", 14, 0, 14)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), new LocationTagged<string>("@", 12, 0, 12))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharacters.None))))
},
{
// Double transition at the end of attribute value
"{<span foo='abc@@' />}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>("'", 17, 0, 17)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
factory.Markup("abc").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), new LocationTagged<string>("abc", 12, 0, 12))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 15, 0, 15), new LocationTagged<string>("@", 15, 0, 15))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharacters.None))))
},
{
// Double transition at the beginning attribute value
"{<span foo='@@def' />}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>("'", 17, 0, 17)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), new LocationTagged<string>("@", 12, 0, 12))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("def").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 14, 0, 14), new LocationTagged<string>("def", 14, 0, 14))),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharacters.None))))
},
{
// Double transition in between attribute value
"{<span foo='abc @@ def' />}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>("'", 22, 0, 22)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
factory.Markup("abc").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), new LocationTagged<string>("abc", 12, 0, 12))),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 15, 0, 15), new LocationTagged<string>("@", 16, 0, 16))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup(" def").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 18, 0, 18), new LocationTagged<string>("def", 19, 0, 19))),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharacters.None))))
},
{
// Double transition with expression block
"{<span foo='@@@DateTime.Now' />}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>("'", 27, 0, 27)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), new LocationTagged<string>("@", 12, 0, 12))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 14, 0, 14), 14, 0, 14),
factory.EmptyHtml().With(SpanCodeGenerator.Null),
datetimeBlock),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharacters.None))))
},
{
"{<span foo='@DateTime.Now @@' />}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>("'", 28, 0, 28)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), 12, 0, 12),
datetimeBlock),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 25, 0, 25), new LocationTagged<string>("@", 26, 0, 26))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharacters.None))))
},
{
"{<span foo='@DateTime.Now@@' />}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>("'", 27, 0, 27)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), 12, 0, 12),
datetimeBlock),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 25, 0, 25), new LocationTagged<string>("@", 25, 0, 25))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharacters.None))))
},
{
"{<span foo='@(2+3)@@@DateTime.Now' />}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>("'", 33, 0, 33)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), 12, 0, 12),
new ExpressionBlock(
factory.CodeTransition(),
factory.MetaCode("(").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None),
factory.Code("2+3").AsExpression(),
factory.MetaCode(")").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 18, 0, 18), new LocationTagged<string>("@", 18, 0, 18))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 20, 0, 20), 20, 0, 20),
factory.EmptyHtml().With(SpanCodeGenerator.Null),
datetimeBlock),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharacters.None))))
},
{
"{<span foo='@@@(2+3)' />}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>("'", 20, 0, 20)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), new LocationTagged<string>("@", 12, 0, 12))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 14, 0, 14), 14, 0, 14),
factory.EmptyHtml().With(SpanCodeGenerator.Null),
new ExpressionBlock(
factory.CodeTransition(),
factory.MetaCode("(").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None),
factory.Code("2+3").AsExpression(),
factory.MetaCode(")").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None))),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharacters.None))))
},
{
// Double transition with email in attribute value
"{<span foo='abc@def.com @@' />}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>("'", 26, 0, 26)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
factory.Markup("abc@def.com").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), new LocationTagged<string>("abc@def.com", 12, 0, 12))),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 23, 0, 23), new LocationTagged<string>("@", 24, 0, 24))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharacters.None))))
},
{
"{<span foo='abc@@def.com @@' />}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>("'", 27, 0, 27)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
factory.Markup("abc").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), new LocationTagged<string>("abc", 12, 0, 12))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 15, 0, 15), new LocationTagged<string>("@", 15, 0, 15))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("def.com").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 17, 0, 17), new LocationTagged<string>("def.com", 17, 0, 17))),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 24, 0, 24), new LocationTagged<string>("@", 25, 0, 25))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharacters.None))))
},
{
// Double transition in complex regex in attribute value
@"{<span foo=""/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@@[a-z0-9]([a-z0-9-]*[a-z0-9])?\.([a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i"" />}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo=\"", 6, 0, 6), new LocationTagged<string>("\"", 112, 0, 112)),
factory.Markup(" foo=\"").With(SpanCodeGenerator.Null),
factory.Markup(@"/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), new LocationTagged<string>(@"/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+", 12, 0, 12))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 44, 0, 44), new LocationTagged<string>("@", 44, 0, 44))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup(@"[a-z0-9]([a-z0-9-]*[a-z0-9])?\.([a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 46, 0, 46), new LocationTagged<string>(@"[a-z0-9]([a-z0-9-]*[a-z0-9])?\.([a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i", 46, 0, 46))),
factory.Markup("\"").With(SpanCodeGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharacters.None))))
},
};
}
}
[Theory]
[MemberData(nameof(BlockWithEscapedTransitionData))]
public void ParseBlock_WithDoubleTransition_DoesNotThrow(string input, Block expected)
{
// Act & Assert
ParseBlockTest(input, expected);
}
[Fact]
public void ParseBlock_WithDoubleTransition_EndOfFile_Throws()
{
// Arrange
var expected = new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>(string.Empty, 14, 0, 14)),
Factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
Factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), new LocationTagged<string>("@", 12, 0, 12))).Accepts(AcceptedCharacters.None),
Factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)))),
Factory.EmptyHtml()));
var expectedErrors = new RazorError[]
{
new RazorError(@"End of file or an unexpected character was reached before the ""span"" tag could be parsed. Elements inside markup blocks must be complete. They must either be self-closing (""<br />"") or have matching end tags (""<p>Hello</p>""). If you intended to display a ""<"" character, use the ""&lt;"" HTML entity.", new SourceLocation(1, 0, 1)),
new RazorError(@"The code block is missing a closing ""}"" character. Make sure you have a matching ""}"" character for all the ""{"" characters within this block, and that none of the ""}"" characters are being interpreted as markup.", new SourceLocation(0, 0, 0)),
};
// Act & Assert
ParseBlockTest("{<span foo='@@", expected, expectedErrors);
}
[Fact]
public void ParseBlock_WithUnexpectedTransitionsInAttributeValue_Throws()
{
// Arrange
var expected = new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 6, 0, 6), new LocationTagged<string>("'", 15, 0, 15)),
Factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 12, 0, 12), 12, 0, 12),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp().AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace))),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(" ", 13, 0, 13), 13, 0, 13),
Factory.Markup(" ").With(SpanCodeGenerator.Null),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp().AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace))),
Factory.Markup("'").With(SpanCodeGenerator.Null)),
Factory.Markup(" />").Accepts(AcceptedCharacters.None))),
Factory.EmptyCSharp().AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None));
var expectedErrors = new RazorError[]
{
new RazorError(@"A space or line break was encountered after the ""@"" character. Only valid identifiers, keywords, comments, ""("" and ""{"" are valid at the start of a code block and they must occur immediately following ""@"" with no space in between.", new SourceLocation(13, 0, 13)),
new RazorError(@"""' />}"" is not valid at the start of a code block. Only identifiers, keywords, comments, ""("" and ""{"" are valid.", new SourceLocation(15, 0, 15)),
};
// Act & Assert
ParseBlockTest("{<span foo='@ @' />}", expected, expectedErrors);
}
private void RunRazorCommentBetweenClausesTest(string preComment, string postComment, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
{
ParseBlockTest(preComment + "@* Foo *@ @* Bar *@" + postComment,
@ -786,5 +1103,24 @@ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
.Accepts(acceptedCharacters)),
errors);
}
private static SpanFactory CreateDefaultSpanFactory()
{
return new SpanFactory
{
MarkupTokenizerFactory = doc => new HtmlTokenizer(doc),
CodeTokenizerFactory = doc => new CSharpTokenizer(doc)
};
}
private static StatementBlock CreateStatementBlock(MarkupBlock block)
{
var factory = CreateDefaultSpanFactory();
return new StatementBlock(
factory.MetaCode("{").Accepts(AcceptedCharacters.None),
block,
factory.EmptyCSharp().AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharacters.None));
}
}
}

View File

@ -6,6 +6,8 @@ using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Test.Framework;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Xunit;
namespace Microsoft.AspNet.Razor.Test.Parser.CSharp
@ -443,5 +445,84 @@ namespace Microsoft.AspNet.Razor.Test.Parser.CSharp
Factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
Factory.EmptyHtml()));
}
public static TheoryData SectionWithEscapedTransitionData
{
get
{
var factory = CreateDefaultSpanFactory();
return new TheoryData<string, Block>
{
{
"@section s {<span foo='@@' />}",
new MarkupBlock(
factory.EmptyHtml(),
new SectionBlock(new SectionCodeGenerator("s"),
factory.CodeTransition(),
factory.MetaCode("section s {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 17, 0, 17), new LocationTagged<string>("'", 25, 0, 25)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 23, 0, 23), new LocationTagged<string>("@", 23, 0, 23))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />"))),
factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
factory.EmptyHtml())
},
{
"@section s {<span foo='@DateTime.Now @@' />}",
new MarkupBlock(
factory.EmptyHtml(),
new SectionBlock(new SectionCodeGenerator("s"),
factory.CodeTransition(),
factory.MetaCode("section s {")
.AutoCompleteWith(null, atEndOfSpan: true),
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 17, 0, 17), new LocationTagged<string>("'", 39, 0, 39)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 23, 0, 23), 23, 0, 23),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime.Now")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace))),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 36, 0, 36), new LocationTagged<string>("@", 37, 0, 37))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />"))),
factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
factory.EmptyHtml())
},
};
}
}
[Theory]
[MemberData(nameof(SectionWithEscapedTransitionData))]
public void ParseSectionBlock_WithDoubleTransition_DoesNotThrow(string input, Block expected)
{
ParseDocumentTest(input, expected);
}
private static SpanFactory CreateDefaultSpanFactory()
{
return new SpanFactory
{
MarkupTokenizerFactory = doc => new HtmlTokenizer(doc),
CodeTokenizerFactory = doc => new CSharpTokenizer(doc)
};
}
}
}

View File

@ -3,9 +3,11 @@
using System;
using Microsoft.AspNet.Razor.Editor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Test.Framework;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
using Xunit;
@ -272,6 +274,49 @@ namespace Microsoft.AspNet.Razor.Test.Parser.CSharp
GetNestedTemplateError(69));
}
[Fact]
public void ParseBlock_WithDoubleTransition_DoesNotThrow()
{
// Arrange
var testTemplateWithDoubleTransitionCode = " @<p foo='@@'>Foo #@item</p>";
var testTemplateWithDoubleTransition = new TemplateBlock(
new MarkupBlock(
Factory.MarkupTransition(),
new MarkupTagBlock(
Factory.Markup("<p"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 46, 0, 46), new LocationTagged<string>("'", 54, 0, 54)),
Factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
Factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 52, 0, 52), new LocationTagged<string>("@", 52, 0, 52))).Accepts(AcceptedCharacters.None),
Factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
Factory.Markup("'").With(SpanCodeGenerator.Null)),
Factory.Markup(">").Accepts(AcceptedCharacters.None)),
Factory.Markup("Foo #"),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("item")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)
),
new MarkupTagBlock(
Factory.Markup("</p>").Accepts(AcceptedCharacters.None))
)
);
var expected = new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharacters.None),
Factory.Code(" var foo = bar; Html.ExecuteTemplate(foo, ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
testTemplateWithDoubleTransition,
Factory.Code("); ").AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharacters.None));
// Act & Assert
ParseBlockTest("{ var foo = bar; Html.ExecuteTemplate(foo," + testTemplateWithDoubleTransitionCode + "); }", expected);
}
private static RazorError GetNestedTemplateError(int characterIndex)
{
return new RazorError(RazorResources.ParseError_InlineMarkup_Blocks_Cannot_Be_Nested, new SourceLocation(characterIndex, 0, characterIndex));

View File

@ -206,6 +206,31 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html
Factory.Markup(" />"))));
}
[Fact]
public void ConditionalAttributeCollapserDoesNotRewriteEscapedTransitions()
{
// Act
var results = ParseDocument("<span foo='@@' />");
var rewritingContext = new RewritingContext(results.Document, new ErrorSink());
new ConditionalAttributeCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(rewritingContext);
var rewritten = rewritingContext.SyntaxTree;
// Assert
Assert.Equal(0, results.ParserErrors.Count());
EvaluateParseTree(rewritten,
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 13, 0, 13)),
Factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
Factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), new LocationTagged<string>("@", 11, 0, 11))).Accepts(AcceptedCharacters.None),
Factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
Factory.Markup("'").With(SpanCodeGenerator.Null)),
Factory.Markup(" />"))));
}
[Fact]
public void ConditionalAttributesDoNotCreateExtraDataForEntirelyLiteralAttribute()
{

View File

@ -2,11 +2,13 @@
// 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 Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Test.Framework;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Xunit;
namespace Microsoft.AspNet.Razor.Test.Parser.Html
@ -281,5 +283,302 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html
var content = Nested1000.ReadAllText();
ParseDocument(content);
}
public static TheoryData BlockWithEscapedTransitionData
{
get
{
var factory = CreateDefaultSpanFactory();
var datetimeBlock = new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime.Now")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace));
return new TheoryData<string, Block>
{
{
// Double transition in attribute value
"<span foo='@@' />",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 13, 0, 13)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), new LocationTagged<string>("@", 11, 0, 11))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />")))
},
{
// Double transition at the end of attribute value
"<span foo='abc@@' />",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 16, 0, 16)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
factory.Markup("abc").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), new LocationTagged<string>("abc", 11, 0, 11))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 14, 0, 14), new LocationTagged<string>("@", 14, 0, 14))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />")))
},
{
// Double transition at the beginning of attribute value
"<span foo='@@def' />",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 16, 0, 16)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), new LocationTagged<string>("@", 11, 0, 11))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("def").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 13, 0, 13), new LocationTagged<string>("def", 13, 0, 13))),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />")))
},
{
// Double transition in between attribute value
"<span foo='abc @@ def' />",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 21, 0, 21)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
factory.Markup("abc").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), new LocationTagged<string>("abc", 11, 0, 11))),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 14, 0, 14), new LocationTagged<string>("@", 15, 0, 15))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup(" def").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 17, 0, 17), new LocationTagged<string>("def", 18, 0, 18))),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />")))
},
{
// Double transition with expression block
"<span foo='@@@DateTime.Now' />",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 26, 0, 26)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), new LocationTagged<string>("@", 11, 0, 11))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 13, 0, 13), 13, 0, 13),
factory.EmptyHtml().With(SpanCodeGenerator.Null),
datetimeBlock),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />")))
},
{
"<span foo='@DateTime.Now @@' />",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 27, 0, 27)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), 11, 0, 11),
datetimeBlock),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 24, 0, 24), new LocationTagged<string>("@", 25, 0, 25))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />")))
},
{
"<span foo='@(2+3)@@@DateTime.Now' />",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 32, 0, 32)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), 11, 0, 11),
new ExpressionBlock(
factory.CodeTransition(),
factory.MetaCode("(").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None),
factory.Code("2+3").AsExpression(),
factory.MetaCode(")").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 17, 0, 17), new LocationTagged<string>("@", 17, 0, 17))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 19, 0, 19), 19, 0, 19),
factory.EmptyHtml().With(SpanCodeGenerator.Null),
datetimeBlock),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />")))
},
{
"<span foo='@@@(2+3)' />",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 19, 0, 19)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), new LocationTagged<string>("@", 11, 0, 11))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 13, 0, 13), 13, 0, 13),
factory.EmptyHtml().With(SpanCodeGenerator.Null),
new ExpressionBlock(
factory.CodeTransition(),
factory.MetaCode("(").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None),
factory.Code("2+3").AsExpression(),
factory.MetaCode(")").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None))),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />")))
},
{
"<span foo='@DateTime.Now@@' />",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 26, 0, 26)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), 11, 0, 11),
datetimeBlock),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 24, 0, 24), new LocationTagged<string>("@", 24, 0, 24))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />")))
},
{
// Double transition with email in attribute value
"<span foo='abc@def.com @@' />",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 25, 0, 25)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
factory.Markup("abc@def.com").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), new LocationTagged<string>("abc@def.com", 11, 0, 11))),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 22, 0, 22), new LocationTagged<string>("@", 23, 0, 23))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />")))
},
{
"<span foo='abc@@def.com @@' />",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 26, 0, 26)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
factory.Markup("abc").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), new LocationTagged<string>("abc", 11, 0, 11))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 14, 0, 14), new LocationTagged<string>("@", 14, 0, 14))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("def.com").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 16, 0, 16), new LocationTagged<string>("def.com", 16, 0, 16))),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(" ", 23, 0, 23), new LocationTagged<string>("@", 24, 0, 24))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("'").With(SpanCodeGenerator.Null)),
factory.Markup(" />")))
},
{
// Double transition before end of file
"<span foo='@@",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>(string.Empty, 13, 0, 13)),
factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), new LocationTagged<string>("@", 11, 0, 11))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)))),
factory.EmptyHtml())
},
{
// Double transition in complex regex in attribute value
@"<span foo=""/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@@[a-z0-9]([a-z0-9-]*[a-z0-9])?\.([a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i"" />",
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo=\"", 5, 0, 5), new LocationTagged<string>("\"", 111, 0, 111)),
factory.Markup(" foo=\"").With(SpanCodeGenerator.Null),
factory.Markup(@"/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), new LocationTagged<string>(@"/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+", 11, 0, 11))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 43, 0, 43), new LocationTagged<string>("@", 43, 0, 43))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup(@"[a-z0-9]([a-z0-9-]*[a-z0-9])?\.([a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 45, 0, 45), new LocationTagged<string>(@"[a-z0-9]([a-z0-9-]*[a-z0-9])?\.([a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i", 45, 0, 45))),
factory.Markup("\"").With(SpanCodeGenerator.Null)),
factory.Markup(" />")))
},
};
}
}
[Theory]
[MemberData(nameof(BlockWithEscapedTransitionData))]
public void ParseBlock_WithDoubleTransition_DoesNotThrow(string input, Block expected)
{
// Act & Assert
ParseDocumentTest(input, expected);
}
[Fact]
public void ParseDocument_WithUnexpectedTransitionsInAttributeValue_Throws()
{
// Arrange
var expected = new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<span"),
new MarkupBlock(
new AttributeBlockCodeGenerator("foo", new LocationTagged<string>(" foo='", 5, 0, 5), new LocationTagged<string>("'", 14, 0, 14)),
Factory.Markup(" foo='").With(SpanCodeGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(string.Empty, 11, 0, 11), 11, 0, 11),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp().AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace))),
new MarkupBlock(
new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(" ", 12, 0, 12), 12, 0, 12),
Factory.Markup(" ").With(SpanCodeGenerator.Null),
new ExpressionBlock(
Factory.CodeTransition().Accepts(AcceptedCharacters.None).With(SpanCodeGenerator.Null),
Factory.EmptyCSharp().AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharacters.NonWhiteSpace))),
Factory.Markup("'").With(SpanCodeGenerator.Null)),
Factory.Markup(" />")));
var expectedErrors = new RazorError[]
{
new RazorError(@"A space or line break was encountered after the ""@"" character. Only valid identifiers, keywords, comments, ""("" and ""{"" are valid at the start of a code block and they must occur immediately following ""@"" with no space in between.", new SourceLocation(12, 0, 12)),
new RazorError(@"""' />"" is not valid at the start of a code block. Only identifiers, keywords, comments, ""("" and ""{"" are valid.", new SourceLocation(14, 0, 14)),
};
// Act & Assert
ParseDocumentTest("<span foo='@ @' />", expected, expectedErrors);
}
private static SpanFactory CreateDefaultSpanFactory()
{
return new SpanFactory
{
MarkupTokenizerFactory = doc => new HtmlTokenizer(doc),
CodeTokenizerFactory = doc => new CSharpTokenizer(doc)
};
}
}
}

View File

@ -352,6 +352,24 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
},
children: factory.Markup("words and spaces")))
},
{
"<div style=\"\" class=\"btn\" catchAll=\"@@hi\" >words and spaces</div>",
new MarkupBlock(
new MarkupTagHelperBlock(
"div",
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
{
new KeyValuePair<string, SyntaxTreeNode>("style", new MarkupBlock()),
new KeyValuePair<string, SyntaxTreeNode>("class", factory.Markup("btn")),
new KeyValuePair<string, SyntaxTreeNode>("catchAll",
new MarkupBlock(
new MarkupBlock(
factory.Markup("@").Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("hi"))),
},
children: factory.Markup("words and spaces")))
},
{
"<div style=\"@DateTime.Now\" class=\"@DateTime.Now\" catchAll=\"@DateTime.Now\" >words and " +
"spaces</div>",
@ -1143,6 +1161,32 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
})),
availableDescriptorsText
},
{
"<PREFIXmyth2 bound=\"@@@DateTime.Now\" />",
new MarkupBlock(
new MarkupTagHelperBlock(
"PREFIXmyth2",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
{
{
new KeyValuePair<string, SyntaxTreeNode>(
"bound",
new MarkupBlock(
new MarkupBlock(
factory.Markup("@").Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
new MarkupBlock(
factory.EmptyHtml(),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime.Now")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)))))
}
})),
availableDescriptorsText
},
};
}
}
@ -2175,6 +2219,41 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
absoluteIndex: 2, lineIndex: 0, columnIndex: 2)
}
},
{
"@{<!p class=\"btn@@}",
buildPartialStatementBlock(
() => new MarkupBlock(
new MarkupTagBlock(
factory.Markup("<"),
factory.BangEscape(),
factory.Markup("p"),
new MarkupBlock(
new AttributeBlockCodeGenerator(
name: "class",
prefix: new LocationTagged<string>(" class=\"", 5, 0, 5),
suffix: new LocationTagged<string>(string.Empty, 19, 0, 19)),
factory.Markup(" class=\"").With(SpanCodeGenerator.Null),
factory.Markup("btn").With(
new LiteralAttributeCodeGenerator(
prefix: new LocationTagged<string>(string.Empty, 13, 0, 13),
value: new LocationTagged<string>("btn", 13, 0, 13))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeCodeGenerator(new LocationTagged<string>(string.Empty, 16, 0, 16), new LocationTagged<string>("@", 16, 0, 16))).Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("}").With(
new LiteralAttributeCodeGenerator(
prefix: new LocationTagged<string>(string.Empty, 18, 0, 18),
value: new LocationTagged<string>("}", 18, 0, 18))))))),
new []
{
new RazorError(
errorMatchingBrace,
absoluteIndex: 1, lineIndex: 0, columnIndex: 1),
new RazorError(
string.Format(errorEOFMatchingBrace, "!p"),
absoluteIndex: 2, lineIndex: 0, columnIndex: 2)
}
},
{
"@{<!p class=\"btn\"}",
buildPartialStatementBlock(
@ -3697,6 +3776,75 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
new MarkupBlock(factory.Markup("Time:"), dateTimeNow))
}))
},
{
"<person age=\"12\" birthday=\"DateTime.Now\" name=\"Time: @@ @DateTime.Now\" />",
new MarkupBlock(
new MarkupTagHelperBlock("person",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
{
new KeyValuePair<string, SyntaxTreeNode>("age", factory.CodeMarkup("12")),
new KeyValuePair<string, SyntaxTreeNode>(
"birthday",
factory.CodeMarkup("DateTime.Now")),
new KeyValuePair<string, SyntaxTreeNode>(
"name",
new MarkupBlock(
factory.Markup("Time:"),
new MarkupBlock(
factory.Markup(" @").Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
dateTimeNow))
}))
},
{
"<person age=\"12\" birthday=\"DateTime.Now\" name=\"@@BoundStringAttribute\" />",
new MarkupBlock(
new MarkupTagHelperBlock("person",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
{
new KeyValuePair<string, SyntaxTreeNode>("age", factory.CodeMarkup("12")),
new KeyValuePair<string, SyntaxTreeNode>(
"birthday",
factory.CodeMarkup("DateTime.Now")),
new KeyValuePair<string, SyntaxTreeNode>(
"name",
new MarkupBlock(
new MarkupBlock(
factory.Markup("@").Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("BoundStringAttribute")))
}))
},
{
"<person age=\"@@@(11+1)\" birthday=\"DateTime.Now\" name=\"Time: @DateTime.Now\" />",
new MarkupBlock(
new MarkupTagHelperBlock("person",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>
{
new KeyValuePair<string, SyntaxTreeNode>(
"age",
new MarkupBlock(
new MarkupBlock(
factory.Markup("@").Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
new MarkupBlock(
factory.EmptyHtml(),
new ExpressionBlock(
factory.CodeTransition(),
factory.MetaCode("(").Accepts(AcceptedCharacters.None),
factory.Code("11+1").AsExpression(),
factory.MetaCode(")").Accepts(AcceptedCharacters.None))))),
new KeyValuePair<string, SyntaxTreeNode>(
"birthday",
factory.CodeMarkup("DateTime.Now")),
new KeyValuePair<string, SyntaxTreeNode>(
"name",
new MarkupBlock(factory.Markup("Time:"), dateTimeNow))
}))
},
};
}
}
@ -4535,6 +4683,25 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
}),
factory.Markup(" World")))
};
yield return new object[] {
"<p>Hello <script class=\"@@foo@bar.com\" style=\"color:red;\"></script> World</p>",
new MarkupBlock(
new MarkupTagHelperBlock("p",
factory.Markup("Hello "),
new MarkupTagHelperBlock("script",
new List<KeyValuePair<string, SyntaxTreeNode>>
{
new KeyValuePair<string, SyntaxTreeNode>(
"class",
new MarkupBlock(
new MarkupBlock(
factory.Markup("@").Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup("foo@bar.com"))),
new KeyValuePair<string, SyntaxTreeNode>("style", factory.Markup("color:red;"))
}),
factory.Markup(" World")))
};
}
}
@ -4651,6 +4818,25 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
},
factory.Markup("Hello World")))
};
yield return new object[] {
"<p class=foo dynamic=@DateTime.Now style=color@@:red;>Hello World</p>",
new MarkupBlock(
new MarkupTagHelperBlock("p",
new List<KeyValuePair<string, SyntaxTreeNode>>
{
new KeyValuePair<string, SyntaxTreeNode>("class", factory.Markup("foo")),
new KeyValuePair<string, SyntaxTreeNode>("dynamic", new MarkupBlock(dateTimeNow)),
new KeyValuePair<string, SyntaxTreeNode>(
"style",
new MarkupBlock(
factory.Markup("color"),
new MarkupBlock(
factory.Markup("@").Accepts(AcceptedCharacters.None),
factory.Markup("@").With(SpanCodeGenerator.Null).Accepts(AcceptedCharacters.None)),
factory.Markup(":red;")))
},
factory.Markup("Hello World")))
};
yield return new object[] {
"<p class=foo dynamic=@DateTime.Now>Hello</p> <p style=color:red; dynamic=@DateTime.Now>World</p>",
new MarkupBlock(