diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeBuilder.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeBuilder.cs index 218a623f95..de30bf6624 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeBuilder.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeBuilder.cs @@ -50,11 +50,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp new CSharpTypeMemberVisitor(writer, Context).Accept(Tree.Chunks); new CSharpDesignTimeHelpersVisitor(writer, Context).AcceptTree(Tree); - writer.WriteLineHiddenDirective(); - using (writer.BuildConstructor(Context.ClassName)) - { - // Any constructor based logic that we need to add? - }; + BuildConstructor(writer); // Add space inbetween constructor and method body writer.WriteLine(); @@ -72,6 +68,15 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp return new CodeBuilderResult(writer.GenerateCode(), writer.LineMappingManager.Mappings); } + protected virtual void BuildConstructor(CSharpCodeWriter writer) + { + writer.WriteLineHiddenDirective(); + using (writer.BuildConstructor(Context.ClassName)) + { + // Any constructor based logic that we need to add? + }; + } + private void AddImports(CodeTree codeTree, CSharpCodeWriter writer, IEnumerable defaultImports) { // Write out using directives diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/ChunkVisitor.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/ChunkVisitor.cs index a1a7790acb..a57d803fe8 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/ChunkVisitor.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/ChunkVisitor.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler } } - public void Accept(Chunk chunk) + public virtual void Accept(Chunk chunk) { if (chunk == null) { @@ -105,10 +105,6 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler { Visit((SessionStateChunk)chunk); } - else - { - throw new InvalidOperationException("Unknown chunk type " + chunk.GetType().Name); - } } protected abstract void Visit(LiteralChunk chunk); diff --git a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Statements.cs b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Statements.cs index d825099696..8503f1c485 100644 --- a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Statements.cs +++ b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Statements.cs @@ -162,7 +162,7 @@ namespace Microsoft.AspNet.Razor.Parser } } - private bool NamespaceOrTypeName() + protected bool NamespaceOrTypeName() { if (Optional(CSharpSymbolType.Identifier) || Optional(CSharpSymbolType.Keyword)) { diff --git a/src/Microsoft.AspNet.Razor/RazorEngineHost.cs b/src/Microsoft.AspNet.Razor/RazorEngineHost.cs index 35316f1bf7..828a3a9c4a 100644 --- a/src/Microsoft.AspNet.Razor/RazorEngineHost.cs +++ b/src/Microsoft.AspNet.Razor/RazorEngineHost.cs @@ -201,5 +201,19 @@ namespace Microsoft.AspNet.Razor } return incomingCodeGenerator; } + + /// + /// Gets an instance of the code builder and is provided an opportunity to decorate or replace it + /// + /// The code builder + /// Either the same code builder, after modifications, or a different code builder. + public virtual CodeBuilder DecorateCodeBuilder(CodeBuilder incomingBuilder, CodeGeneratorContext context) + { + if (incomingBuilder == null) + { + throw new ArgumentNullException("incomingBuilder"); + } + return incomingBuilder; + } } } diff --git a/src/Microsoft.AspNet.Razor/RazorTemplateEngine.cs b/src/Microsoft.AspNet.Razor/RazorTemplateEngine.cs index 298669bff6..d08470f532 100644 --- a/src/Microsoft.AspNet.Razor/RazorTemplateEngine.cs +++ b/src/Microsoft.AspNet.Razor/RazorTemplateEngine.cs @@ -167,7 +167,7 @@ namespace Microsoft.AspNet.Razor generator.DesignTimeMode = Host.DesignTimeMode; generator.Visit(results); - var builder = Host.CodeLanguage.CreateCodeBuilder(generator.Context); + var builder = CreateCodeBuilder(generator.Context); var builderResult = builder.Build(); // Collect results and return @@ -191,5 +191,11 @@ namespace Microsoft.AspNet.Razor DesignTimeMode = Host.DesignTimeMode }; } + + protected internal virtual CodeBuilder CreateCodeBuilder(CodeGeneratorContext context) + { + return Host.DecorateCodeBuilder(Host.CodeLanguage.CreateCodeBuilder(context), + context); + } } } diff --git a/test/Microsoft.AspNet.Razor.Test/CSharpRazorCodeLanguageTest.cs b/test/Microsoft.AspNet.Razor.Test/CSharpRazorCodeLanguageTest.cs index 0b140e9999..4dd8f72859 100644 --- a/test/Microsoft.AspNet.Razor.Test/CSharpRazorCodeLanguageTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/CSharpRazorCodeLanguageTest.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Razor.Generator; +using Microsoft.AspNet.Razor.Generator.Compiler.CSharp; using Microsoft.AspNet.Razor.Parser; -using Microsoft.CSharp; using Microsoft.TestCommon; namespace Microsoft.AspNet.Razor.Test @@ -42,5 +42,24 @@ namespace Microsoft.AspNet.Razor.Test Assert.Equal("Baz", generator.SourceFileName); Assert.Same(host, generator.Host); } + + [Fact] + public void CreateCodeBuilder_ReturnsNewCSharpCodeBuilder() + { + // Arrange + var language = new CSharpRazorCodeLanguage(); + var host = new RazorEngineHost(language); + var context = CodeGeneratorContext.Create(host, + "myclass", + "myns", + string.Empty, + shouldGenerateLinePragmas: false); + + // Act + var generator = language.CreateCodeBuilder(context); + + // Assert + Assert.IsType(generator); + } } } diff --git a/test/Microsoft.AspNet.Razor.Test/Generator/CodeTree/ChunkVisitorTests.cs b/test/Microsoft.AspNet.Razor.Test/Generator/CodeTree/ChunkVisitorTests.cs new file mode 100644 index 0000000000..7977cc0ed1 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Test/Generator/CodeTree/ChunkVisitorTests.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNet.Razor.Generator; +using Microsoft.AspNet.Razor.Generator.Compiler; +using Microsoft.TestCommon; +using Moq; +using Moq.Protected; + +namespace Microsoft.AspNet.Razor +{ + public class ChunkVisitorTests + { + [Fact] + public void Accept_InvokesAppropriateOverload() + { + // Arrange + var chunks = new Chunk[] { new LiteralChunk(), new StatementChunk() }; + var visitor = CreateVisitor(); + + // Act + visitor.Object.Accept(chunks); + + // Assert + visitor.Protected().Verify("Visit", Times.AtMostOnce(), chunks[0]); + visitor.Protected().Verify("Visit", Times.AtMostOnce(), chunks[1]); + } + + private static Mock> CreateVisitor() + { + var context = CodeGeneratorContext.Create(new RazorEngineHost(new CSharpRazorCodeLanguage()), + "myclass", + "myns", + string.Empty, + shouldGenerateLinePragmas: false); + var writer = Mock.Of(); + return new Mock>(writer, context); + } + + private class MyTestChunk : Chunk + { + } + } +} diff --git a/test/Microsoft.AspNet.Razor.Test/Microsoft.AspNet.Razor.Test.csproj b/test/Microsoft.AspNet.Razor.Test/Microsoft.AspNet.Razor.Test.csproj index 719a0201ca..aae5f95a05 100644 --- a/test/Microsoft.AspNet.Razor.Test/Microsoft.AspNet.Razor.Test.csproj +++ b/test/Microsoft.AspNet.Razor.Test/Microsoft.AspNet.Razor.Test.csproj @@ -67,6 +67,7 @@ + diff --git a/test/Microsoft.AspNet.Razor.Test/RazorTemplateEngineTest.cs b/test/Microsoft.AspNet.Razor.Test/RazorTemplateEngineTest.cs index 16b4e8e839..f74f7fe6cc 100644 --- a/test/Microsoft.AspNet.Razor.Test/RazorTemplateEngineTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/RazorTemplateEngineTest.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading; using System.Web.WebPages.TestUtils; using Microsoft.AspNet.Razor.Generator; +using Microsoft.AspNet.Razor.Generator.Compiler.CSharp; using Microsoft.AspNet.Razor.Parser; using Microsoft.AspNet.Razor.Text; using Microsoft.TestCommon; @@ -104,6 +105,29 @@ namespace Microsoft.AspNet.Razor.Test Assert.Equal(expected, actual); } + [Fact] + public void CreateCodeBuilder_PassesCodeGeneratorThroughDecorateMethodOnHost() + { + // Arrange + var mockHost = new Mock(new CSharpRazorCodeLanguage()) { CallBase = true }; + var context = CodeGeneratorContext.Create(mockHost.Object, + "different-class", + "different-ns", + string.Empty, + shouldGenerateLinePragmas: true); + var expected = new CSharpCodeBuilder(context); + + mockHost.Setup(h => h.DecorateCodeBuilder(It.IsAny(), context)) + .Returns(expected); + var engine = new RazorTemplateEngine(mockHost.Object); + + // Act + var actual = engine.CreateCodeBuilder(context); + + // Assert + Assert.Equal(expected, actual); + } + [Fact] public void ParseTemplateCopiesTextReaderContentToSeekableTextReaderAndPassesToParseTemplateCore() {