From d458e8ecb24d01b405e3d7706fc7a2f939033269 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Thu, 3 Sep 2015 16:34:54 -0700 Subject: [PATCH] Change HTML in nested C# blocks to properly handle dots. - Prior to this change adding a `.` after an implicit expression would result in compile errors due to Razor thinking the `.` was part of the C# (normally not the case). - Added a code generation and unit tests to validate behavior. #491 --- .../Parser/CSharpCodeParser.cs | 6 ++ .../CSharpRazorChunkGeneratorTest.cs | 50 ++++++++++++++++ .../Parser/CSharp/CSharpBlockTest.cs | 25 ++++++++ .../Parser/Html/HtmlDocumentTest.cs | 28 +++++++++ .../Output/NestedCSharp.DesignTime.cs | 58 ++++++++++++++++++ .../CodeGenerator/Output/NestedCSharp.cs | 59 +++++++++++++++++++ .../CodeGenerator/Source/NestedCSharp.cshtml | 8 +++ 7 files changed, 234 insertions(+) create mode 100644 test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NestedCSharp.DesignTime.cs create mode 100644 test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NestedCSharp.cs create mode 100644 test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Source/NestedCSharp.cshtml diff --git a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs index 9ac442e74c..9f7b2b416e 100644 --- a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs +++ b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs @@ -631,6 +631,11 @@ namespace Microsoft.AspNet.Razor.Parser private void ParseWithOtherParser(Action parseAction) { + // When transitioning to the HTML parser we no longer want to act as if we're in a nested C# state. + // For instance, if
@hello.
is in a nested C# block we don't want the trailing '.' to be handled + // as C#; it should be handled as a period because it's wrapped in markup. + var wasNested = IsNested; + IsNested = false; using (PushSpanConfig()) { Context.SwitchActiveParser(); @@ -638,6 +643,7 @@ namespace Microsoft.AspNet.Razor.Parser Context.SwitchActiveParser(); } Initialize(Span); + IsNested = wasNested; NextToken(); } } diff --git a/test/Microsoft.AspNet.Razor.Test/CodeGenerators/CSharpRazorChunkGeneratorTest.cs b/test/Microsoft.AspNet.Razor.Test/CodeGenerators/CSharpRazorChunkGeneratorTest.cs index 5f144fb645..f4604fc06f 100644 --- a/test/Microsoft.AspNet.Razor.Test/CodeGenerators/CSharpRazorChunkGeneratorTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/CodeGenerators/CSharpRazorChunkGeneratorTest.cs @@ -52,6 +52,7 @@ namespace Microsoft.AspNet.Razor.Test.Generator } [Theory] + [InlineData("NestedCSharp")] [InlineData("NullConditionalExpressions")] [InlineData("NestedCodeBlocks")] [InlineData("CodeBlock")] @@ -75,6 +76,55 @@ namespace Microsoft.AspNet.Razor.Test.Generator RunTest(testType); } + [Fact] + public void CSharpChunkGeneratorCorrectlyGeneratesMappingsForNestedCSharp() + { + RunTest( + "NestedCSharp", + "NestedCSharp.DesignTime", + designTimeMode: true, + tabTest: TabTest.NoTabs, + expectedDesignTimePragmas: new List + { + BuildLineMapping( + documentAbsoluteIndex: 2, + documentLineIndex: 0, + generatedAbsoluteIndex: 522, + generatedLineIndex: 22, + characterOffsetIndex: 2, + contentLength: 6), + BuildLineMapping( + documentAbsoluteIndex: 9, + documentLineIndex: 1, + documentCharacterOffsetIndex: 5, + generatedAbsoluteIndex: 598, + generatedLineIndex: 29, + generatedCharacterOffsetIndex: 4, + contentLength: 53), + BuildLineMapping( + documentAbsoluteIndex: 82, + documentLineIndex: 4, + generatedAbsoluteIndex: 730, + generatedLineIndex: 37, + characterOffsetIndex: 13, + contentLength: 16), + BuildLineMapping( + documentAbsoluteIndex: 115, + documentLineIndex: 5, + generatedAbsoluteIndex: 825, + generatedLineIndex: 42, + characterOffsetIndex: 14, + contentLength: 7), + BuildLineMapping( + documentAbsoluteIndex: 122, + documentLineIndex: 6, + generatedAbsoluteIndex: 903, + generatedLineIndex: 49, + characterOffsetIndex: 5, + contentLength: 2), + }); + } + [Fact] public void CSharpChunkGeneratorCorrectlyGeneratesMappingsForNullConditionalOperator() { diff --git a/test/Microsoft.AspNet.Razor.Test/Parser/CSharp/CSharpBlockTest.cs b/test/Microsoft.AspNet.Razor.Test/Parser/CSharp/CSharpBlockTest.cs index a0283080bd..915f0ac872 100644 --- a/test/Microsoft.AspNet.Razor.Test/Parser/CSharp/CSharpBlockTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/Parser/CSharp/CSharpBlockTest.cs @@ -15,6 +15,31 @@ namespace Microsoft.AspNet.Razor.Test.Parser.CSharp { public class CSharpBlockTest : CsHtmlCodeParserTestBase { + [Fact] + public void ParseBlock_NestedCodeBlockWithMarkupSetsDotAsMarkup() + { + ParseBlockTest("if (true) { @if(false) {
@something.
} }", + new StatementBlock( + Factory.Code("if (true) { ").AsStatement(), + new StatementBlock( + Factory.CodeTransition(), + Factory.Code("if(false) {").AsStatement(), + new MarkupBlock( + Factory.Markup(" "), + BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None), + Factory.EmptyHtml(), + new ExpressionBlock( + Factory.CodeTransition(), + Factory.Code("something") + .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false) + .Accepts(AcceptedCharacters.NonWhiteSpace)), + Factory.Markup("."), + BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None), + Factory.Markup(" ").Accepts(AcceptedCharacters.None)), + Factory.Code("}").AsStatement()), + Factory.Code(" }").AsStatement())); + } + [Fact] public void ParseBlockMethodThrowsArgNullExceptionOnNullContext() { diff --git a/test/Microsoft.AspNet.Razor.Test/Parser/Html/HtmlDocumentTest.cs b/test/Microsoft.AspNet.Razor.Test/Parser/Html/HtmlDocumentTest.cs index e8979946f6..ed2df24a9e 100644 --- a/test/Microsoft.AspNet.Razor.Test/Parser/Html/HtmlDocumentTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/Parser/Html/HtmlDocumentTest.cs @@ -16,6 +16,34 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html { private static readonly TestFile Nested1000 = TestFile.Create("TestFiles/nested-1000.html"); + [Fact] + public void ParseDocument_NestedCodeBlockWithMarkupSetsDotAsMarkup() + { + ParseDocumentTest("@if (true) { @if(false) {
@something.
} }", + new MarkupBlock( + Factory.EmptyHtml(), + new StatementBlock( + Factory.CodeTransition(), + Factory.Code("if (true) { ").AsStatement(), + new StatementBlock( + Factory.CodeTransition(), + Factory.Code("if(false) {").AsStatement(), + new MarkupBlock( + Factory.Markup(" "), + BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None), + Factory.EmptyHtml(), + new ExpressionBlock( + Factory.CodeTransition(), + Factory.Code("something") + .AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: false) + .Accepts(AcceptedCharacters.NonWhiteSpace)), + Factory.Markup("."), + BlockFactory.MarkupTagBlock("
", AcceptedCharacters.None), + Factory.Markup(" ").Accepts(AcceptedCharacters.None)), + Factory.Code("}").AsStatement()), + Factory.Code(" }").AsStatement()))); + } + [Fact] public void ParseDocumentMethodThrowsArgNullExceptionOnNullContext() { diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NestedCSharp.DesignTime.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NestedCSharp.DesignTime.cs new file mode 100644 index 0000000000..f9c3a3651a --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NestedCSharp.DesignTime.cs @@ -0,0 +1,58 @@ +namespace TestOutput +{ + using System; + using System.Threading.Tasks; + + public class NestedCSharp + { + private static object @__o; + private void @__RazorDesignTimeHelpers__() + { + #pragma warning disable 219 + #pragma warning restore 219 + } + #line hidden + public NestedCSharp() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { +#line 1 "NestedCSharp.cshtml" + + + +#line default +#line hidden + +#line 2 "NestedCSharp.cshtml" + foreach (var result in (dynamic)Url) + { + + +#line default +#line hidden + +#line 5 "NestedCSharp.cshtml" + __o = result.SomeValue; + +#line default +#line hidden +#line 6 "NestedCSharp.cshtml" + + } + +#line default +#line hidden + +#line 7 "NestedCSharp.cshtml" + + +#line default +#line hidden + + } + #pragma warning restore 1998 + } +} diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NestedCSharp.cs b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NestedCSharp.cs new file mode 100644 index 0000000000..ec93481e5b --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Output/NestedCSharp.cs @@ -0,0 +1,59 @@ +#pragma checksum "NestedCSharp.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "2b9e8dcf7c08153c15ac84973938a7c0254f2369" +namespace TestOutput +{ + using System; + using System.Threading.Tasks; + + public class NestedCSharp + { + #line hidden + public NestedCSharp() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { +#line 1 "NestedCSharp.cshtml" + + + +#line default +#line hidden + +#line 2 "NestedCSharp.cshtml" + foreach (var result in (dynamic)Url) + { + +#line default +#line hidden + + Instrumentation.BeginContext(54, 27, true); + WriteLiteral("
\r\n "); + Instrumentation.EndContext(); + Instrumentation.BeginContext(82, 16, false); +#line 5 "NestedCSharp.cshtml" + Write(result.SomeValue); + +#line default +#line hidden + Instrumentation.EndContext(); + Instrumentation.BeginContext(98, 19, true); + WriteLiteral(".\r\n
\r\n"); + Instrumentation.EndContext(); +#line 7 "NestedCSharp.cshtml" + } + +#line default +#line hidden + +#line 7 "NestedCSharp.cshtml" + + +#line default +#line hidden + + } + #pragma warning restore 1998 + } +} diff --git a/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Source/NestedCSharp.cshtml b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Source/NestedCSharp.cshtml new file mode 100644 index 0000000000..8387e8a28a --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Test/TestFiles/CodeGenerator/Source/NestedCSharp.cshtml @@ -0,0 +1,8 @@ +@{ + @foreach (var result in (dynamic)Url) + { +
+ @result.SomeValue. +
+ } +} \ No newline at end of file