From cc0d5dd324e8a2230e8c21d69fbe92af26faa651 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Tue, 25 Nov 2014 16:57:16 -0800 Subject: [PATCH] Add found TagHelperDescriptors on a Razor page to GeneratorResults. - Ultimately this enables tooling to inspect what TagHelperDescriptors were found on a document and construct HTML schema based off of them. - Added XML doc on the classes I touched that didn't have docs. - Added [NotNull] to the result construct parameters. - Added tests to validate that TagHelperDescriptors flow when found after parsing a Razor document. #215 --- .../GeneratorResults.cs | 69 +++++++++++--- .../Parser/ParserContext.cs | 7 +- .../Parser/RazorParser.cs | 6 +- src/Microsoft.AspNet.Razor/ParserResults.cs | 35 ++++++- .../Generator/CSharpTagHelperRenderingTest.cs | 93 +++++++++++++++++++ .../Generator/TagHelperTestBase.cs | 6 +- .../Parser/ParserVisitorExtensionsTest.cs | 14 ++- 7 files changed, 205 insertions(+), 25 deletions(-) diff --git a/src/Microsoft.AspNet.Razor/GeneratorResults.cs b/src/Microsoft.AspNet.Razor/GeneratorResults.cs index 2ea36e30bf..ec02a3f935 100644 --- a/src/Microsoft.AspNet.Razor/GeneratorResults.cs +++ b/src/Microsoft.AspNet.Razor/GeneratorResults.cs @@ -4,43 +4,84 @@ using System.Collections.Generic; using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Parser.SyntaxTree; +using Microsoft.AspNet.Razor.TagHelpers; namespace Microsoft.AspNet.Razor { + /// + /// The results of parsing and generating code for a Razor document. + /// public class GeneratorResults : ParserResults { - public GeneratorResults(ParserResults parserResults, CodeBuilderResult codeBuilderResult, CodeTree codeTree) + /// + /// Instantiates a new instance. + /// + /// The results of parsing a document. + /// The results of generating code for the document. + /// A for the document. + public GeneratorResults([NotNull] ParserResults parserResults, + [NotNull] CodeBuilderResult codeBuilderResult, + [NotNull] CodeTree codeTree) : this(parserResults.Document, + parserResults.TagHelperDescriptors, parserResults.ParserErrors, codeBuilderResult, codeTree) { } - public GeneratorResults(Block document, - IList parserErrors, - CodeBuilderResult codeBuilderResult, - CodeTree codeTree) - : this(parserErrors.Count == 0, document, parserErrors, codeBuilderResult, codeTree) + /// + /// Instantiates a new instance. + /// + /// The for the syntax tree. + /// s for the document. + /// s encountered when parsing the document. + /// The results of generating code for the document. + /// A for the document. + public GeneratorResults([NotNull] Block document, + [NotNull] IEnumerable tagHelperDescriptors, + [NotNull] IList parserErrors, + [NotNull] CodeBuilderResult codeBuilderResult, + [NotNull] CodeTree codeTree) + : this(parserErrors.Count == 0, document, tagHelperDescriptors, parserErrors, codeBuilderResult, codeTree) { } + /// + /// Instantiates a new instance. + /// + /// true if parsing was successful, false otherwise. + /// The for the syntax tree. + /// s for the document. + /// s encountered when parsing the document. + /// The results of generating code for the document. + /// A for the document. protected GeneratorResults(bool success, - Block document, - IList parserErrors, - CodeBuilderResult codeBuilderResult, - CodeTree codeTree) - : base(success, document, parserErrors) + [NotNull] Block document, + [NotNull] IEnumerable tagHelperDescriptors, + [NotNull] IList parserErrors, + [NotNull] CodeBuilderResult codeBuilderResult, + [NotNull] CodeTree codeTree) + : base(success, document, tagHelperDescriptors, parserErrors) { GeneratedCode = codeBuilderResult.Code; DesignTimeLineMappings = codeBuilderResult.DesignTimeLineMappings; CodeTree = codeTree; } - public string GeneratedCode { get; private set; } + /// + /// The generated code for the document. + /// + public string GeneratedCode { get; } - public IList DesignTimeLineMappings { get; private set; } + /// + /// s used to project code from a file during design time. + /// + public IList DesignTimeLineMappings { get; } - public CodeTree CodeTree { get; private set; } + /// + /// A for the document. + /// + public CodeTree CodeTree { get; } } } diff --git a/src/Microsoft.AspNet.Razor/Parser/ParserContext.cs b/src/Microsoft.AspNet.Razor/Parser/ParserContext.cs index cd5d0ce808..fa0a9bbf4b 100644 --- a/src/Microsoft.AspNet.Razor/Parser/ParserContext.cs +++ b/src/Microsoft.AspNet.Razor/Parser/ParserContext.cs @@ -9,6 +9,7 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Razor.Parser.SyntaxTree; +using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.AspNet.Razor.Text; using Microsoft.AspNet.Razor.Utils; @@ -225,7 +226,11 @@ namespace Microsoft.AspNet.Razor.Parser throw new InvalidOperationException(RazorResources.ParserContext_CannotCompleteTree_OutstandingBlocks); } - return new ParserResults(_blockStack.Pop().Build(), _errorSink.Errors.ToList()); + return new ParserResults(_blockStack.Pop().Build(), + // TagHelperDescriptors are not found by default. The RazorParser is responsible + // for identifying TagHelperDescriptors and rebuilding ParserResults. + tagHelperDescriptors: Enumerable.Empty(), + parserErrors: _errorSink.Errors.ToList()); } [Conditional("DEBUG")] diff --git a/src/Microsoft.AspNet.Razor/Parser/RazorParser.cs b/src/Microsoft.AspNet.Razor/Parser/RazorParser.cs index 4d2a21d1ec..01a04ed8e3 100644 --- a/src/Microsoft.AspNet.Razor/Parser/RazorParser.cs +++ b/src/Microsoft.AspNet.Razor/Parser/RazorParser.cs @@ -170,9 +170,11 @@ namespace Microsoft.AspNet.Razor.Parser rewriter.Rewrite(rewritingContext); } + var descriptors = Enumerable.Empty(); + if (TagHelperDescriptorResolver != null) { - var descriptors = GetTagHelperDescriptors(rewritingContext.SyntaxTree, rewritingContext.ErrorSink); + descriptors = GetTagHelperDescriptors(rewritingContext.SyntaxTree, rewritingContext.ErrorSink); var tagHelperProvider = new TagHelperDescriptorProvider(descriptors); var tagHelperParseTreeRewriter = new TagHelperParseTreeRewriter(tagHelperProvider); @@ -195,7 +197,7 @@ namespace Microsoft.AspNet.Razor.Parser } // Return the new result - return new ParserResults(syntaxTree, errorSink.Errors.ToList()); + return new ParserResults(syntaxTree, descriptors, errorSink.Errors.ToList()); } /// diff --git a/src/Microsoft.AspNet.Razor/ParserResults.cs b/src/Microsoft.AspNet.Razor/ParserResults.cs index 19a8f0230e..97df423630 100644 --- a/src/Microsoft.AspNet.Razor/ParserResults.cs +++ b/src/Microsoft.AspNet.Razor/ParserResults.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Microsoft.AspNet.Razor.Parser.SyntaxTree; +using Microsoft.AspNet.Razor.TagHelpers; namespace Microsoft.AspNet.Razor { @@ -11,15 +12,38 @@ namespace Microsoft.AspNet.Razor /// public class ParserResults { - public ParserResults(Block document, IList parserErrors) - : this(parserErrors == null || parserErrors.Count == 0, document, parserErrors) + /// + /// Instantiates a new instance. + /// + /// The Razor syntax tree. + /// s that apply to the current Razor + /// document. + /// s encountered when parsing the current Razor + /// document. + public ParserResults([NotNull] Block document, + [NotNull] IEnumerable tagHelperDescriptors, + [NotNull] IList parserErrors) + : this(parserErrors == null || parserErrors.Count == 0, document, tagHelperDescriptors, parserErrors) { } - protected ParserResults(bool success, Block document, IList errors) + /// + /// Instantiates a new instance. + /// + /// true if parsing was successful, false otherwise. + /// The Razor syntax tree. + /// s that apply to the current Razor + /// document. + /// s encountered when parsing the current Razor + /// document. + protected ParserResults(bool success, + [NotNull] Block document, + [NotNull] IEnumerable tagHelperDescriptors, + [NotNull] IList errors) { Success = success; Document = document; + TagHelperDescriptors = tagHelperDescriptors; ParserErrors = errors ?? new List(); } @@ -37,5 +61,10 @@ namespace Microsoft.AspNet.Razor /// The list of errors which occurred during parsing. /// public IList ParserErrors { get; private set; } + + /// + /// The s found for the current Razor document. + /// + public IEnumerable TagHelperDescriptors { get; private set; } } } diff --git a/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs b/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs index 85dee97c23..ef5c9e36de 100644 --- a/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/Generator/CSharpTagHelperRenderingTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Linq; #if ASPNETCORE50 using System.Reflection; #endif @@ -63,6 +64,98 @@ namespace Microsoft.AspNet.Razor.Test.Generator } } + public static TheoryData TagHelperDescriptorFlowTestData + { + get + { + return new TheoryData, // TagHelperDescriptors provided + IEnumerable, // Expected TagHelperDescriptors + bool> // Design time mode. + { + { + "SingleTagHelper", + "SingleTagHelper", + PAndInputTagHelperDescriptors, + PAndInputTagHelperDescriptors, + false + }, + { + "SingleTagHelper", + "SingleTagHelper.DesignTime", + PAndInputTagHelperDescriptors, + PAndInputTagHelperDescriptors, + true + }, + { + "BasicTagHelpers", + "BasicTagHelpers", + PAndInputTagHelperDescriptors, + PAndInputTagHelperDescriptors, + false + }, + { + "BasicTagHelpers", + "BasicTagHelpers.DesignTime", + PAndInputTagHelperDescriptors, + PAndInputTagHelperDescriptors, + true + }, + { + "BasicTagHelpers.RemoveTagHelper", + "BasicTagHelpers.RemoveTagHelper", + PAndInputTagHelperDescriptors, + Enumerable.Empty(), + false + }, + { + "ContentBehaviorTagHelpers", + "ContentBehaviorTagHelpers", + ContentBehaviorTagHelperDescriptors, + ContentBehaviorTagHelperDescriptors, + false + }, + { + "ComplexTagHelpers", + "ComplexTagHelpers", + PAndInputTagHelperDescriptors, + PAndInputTagHelperDescriptors, + false + }, + { + "ComplexTagHelpers", + "ComplexTagHelpers.DesignTime", + PAndInputTagHelperDescriptors, + PAndInputTagHelperDescriptors, + true + } + }; + } + } + + [Theory] + [MemberData(nameof(TagHelperDescriptorFlowTestData))] + public void TagHelpers_RenderingOutputFlowsFoundTagHelperDescriptors( + string testName, + string baselineName, + IEnumerable tagHelperDescriptors, + IEnumerable expectedTagHelperDescriptors, + bool designTimeMode) + { + RunTagHelperTest( + testName, + baseLineName: baselineName, + tagHelperDescriptors: tagHelperDescriptors, + onResults: (results) => + { + Assert.Equal(expectedTagHelperDescriptors, + results.TagHelperDescriptors, + TagHelperDescriptorComparer.Default); + }, + designTimeMode: designTimeMode); + } + public static TheoryData DesignTimeTagHelperTestData { get diff --git a/test/Microsoft.AspNet.Razor.Test/Generator/TagHelperTestBase.cs b/test/Microsoft.AspNet.Razor.Test/Generator/TagHelperTestBase.cs index 37cc1214f3..31ac2e2dcd 100644 --- a/test/Microsoft.AspNet.Razor.Test/Generator/TagHelperTestBase.cs +++ b/test/Microsoft.AspNet.Razor.Test/Generator/TagHelperTestBase.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Parser; using Microsoft.AspNet.Razor.TagHelpers; +using Xunit; namespace Microsoft.AspNet.Razor.Test.Generator { @@ -17,7 +19,8 @@ namespace Microsoft.AspNet.Razor.Test.Generator bool designTimeMode = false, IEnumerable tagHelperDescriptors = null, Func hostConfig = null, - IList expectedDesignTimePragmas = null) + IList expectedDesignTimePragmas = null, + Action onResults = null) { RunTest(name: testName, baselineName: baseLineName, @@ -27,6 +30,7 @@ namespace Microsoft.AspNet.Razor.Test.Generator { return new TagHelperTemplateEngine(engine, tagHelperDescriptors); }, + onResults: onResults, hostConfig: hostConfig, expectedDesignTimePragmas: expectedDesignTimePragmas); } diff --git a/test/Microsoft.AspNet.Razor.Test/Parser/ParserVisitorExtensionsTest.cs b/test/Microsoft.AspNet.Razor.Test/Parser/ParserVisitorExtensionsTest.cs index 31535af4a9..264c8be456 100644 --- a/test/Microsoft.AspNet.Razor.Test/Parser/ParserVisitorExtensionsTest.cs +++ b/test/Microsoft.AspNet.Razor.Test/Parser/ParserVisitorExtensionsTest.cs @@ -4,8 +4,10 @@ #if !ASPNETCORE50 using System; using System.Collections.Generic; +using System.Linq; using Microsoft.AspNet.Razor.Parser; using Microsoft.AspNet.Razor.Parser.SyntaxTree; +using Microsoft.AspNet.Razor.TagHelpers; using Moq; using Xunit; @@ -17,7 +19,9 @@ namespace Microsoft.AspNet.Razor.Test.Parser public void VisitThrowsOnNullVisitor() { ParserVisitor target = null; - var results = new ParserResults(new BlockBuilder() { Type = BlockType.Comment }.Build(), new List()); + var results = new ParserResults(new BlockBuilder() { Type = BlockType.Comment }.Build(), + Enumerable.Empty(), + parserErrors: new List()); Assert.Throws("self", () => target.Visit(results)); } @@ -35,7 +39,9 @@ namespace Microsoft.AspNet.Razor.Test.Parser // Arrange Mock targetMock = new Mock(); var root = new BlockBuilder() { Type = BlockType.Comment }.Build(); - var results = new ParserResults(root, new List()); + var results = new ParserResults(root, + Enumerable.Empty(), + parserErrors: new List()); // Act targetMock.Object.Visit(results); @@ -54,7 +60,7 @@ namespace Microsoft.AspNet.Razor.Test.Parser new RazorError("Foo", 1, 0, 1), new RazorError("Bar", 2, 0, 2) }; - var results = new ParserResults(root, errors); + var results = new ParserResults(root, Enumerable.Empty(), errors); // Act targetMock.Object.Visit(results); @@ -74,7 +80,7 @@ namespace Microsoft.AspNet.Razor.Test.Parser new RazorError("Foo", 1, 0, 1), new RazorError("Bar", 2, 0, 2) }; - var results = new ParserResults(root, errors); + var results = new ParserResults(root, Enumerable.Empty(), errors); // Act targetMock.Object.Visit(results);