diff --git a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs index 9f7b2b416e..d06bf98b0e 100644 --- a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs +++ b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs @@ -316,12 +316,16 @@ namespace Microsoft.AspNet.Razor.Parser Span.ChunkGenerator = SpanChunkGenerator.Null; } - if (!At(CSharpSymbolType.WhiteSpace) && !At(CSharpSymbolType.NewLine)) + if (!IsNested) { - PutCurrentBack(); + EnsureCurrent(); + if (At(CSharpSymbolType.NewLine) || + (At(CSharpSymbolType.WhiteSpace) && NextIs(CSharpSymbolType.NewLine))) + { + Context.NullGenerateWhitespaceAndNewLine = true; + } } - CompleteBlock(insertMarkerIfNecessary: false); Output(SpanKind.MetaCode); } diff --git a/src/Microsoft.AspNet.Razor/Parser/HtmlMarkupParser.cs b/src/Microsoft.AspNet.Razor/Parser/HtmlMarkupParser.cs index 8da02adc94..6bd518f720 100644 --- a/src/Microsoft.AspNet.Razor/Parser/HtmlMarkupParser.cs +++ b/src/Microsoft.AspNet.Razor/Parser/HtmlMarkupParser.cs @@ -71,7 +71,19 @@ namespace Microsoft.AspNet.Razor.Parser var startOfLine = false; while (!EndOfFile && !condition(CurrentSymbol)) { - if (At(HtmlSymbolType.NewLine)) + if (Context.NullGenerateWhitespaceAndNewLine) + { + Context.NullGenerateWhitespaceAndNewLine = false; + Span.ChunkGenerator = SpanChunkGenerator.Null; + AcceptWhile(symbol => symbol.Type == HtmlSymbolType.WhiteSpace); + if (At(HtmlSymbolType.NewLine)) + { + AcceptAndMoveNext(); + } + + Output(SpanKind.Markup); + } + else if (At(HtmlSymbolType.NewLine)) { if (last != null) { diff --git a/src/Microsoft.AspNet.Razor/Parser/ParserContext.cs b/src/Microsoft.AspNet.Razor/Parser/ParserContext.cs index 07e0ba2ec2..912818a8ce 100644 --- a/src/Microsoft.AspNet.Razor/Parser/ParserContext.cs +++ b/src/Microsoft.AspNet.Razor/Parser/ParserContext.cs @@ -67,6 +67,8 @@ namespace Microsoft.AspNet.Razor.Parser public Span LastSpan { get; private set; } public bool WhiteSpaceIsSignificantToAncestorBlock { get; set; } + public bool NullGenerateWhitespaceAndNewLine { get; set; } + public AcceptedCharacters LastAcceptedCharacters { get diff --git a/test/Microsoft.AspNet.Razor.Test/Parser/Html/HtmlDocumentTest.cs b/test/Microsoft.AspNet.Razor.Test/Parser/Html/HtmlDocumentTest.cs index e87afa8e1f..eacf1e57df 100644 --- a/test/Microsoft.AspNet.Razor.Test/Parser/Html/HtmlDocumentTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/Parser/Html/HtmlDocumentTest.cs @@ -290,6 +290,134 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html BlockFactory.MarkupTagBlock(""))); } + [Fact] + public void ParseDocumentDoesNotRenderExtraNewLineAtTheEndOfVerbatimBlock() + { + ParseDocumentTest("@{\r\n}\r\n", + new MarkupBlock( + Factory.EmptyHtml(), + new StatementBlock( + Factory.CodeTransition(), + Factory.MetaCode("{").Accepts(AcceptedCharacters.None), + Factory.Code("\r\n").AsStatement().AutoCompleteWith(null, false), + Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), + Factory.Markup("\r\n").With(SpanChunkGenerator.Null), + BlockFactory.MarkupTagBlock(""))); + } + + [Fact] + public void ParseDocumentDoesNotRenderExtraWhitespaceAndNewLineAtTheEndOfVerbatimBlock() + { + ParseDocumentTest("@{\r\n} \t\r\n", + new MarkupBlock( + Factory.EmptyHtml(), + new StatementBlock( + Factory.CodeTransition(), + Factory.MetaCode("{").Accepts(AcceptedCharacters.None), + Factory.Code("\r\n").AsStatement().AutoCompleteWith(null, false), + Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), + Factory.Markup(" \t\r\n").With(SpanChunkGenerator.Null), + BlockFactory.MarkupTagBlock(""))); + } + + [Fact] + public void ParseDocumentDoesNotIgnoreNewLineAtTheEndOfMarkupBlock() + { + ParseDocumentTest("@{\r\n}\r\n\r\n", + new MarkupBlock( + Factory.EmptyHtml(), + new StatementBlock( + Factory.CodeTransition(), + Factory.MetaCode("{").Accepts(AcceptedCharacters.None), + Factory.Code("\r\n").AsStatement().AutoCompleteWith(null, false), + Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), + Factory.Markup("\r\n").With(SpanChunkGenerator.Null), + BlockFactory.MarkupTagBlock(""), + Factory.Markup("\r\n"))); + } + + [Fact] + public void ParseDocumentDoesNotIgnoreWhitespaceAtTheEndOfVerbatimBlockIfNoNewlinePresent() + { + ParseDocumentTest("@{\r\n} \t\r\n", + new MarkupBlock( + Factory.EmptyHtml(), + new StatementBlock( + Factory.CodeTransition(), + Factory.MetaCode("{").Accepts(AcceptedCharacters.None), + Factory.Code("\r\n").AsStatement().AutoCompleteWith(null, false), + Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), + Factory.Markup(" \t"), + BlockFactory.MarkupTagBlock(""), + Factory.Markup("\r\n"))); + } + + [Fact] + public void ParseDocumentHandlesNewLineInNestedBlock() + { + ParseDocumentTest("@{\r\n@if(true){\r\n} \r\n}\r\n", + new MarkupBlock( + Factory.EmptyHtml(), + new StatementBlock( + Factory.CodeTransition(), + Factory.MetaCode("{").Accepts(AcceptedCharacters.None), + Factory.Code("\r\n").AsStatement().AutoCompleteWith(null, false), + new StatementBlock( + Factory.CodeTransition(), + Factory.Code("if(true){\r\n}").AsStatement()), + Factory.Code(" \r\n").AsStatement(), + Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), + Factory.Markup("\r\n").With(SpanChunkGenerator.Null), + BlockFactory.MarkupTagBlock(""))); + } + + [Fact] + public void ParseDocumentHandlesNewLineAndMarkupInNestedBlock() + { + ParseDocumentTest("@{\r\n@if(true){\r\n} }", + new MarkupBlock( + Factory.EmptyHtml(), + new StatementBlock( + Factory.CodeTransition(), + Factory.MetaCode("{").Accepts(AcceptedCharacters.None), + Factory.Code("\r\n").AsStatement().AutoCompleteWith(null, false), + new StatementBlock( + Factory.CodeTransition(), + Factory.Code("if(true){\r\n}").AsStatement()), + new MarkupBlock( + Factory.Markup(" "), + new MarkupTagBlock( + Factory.Markup("").Accepts(AcceptedCharacters.None)), + Factory.Markup(" ").Accepts(AcceptedCharacters.None)), + Factory.EmptyCSharp().AsStatement(), + Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), + Factory.EmptyHtml())); + } + + [Fact] + public void ParseDocumentHandlesExtraNewLineBeforeMarkupInNestedBlock() + { + ParseDocumentTest("@{\r\n@if(true){\r\n} \r\n \r\n}", + new MarkupBlock( + Factory.EmptyHtml(), + new StatementBlock( + Factory.CodeTransition(), + Factory.MetaCode("{").Accepts(AcceptedCharacters.None), + Factory.Code("\r\n").AsStatement().AutoCompleteWith(null, false), + new StatementBlock( + Factory.CodeTransition(), + Factory.Code("if(true){\r\n}").AsStatement()), + Factory.Code(" \r\n").AsStatement(), + new MarkupBlock( + new MarkupTagBlock( + Factory.Markup("").Accepts(AcceptedCharacters.None)), + Factory.Markup(" \r\n").Accepts(AcceptedCharacters.None)), + Factory.EmptyCSharp().AsStatement(), + Factory.MetaCode("}").Accepts(AcceptedCharacters.None)), + new MarkupTagBlock( + Factory.Markup("")))); + } + [Fact] public void ParseSectionIgnoresTagsInContentsOfScriptTag() { diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/Blocks.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/Blocks.cs index d8d8010fd4..85e1499d82 100644 --- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/Blocks.cs +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/Blocks.cs @@ -21,8 +21,8 @@ namespace TestOutput #line default #line hidden - Instrumentation.BeginContext(21, 4, true); - WriteLiteral("\r\n\r\n"); + Instrumentation.BeginContext(23, 2, true); + WriteLiteral("\r\n"); Instrumentation.EndContext(); #line 5 "Blocks.cshtml" while(i <= 10) { diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/CodeBlockWithTextElement.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/CodeBlockWithTextElement.cs index fd92a4b060..30b57a0f13 100644 --- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/CodeBlockWithTextElement.cs +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/CodeBlockWithTextElement.cs @@ -57,9 +57,6 @@ namespace TestOutput #line default #line hidden - Instrumentation.BeginContext(83, 2, true); - WriteLiteral("\r\n"); - Instrumentation.EndContext(); } #pragma warning restore 1998 } diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/ComplexTagHelpers.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/ComplexTagHelpers.cs index 6ef107ad60..83b2dc7565 100644 --- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/ComplexTagHelpers.cs +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/ComplexTagHelpers.cs @@ -265,8 +265,8 @@ if(true) { #line default #line hidden - Instrumentation.BeginContext(805, 14, true); - WriteLiteral("\r\n "); + Instrumentation.BeginContext(807, 12, true); + WriteLiteral(" "); Instrumentation.EndContext(); __tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", TagMode.StartTagOnly, "test", async() => { } diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/ExpressionsInCode.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/ExpressionsInCode.cs index db8aa9c740..ca08cf1f7d 100644 --- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/ExpressionsInCode.cs +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/ExpressionsInCode.cs @@ -22,8 +22,8 @@ namespace TestOutput #line default #line hidden - Instrumentation.BeginContext(54, 4, true); - WriteLiteral("\r\n\r\n"); + Instrumentation.BeginContext(56, 2, true); + WriteLiteral("\r\n"); Instrumentation.EndContext(); #line 6 "ExpressionsInCode.cshtml" if(foo != null) { diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/Instrumented.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/Instrumented.cs index d54248f38e..e18a590214 100644 --- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/Instrumented.cs +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/Instrumented.cs @@ -42,8 +42,8 @@ namespace TestOutput #line default #line hidden - Instrumentation.BeginContext(94, 4, true); - WriteLiteral("\r\n\r\n"); + Instrumentation.BeginContext(96, 2, true); + WriteLiteral("\r\n"); Instrumentation.EndContext(); #line 8 "Instrumented.cshtml" while(i <= 10) { diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/MarkupInCodeBlock.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/MarkupInCodeBlock.cs index 6b5dc89a78..48cbe037cf 100644 --- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/MarkupInCodeBlock.cs +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/MarkupInCodeBlock.cs @@ -40,9 +40,6 @@ namespace TestOutput #line default #line hidden - Instrumentation.BeginContext(96, 2, true); - WriteLiteral("\r\n"); - Instrumentation.EndContext(); } #pragma warning restore 1998 } diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NoLinePragmas.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NoLinePragmas.cs index 40e3b800bf..86d4c229b3 100644 --- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NoLinePragmas.cs +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NoLinePragmas.cs @@ -21,8 +21,8 @@ namespace TestOutput #line default #line hidden - Instrumentation.BeginContext(21, 4, true); - WriteLiteral("\r\n\r\n"); + Instrumentation.BeginContext(23, 2, true); + WriteLiteral("\r\n"); Instrumentation.EndContext(); #line 5 "" while(i <= 10) { diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NullConditionalExpressions.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NullConditionalExpressions.cs index 1dff885ddf..c5bd057802 100644 --- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NullConditionalExpressions.cs +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NullConditionalExpressions.cs @@ -76,8 +76,8 @@ Write(ViewBag?.Method(Value?[23]?.More)?["key"]); #line default #line hidden - Instrumentation.BeginContext(135, 4, true); - WriteLiteral("\r\n\r\n"); + Instrumentation.BeginContext(137, 2, true); + WriteLiteral("\r\n"); Instrumentation.EndContext(); Instrumentation.BeginContext(140, 13, false); #line 8 "NullConditionalExpressions.cshtml" diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/PrefixedAttributeTagHelpers.Reversed.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/PrefixedAttributeTagHelpers.Reversed.cs index db1b101ffd..aa4fbe761f 100644 --- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/PrefixedAttributeTagHelpers.Reversed.cs +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/PrefixedAttributeTagHelpers.Reversed.cs @@ -43,8 +43,8 @@ namespace TestOutput #line default #line hidden - Instrumentation.BeginContext(280, 51, true); - WriteLiteral("\r\n\r\n
But this should show the comment syntax: "); + Instrumentation.BeginContext(265, 44, true); + WriteLiteral("
But this should show the comment syntax: "); Instrumentation.EndContext(); Instrumentation.BeginContext(310, 3, false); #line 13 "RazorComments.cshtml" diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/Sections.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/Sections.cs index f1faa0744f..65b5664e29 100644 --- a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/Sections.cs +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/Sections.cs @@ -21,8 +21,8 @@ namespace TestOutput #line default #line hidden - Instrumentation.BeginContext(47, 33, true); - WriteLiteral("\r\n\r\n