From ff4100e2925579b567be463474ffde255c8a3a6a Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 22 Sep 2015 22:43:22 -0700 Subject: [PATCH] Generate an expression to create line mappings for ModelChunk directives --- .../MvcCSharpCodeGenerator.cs | 23 ++++ .../MvcCSharpDesignTimeCodeVisitor.cs | 70 ++++++++++ .../MvcRazorHost.cs | 5 +- .../MvcRazorHostTest.cs | 123 ++++++++++-------- .../TestFiles/Input/MultipleModels.cshtml | 2 + .../TestFiles/Output/DesignTime/Basic.cs | 5 - .../TestFiles/Output/DesignTime/Inject.cs | 5 - .../Output/DesignTime/InjectWithModel.cs | 2 +- .../Output/DesignTime/InjectWithSemicolon.cs | 2 +- .../TestFiles/Output/DesignTime/Model.cs | 2 +- .../DesignTime/ModelExpressionTagHelper.cs | 2 +- .../Output/DesignTime/MultipleModels.cs | 40 ++++++ 12 files changed, 213 insertions(+), 68 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpDesignTimeCodeVisitor.cs create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/MultipleModels.cshtml create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/MultipleModels.cs diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs index 76f4d2428a..146a3c53cb 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs @@ -67,6 +67,29 @@ namespace Microsoft.AspNet.Mvc.Razor return csharpCodeVisitor; } + protected override CSharpDesignTimeCodeVisitor CreateCSharpDesignTimeCodeVisitor( + CSharpCodeVisitor csharpCodeVisitor, + CSharpCodeWriter writer, + CodeGeneratorContext context) + { + if (csharpCodeVisitor == null) + { + throw new ArgumentNullException(nameof(csharpCodeVisitor)); + } + + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + return new MvcCSharpDesignTimeCodeVisitor(csharpCodeVisitor, writer, context); + } + protected override void BuildConstructor(CSharpCodeWriter writer) { if (writer == null) diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpDesignTimeCodeVisitor.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpDesignTimeCodeVisitor.cs new file mode 100644 index 0000000000..e2db2dfc54 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpDesignTimeCodeVisitor.cs @@ -0,0 +1,70 @@ +// 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.Diagnostics; +using Microsoft.AspNet.Razor.Chunks; +using Microsoft.AspNet.Razor.CodeGenerators; +using Microsoft.AspNet.Razor.CodeGenerators.Visitors; + +namespace Microsoft.AspNet.Mvc.Razor +{ + public class MvcCSharpDesignTimeCodeVisitor : CSharpDesignTimeCodeVisitor + { + private const string ModelVariable = "__modelHelper"; + private ModelChunk _modelChunk; + + public MvcCSharpDesignTimeCodeVisitor( + CSharpCodeVisitor csharpCodeVisitor, + CSharpCodeWriter writer, + CodeGeneratorContext context) + : base(csharpCodeVisitor, writer, context) + { + } + + protected override void AcceptTreeCore(ChunkTree tree) + { + base.AcceptTreeCore(tree); + + if (_modelChunk != null) + { + WriteModelChunkLineMapping(); + } + } + + public override void Accept(Chunk chunk) + { + if (chunk is ModelChunk) + { + Visit((ModelChunk)chunk); + } + + base.Accept(chunk); + } + + private void Visit(ModelChunk chunk) + { + Debug.Assert(chunk != null); + _modelChunk = chunk; + } + + private void WriteModelChunkLineMapping() + { + Debug.Assert(Context.Host.DesignTimeMode); + + using (var lineMappingWriter = + Writer.BuildLineMapping(_modelChunk.Start, _modelChunk.ModelType.Length, Context.SourceFile)) + { + // var __modelHelper = default(MyModel); + Writer.Write("var ") + .Write(ModelVariable) + .Write(" = default("); + + lineMappingWriter.MarkLineMappingStart(); + Writer.Write(_modelChunk.ModelType); + lineMappingWriter.MarkLineMappingEnd(); + + Writer.WriteLine(");"); + } + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs index 4f9c032894..7a3a6db05c 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs @@ -45,7 +45,10 @@ namespace Microsoft.AspNet.Mvc.Razor new SetBaseTypeChunk { // Microsoft.Aspnet.Mvc.Razor.RazorPage - TypeName = BaseType + ChunkHelper.TModelToken + TypeName = BaseType + ChunkHelper.TModelToken, + // Set the Start to Undefined to prevent Razor design time code generation from rendering a line mapping + // for this chunk. + Start = SourceLocation.Undefined } }; diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs index 3e27fa8245..fb741a2220 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs @@ -137,18 +137,18 @@ namespace Microsoft.AspNet.Mvc.Razor generatedCharacterIndex: 14, contentLength: 85), BuildLineMapping( - documentAbsoluteIndex: 0, + documentAbsoluteIndex: 7, documentLineIndex: 0, - documentCharacterIndex: 0, - generatedAbsoluteIndex: 910, + documentCharacterIndex: 7, + generatedAbsoluteIndex: 938, generatedLineIndex: 25, - generatedCharacterIndex: 0, - contentLength: 46), + generatedCharacterIndex: 28, + contentLength: 8), BuildLineMapping( documentAbsoluteIndex: 139, documentLineIndex: 4, documentCharacterIndex: 17, - generatedAbsoluteIndex: 2371, + generatedAbsoluteIndex: 2338, generatedLineIndex: 54, generatedCharacterIndex: 95, contentLength: 3), @@ -156,16 +156,17 @@ namespace Microsoft.AspNet.Mvc.Razor documentAbsoluteIndex: 166, documentLineIndex: 5, documentCharacterIndex: 18, - generatedAbsoluteIndex: 2684, + generatedAbsoluteIndex: 2651, generatedLineIndex: 60, generatedCharacterIndex: 87, contentLength: 5), }; // Act and Assert - RunDesignTimeTest(host, - testName: "ModelExpressionTagHelper", - expectedLineMappings: expectedLineMappings); + RunDesignTimeTest( + host, + testName: "ModelExpressionTagHelper", + expectedLineMappings: expectedLineMappings); } [Theory] @@ -197,28 +198,20 @@ namespace Microsoft.AspNet.Mvc.Razor host.NamespaceImports.Clear(); var expectedLineMappings = new[] { - BuildLineMapping( - documentAbsoluteIndex: 0, - documentLineIndex: 0, - documentCharacterIndex: 0, - generatedAbsoluteIndex: 343, - generatedLineIndex: 11, - generatedCharacterIndex: 0, - contentLength: 45), BuildLineMapping( documentAbsoluteIndex: 13, documentLineIndex: 0, documentCharacterIndex: 13, - generatedAbsoluteIndex: 1412, - generatedLineIndex: 37, + generatedAbsoluteIndex: 1269, + generatedLineIndex: 32, generatedCharacterIndex: 13, contentLength: 4), BuildLineMapping( documentAbsoluteIndex: 43, documentLineIndex: 2, documentCharacterIndex: 5, - generatedAbsoluteIndex: 1496, - generatedLineIndex: 42, + generatedAbsoluteIndex: 1353, + generatedLineIndex: 37, generatedCharacterIndex: 6, contentLength: 21), }; @@ -247,20 +240,12 @@ namespace Microsoft.AspNet.Mvc.Razor generatedLineIndex: 3, generatedCharacterIndex: 0, contentLength: 17), - BuildLineMapping( - documentAbsoluteIndex: 0, - documentLineIndex: 0, - documentCharacterIndex: 0, - generatedAbsoluteIndex: 443, - generatedLineIndex: 17, - generatedCharacterIndex: 0, - contentLength: 45), BuildLineMapping( documentAbsoluteIndex: 28, documentLineIndex: 1, documentCharacterIndex: 8, - generatedAbsoluteIndex: 850, - generatedLineIndex: 31, + generatedAbsoluteIndex: 706, + generatedLineIndex: 26, generatedCharacterIndex: 8, contentLength: 20), }; @@ -282,18 +267,18 @@ namespace Microsoft.AspNet.Mvc.Razor var expectedLineMappings = new[] { BuildLineMapping( - documentAbsoluteIndex: 0, + documentAbsoluteIndex: 7, documentLineIndex: 0, - documentCharacterIndex: 0, - generatedAbsoluteIndex: 363, + documentCharacterIndex: 7, + generatedAbsoluteIndex: 391, generatedLineIndex: 11, - generatedCharacterIndex: 0, - contentLength: 45), + generatedCharacterIndex: 28, + contentLength: 7), BuildLineMapping( documentAbsoluteIndex: 24, documentLineIndex: 1, documentCharacterIndex: 8, - generatedAbsoluteIndex: 788, + generatedAbsoluteIndex: 755, generatedLineIndex: 25, generatedCharacterIndex: 8, contentLength: 20), @@ -301,7 +286,7 @@ namespace Microsoft.AspNet.Mvc.Razor documentAbsoluteIndex: 54, documentLineIndex: 2, documentCharacterIndex: 8, - generatedAbsoluteIndex: 1014, + generatedAbsoluteIndex: 981, generatedLineIndex: 33, generatedCharacterIndex: 8, contentLength: 23), @@ -324,18 +309,18 @@ namespace Microsoft.AspNet.Mvc.Razor var expectedLineMappings = new[] { BuildLineMapping( - documentAbsoluteIndex: 0, + documentAbsoluteIndex: 7, documentLineIndex: 0, - documentCharacterIndex: 0, - generatedAbsoluteIndex: 371, + documentCharacterIndex: 7, + generatedAbsoluteIndex: 399, generatedLineIndex: 11, - generatedCharacterIndex: 0, - contentLength: 45), + generatedCharacterIndex: 28, + contentLength: 7), BuildLineMapping( documentAbsoluteIndex: 24, documentLineIndex: 1, documentCharacterIndex: 8, - generatedAbsoluteIndex: 804, + generatedAbsoluteIndex: 771, generatedLineIndex: 25, generatedCharacterIndex: 8, contentLength: 20), @@ -343,7 +328,7 @@ namespace Microsoft.AspNet.Mvc.Razor documentAbsoluteIndex: 58, documentLineIndex: 2, documentCharacterIndex: 8, - generatedAbsoluteIndex: 1034, + generatedAbsoluteIndex: 1001, generatedLineIndex: 33, generatedCharacterIndex: 8, contentLength: 23), @@ -351,7 +336,7 @@ namespace Microsoft.AspNet.Mvc.Razor documentAbsoluteIndex: 93, documentLineIndex: 3, documentCharacterIndex: 8, - generatedAbsoluteIndex: 1267, + generatedAbsoluteIndex: 1234, generatedLineIndex: 41, generatedCharacterIndex: 8, contentLength: 21), @@ -359,7 +344,7 @@ namespace Microsoft.AspNet.Mvc.Razor documentAbsoluteIndex: 129, documentLineIndex: 4, documentCharacterIndex: 8, - generatedAbsoluteIndex: 1498, + generatedAbsoluteIndex: 1465, generatedLineIndex: 49, generatedCharacterIndex: 8, contentLength: 24), @@ -382,19 +367,51 @@ namespace Microsoft.AspNet.Mvc.Razor var expectedLineMappings = new[] { BuildLineMapping( - documentAbsoluteIndex: 0, + documentAbsoluteIndex: 7, documentLineIndex: 0, - documentCharacterIndex: 0, - generatedAbsoluteIndex: 366, + documentCharacterIndex: 7, + generatedAbsoluteIndex: 394, generatedLineIndex: 11, - generatedCharacterIndex: 0, - contentLength: 68), + generatedCharacterIndex: 28, + contentLength: 30), }; // Act and Assert RunDesignTimeTest(host, "Model", expectedLineMappings); } + [Fact] + public void ModelVisitor_GeneratesLineMappingsForLastModel_WhenMultipleModelsArePresent() + { + // Arrange + var fileProvider = new TestFileProvider(); + var host = new MvcRazorHostWithNormalizedNewLine(new DefaultChunkTreeCache(fileProvider)) + { + DesignTimeMode = true + }; + host.NamespaceImports.Clear(); + var inputFile = "TestFiles/Input/MultipleModels.cshtml"; + var outputFile = "TestFiles/Output/DesignTime/MultipleModels.cs"; + var expectedCode = ResourceFile.ReadResource(_assembly, outputFile, sourceFile: false); + + // Act + GeneratorResults results; + using (var stream = ResourceFile.GetResourceStream(_assembly, inputFile, sourceFile: true)) + { + results = host.GenerateCode(inputFile, stream); + } + + // Assert + Assert.False(results.Success); + var parserError = Assert.Single(results.ParserErrors); + Assert.Equal("Only one 'model' statement is allowed in a file.", parserError.Message); +#if GENERATE_BASELINES + ResourceFile.UpdateFile(_assembly, outputFile, expectedCode, results.GeneratedCode); +#else + Assert.Equal(expectedCode, results.GeneratedCode, ignoreLineEndingDifferences: true); +#endif + } + private static void RunRuntimeTest( MvcRazorHost host, string testName) diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/MultipleModels.cshtml b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/MultipleModels.cshtml new file mode 100644 index 0000000000..0a05b3030d --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/MultipleModels.cshtml @@ -0,0 +1,2 @@ +@model ThisShouldNotBeGenerated +@model System.Collections.IEnumerable diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/Basic.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/Basic.cs index 0b4e72cce3..12b8fe71b3 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/Basic.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/Basic.cs @@ -8,11 +8,6 @@ namespace Asp private void @__RazorDesignTimeHelpers__() { #pragma warning disable 219 -#line 1 "TestFiles/Input/Basic.cshtml" -Microsoft.AspNet.Mvc.Razor.RazorPage __inheritsHelper = null; - -#line default -#line hidden #pragma warning restore 219 } #line hidden diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/Inject.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/Inject.cs index 0525e6b978..daeb196d90 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/Inject.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/Inject.cs @@ -14,11 +14,6 @@ using MyNamespace private void @__RazorDesignTimeHelpers__() { #pragma warning disable 219 -#line 1 "TestFiles/Input/Inject.cshtml" -Microsoft.AspNet.Mvc.Razor.RazorPage __inheritsHelper = null; - -#line default -#line hidden #pragma warning restore 219 } #line hidden diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/InjectWithModel.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/InjectWithModel.cs index 85d744bcf4..d493fee840 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/InjectWithModel.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/InjectWithModel.cs @@ -9,7 +9,7 @@ namespace Asp { #pragma warning disable 219 #line 1 "TestFiles/Input/InjectWithModel.cshtml" -Microsoft.AspNet.Mvc.Razor.RazorPage __inheritsHelper = null; +var __modelHelper = default(MyModel); #line default #line hidden diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.cs index e21e518fdd..bc1e89c2da 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.cs @@ -9,7 +9,7 @@ namespace Asp { #pragma warning disable 219 #line 1 "TestFiles/Input/InjectWithSemicolon.cshtml" -Microsoft.AspNet.Mvc.Razor.RazorPage __inheritsHelper = null; +var __modelHelper = default(MyModel); #line default #line hidden diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/Model.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/Model.cs index 1f5d2ac9d9..57ebd130fe 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/Model.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/Model.cs @@ -9,7 +9,7 @@ namespace Asp { #pragma warning disable 219 #line 1 "TestFiles/Input/Model.cshtml" -Microsoft.AspNet.Mvc.Razor.RazorPage __inheritsHelper = null; +var __modelHelper = default(System.Collections.IEnumerable); #line default #line hidden diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/ModelExpressionTagHelper.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/ModelExpressionTagHelper.cs index 2ff199a009..1e70626c41 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/ModelExpressionTagHelper.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/ModelExpressionTagHelper.cs @@ -23,7 +23,7 @@ namespace Asp #line hidden ; #line 1 "TestFiles/Input/ModelExpressionTagHelper.cshtml" -Microsoft.AspNet.Mvc.Razor.RazorPage __inheritsHelper = null; +var __modelHelper = default(DateTime); #line default #line hidden diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/MultipleModels.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/MultipleModels.cs new file mode 100644 index 0000000000..c6cd2d8ae0 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/MultipleModels.cs @@ -0,0 +1,40 @@ +namespace Asp +{ + using System.Threading.Tasks; + + public class ASPV_TestFiles_Input_MultipleModels_cshtml : Microsoft.AspNet.Mvc.Razor.RazorPage + { + private static object @__o; + private void @__RazorDesignTimeHelpers__() + { + #pragma warning disable 219 +#line 2 "TestFiles/Input/MultipleModels.cshtml" +var __modelHelper = default(System.Collections.IEnumerable); + +#line default +#line hidden + #pragma warning restore 219 + } + #line hidden + public ASPV_TestFiles_Input_MultipleModels_cshtml() + { + } + #line hidden + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public Microsoft.AspNet.Mvc.IUrlHelper Url { get; private set; } + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public Microsoft.AspNet.Mvc.IViewComponentHelper Component { get; private set; } + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public Microsoft.AspNet.Mvc.Rendering.IJsonHelper Json { get; private set; } + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public Microsoft.AspNet.Mvc.Rendering.IHtmlHelper Html { get; private set; } + + #line hidden + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + } + #pragma warning restore 1998 + } +}