298 lines
12 KiB
C#
298 lines
12 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|
{
|
|
public class CSharpImplicitExpressionTest : CsHtmlCodeParserTestBase
|
|
{
|
|
private const string TestExtraKeyword = "model";
|
|
|
|
public static TheoryData NullConditionalOperatorData_Bracket
|
|
{
|
|
get
|
|
{
|
|
var noErrors = new RazorError[0];
|
|
Func<int, RazorError[]> missingEndParenError = (index) =>
|
|
new RazorError[1]
|
|
{
|
|
new RazorError(
|
|
"An opening \"(\" is missing the corresponding closing \")\".",
|
|
new SourceLocation(index, 0, index),
|
|
length: 1)
|
|
};
|
|
Func<int, RazorError[]> missingEndBracketError = (index) =>
|
|
new RazorError[1]
|
|
{
|
|
new RazorError(
|
|
"An opening \"[\" is missing the corresponding closing \"]\".",
|
|
new SourceLocation(index, 0, index),
|
|
length: 1)
|
|
};
|
|
|
|
// implicitExpression, expectedImplicitExpression, acceptedCharacters, expectedErrors
|
|
return new TheoryData<string, string, AcceptedCharacters, RazorError[]>
|
|
{
|
|
{ "val??[", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
|
|
{ "val??[0", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
|
|
{ "val?[", "val?[", AcceptedCharacters.Any, missingEndBracketError(5) },
|
|
{ "val?(", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
|
|
{ "val?[more", "val?[more", AcceptedCharacters.Any, missingEndBracketError(5) },
|
|
{ "val?[0]", "val?[0]", AcceptedCharacters.NonWhiteSpace, noErrors },
|
|
{ "val?[<p>", "val?[", AcceptedCharacters.Any, missingEndBracketError(5) },
|
|
{ "val?[more.<p>", "val?[more.", AcceptedCharacters.Any, missingEndBracketError(5) },
|
|
{ "val??[more<p>", "val", AcceptedCharacters.NonWhiteSpace, noErrors },
|
|
{ "val?[-1]?", "val?[-1]", AcceptedCharacters.NonWhiteSpace, noErrors },
|
|
{ "val?[abc]?[def", "val?[abc]?[def", AcceptedCharacters.Any, missingEndBracketError(11) },
|
|
{ "val?[abc]?[2]", "val?[abc]?[2]", AcceptedCharacters.NonWhiteSpace, noErrors },
|
|
{ "val?[abc]?.more?[def]", "val?[abc]?.more?[def]", AcceptedCharacters.NonWhiteSpace, noErrors },
|
|
{ "val?[abc]?.more?.abc", "val?[abc]?.more?.abc", AcceptedCharacters.NonWhiteSpace, noErrors },
|
|
{ "val?[null ?? true]", "val?[null ?? true]", AcceptedCharacters.NonWhiteSpace, noErrors },
|
|
{ "val?[abc?.gef?[-1]]", "val?[abc?.gef?[-1]]", AcceptedCharacters.NonWhiteSpace, noErrors },
|
|
};
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(NullConditionalOperatorData_Bracket))]
|
|
public void ParseBlockMethodParsesNullConditionalOperatorImplicitExpression_Bracket(
|
|
string implicitExpresison,
|
|
string expectedImplicitExpression,
|
|
object acceptedCharacters,
|
|
object expectedErrors)
|
|
{
|
|
// Act & Assert
|
|
ImplicitExpressionTest(
|
|
implicitExpresison,
|
|
expectedImplicitExpression,
|
|
(AcceptedCharacters)acceptedCharacters,
|
|
(RazorError[])expectedErrors);
|
|
}
|
|
|
|
public static TheoryData NullConditionalOperatorData_Dot
|
|
{
|
|
get
|
|
{
|
|
// implicitExpression, expectedImplicitExpression
|
|
return new TheoryData<string, string>
|
|
{
|
|
{ "val?", "val" },
|
|
{ "val??", "val" },
|
|
{ "val??more", "val" },
|
|
{ "val?!", "val" },
|
|
{ "val?.", "val?." },
|
|
{ "val??.", "val" },
|
|
{ "val?.(abc)", "val?." },
|
|
{ "val?.<p>", "val?." },
|
|
{ "val?.more", "val?.more" },
|
|
{ "val?.more<p>", "val?.more" },
|
|
{ "val??.more<p>", "val" },
|
|
{ "val?.more(false)?.<p>", "val?.more(false)?." },
|
|
{ "val?.more(false)?.abc", "val?.more(false)?.abc" },
|
|
{ "val?.more(null ?? true)?.abc", "val?.more(null ?? true)?.abc" },
|
|
};
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(NullConditionalOperatorData_Dot))]
|
|
public void ParseBlockMethodParsesNullConditionalOperatorImplicitExpression_Dot(
|
|
string implicitExpresison,
|
|
string expectedImplicitExpression)
|
|
{
|
|
// Act & Assert
|
|
ImplicitExpressionTest(implicitExpresison, expectedImplicitExpression);
|
|
}
|
|
|
|
[Fact]
|
|
public void NestedImplicitExpression()
|
|
{
|
|
ParseBlockTest("if (true) { @foo }",
|
|
new StatementBlock(
|
|
Factory.Code("if (true) { ").AsStatement(),
|
|
new ExpressionBlock(
|
|
Factory.CodeTransition(),
|
|
Factory.Code("foo")
|
|
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
|
|
.Accepts(AcceptedCharacters.NonWhiteSpace)),
|
|
Factory.Code(" }").AsStatement()));
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockAcceptsNonEnglishCharactersThatAreValidIdentifiers()
|
|
{
|
|
ImplicitExpressionTest("हळूँजद॔.", "हळूँजद॔");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockOutputsZeroLengthCodeSpanIfInvalidCharacterFollowsTransition()
|
|
{
|
|
ParseBlockTest("@/",
|
|
new ExpressionBlock(
|
|
Factory.CodeTransition(),
|
|
Factory.EmptyCSharp()
|
|
.AsImplicitExpression(KeywordSet)
|
|
.Accepts(AcceptedCharacters.NonWhiteSpace)),
|
|
new RazorError(
|
|
LegacyResources.FormatParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS("/"),
|
|
new SourceLocation(1, 0, 1),
|
|
length: 1));
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockOutputsZeroLengthCodeSpanIfEOFOccursAfterTransition()
|
|
{
|
|
ParseBlockTest("@",
|
|
new ExpressionBlock(
|
|
Factory.CodeTransition(),
|
|
Factory.EmptyCSharp()
|
|
.AsImplicitExpression(KeywordSet)
|
|
.Accepts(AcceptedCharacters.NonWhiteSpace)),
|
|
new RazorError(
|
|
LegacyResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock,
|
|
new SourceLocation(1, 0, 1),
|
|
length: 1));
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockSupportsSlashesWithinComplexImplicitExpressions()
|
|
{
|
|
ImplicitExpressionTest("DataGridColumn.Template(\"Years of Service\", e => (int)Math.Round((DateTime.Now - dt).TotalDays / 365))");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockMethodParsesSingleIdentifierAsImplicitExpression()
|
|
{
|
|
ImplicitExpressionTest("foo");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockMethodDoesNotAcceptSemicolonIfExpressionTerminatedByWhitespace()
|
|
{
|
|
ImplicitExpressionTest("foo ;", "foo");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockMethodIgnoresSemicolonAtEndOfSimpleImplicitExpression()
|
|
{
|
|
RunTrailingSemicolonTest("foo");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockMethodParsesDottedIdentifiersAsImplicitExpression()
|
|
{
|
|
ImplicitExpressionTest("foo.bar.baz");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockMethodIgnoresSemicolonAtEndOfDottedIdentifiers()
|
|
{
|
|
RunTrailingSemicolonTest("foo.bar.baz");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockMethodDoesNotIncludeDotAtEOFInImplicitExpression()
|
|
{
|
|
ImplicitExpressionTest("foo.bar.", "foo.bar");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockMethodDoesNotIncludeDotFollowedByInvalidIdentifierCharacterInImplicitExpression()
|
|
{
|
|
ImplicitExpressionTest("foo.bar.0", "foo.bar");
|
|
ImplicitExpressionTest("foo.bar.</p>", "foo.bar");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockMethodDoesNotIncludeSemicolonAfterDot()
|
|
{
|
|
ImplicitExpressionTest("foo.bar.;", "foo.bar");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockMethodTerminatesAfterIdentifierUnlessFollowedByDotOrParenInImplicitExpression()
|
|
{
|
|
ImplicitExpressionTest("foo.bar</p>", "foo.bar");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockProperlyParsesParenthesesAndBalancesThemInImplicitExpression()
|
|
{
|
|
ImplicitExpressionTest(@"foo().bar(""bi\""z"", 4)(""chained method; call"").baz(@""bo""""z"", '\'', () => { return 4; }, (4+5+new { foo = bar[4] }))");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockProperlyParsesBracketsAndBalancesThemInImplicitExpression()
|
|
{
|
|
ImplicitExpressionTest(@"foo.bar[4 * (8 + 7)][""fo\""o""].baz");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockTerminatesImplicitExpressionAtHtmlEndTag()
|
|
{
|
|
ImplicitExpressionTest("foo().bar.baz</p>zoop", "foo().bar.baz");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockTerminatesImplicitExpressionAtHtmlStartTag()
|
|
{
|
|
ImplicitExpressionTest("foo().bar.baz<p>zoop", "foo().bar.baz");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockTerminatesImplicitExpressionBeforeDotIfDotNotFollowedByIdentifierStartCharacter()
|
|
{
|
|
ImplicitExpressionTest("foo().bar.baz.42", "foo().bar.baz");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockStopsBalancingParenthesesAtEOF()
|
|
{
|
|
ImplicitExpressionTest(
|
|
"foo(()", "foo(()",
|
|
acceptedCharacters: AcceptedCharacters.Any,
|
|
errors: new RazorError(
|
|
LegacyResources.FormatParseError_Expected_CloseBracket_Before_EOF("(", ")"),
|
|
new SourceLocation(4, 0, 4),
|
|
length: 1));
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockTerminatesImplicitExpressionIfCloseParenFollowedByAnyWhiteSpace()
|
|
{
|
|
ImplicitExpressionTest("foo.bar() (baz)", "foo.bar()");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockTerminatesImplicitExpressionIfIdentifierFollowedByAnyWhiteSpace()
|
|
{
|
|
ImplicitExpressionTest("foo .bar() (baz)", "foo");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockTerminatesImplicitExpressionAtLastValidPointIfDotFollowedByWhitespace()
|
|
{
|
|
ImplicitExpressionTest("foo. bar() (baz)", "foo");
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseBlockOutputExpressionIfModuleTokenNotFollowedByBrace()
|
|
{
|
|
ImplicitExpressionTest("module.foo()");
|
|
}
|
|
|
|
private void RunTrailingSemicolonTest(string expr)
|
|
{
|
|
ParseBlockTest(SyntaxConstants.TransitionString + expr + ";",
|
|
new ExpressionBlock(
|
|
Factory.CodeTransition(),
|
|
Factory.Code(expr)
|
|
.AsImplicitExpression(KeywordSet)
|
|
.Accepts(AcceptedCharacters.NonWhiteSpace)
|
|
));
|
|
}
|
|
}
|
|
}
|