PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/test/System.Web.Razor.Test/Parser/CSharp/CSharpBlockTest.cs

https://bitbucket.org/mdavid/aspnetwebstack
C# | 731 lines | 638 code | 83 blank | 10 comment | 2 complexity | 45e43a82534a3d1f962ee81bd75ae6eb MD5 | raw file
  1. using System.Web.Razor.Generator;
  2. using System.Web.Razor.Parser;
  3. using System.Web.Razor.Parser.SyntaxTree;
  4. using System.Web.Razor.Resources;
  5. using System.Web.Razor.Test.Framework;
  6. using System.Web.Razor.Text;
  7. using System.Web.Razor.Tokenizer.Symbols;
  8. using Xunit;
  9. using Assert = Microsoft.TestCommon.AssertEx;
  10. namespace System.Web.Razor.Test.Parser.CSharp
  11. {
  12. public class CSharpBlockTest : CsHtmlCodeParserTestBase
  13. {
  14. [Fact]
  15. public void ParseBlockMethodThrowsArgNullExceptionOnNullContext()
  16. {
  17. // Arrange
  18. CSharpCodeParser parser = new CSharpCodeParser();
  19. // Act and Assert
  20. Assert.Throws<InvalidOperationException>(() => parser.ParseBlock(), RazorResources.Parser_Context_Not_Set);
  21. }
  22. [Fact]
  23. public void BalancingBracketsIgnoresStringLiteralCharactersAndBracketsInsideSingleLineComments()
  24. {
  25. SingleSpanBlockTest(@"if(foo) {
  26. // bar } "" baz '
  27. zoop();
  28. }", BlockType.Statement, SpanKind.Code);
  29. }
  30. [Fact]
  31. public void NestedCodeBlockWithAtCausesError()
  32. {
  33. ParseBlockTest("if (true) { @if(false) { } }",
  34. new StatementBlock(
  35. Factory.Code("if (true) { ").AsStatement(),
  36. new StatementBlock(
  37. Factory.CodeTransition(),
  38. Factory.Code("if(false) { }").AsStatement()
  39. ),
  40. Factory.Code(" }").AsStatement()),
  41. new RazorError(
  42. String.Format(RazorResources.ParseError_Unexpected_Keyword_After_At,
  43. "if"),
  44. new SourceLocation(13, 0, 13)));
  45. }
  46. [Fact]
  47. public void BalancingBracketsIgnoresStringLiteralCharactersAndBracketsInsideBlockComments()
  48. {
  49. SingleSpanBlockTest(
  50. @"if(foo) {
  51. /* bar } "" */ ' baz } '
  52. zoop();
  53. }", BlockType.Statement, SpanKind.Code);
  54. }
  55. [Fact]
  56. public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsForKeyword()
  57. {
  58. SingleSpanBlockTest("for(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  59. }
  60. [Fact]
  61. public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsForeachKeyword()
  62. {
  63. SingleSpanBlockTest("foreach(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  64. }
  65. [Fact]
  66. public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsWhileKeyword()
  67. {
  68. SingleSpanBlockTest("while(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  69. }
  70. [Fact]
  71. public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsUsingKeywordFollowedByParen()
  72. {
  73. SingleSpanBlockTest("using(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  74. }
  75. [Fact]
  76. public void ParseBlockSupportsUsingsNestedWithinOtherBlocks()
  77. {
  78. SingleSpanBlockTest("if(foo) { using(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); } }", BlockType.Statement, SpanKind.Code);
  79. }
  80. [Fact]
  81. public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsIfKeywordWithNoElseBranches()
  82. {
  83. SingleSpanBlockTest("if(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code);
  84. }
  85. [Fact]
  86. public void ParseBlockAllowsEmptyBlockStatement()
  87. {
  88. SingleSpanBlockTest("if(false) { }", BlockType.Statement, SpanKind.Code);
  89. }
  90. [Fact]
  91. public void ParseBlockTerminatesParenBalancingAtEOF()
  92. {
  93. ImplicitExpressionTest("Html.En(code()", "Html.En(code()",
  94. AcceptedCharacters.Any,
  95. new RazorError(
  96. String.Format(RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
  97. "(", ")"),
  98. new SourceLocation(8, 0, 8)));
  99. }
  100. [Fact]
  101. public void ParseBlockSupportsBlockCommentBetweenIfAndElseClause()
  102. {
  103. SingleSpanBlockTest("if(foo) { bar(); } /* Foo */ /* Bar */ else { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  104. }
  105. [Fact]
  106. public void ParseBlockSupportsRazorCommentBetweenIfAndElseClause()
  107. {
  108. RunRazorCommentBetweenClausesTest("if(foo) { bar(); } ", " else { baz(); }", acceptedCharacters: AcceptedCharacters.None);
  109. }
  110. [Fact]
  111. public void ParseBlockSupportsBlockCommentBetweenElseIfAndElseClause()
  112. {
  113. SingleSpanBlockTest("if(foo) { bar(); } else if(bar) { baz(); } /* Foo */ /* Bar */ else { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  114. }
  115. [Fact]
  116. public void ParseBlockSupportsRazorCommentBetweenElseIfAndElseClause()
  117. {
  118. RunRazorCommentBetweenClausesTest("if(foo) { bar(); } else if(bar) { baz(); } ", " else { baz(); }", acceptedCharacters: AcceptedCharacters.None);
  119. }
  120. [Fact]
  121. public void ParseBlockSupportsBlockCommentBetweenIfAndElseIfClause()
  122. {
  123. SingleSpanBlockTest("if(foo) { bar(); } /* Foo */ /* Bar */ else if(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
  124. }
  125. [Fact]
  126. public void ParseBlockSupportsRazorCommentBetweenIfAndElseIfClause()
  127. {
  128. RunRazorCommentBetweenClausesTest("if(foo) { bar(); } ", " else if(bar) { baz(); }");
  129. }
  130. [Fact]
  131. public void ParseBlockSupportsLineCommentBetweenIfAndElseClause()
  132. {
  133. SingleSpanBlockTest(@"if(foo) { bar(); }
  134. // Foo
  135. // Bar
  136. else { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  137. }
  138. [Fact]
  139. public void ParseBlockSupportsLineCommentBetweenElseIfAndElseClause()
  140. {
  141. SingleSpanBlockTest(@"if(foo) { bar(); } else if(bar) { baz(); }
  142. // Foo
  143. // Bar
  144. else { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  145. }
  146. [Fact]
  147. public void ParseBlockSupportsLineCommentBetweenIfAndElseIfClause()
  148. {
  149. SingleSpanBlockTest(@"if(foo) { bar(); }
  150. // Foo
  151. // Bar
  152. else if(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
  153. }
  154. [Fact]
  155. public void ParseBlockParsesElseIfBranchesOfIfStatement()
  156. {
  157. const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
  158. Debug.WriteLine(@""foo } bar"");
  159. }";
  160. const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
  161. Debug.WriteLine(@""bar } baz"");
  162. }";
  163. const string document = ifStatement + elseIfBranch;
  164. SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
  165. }
  166. [Fact]
  167. public void ParseBlockParsesMultipleElseIfBranchesOfIfStatement()
  168. {
  169. const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
  170. Debug.WriteLine(@""foo } bar"");
  171. }";
  172. const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
  173. Debug.WriteLine(@""bar } baz"");
  174. }";
  175. const string document = ifStatement + elseIfBranch + elseIfBranch + elseIfBranch + elseIfBranch;
  176. SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
  177. }
  178. [Fact]
  179. public void ParseBlockParsesMultipleElseIfBranchesOfIfStatementFollowedByOneElseBranch()
  180. {
  181. const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
  182. Debug.WriteLine(@""foo } bar"");
  183. }";
  184. const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
  185. Debug.WriteLine(@""bar } baz"");
  186. }";
  187. const string elseBranch = @" else { Debug.WriteLine(@""bar } baz""); }";
  188. const string document = ifStatement + elseIfBranch + elseIfBranch + elseBranch;
  189. SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  190. }
  191. [Fact]
  192. public void ParseBlockStopsParsingCodeAfterElseBranch()
  193. {
  194. const string ifStatement = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
  195. Debug.WriteLine(@""foo } bar"");
  196. }";
  197. const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
  198. Debug.WriteLine(@""bar } baz"");
  199. }";
  200. const string elseBranch = @" else { Debug.WriteLine(@""bar } baz""); }";
  201. const string document = ifStatement + elseIfBranch + elseBranch + elseIfBranch;
  202. const string expected = ifStatement + elseIfBranch + elseBranch;
  203. ParseBlockTest(document, new StatementBlock(Factory.Code(expected).AsStatement().Accepts(AcceptedCharacters.None)));
  204. }
  205. [Fact]
  206. public void ParseBlockStopsParsingIfIfStatementNotFollowedByElse()
  207. {
  208. const string document = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
  209. Debug.WriteLine(@""foo } bar"");
  210. }";
  211. SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
  212. }
  213. [Fact]
  214. public void ParseBlockAcceptsElseIfWithNoCondition()
  215. {
  216. // We don't want to be a full C# parser - If the else if is missing it's condition, the C# compiler can handle that, we have all the info we need to keep parsing
  217. const string ifBranch = @"if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
  218. Debug.WriteLine(@""foo } bar"");
  219. }";
  220. const string elseIfBranch = @" else if { foo(); }";
  221. const string document = ifBranch + elseIfBranch;
  222. SingleSpanBlockTest(document, BlockType.Statement, SpanKind.Code);
  223. }
  224. [Fact]
  225. public void ParseBlockCorrectlyParsesDoWhileBlock()
  226. {
  227. SingleSpanBlockTest("do { var foo = bar; } while(foo != bar);", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  228. }
  229. [Fact]
  230. public void ParseBlockCorrectlyParsesDoWhileBlockMissingSemicolon()
  231. {
  232. SingleSpanBlockTest("do { var foo = bar; } while(foo != bar)", BlockType.Statement, SpanKind.Code);
  233. }
  234. [Fact]
  235. public void ParseBlockCorrectlyParsesDoWhileBlockMissingWhileCondition()
  236. {
  237. SingleSpanBlockTest("do { var foo = bar; } while", BlockType.Statement, SpanKind.Code);
  238. }
  239. [Fact]
  240. public void ParseBlockCorrectlyParsesDoWhileBlockMissingWhileConditionWithSemicolon()
  241. {
  242. SingleSpanBlockTest("do { var foo = bar; } while;", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  243. }
  244. [Fact]
  245. public void ParseBlockCorrectlyParsesDoWhileBlockMissingWhileClauseEntirely()
  246. {
  247. SingleSpanBlockTest("do { var foo = bar; } narf;", "do { var foo = bar; }", BlockType.Statement, SpanKind.Code);
  248. }
  249. [Fact]
  250. public void ParseBlockSupportsBlockCommentBetweenDoAndWhileClause()
  251. {
  252. SingleSpanBlockTest("do { var foo = bar; } /* Foo */ /* Bar */ while(true);", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  253. }
  254. [Fact]
  255. public void ParseBlockSupportsLineCommentBetweenDoAndWhileClause()
  256. {
  257. SingleSpanBlockTest(@"do { var foo = bar; }
  258. // Foo
  259. // Bar
  260. while(true);", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  261. }
  262. [Fact]
  263. public void ParseBlockSupportsRazorCommentBetweenDoAndWhileClause()
  264. {
  265. RunRazorCommentBetweenClausesTest("do { var foo = bar; } ", " while(true);", acceptedCharacters: AcceptedCharacters.None);
  266. }
  267. [Fact]
  268. public void ParseBlockCorrectlyParsesMarkupInDoWhileBlock()
  269. {
  270. ParseBlockTest("@do { var foo = bar; <p>Foo</p> foo++; } while (foo<bar>);",
  271. new StatementBlock(
  272. Factory.CodeTransition(),
  273. Factory.Code("do { var foo = bar;").AsStatement(),
  274. new MarkupBlock(
  275. Factory.Markup(" <p>Foo</p> ").Accepts(AcceptedCharacters.None)
  276. ),
  277. Factory.Code("foo++; } while (foo<bar>);").AsStatement().Accepts(AcceptedCharacters.None)
  278. ));
  279. }
  280. [Fact]
  281. public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsSwitchKeyword()
  282. {
  283. SingleSpanBlockTest(@"switch(foo) {
  284. case 0:
  285. break;
  286. case 1:
  287. {
  288. break;
  289. }
  290. case 2:
  291. return;
  292. default:
  293. return;
  294. }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  295. }
  296. [Fact]
  297. public void ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsLockKeyword()
  298. {
  299. SingleSpanBlockTest("lock(foo) { Debug.WriteLine(@\"foo } bar\"); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  300. }
  301. [Fact]
  302. public void ParseBlockHasErrorsIfNamespaceImportMissingSemicolon()
  303. {
  304. NamespaceImportTest("using Foo.Bar.Baz", " Foo.Bar.Baz", acceptedCharacters: AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace, location: new SourceLocation(17, 0, 17));
  305. }
  306. [Fact]
  307. public void ParseBlockHasErrorsIfNamespaceAliasMissingSemicolon()
  308. {
  309. NamespaceImportTest("using Foo.Bar.Baz = FooBarBaz", " Foo.Bar.Baz = FooBarBaz", acceptedCharacters: AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace, location: new SourceLocation(29, 0, 29));
  310. }
  311. [Fact]
  312. public void ParseBlockParsesNamespaceImportWithSemicolonForUsingKeywordIfIsInValidFormat()
  313. {
  314. NamespaceImportTest("using Foo.Bar.Baz;", " Foo.Bar.Baz", AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace);
  315. }
  316. [Fact]
  317. public void ParseBlockDoesntCaptureWhitespaceAfterUsing()
  318. {
  319. ParseBlockTest("using Foo ",
  320. new DirectiveBlock(
  321. Factory.Code("using Foo")
  322. .AsNamespaceImport(" Foo", CSharpCodeParser.UsingKeywordLength)
  323. .Accepts(AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace)));
  324. }
  325. [Fact]
  326. public void ParseBlockParsesNamespaceAliasWithSemicolonForUsingKeywordIfIsInValidFormat()
  327. {
  328. NamespaceImportTest("using FooBarBaz = FooBarBaz;", " FooBarBaz = FooBarBaz", AcceptedCharacters.NonWhiteSpace | AcceptedCharacters.WhiteSpace);
  329. }
  330. [Fact]
  331. public void ParseBlockTerminatesUsingKeywordAtEOFAndOutputsFileCodeBlock()
  332. {
  333. SingleSpanBlockTest("using ", BlockType.Statement, SpanKind.Code);
  334. }
  335. [Fact]
  336. public void ParseBlockTerminatesSingleLineCommentAtEndOfFile()
  337. {
  338. const string document = "foreach(var f in Foo) { // foo bar baz";
  339. SingleSpanBlockTest(document, document, BlockType.Statement, SpanKind.Code,
  340. new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "foreach", '}', '{'), SourceLocation.Zero));
  341. }
  342. [Fact]
  343. public void ParseBlockTerminatesBlockCommentAtEndOfFile()
  344. {
  345. const string document = "foreach(var f in Foo) { /* foo bar baz";
  346. SingleSpanBlockTest(document, document, BlockType.Statement, SpanKind.Code,
  347. new RazorError(String.Format(RazorResources.ParseError_BlockComment_Not_Terminated), 24, 0, 24),
  348. new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "foreach", '}', '{'), SourceLocation.Zero));
  349. }
  350. [Fact]
  351. public void ParseBlockTerminatesSingleSlashAtEndOfFile()
  352. {
  353. const string document = "foreach(var f in Foo) { / foo bar baz";
  354. SingleSpanBlockTest(document, document, BlockType.Statement, SpanKind.Code,
  355. new RazorError(String.Format(RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, "foreach", '}', '{'), SourceLocation.Zero));
  356. }
  357. [Fact]
  358. public void ParseBlockSupportsBlockCommentBetweenTryAndFinallyClause()
  359. {
  360. SingleSpanBlockTest("try { bar(); } /* Foo */ /* Bar */ finally { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  361. }
  362. [Fact]
  363. public void ParseBlockSupportsRazorCommentBetweenTryAndFinallyClause()
  364. {
  365. RunRazorCommentBetweenClausesTest("try { bar(); } ", " finally { biz(); }", acceptedCharacters: AcceptedCharacters.None);
  366. }
  367. [Fact]
  368. public void ParseBlockSupportsBlockCommentBetweenCatchAndFinallyClause()
  369. {
  370. SingleSpanBlockTest("try { bar(); } catch(bar) { baz(); } /* Foo */ /* Bar */ finally { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  371. }
  372. [Fact]
  373. public void ParseBlockSupportsRazorCommentBetweenCatchAndFinallyClause()
  374. {
  375. RunRazorCommentBetweenClausesTest("try { bar(); } catch(bar) { baz(); } ", " finally { biz(); }", acceptedCharacters: AcceptedCharacters.None);
  376. }
  377. [Fact]
  378. public void ParseBlockSupportsBlockCommentBetweenTryAndCatchClause()
  379. {
  380. SingleSpanBlockTest("try { bar(); } /* Foo */ /* Bar */ catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
  381. }
  382. [Fact]
  383. public void ParseBlockSupportsRazorCommentBetweenTryAndCatchClause()
  384. {
  385. RunRazorCommentBetweenClausesTest("try { bar(); }", " catch(bar) { baz(); }");
  386. }
  387. [Fact]
  388. public void ParseBlockSupportsLineCommentBetweenTryAndFinallyClause()
  389. {
  390. SingleSpanBlockTest(@"try { bar(); }
  391. // Foo
  392. // Bar
  393. finally { baz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  394. }
  395. [Fact]
  396. public void ParseBlockSupportsLineCommentBetweenCatchAndFinallyClause()
  397. {
  398. SingleSpanBlockTest(@"try { bar(); } catch(bar) { baz(); }
  399. // Foo
  400. // Bar
  401. finally { biz(); }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  402. }
  403. [Fact]
  404. public void ParseBlockSupportsLineCommentBetweenTryAndCatchClause()
  405. {
  406. SingleSpanBlockTest(@"try { bar(); }
  407. // Foo
  408. // Bar
  409. catch(bar) { baz(); }", BlockType.Statement, SpanKind.Code);
  410. }
  411. [Fact]
  412. public void ParseBlockSupportsTryStatementWithNoAdditionalClauses()
  413. {
  414. SingleSpanBlockTest("try { var foo = new { } }", BlockType.Statement, SpanKind.Code);
  415. }
  416. [Fact]
  417. public void ParseBlockSupportsMarkupWithinTryClause()
  418. {
  419. RunSimpleWrappedMarkupTest("try {", " <p>Foo</p> ", "}");
  420. }
  421. [Fact]
  422. public void ParseBlockSupportsTryStatementWithOneCatchClause()
  423. {
  424. SingleSpanBlockTest("try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } }", BlockType.Statement, SpanKind.Code);
  425. }
  426. [Fact]
  427. public void ParseBlockSupportsMarkupWithinCatchClause()
  428. {
  429. RunSimpleWrappedMarkupTest("try { var foo = new { } } catch(Foo Bar Baz) {", " <p>Foo</p> ", "}");
  430. }
  431. [Fact]
  432. public void ParseBlockSupportsTryStatementWithMultipleCatchClause()
  433. {
  434. 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 { } }", BlockType.Statement, SpanKind.Code);
  435. }
  436. [Fact]
  437. public void ParseBlockSupportsExceptionLessCatchClauses()
  438. {
  439. SingleSpanBlockTest("try { var foo = new { } } catch { var foo = new { } }", BlockType.Statement, SpanKind.Code);
  440. }
  441. [Fact]
  442. public void ParseBlockSupportsMarkupWithinAdditionalCatchClauses()
  443. {
  444. RunSimpleWrappedMarkupTest("try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) {", " <p>Foo</p> ", "}");
  445. }
  446. [Fact]
  447. public void ParseBlockSupportsTryStatementWithFinallyClause()
  448. {
  449. SingleSpanBlockTest("try { var foo = new { } } finally { var foo = new { } }", BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  450. }
  451. [Fact]
  452. public void ParseBlockSupportsMarkupWithinFinallyClause()
  453. {
  454. RunSimpleWrappedMarkupTest("try { var foo = new { } } finally {", " <p>Foo</p> ", "}", acceptedCharacters: AcceptedCharacters.None);
  455. }
  456. [Fact]
  457. public void ParseBlockStopsParsingCatchClausesAfterFinallyBlock()
  458. {
  459. string expectedContent = "try { var foo = new { } } finally { var foo = new { } }";
  460. SingleSpanBlockTest(expectedContent + " catch(Foo Bar Baz) { }", expectedContent, BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  461. }
  462. [Fact]
  463. public void ParseBlockDoesNotAllowMultipleFinallyBlocks()
  464. {
  465. string expectedContent = "try { var foo = new { } } finally { var foo = new { } }";
  466. SingleSpanBlockTest(expectedContent + " finally { }", expectedContent, BlockType.Statement, SpanKind.Code, acceptedCharacters: AcceptedCharacters.None);
  467. }
  468. [Fact]
  469. public void ParseBlockAcceptsTrailingDotIntoImplicitExpressionWhenEmbeddedInCode()
  470. {
  471. // Arrange
  472. ParseBlockTest(@"if(foo) { @foo. }",
  473. new StatementBlock(
  474. Factory.Code("if(foo) { ").AsStatement(),
  475. new ExpressionBlock(
  476. Factory.CodeTransition(),
  477. Factory.Code("foo.")
  478. .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
  479. .Accepts(AcceptedCharacters.NonWhiteSpace)
  480. ),
  481. Factory.Code(" }").AsStatement()
  482. ));
  483. }
  484. [Fact]
  485. public void ParseBlockParsesExpressionOnSwitchCharacterFollowedByOpenParen()
  486. {
  487. // Arrange
  488. ParseBlockTest(@"if(foo) { @(foo + bar) }",
  489. new StatementBlock(
  490. Factory.Code("if(foo) { ").AsStatement(),
  491. new ExpressionBlock(
  492. Factory.CodeTransition(),
  493. Factory.MetaCode("(").Accepts(AcceptedCharacters.None),
  494. Factory.Code("foo + bar").AsExpression(),
  495. Factory.MetaCode(")").Accepts(AcceptedCharacters.None)
  496. ),
  497. Factory.Code(" }").AsStatement()
  498. ));
  499. }
  500. [Fact]
  501. public void ParseBlockParsesExpressionOnSwitchCharacterFollowedByIdentifierStart()
  502. {
  503. // Arrange
  504. ParseBlockTest(@"if(foo) { @foo[4].bar() }",
  505. new StatementBlock(
  506. Factory.Code("if(foo) { ").AsStatement(),
  507. new ExpressionBlock(
  508. Factory.CodeTransition(),
  509. Factory.Code("foo[4].bar()")
  510. .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
  511. .Accepts(AcceptedCharacters.NonWhiteSpace)
  512. ),
  513. Factory.Code(" }").AsStatement()
  514. ));
  515. }
  516. [Fact]
  517. public void ParseBlockTreatsDoubleAtSignAsEscapeSequenceIfAtStatementStart()
  518. {
  519. // Arrange
  520. ParseBlockTest(@"if(foo) { @@class.Foo() }",
  521. new StatementBlock(
  522. Factory.Code("if(foo) { ").AsStatement(),
  523. Factory.Code("@").Hidden(),
  524. Factory.Code("@class.Foo() }").AsStatement()
  525. ));
  526. }
  527. [Fact]
  528. public void ParseBlockTreatsAtSignsAfterFirstPairAsPartOfCSharpStatement()
  529. {
  530. // Arrange
  531. ParseBlockTest(@"if(foo) { @@@@class.Foo() }",
  532. new StatementBlock(
  533. Factory.Code("if(foo) { ").AsStatement(),
  534. Factory.Code("@").Hidden(),
  535. Factory.Code("@@@class.Foo() }").AsStatement()
  536. ));
  537. }
  538. [Fact]
  539. public void ParseBlockDoesNotParseMarkupStatementOrExpressionOnSwitchCharacterNotFollowedByOpenAngleOrColon()
  540. {
  541. // Arrange
  542. ParseBlockTest("if(foo) { @\"Foo\".ToString(); }",
  543. new StatementBlock(
  544. Factory.Code("if(foo) { @\"Foo\".ToString(); }").AsStatement()));
  545. }
  546. [Fact]
  547. public void ParsersCanNestRecursively()
  548. {
  549. // Arrange
  550. ParseBlockTest(@"foreach(var c in db.Categories) {
  551. <div>
  552. <h1>@c.Name</h1>
  553. <ul>
  554. @foreach(var p in c.Products) {
  555. <li><a href=""@Html.ActionUrl(""Products"", ""Detail"", new { id = p.Id })"">@p.Name</a></li>
  556. }
  557. </ul>
  558. </div>
  559. }",
  560. new StatementBlock(
  561. Factory.Code("foreach(var c in db.Categories) {\r\n").AsStatement(),
  562. new MarkupBlock(
  563. Factory.Markup(" <div>\r\n <h1>"),
  564. new ExpressionBlock(
  565. Factory.CodeTransition(),
  566. Factory.Code("c.Name")
  567. .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
  568. .Accepts(AcceptedCharacters.NonWhiteSpace)),
  569. Factory.Markup("</h1>\r\n <ul>\r\n"),
  570. new StatementBlock(
  571. Factory.Code(@" ").AsStatement(),
  572. Factory.CodeTransition(),
  573. Factory.Code("foreach(var p in c.Products) {\r\n").AsStatement(),
  574. new MarkupBlock(
  575. Factory.Markup(" <li><a"),
  576. new MarkupBlock(new AttributeBlockCodeGenerator("href", new LocationTagged<string>(" href=\"", 193, 5, 30), new LocationTagged<string>("\"", 256, 5, 93)),
  577. Factory.Markup(" href=\"").With(SpanCodeGenerator.Null),
  578. new MarkupBlock(new DynamicAttributeBlockCodeGenerator(new LocationTagged<string>(String.Empty, 200, 5, 37), 200, 5, 37),
  579. new ExpressionBlock(
  580. Factory.CodeTransition(),
  581. Factory.Code("Html.ActionUrl(\"Products\", \"Detail\", new { id = p.Id })")
  582. .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
  583. .Accepts(AcceptedCharacters.NonWhiteSpace))),
  584. Factory.Markup("\"").With(SpanCodeGenerator.Null)),
  585. Factory.Markup(">"),
  586. new ExpressionBlock(
  587. Factory.CodeTransition(),
  588. Factory.Code("p.Name")
  589. .AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
  590. .Accepts(AcceptedCharacters.NonWhiteSpace)),
  591. Factory.Markup("</a></li>\r\n").Accepts(AcceptedCharacters.None)),
  592. Factory.Code(" }\r\n").AsStatement().Accepts(AcceptedCharacters.None)),
  593. Factory.Markup(" </ul>\r\n </div>\r\n")
  594. .Accepts(AcceptedCharacters.None)),
  595. Factory.Code(" }").AsStatement().Accepts(AcceptedCharacters.None)));
  596. }
  597. private void RunRazorCommentBetweenClausesTest(string preComment, string postComment, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
  598. {
  599. ParseBlockTest(preComment + "@* Foo *@ @* Bar *@" + postComment,
  600. new StatementBlock(
  601. Factory.Code(preComment).AsStatement(),
  602. new CommentBlock(
  603. Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
  604. Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
  605. Factory.Comment(" Foo ", CSharpSymbolType.RazorComment),
  606. Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
  607. Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
  608. ),
  609. Factory.Code(" ").AsStatement(),
  610. new CommentBlock(
  611. Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition),
  612. Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
  613. Factory.Comment(" Bar ", CSharpSymbolType.RazorComment),
  614. Factory.MetaCode("*", CSharpSymbolType.RazorCommentStar).Accepts(AcceptedCharacters.None),
  615. Factory.CodeTransition(CSharpSymbolType.RazorCommentTransition)
  616. ),
  617. Factory.Code(postComment).AsStatement().Accepts(acceptedCharacters)));
  618. }
  619. private void RunSimpleWrappedMarkupTest(string prefix, string markup, string suffix, AcceptedCharacters acceptedCharacters = AcceptedCharacters.Any)
  620. {
  621. ParseBlockTest(prefix + markup + suffix,
  622. new StatementBlock(
  623. Factory.Code(prefix).AsStatement(),
  624. new MarkupBlock(
  625. Factory.Markup(markup).Accepts(AcceptedCharacters.None)
  626. ),
  627. Factory.Code(suffix).AsStatement().Accepts(acceptedCharacters)
  628. ));
  629. }
  630. private void NamespaceImportTest(string content, string expectedNS, AcceptedCharacters acceptedCharacters = AcceptedCharacters.None, string errorMessage = null, SourceLocation? location = null)
  631. {
  632. var errors = new RazorError[0];
  633. if (!String.IsNullOrEmpty(errorMessage) && location.HasValue)
  634. {
  635. errors = new RazorError[]
  636. {
  637. new RazorError(errorMessage, location.Value)
  638. };
  639. }
  640. ParseBlockTest(content,
  641. new DirectiveBlock(
  642. Factory.Code(content)
  643. .AsNamespaceImport(expectedNS, CSharpCodeParser.UsingKeywordLength)
  644. .Accepts(acceptedCharacters)),
  645. errors);
  646. }
  647. }
  648. }