", AcceptedCharactersInternal.None),
Factory.Markup("Foo"),
BlockFactory.MarkupTagBlock("
", AcceptedCharactersInternal.None),
Factory.Markup(" ").Accepts(AcceptedCharactersInternal.None)
),
Factory.Code("foo++; } while (foo);").AsStatement().Accepts(AcceptedCharactersInternal.None)
));
}
[Fact]
public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsSwitchKeyword()
{
SingleSpanBlockTest(@"switch(foo) {
case 0:
break;
case 1:
{
break;
}
case 2:
return;
default:
return;
}", BlockKindInternal.Statement, SpanKindInternal.Code, acceptedCharacters: AcceptedCharactersInternal.None);
}
[Fact]
public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsLockKeyword()
{
SingleSpanBlockTest(
"lock(foo) { Debug.WriteLine(@\"foo } bar\"); }",
BlockKindInternal.Statement,
SpanKindInternal.Code,
acceptedCharacters: AcceptedCharactersInternal.None);
}
[Fact]
public void ParseBlockHasErrorsIfNamespaceImportMissingSemicolon()
{
NamespaceImportTest(
"using Foo.Bar.Baz",
" Foo.Bar.Baz",
acceptedCharacters: AcceptedCharactersInternal.NonWhiteSpace | AcceptedCharactersInternal.WhiteSpace,
location: new SourceLocation(17, 0, 17));
}
[Fact]
public void ParseBlockHasErrorsIfNamespaceAliasMissingSemicolon()
{
NamespaceImportTest(
"using Foo.Bar.Baz = FooBarBaz",
" Foo.Bar.Baz = FooBarBaz",
acceptedCharacters: AcceptedCharactersInternal.NonWhiteSpace | AcceptedCharactersInternal.WhiteSpace,
location: new SourceLocation(29, 0, 29));
}
[Fact]
public void ParseBlockParsesNamespaceImportWithSemicolonForUsingKeywordIfIsInValidFormat()
{
NamespaceImportTest(
"using Foo.Bar.Baz;",
" Foo.Bar.Baz",
AcceptedCharactersInternal.NonWhiteSpace | AcceptedCharactersInternal.WhiteSpace);
}
[Fact]
public void ParseBlockDoesntCaptureWhitespaceAfterUsing()
{
ParseBlockTest("using Foo ",
new DirectiveBlock(
Factory.Code("using Foo")
.AsNamespaceImport(" Foo")
.Accepts(AcceptedCharactersInternal.NonWhiteSpace | AcceptedCharactersInternal.WhiteSpace)));
}
[Fact]
public void ParseBlockCapturesNewlineAfterUsing()
{
ParseBlockTest($"using Foo{Environment.NewLine}",
new DirectiveBlock(
Factory.Code($"using Foo{Environment.NewLine}")
.AsNamespaceImport(" Foo")
.Accepts(AcceptedCharactersInternal.NonWhiteSpace | AcceptedCharactersInternal.WhiteSpace)));
}
[Fact]
public void ParseBlockParsesNamespaceAliasWithSemicolonForUsingKeywordIfIsInValidFormat()
{
NamespaceImportTest(
"using FooBarBaz = FooBarBaz;",
" FooBarBaz = FooBarBaz",
AcceptedCharactersInternal.NonWhiteSpace | AcceptedCharactersInternal.WhiteSpace);
}
[Fact]
public void ParseBlockTerminatesUsingKeywordAtEOFAndOutputsFileCodeBlock()
{
SingleSpanBlockTest("using ", BlockKindInternal.Statement, SpanKindInternal.Code);
}
[Fact]
public void ParseBlockTerminatesSingleLineCommentAtEndOfFile()
{
const string document = "foreach(var f in Foo) { // foo bar baz";
SingleSpanBlockTest(
document,
document,
BlockKindInternal.Statement,
SpanKindInternal.Code,
RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
new SourceSpan(SourceLocation.Zero, contentLength: 1), "foreach", "}", "{"));
}
[Fact]
public void ParseBlockTerminatesBlockCommentAtEndOfFile()
{
const string document = "foreach(var f in Foo) { /* foo bar baz";
SingleSpanBlockTest(
document,
document,
BlockKindInternal.Statement,
SpanKindInternal.Code,
RazorDiagnosticFactory.CreateParsing_BlockCommentNotTerminated(
new SourceSpan(new SourceLocation(24, 0, 24), contentLength: 1)),
RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
new SourceSpan(SourceLocation.Zero, contentLength: 1), "foreach", "}", "{"));
}
[Fact]
public void ParseBlockTerminatesSingleSlashAtEndOfFile()
{
const string document = "foreach(var f in Foo) { / foo bar baz";
SingleSpanBlockTest(
document,
document,
BlockKindInternal.Statement,
SpanKindInternal.Code,
RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
new SourceSpan(SourceLocation.Zero, contentLength: 1), "foreach", "}", "{"));
}
[Fact]
public void ParseBlockSupportsBlockCommentBetweenTryAndFinallyClause()
{
SingleSpanBlockTest("try { bar(); } /* Foo */ /* Bar */ finally { baz(); }", BlockKindInternal.Statement, SpanKindInternal.Code, acceptedCharacters: AcceptedCharactersInternal.None);
}
[Fact]
public void ParseBlockSupportsRazorCommentBetweenTryAndFinallyClause()
{
RunRazorCommentBetweenClausesTest("try { bar(); } ", " finally { biz(); }", acceptedCharacters: AcceptedCharactersInternal.None);
}
[Fact]
public void ParseBlockSupportsBlockCommentBetweenCatchAndFinallyClause()
{
SingleSpanBlockTest(
"try { bar(); } catch(bar) { baz(); } /* Foo */ /* Bar */ finally { biz(); }",
BlockKindInternal.Statement,
SpanKindInternal.Code,
acceptedCharacters: AcceptedCharactersInternal.None);
}
[Fact]
public void ParseBlockSupportsRazorCommentBetweenCatchAndFinallyClause()
{
RunRazorCommentBetweenClausesTest(
"try { bar(); } catch(bar) { baz(); } ", " finally { biz(); }",
acceptedCharacters: AcceptedCharactersInternal.None);
}
[Fact]
public void ParseBlockSupportsBlockCommentBetweenTryAndCatchClause()
{
SingleSpanBlockTest("try { bar(); } /* Foo */ /* Bar */ catch(bar) { baz(); }", BlockKindInternal.Statement, SpanKindInternal.Code);
}
[Fact]
public void ParseBlockSupportsRazorCommentBetweenTryAndCatchClause()
{
RunRazorCommentBetweenClausesTest("try { bar(); }", " catch(bar) { baz(); }");
}
[Fact]
public void ParseBlockSupportsLineCommentBetweenTryAndFinallyClause()
{
SingleSpanBlockTest(@"try { bar(); }
// Foo
// Bar
finally { baz(); }", BlockKindInternal.Statement, SpanKindInternal.Code, acceptedCharacters: AcceptedCharactersInternal.None);
}
[Fact]
public void ParseBlockSupportsLineCommentBetweenCatchAndFinallyClause()
{
SingleSpanBlockTest(@"try { bar(); } catch(bar) { baz(); }
// Foo
// Bar
finally { biz(); }", BlockKindInternal.Statement, SpanKindInternal.Code, acceptedCharacters: AcceptedCharactersInternal.None);
}
[Fact]
public void ParseBlockSupportsLineCommentBetweenTryAndCatchClause()
{
SingleSpanBlockTest(@"try { bar(); }
// Foo
// Bar
catch(bar) { baz(); }", BlockKindInternal.Statement, SpanKindInternal.Code);
}
[Fact]
public void ParseBlockSupportsTryStatementWithNoAdditionalClauses()
{
SingleSpanBlockTest("try { var foo = new { } }", BlockKindInternal.Statement, SpanKindInternal.Code);
}
[Fact]
public void ParseBlockSupportsMarkupWithinTryClause()
{
RunSimpleWrappedMarkupTest(
prefix: "try {",
markup: " Foo
",
suffix: "}",
expectedStart: new SourceLocation(5, 0, 5),
expectedMarkup: new MarkupBlock(
Factory.Markup(" "),
BlockFactory.MarkupTagBlock("", AcceptedCharactersInternal.None),
Factory.Markup("Foo"),
BlockFactory.MarkupTagBlock("
", AcceptedCharactersInternal.None),
Factory.Markup(" ").Accepts(AcceptedCharactersInternal.None)));
}
[Fact]
public void ParseBlockSupportsTryStatementWithOneCatchClause()
{
SingleSpanBlockTest("try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } }", BlockKindInternal.Statement, SpanKindInternal.Code);
}
[Fact]
public void ParseBlockSupportsMarkupWithinCatchClause()
{
RunSimpleWrappedMarkupTest(
prefix: "try { var foo = new { } } catch(Foo Bar Baz) {",
markup: " Foo
",
suffix: "}",
expectedStart: new SourceLocation(46, 0, 46),
expectedMarkup: new MarkupBlock(
Factory.Markup(" "),
BlockFactory.MarkupTagBlock("", AcceptedCharactersInternal.None),
Factory.Markup("Foo"),
BlockFactory.MarkupTagBlock("
", AcceptedCharactersInternal.None),
Factory.Markup(" ").Accepts(AcceptedCharactersInternal.None)));
}
[Fact]
public void ParseBlockSupportsTryStatementWithMultipleCatchClause()
{
SingleSpanBlockTest(
"try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) " +
"{ var foo = new { } } catch(Foo Bar Baz) { var foo = new { } }",
BlockKindInternal.Statement,
SpanKindInternal.Code);
}
[Fact]
public void ParseBlockSupportsExceptionLessCatchClauses()
{
SingleSpanBlockTest("try { var foo = new { } } catch { var foo = new { } }", BlockKindInternal.Statement, SpanKindInternal.Code);
}
[Fact]
public void ParseBlockSupportsMarkupWithinAdditionalCatchClauses()
{
RunSimpleWrappedMarkupTest(
prefix: "try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) " +
"{ var foo = new { } } catch(Foo Bar Baz) {",
markup: " Foo
",
suffix: "}",
expectedStart: new SourceLocation(128, 0, 128),
expectedMarkup: new MarkupBlock(
Factory.Markup(" "),
BlockFactory.MarkupTagBlock("", AcceptedCharactersInternal.None),
Factory.Markup("Foo"),
BlockFactory.MarkupTagBlock("
", AcceptedCharactersInternal.None),
Factory.Markup(" ").Accepts(AcceptedCharactersInternal.None)));
}
[Fact]
public void ParseBlockSupportsTryStatementWithFinallyClause()
{
SingleSpanBlockTest("try { var foo = new { } } finally { var foo = new { } }", BlockKindInternal.Statement, SpanKindInternal.Code, acceptedCharacters: AcceptedCharactersInternal.None);
}
[Fact]
public void ParseBlockSupportsMarkupWithinFinallyClause()
{
RunSimpleWrappedMarkupTest(
prefix: "try { var foo = new { } } finally {",
markup: " Foo
",
suffix: "}",
expectedStart: new SourceLocation(35, 0, 35),
expectedMarkup: new MarkupBlock(
Factory.Markup(" "),
BlockFactory.MarkupTagBlock("", AcceptedCharactersInternal.None),
Factory.Markup("Foo"),
BlockFactory.MarkupTagBlock("
", AcceptedCharactersInternal.None),
Factory.Markup(" ").Accepts(AcceptedCharactersInternal.None)),
acceptedCharacters: AcceptedCharactersInternal.None);
}
[Fact]
public void ParseBlockStopsParsingCatchClausesAfterFinallyBlock()
{
var expectedContent = "try { var foo = new { } } finally { var foo = new { } }";
SingleSpanBlockTest(expectedContent + " catch(Foo Bar Baz) { }", expectedContent, BlockKindInternal.Statement, SpanKindInternal.Code, acceptedCharacters: AcceptedCharactersInternal.None);
}
[Fact]
public void ParseBlockDoesNotAllowMultipleFinallyBlocks()
{
var expectedContent = "try { var foo = new { } } finally { var foo = new { } }";
SingleSpanBlockTest(expectedContent + " finally { }", expectedContent, BlockKindInternal.Statement, SpanKindInternal.Code, acceptedCharacters: AcceptedCharactersInternal.None);
}
[Fact]
public void ParseBlockAcceptsTrailingDotIntoImplicitExpressionWhenEmbeddedInCode()
{
// Arrange
ParseBlockTest(@"if(foo) { @foo. }",
new StatementBlock(
Factory.Code("if(foo) { ").AsStatement(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("foo.")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)
),
Factory.Code(" }").AsStatement()
));
}
[Fact]
public void ParseBlockParsesExpressionOnSwitchCharacterFollowedByOpenParen()
{
// Arrange
ParseBlockTest(@"if(foo) { @(foo + bar) }",
new StatementBlock(
Factory.Code("if(foo) { ").AsStatement(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.MetaCode("(").Accepts(AcceptedCharactersInternal.None),
Factory.Code("foo + bar").AsExpression(),
Factory.MetaCode(")").Accepts(AcceptedCharactersInternal.None)
),
Factory.Code(" }").AsStatement()
));
}
[Fact]
public void ParseBlockParsesExpressionOnSwitchCharacterFollowedByIdentifierStart()
{
// Arrange
ParseBlockTest(@"if(foo) { @foo[4].bar() }",
new StatementBlock(
Factory.Code("if(foo) { ").AsStatement(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("foo[4].bar()")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)
),
Factory.Code(" }").AsStatement()
));
}
[Fact]
public void ParseBlockTreatsDoubleAtSignAsEscapeSequenceIfAtStatementStart()
{
// Arrange
ParseBlockTest(@"if(foo) { @@class.Foo() }",
new StatementBlock(
Factory.Code("if(foo) { ").AsStatement(),
Factory.Code("@").Hidden(),
Factory.Code("@class.Foo() }").AsStatement()
));
}
[Fact]
public void ParseBlockTreatsAtSignsAfterFirstPairAsPartOfCSharpStatement()
{
// Arrange
ParseBlockTest(@"if(foo) { @@@@class.Foo() }",
new StatementBlock(
Factory.Code("if(foo) { ").AsStatement(),
Factory.Code("@").Hidden(),
Factory.Code("@@@class.Foo() }").AsStatement()
));
}
[Fact]
public void ParseBlockDoesNotParseMarkupStatementOrExpressionOnSwitchCharacterNotFollowedByOpenAngleOrColon()
{
// Arrange
ParseBlockTest("if(foo) { @\"Foo\".ToString(); }",
new StatementBlock(
Factory.Code("if(foo) { @\"Foo\".ToString(); }").AsStatement()));
}
[Fact]
public void ParsersCanNestRecursively()
{
// Arrange
ParseBlockTest("foreach(var c in db.Categories) {" + Environment.NewLine
+ " " + Environment.NewLine
+ "
@c.Name
" + Environment.NewLine
+ "
" + Environment.NewLine
+ " @foreach(var p in c.Products) {" + Environment.NewLine
+ " - @p.Name
" + Environment.NewLine
+ " }" + Environment.NewLine
+ "
" + Environment.NewLine
+ "
" + Environment.NewLine
+ " }",
new StatementBlock(
Factory.Code("foreach(var c in db.Categories) {" + Environment.NewLine).AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
BlockFactory.MarkupTagBlock("", AcceptedCharactersInternal.None),
Factory.Markup(Environment.NewLine + " "),
BlockFactory.MarkupTagBlock("
", AcceptedCharactersInternal.None),
Factory.EmptyHtml(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("c.Name")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
BlockFactory.MarkupTagBlock("
", AcceptedCharactersInternal.None),
Factory.Markup(Environment.NewLine + " "),
BlockFactory.MarkupTagBlock("
", AcceptedCharactersInternal.None),
Factory.Markup(Environment.NewLine),
new StatementBlock(
Factory.Code(@" ").AsStatement(),
Factory.CodeTransition(),
Factory.Code("foreach(var p in c.Products) {" + Environment.NewLine).AsStatement(),
new MarkupBlock(
Factory.Markup(" "),
BlockFactory.MarkupTagBlock("- ", AcceptedCharactersInternal.None),
new MarkupTagBlock(
Factory.Markup("(" href=\"", 183 + Environment.NewLine.Length * 5, 5, 30),
new LocationTagged("\"", 246 + Environment.NewLine.Length * 5, 5, 93)),
Factory.Markup(" href=\"").With(SpanChunkGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockChunkGenerator(
new LocationTagged(string.Empty, 190 + Environment.NewLine.Length * 5, 5, 37), 190 + Environment.NewLine.Length * 5, 5, 37),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("Html.ActionUrl(\"Products\", \"Detail\", new { id = p.Id })")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace))),
Factory.Markup("\"").With(SpanChunkGenerator.Null)),
Factory.Markup(">").Accepts(AcceptedCharactersInternal.None)),
Factory.EmptyHtml(),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("p.Name")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
BlockFactory.MarkupTagBlock("", AcceptedCharactersInternal.None),
BlockFactory.MarkupTagBlock("
", AcceptedCharactersInternal.None),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharactersInternal.None)),
Factory.Code(" }" + Environment.NewLine).AsStatement().Accepts(AcceptedCharactersInternal.None)),
Factory.Markup(" "),
BlockFactory.MarkupTagBlock("
", AcceptedCharactersInternal.None),
Factory.Markup(Environment.NewLine + " "),
BlockFactory.MarkupTagBlock("
", AcceptedCharactersInternal.None),
Factory.Markup(Environment.NewLine).Accepts(AcceptedCharactersInternal.None)),
Factory.Code(" }").AsStatement().Accepts(AcceptedCharactersInternal.None)));
}
public static TheoryData BlockWithEscapedTransitionData
{
get
{
var factory = new SpanFactory();
var datetimeBlock = new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime.Now")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace));
return new TheoryData
{
{
// Double transition in attribute value
"{}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 14, 0, 14)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("@", 12, 0, 12))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))))
},
{
// Double transition at the end of attribute value
"{}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 17, 0, 17)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
factory.Markup("abc").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("abc", 12, 0, 12))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 15, 0, 15), new LocationTagged("@", 15, 0, 15))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))))
},
{
// Double transition at the beginning attribute value
"{}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 17, 0, 17)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("@", 12, 0, 12))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
factory.Markup("def").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 14, 0, 14), new LocationTagged("def", 14, 0, 14))),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))))
},
{
// Double transition in between attribute value
"{}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 22, 0, 22)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
factory.Markup("abc").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("abc", 12, 0, 12))),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeChunkGenerator(new LocationTagged(" ", 15, 0, 15), new LocationTagged("@", 16, 0, 16))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
factory.Markup(" def").With(new LiteralAttributeChunkGenerator(new LocationTagged(" ", 18, 0, 18), new LocationTagged("def", 19, 0, 19))),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))))
},
{
// Double transition with expression block
"{}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 27, 0, 27)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("@", 12, 0, 12))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
new MarkupBlock(
new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 14, 0, 14), 14, 0, 14),
factory.EmptyHtml().With(SpanChunkGenerator.Null),
datetimeBlock),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))))
},
{
"{}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 28, 0, 28)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), 12, 0, 12),
datetimeBlock),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeChunkGenerator(new LocationTagged(" ", 25, 0, 25), new LocationTagged("@", 26, 0, 26))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))))
},
{
"{}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 27, 0, 27)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), 12, 0, 12),
datetimeBlock),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 25, 0, 25), new LocationTagged("@", 25, 0, 25))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))))
},
{
"{}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 33, 0, 33)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), 12, 0, 12),
new ExpressionBlock(
factory.CodeTransition(),
factory.MetaCode("(").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None),
factory.Code("2+3").AsExpression(),
factory.MetaCode(")").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 18, 0, 18), new LocationTagged("@", 18, 0, 18))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
new MarkupBlock(
new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 20, 0, 20), 20, 0, 20),
factory.EmptyHtml().With(SpanChunkGenerator.Null),
datetimeBlock),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))))
},
{
"{}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 20, 0, 20)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("@", 12, 0, 12))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
new MarkupBlock(
new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 14, 0, 14), 14, 0, 14),
factory.EmptyHtml().With(SpanChunkGenerator.Null),
new ExpressionBlock(
factory.CodeTransition(),
factory.MetaCode("(").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None),
factory.Code("2+3").AsExpression(),
factory.MetaCode(")").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None))),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))))
},
{
// Double transition with email in attribute value
"{}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 26, 0, 26)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
factory.Markup("abc@def.com").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("abc@def.com", 12, 0, 12))),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeChunkGenerator(new LocationTagged(" ", 23, 0, 23), new LocationTagged("@", 24, 0, 24))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))))
},
{
"{}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("(" foo='", 6, 0, 6), new LocationTagged("'", 27, 0, 27)),
factory.Markup(" foo='").With(SpanChunkGenerator.Null),
factory.Markup("abc").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("abc", 12, 0, 12))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 15, 0, 15), new LocationTagged("@", 15, 0, 15))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
factory.Markup("def.com").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 17, 0, 17), new LocationTagged("def.com", 17, 0, 17))),
new MarkupBlock(
factory.Markup(" @").With(new LiteralAttributeChunkGenerator(new LocationTagged(" ", 24, 0, 24), new LocationTagged("@", 25, 0, 25))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
factory.Markup("'").With(SpanChunkGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))))
},
{
// Double transition in complex regex in attribute value
@"{}",
CreateStatementBlock(
new MarkupBlock(
new MarkupTagBlock(
factory.Markup("(" foo=\"", 6, 0, 6), new LocationTagged("\"", 112, 0, 112)),
factory.Markup(" foo=\"").With(SpanChunkGenerator.Null),
factory.Markup(@"/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged(@"/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+", 12, 0, 12))),
new MarkupBlock(
factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 44, 0, 44), new LocationTagged("@", 44, 0, 44))).Accepts(AcceptedCharactersInternal.None),
factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)),
factory.Markup(@"[a-z0-9]([a-z0-9-]*[a-z0-9])?\.([a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 46, 0, 46), new LocationTagged(@"[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(SpanChunkGenerator.Null)),
factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))))
},
};
}
}
[Theory]
[MemberData(nameof(BlockWithEscapedTransitionData))]
public void ParseBlock_WithDoubleTransition_DoesNotThrow(string input, object expected)
{
FixupSpans = true;
// Act & Assert
ParseBlockTest(input, (Block)expected);
}
[Fact]
public void ParseBlock_WithDoubleTransition_EndOfFile_Throws()
{
// Arrange
var expected = new StatementBlock(
Factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("(" foo='", 6, 0, 6), new LocationTagged(string.Empty, 14, 0, 14)),
Factory.Markup(" foo='").With(SpanChunkGenerator.Null),
new MarkupBlock(
Factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), new LocationTagged("@", 12, 0, 12))).Accepts(AcceptedCharactersInternal.None),
Factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharactersInternal.None)))),
Factory.EmptyHtml()));
var expectedErrors = new RazorDiagnostic[]
{
RazorDiagnosticFactory.CreateParsing_UnfinishedTag(
new SourceSpan(new SourceLocation(2, 0, 2), contentLength: 4), "span"),
RazorDiagnosticFactory.CreateParsing_ExpectedEndOfBlockBeforeEOF(
new SourceSpan(SourceLocation.Zero, contentLength: 1), Resources.BlockName_Code, "}", "{"),
};
// Act & Assert
ParseBlockTest("{("'", 15, 0, 15)),
Factory.Markup(" foo='").With(SpanChunkGenerator.Null),
new MarkupBlock(
new DynamicAttributeBlockChunkGenerator(new LocationTagged(string.Empty, 12, 0, 12), 12, 0, 12),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp().AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace))),
new MarkupBlock(
new DynamicAttributeBlockChunkGenerator(new LocationTagged(" ", 13, 0, 13), 13, 0, 13),
Factory.Markup(" ").With(SpanChunkGenerator.Null),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.EmptyCSharp().AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace))),
Factory.Markup("'").With(SpanChunkGenerator.Null)),
Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))),
Factory.EmptyCSharp().AsStatement(),
Factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None));
var expectedErrors = new RazorDiagnostic[]
{
RazorDiagnosticFactory.CreateParsing_UnexpectedWhiteSpaceAtStartOfCodeBlock(
new SourceSpan(new SourceLocation(13, 0, 13), contentLength: 1)),
RazorDiagnosticFactory.CreateParsing_UnexpectedCharacterAtStartOfCodeBlock(
new SourceSpan(new SourceLocation(15, 0, 15), contentLength: 5),
"' />}"),
};
// Act & Assert
ParseBlockTest("{}", expected, expectedErrors);
}
private void RunRazorCommentBetweenClausesTest(string preComment, string postComment, AcceptedCharactersInternal acceptedCharacters = AcceptedCharactersInternal.Any)
{
ParseBlockTest(preComment + "@* Foo *@ @* Bar *@" + postComment,
new StatementBlock(
Factory.Code(preComment).AsStatement(),
new CommentBlock(
Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharactersInternal.None),
Factory.Comment(" Foo ", CSharpSymbolType.RazorComment),
Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharactersInternal.None),
Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
),
Factory.Code(" ").AsStatement(),
new CommentBlock(
Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharactersInternal.None),
Factory.Comment(" Bar ", CSharpSymbolType.RazorComment),
Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharactersInternal.None),
Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
),
Factory.Code(postComment).AsStatement().Accepts(acceptedCharacters)));
}
private void RunSimpleWrappedMarkupTest(string prefix, string markup, string suffix, MarkupBlock expectedMarkup, SourceLocation expectedStart, AcceptedCharactersInternal acceptedCharacters = AcceptedCharactersInternal.Any)
{
var expected = new StatementBlock(
Factory.Code(prefix).AsStatement(),
expectedMarkup,
Factory.Code(suffix).AsStatement().Accepts(acceptedCharacters));
// Since we're building the 'expected' input out of order we need to do some trickery
// to get the locations right.
SpancestryCorrector.Correct(expected);
expected.FindFirstDescendentSpan().ChangeStart(SourceLocation.Zero);
// We make the caller pass a start location so we can verify that nothing has gone awry.
Assert.Equal(expectedStart, expectedMarkup.Start);
ParseBlockTest(prefix + markup + suffix, expected);
}
private void NamespaceImportTest(string content, string expectedNS, AcceptedCharactersInternal acceptedCharacters = AcceptedCharactersInternal.None, SourceLocation? location = null)
{
ParseBlockTest(content,
new DirectiveBlock(
Factory.Code(content)
.AsNamespaceImport(expectedNS)
.Accepts(acceptedCharacters)));
}
private static StatementBlock CreateStatementBlock(MarkupBlock block)
{
var factory = new SpanFactory();
return new StatementBlock(
factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
block,
factory.EmptyCSharp().AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None));
}
}
}