From 489c0d904690f2b74095d3b33e5a6d2135895b1a Mon Sep 17 00:00:00 2001 From: Crystal Qian Date: Wed, 24 Aug 2016 16:01:59 -0700 Subject: [PATCH] Added view component tag helper updates. (#823) Added view component tag helper updates. aspnet/mvc#1051 --- .../TagHelpers/TagHelperDescriptorFactory.cs | 4 +- .../CodeGenerators/CSharpCodeGenerator.cs | 13 +- .../CodeGenerators/CSharpCodeWriter.cs | 11 ++ .../TagHelperDescriptorFactoryTest.cs | 29 ++++ .../CodeGenerators/CSharpCodeGeneratorTest.cs | 161 +++++++++++++++++- .../AddGenerateChunkTest.cs | 27 +++ .../BuildAfterExecuteContentTest.cs | 27 +++ .../ChunkTreeWithUsings.cs} | 0 .../ClearGenerateChunkTest.cs | 18 ++ .../DefaultGenerateChunkTest.cs | 26 +++ 10 files changed, 308 insertions(+), 8 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/AddGenerateChunkTest.cs create mode 100644 test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/BuildAfterExecuteContentTest.cs rename test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/{CSharpCodeGenerator.cs => CSharpCodeGenerator/ChunkTreeWithUsings.cs} (100%) create mode 100644 test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/ClearGenerateChunkTest.cs create mode 100644 test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/DefaultGenerateChunkTest.cs diff --git a/src/Microsoft.AspNetCore.Razor.Runtime/Runtime/TagHelpers/TagHelperDescriptorFactory.cs b/src/Microsoft.AspNetCore.Razor.Runtime/Runtime/TagHelpers/TagHelperDescriptorFactory.cs index b6defbda5a..92916c8d48 100644 --- a/src/Microsoft.AspNetCore.Razor.Runtime/Runtime/TagHelpers/TagHelperDescriptorFactory.cs +++ b/src/Microsoft.AspNetCore.Razor.Runtime/Runtime/TagHelpers/TagHelperDescriptorFactory.cs @@ -703,6 +703,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers /// Converts from pascal/camel case to lower kebab-case. /// /// + /// /// SomeThing => some-thing /// capsONInside => caps-on-inside /// CAPSOnOUTSIDE => caps-on-outside @@ -710,8 +711,9 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers /// One1Two2Three3 => one1-two2-three3 /// ONE1TWO2THREE3 => one1two2three3 /// First_Second_ThirdHi => first_second_third-hi + /// /// - private static string ToHtmlCase(string name) + public static string ToHtmlCase(string name) { return HtmlCaseRegex.Replace(name, HtmlCaseRegexReplacement).ToLowerInvariant(); } diff --git a/src/Microsoft.AspNetCore.Razor/CodeGenerators/CSharpCodeGenerator.cs b/src/Microsoft.AspNetCore.Razor/CodeGenerators/CSharpCodeGenerator.cs index a67b9a865a..17141eb07e 100644 --- a/src/Microsoft.AspNetCore.Razor/CodeGenerators/CSharpCodeGenerator.cs +++ b/src/Microsoft.AspNetCore.Razor/CodeGenerators/CSharpCodeGenerator.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Razor.CodeGenerators { } - private ChunkTree Tree { get { return Context.ChunkTreeBuilder.Root; } } + protected ChunkTree Tree { get { return Context.ChunkTreeBuilder.Root; } } public RazorEngineHost Host { get { return Context.Host; } } /// @@ -82,12 +82,23 @@ namespace Microsoft.AspNetCore.Razor.CodeGenerators csharpCodeVisitor.Accept(Tree.Children); } } + + BuildAfterExecuteContent(writer, Tree.Children); } } return new CodeGeneratorResult(writer.GenerateCode(), writer.LineMappingManager.Mappings); } + /// + /// Provides an entry point to append code (after execute content) to a generated Razor class. + /// + /// The to receive the additional content. + /// The list of s for the generated program. + protected virtual void BuildAfterExecuteContent(CSharpCodeWriter writer, IList chunks) + { + } + protected virtual CSharpCodeVisitor CreateCSharpCodeVisitor( CSharpCodeWriter writer, CodeGeneratorContext context) diff --git a/src/Microsoft.AspNetCore.Razor/CodeGenerators/CSharpCodeWriter.cs b/src/Microsoft.AspNetCore.Razor/CodeGenerators/CSharpCodeWriter.cs index 559e8ced77..c53ff2fc62 100644 --- a/src/Microsoft.AspNetCore.Razor/CodeGenerators/CSharpCodeWriter.cs +++ b/src/Microsoft.AspNetCore.Razor/CodeGenerators/CSharpCodeWriter.cs @@ -319,6 +319,17 @@ namespace Microsoft.AspNetCore.Razor.CodeGenerators .WriteEndMethodInvocation(endLine); } + public CSharpCodeWriter WriteAutoPropertyDeclaration(string accessibility, string typeName, string name) + { + return Write(accessibility) + .Write(" ") + .Write(typeName) + .Write(" ") + .Write(name) + .Write(" { get; set; }") + .WriteLine(); + } + public CSharpDisableWarningScope BuildDisableWarningScope(int warning) { return new CSharpDisableWarningScope(this, warning); diff --git a/test/Microsoft.AspNetCore.Razor.Runtime.Test/Runtime/TagHelpers/TagHelperDescriptorFactoryTest.cs b/test/Microsoft.AspNetCore.Razor.Runtime.Test/Runtime/TagHelpers/TagHelperDescriptorFactoryTest.cs index 02ab2a2de0..c68aa85e6f 100644 --- a/test/Microsoft.AspNetCore.Razor.Runtime.Test/Runtime/TagHelpers/TagHelperDescriptorFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Runtime.Test/Runtime/TagHelpers/TagHelperDescriptorFactoryTest.cs @@ -2172,6 +2172,35 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers TagHelperAttributeDescriptorComparer.Default); } + public static TheoryData HtmlConversionData + { + get + { + return new TheoryData + { + { "SomeThing", "some-thing" }, + { "someOtherThing", "some-other-thing" }, + { "capsONInside", "caps-on-inside" }, + { "CAPSOnOUTSIDE", "caps-on-outside" }, + { "ALLCAPS", "allcaps" }, + { "One1Two2Three3", "one1-two2-three3" }, + { "ONE1TWO2THREE3", "one1two2three3" }, + { "First_Second_ThirdHi", "first_second_third-hi" } + }; + } + } + + [Theory] + [MemberData(nameof(HtmlConversionData))] + public void ToHtmlCase_ReturnsExpectedConversions(string input, string expectedOutput) + { + // Arrange, Act + var output = TagHelperDescriptorFactory.ToHtmlCase(input); + + // Assert + Assert.Equal(output, expectedOutput); + } + // TagHelperDesignTimeDescriptors are not created in CoreCLR. #if !NETCOREAPP1_0 public static TheoryData OutputElementHintData diff --git a/test/Microsoft.AspNetCore.Razor.Test/CodeGenerators/CSharpCodeGeneratorTest.cs b/test/Microsoft.AspNetCore.Razor.Test/CodeGenerators/CSharpCodeGeneratorTest.cs index baaada48d7..5bab80810c 100644 --- a/test/Microsoft.AspNetCore.Razor.Test/CodeGenerators/CSharpCodeGeneratorTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Test/CodeGenerators/CSharpCodeGeneratorTest.cs @@ -4,6 +4,10 @@ #if GENERATE_BASELINES using System; #endif +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Razor.Chunks; +using Microsoft.AspNetCore.Razor.Chunks.Generators; using Microsoft.AspNetCore.Razor.Parser.SyntaxTree; using Microsoft.AspNetCore.Razor.Test.CodeGenerators; using Microsoft.AspNetCore.Razor.Test.Utils; @@ -14,9 +18,15 @@ namespace Microsoft.AspNetCore.Razor.CodeGenerators { public class CSharpCodeGeneratorTest { +#if GENERATE_BASELINES + private static readonly string _baselinePathStart = "test/Microsoft.AspNetCore.Razor.Test"; +#endif + private static readonly string _testOutputDirectory = "TestFiles/CodeGenerator/Output/CSharpCodeGenerator"; + [Fact] public void ChunkTreeWithUsings() { + // Arrange var syntaxTreeNode = new Mock(new SpanBuilder()); var language = new CSharpRazorCodeLanguage(); var host = new CodeGenTestHost(language); @@ -30,7 +40,9 @@ namespace Microsoft.AspNetCore.Razor.CodeGenerators codeGeneratorContext.ChunkTreeBuilder.AddUsingChunk("FakeNamespace1", syntaxTreeNode.Object); codeGeneratorContext.ChunkTreeBuilder.AddUsingChunk("FakeNamespace2.SubNamespace", syntaxTreeNode.Object); var codeGenerator = new CodeGenTestCodeGenerator(codeGeneratorContext); - var testFile = TestFile.Create("TestFiles/CodeGenerator/Output/CSharpCodeGenerator.cs"); + + var path = $"{_testOutputDirectory}/ChunkTreeWithUsings.cs"; + var testFile = TestFile.Create(path); string expectedOutput; #if GENERATE_BASELINES @@ -54,14 +66,151 @@ namespace Microsoft.AspNetCore.Razor.CodeGenerators // Update baseline files if files do not already match. if (!string.Equals(expectedOutput, result.Code, StringComparison.Ordinal)) { - BaselineWriter.WriteBaseline( - @"test\Microsoft.AspNetCore.Razor.Test\TestFiles\CodeGenerator\Output\CSharpCodeGenerator.cs", - result.Code); + var baselinePath = $"{_baselinePathStart}/{_testOutputDirectory}/ChunkTreeWithUsings.cs"; + BaselineWriter.WriteBaseline( baselinePath, result.Code); } #else Assert.Equal(expectedOutput, result.Code); #endif } - } -} + public static TheoryData ModifyOutputData + { + get + { + var addFileName = "AddGenerateChunkTest.cs"; + var addGenerator = new AddGenerateTestCodeGenerator(CreateContext()); + var addResult = CreateCodeGeneratorResult(addFileName); + + var clearFileName = "ClearGenerateChunkTest.cs"; + var clearGenerator = new ClearGenerateTestCodeGenerator(CreateContext()); + var clearResult = CreateCodeGeneratorResult(clearFileName); + + var defaultFileName = "DefaultGenerateChunkTest.cs"; + var defaultGenerator = new CSharpCodeGenerator(CreateContext()); + var defaultResult = CreateCodeGeneratorResult(defaultFileName); + + var commentFileName = "BuildAfterExecuteContentTest.cs"; + var commentGenerator = new BuildAfterExecuteContentTestCodeGenerator(CreateContext()); + var commentResult = CreateCodeGeneratorResult(commentFileName); + + return new TheoryData + { + {addGenerator, addResult, addFileName}, + {clearGenerator, clearResult, clearFileName }, + {defaultGenerator, defaultResult, defaultFileName }, + {commentGenerator, commentResult, commentFileName } + }; + } + } + + [Theory] + [MemberData(nameof(ModifyOutputData))] + public void BuildAfterExecuteContent_ModifyChunks_ModifyOutput( + CSharpCodeGenerator generator, + CodeGeneratorResult expectedResult, + string fileName) + { + // Arrange, Act + var result = generator.Generate(); + + // Assert +#if GENERATE_BASELINES + // Update baseline files if files do not already match. + if (!string.Equals(expectedResult.Code, result.Code, StringComparison.Ordinal)) + { + var baselinePath = $"{_baselinePathStart}/{_testOutputDirectory}/{fileName}"; + BaselineWriter.WriteBaseline( baselinePath, result.Code); + } +#else + Assert.Equal(result.Code, expectedResult.Code); +#endif + } + + + private static CodeGeneratorResult CreateCodeGeneratorResult(string fileName) + { + var path = $"{_testOutputDirectory}/{fileName}"; + var file = TestFile.Create(path); + string code; + +#if GENERATE_BASELINES + if (file.Exists()) + { + code = file.ReadAllText(); + } + else + { + code = null; + } +#else + code = file.ReadAllText(); +#endif + + var result = new CodeGeneratorResult(code, new List()); + return result; + } + + // Returns a context with two literal chunks. + private static CodeGeneratorContext CreateContext() + { + var language = new CSharpRazorCodeLanguage(); + var host = new CodeGenTestHost(language); + + var codeGeneratorContext = new CodeGeneratorContext( + new ChunkGeneratorContext( + host, + host.DefaultClassName, + host.DefaultNamespace, + "", + shouldGenerateLinePragmas: false), + new ErrorSink()); + + codeGeneratorContext.ChunkTreeBuilder = new ChunkTreeBuilder(); + var syntaxTreeNode = new Mock(new SpanBuilder()); + codeGeneratorContext.ChunkTreeBuilder.AddLiteralChunk("hello", syntaxTreeNode.Object); + codeGeneratorContext.ChunkTreeBuilder.AddStatementChunk("// asdf", syntaxTreeNode.Object); + codeGeneratorContext.ChunkTreeBuilder.AddLiteralChunk("world", syntaxTreeNode.Object); + return codeGeneratorContext; + } + + private class BuildAfterExecuteContentTestCodeGenerator : CSharpCodeGenerator + { + public BuildAfterExecuteContentTestCodeGenerator(CodeGeneratorContext context) : base(context) + { + } + + protected override void BuildAfterExecuteContent(CSharpCodeWriter writer, IList chunks) + { + writer.WriteLine("// test add content."); + } + } + + private class AddGenerateTestCodeGenerator : CSharpCodeGenerator + { + public AddGenerateTestCodeGenerator(CodeGeneratorContext context) : base(context) + { + } + + public override CodeGeneratorResult Generate() + { + var firstChunk = Tree.Children.First(); + Tree.Children.Add(firstChunk); + return base.Generate(); + } + } + + private class ClearGenerateTestCodeGenerator : CSharpCodeGenerator + { + public ClearGenerateTestCodeGenerator(CodeGeneratorContext context) : base(context) + { + } + + public override CodeGeneratorResult Generate() + { + Tree.Children.Clear(); + return base.Generate(); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/AddGenerateChunkTest.cs b/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/AddGenerateChunkTest.cs new file mode 100644 index 0000000000..d78105f7ef --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/AddGenerateChunkTest.cs @@ -0,0 +1,27 @@ +namespace Razor +{ + using System.Threading.Tasks; + + public class __CompiledTemplate + { + #line hidden + public __CompiledTemplate() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + WriteLiteral("hello"); +#line 1 "" +// asdf + +#line default +#line hidden + + WriteLiteral("world"); + WriteLiteral("hello"); + } + #pragma warning restore 1998 + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/BuildAfterExecuteContentTest.cs b/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/BuildAfterExecuteContentTest.cs new file mode 100644 index 0000000000..f7092af05a --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/BuildAfterExecuteContentTest.cs @@ -0,0 +1,27 @@ +namespace Razor +{ + using System.Threading.Tasks; + + public class __CompiledTemplate + { + #line hidden + public __CompiledTemplate() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + WriteLiteral("hello"); +#line 1 "" +// asdf + +#line default +#line hidden + + WriteLiteral("world"); + } + #pragma warning restore 1998 + // test add content. + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator.cs b/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/ChunkTreeWithUsings.cs similarity index 100% rename from test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator.cs rename to test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/ChunkTreeWithUsings.cs diff --git a/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/ClearGenerateChunkTest.cs b/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/ClearGenerateChunkTest.cs new file mode 100644 index 0000000000..c3d01fabd7 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/ClearGenerateChunkTest.cs @@ -0,0 +1,18 @@ +namespace Razor +{ + using System.Threading.Tasks; + + public class __CompiledTemplate + { + #line hidden + public __CompiledTemplate() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + } + #pragma warning restore 1998 + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/DefaultGenerateChunkTest.cs b/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/DefaultGenerateChunkTest.cs new file mode 100644 index 0000000000..2c1560599f --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Test/TestFiles/CodeGenerator/Output/CSharpCodeGenerator/DefaultGenerateChunkTest.cs @@ -0,0 +1,26 @@ +namespace Razor +{ + using System.Threading.Tasks; + + public class __CompiledTemplate + { + #line hidden + public __CompiledTemplate() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + WriteLiteral("hello"); +#line 1 "" +// asdf + +#line default +#line hidden + + WriteLiteral("world"); + } + #pragma warning restore 1998 + } +}