public void InstrumentationPass_SkipsTagHelper_WithoutLocation() { // Arrange var document = new DocumentIntermediateNode() { Options = RazorCodeGenerationOptions.CreateDefault(), }; var builder = IntermediateNodeBuilder.Create(document); builder.Push(new TagHelperIntermediateNode()); var pass = new InstrumentationPass() { Engine = RazorProjectEngine.CreateEmpty().Engine, }; // Act pass.Execute(TestRazorCodeDocument.CreateEmpty(), document); // Assert Children( document, n => Assert.IsType <TagHelperIntermediateNode>(n)); }
public void InstrumentationPass_SkipsCSharpExpression_WithoutLocation() { // Arrange var document = new DocumentIntermediateNode() { Options = RazorCodeGenerationOptions.CreateDefault(), }; var builder = IntermediateNodeBuilder.Create(document); builder.Push(new CSharpExpressionIntermediateNode()); builder.Add(new IntermediateToken() { Content = "Hi", Kind = TokenKind.CSharp, }); var pass = new InstrumentationPass() { Engine = RazorProjectEngine.CreateEmpty().Engine, }; // Act pass.Execute(TestRazorCodeDocument.CreateEmpty(), document); // Assert Children( document, n => CSharpExpression("Hi", n)); }
public void Execute_Match_AddsGlobalTargetExtensions() { // Arrange var documentNode = new DocumentIntermediateNode() { Options = RazorCodeGenerationOptions.CreateDefault(), }; var expected = new ICodeTargetExtension[] { new MyExtension1(), new MyExtension2(), }; var pass = new TestDocumentClassifierPass(); pass.Engine = RazorProjectEngine.CreateEmpty(b => { for (var i = 0; i < expected.Length; i++) { b.AddTargetExtension(expected[i]); } }).Engine; ICodeTargetExtension[] extensions = null; pass.CodeTargetCallback = (builder) => extensions = builder.TargetExtensions.ToArray(); // Act pass.Execute(TestRazorCodeDocument.CreateEmpty(), documentNode); // Assert Assert.Equal(expected, extensions); }
public void Execute_ErrorsForRazorBlockFileScopedSinglyOccurringDirectives() { // Arrange var directive = DirectiveDescriptor.CreateRazorBlockDirective("custom", b => b.Usage = DirectiveUsage.FileScopedSinglyOccurring); var phase = new DefaultRazorIntermediateNodeLoweringPhase(); var engine = RazorProjectEngine.CreateEmpty(b => { b.Phases.Add(phase); b.Features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false)); b.AddDirective(directive); }); var options = RazorParserOptions.Create(builder => builder.Directives.Add(directive)); var importSource = TestRazorSourceDocument.Create("@custom { }", filePath: "import.cshtml"); var codeDocument = TestRazorCodeDocument.Create("<p>NonDirective</p>"); codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source, options)); codeDocument.SetImportSyntaxTrees(new[] { RazorSyntaxTree.Parse(importSource, options) }); var expectedDiagnostic = RazorDiagnosticFactory.CreateDirective_BlockDirectiveCannotBeImported("custom"); // Act phase.Execute(codeDocument); // Assert var documentNode = codeDocument.GetDocumentIntermediateNode(); var directives = documentNode.Children.OfType <DirectiveIntermediateNode>(); Assert.Empty(directives); var diagnostic = Assert.Single(documentNode.GetAllDiagnostics()); Assert.Equal(expectedDiagnostic, diagnostic); }
public void InstrumentationPass_InstrumentsTagHelper() { // Arrange var document = new DocumentIntermediateNode() { Options = RazorCodeGenerationOptions.CreateDefault(), }; var builder = IntermediateNodeBuilder.Create(document); builder.Add(new TagHelperIntermediateNode() { Source = CreateSource(3), }); var pass = new InstrumentationPass() { Engine = RazorProjectEngine.CreateEmpty().Engine, }; // Act pass.Execute(TestRazorCodeDocument.CreateEmpty(), document); // Assert Children( document, n => BeginInstrumentation("3, 3, false", n), n => Assert.IsType <TagHelperIntermediateNode>(n), n => EndInstrumentation(n)); }
public void Execute_ParsesImports() { // Arrange var phase = new DefaultRazorParsingPhase(); var engine = RazorProjectEngine.CreateEmpty((builder) => { builder.Phases.Add(phase); builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorLanguageVersion.Latest, fileKind: null)); builder.Features.Add(new MyParserOptionsFeature()); }); var imports = new[] { TestRazorSourceDocument.Create(), TestRazorSourceDocument.Create(), }; var codeDocument = TestRazorCodeDocument.Create(TestRazorSourceDocument.Create(), imports); // Act phase.Execute(codeDocument); // Assert Assert.Collection( codeDocument.GetImportSyntaxTrees(), t => { Assert.Same(t.Source, imports[0]); Assert.Equal("test", Assert.Single(t.Options.Directives).Directive); }, t => { Assert.Same(t.Source, imports[1]); Assert.Equal("test", Assert.Single(t.Options.Directives).Directive); }); }
public void Execute_AutomaticallyOverridesImportedSingleLineSinglyOccurringDirective_MainDocument() { // Arrange var directive = DirectiveDescriptor.CreateSingleLineDirective( "custom", builder => { builder.AddStringToken(); builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; }); var phase = new DefaultRazorIntermediateNodeLoweringPhase(); var engine = RazorProjectEngine.CreateEmpty(b => { b.Phases.Add(phase); b.Features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false)); b.AddDirective(directive); }); var options = RazorParserOptions.Create(builder => builder.Directives.Add(directive)); var importSource = TestRazorSourceDocument.Create("@custom \"hello\"", filePath: "import.cshtml"); var codeDocument = TestRazorCodeDocument.Create("@custom \"world\""); codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source, options)); codeDocument.SetImportSyntaxTrees(new[] { RazorSyntaxTree.Parse(importSource, options) }); // Act phase.Execute(codeDocument); // Assert var documentNode = codeDocument.GetDocumentIntermediateNode(); var customDirectives = documentNode.FindDirectiveReferences(directive); var customDirective = (DirectiveIntermediateNode)Assert.Single(customDirectives).Node; var stringToken = Assert.Single(customDirective.Tokens); Assert.Equal("\"world\"", stringToken.Content); }
public void InstrumentationPass_NoOps_ForDesignTime() { // Arrange var document = new DocumentIntermediateNode() { Options = RazorCodeGenerationOptions.CreateDesignTimeDefault(), }; var builder = IntermediateNodeBuilder.Create(document); builder.Push(new HtmlContentIntermediateNode()); builder.Add(new IntermediateToken() { Content = "Hi", Kind = TokenKind.Html, }); builder.Pop(); var pass = new InstrumentationPass() { Engine = RazorProjectEngine.CreateEmpty().Engine, }; // Act pass.Execute(TestRazorCodeDocument.CreateEmpty(), document); // Assert Children( document, n => IntermediateNodeAssert.Html("Hi", n)); }
public void DescriptorProvider_FindsVCTH() { // Arrange var code = @" public class StringParameterViewComponent { public string Invoke(string foo, string bar) => null; } "; var compilation = MvcShim.BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(code)); var context = TagHelperDescriptorProviderContext.Create(); context.SetCompilation(compilation); var provider = new ViewComponentTagHelperDescriptorProvider() { Engine = RazorProjectEngine.CreateEmpty().Engine, }; var expectedDescriptor = TagHelperDescriptorBuilder.Create( ViewComponentTagHelperConventions.Kind, "__Generated__StringParameterViewComponentTagHelper", TestCompilation.AssemblyName) .TypeName("__Generated__StringParameterViewComponentTagHelper") .DisplayName("StringParameterViewComponentTagHelper") .TagMatchingRuleDescriptor(rule => rule .RequireTagName("vc:string-parameter") .RequireAttributeDescriptor(attribute => attribute.Name("foo")) .RequireAttributeDescriptor(attribute => attribute.Name("bar"))) .BoundAttributeDescriptor(attribute => attribute .Name("foo") .PropertyName("foo") .TypeName(typeof(string).FullName) .DisplayName("string StringParameterViewComponentTagHelper.foo")) .BoundAttributeDescriptor(attribute => attribute .Name("bar") .PropertyName("bar") .TypeName(typeof(string).FullName) .DisplayName("string StringParameterViewComponentTagHelper.bar")) .AddMetadata(ViewComponentTagHelperMetadata.Name, "StringParameter") .Build(); // Act provider.Execute(context); // Assert Assert.Single(context.Results, d => TagHelperDescriptorComparer.Default.Equals(d, expectedDescriptor)); }
public void InstrumentationPass_SkipsCSharpExpression_InsideTagHelperProperty() { // Arrange var document = new DocumentIntermediateNode() { Options = RazorCodeGenerationOptions.CreateDefault(), }; var builder = IntermediateNodeBuilder.Create(document); builder.Push(new TagHelperIntermediateNode()); builder.Push(new TagHelperPropertyIntermediateNode()); builder.Push(new CSharpExpressionIntermediateNode() { Source = CreateSource(5) }); builder.Add(new IntermediateToken() { Content = "Hi", Kind = TokenKind.CSharp, }); var pass = new InstrumentationPass() { Engine = RazorProjectEngine.CreateEmpty().Engine, }; // Act pass.Execute(TestRazorCodeDocument.CreateEmpty(), document); // Assert Children( document, n => { Assert.IsType <TagHelperIntermediateNode>(n); Children( n, c => { Assert.IsType <TagHelperPropertyIntermediateNode>(c); Children( c, s => CSharpExpression("Hi", s)); }); }); }
public void Execute_ThrowsForMissingDependency() { // Arrange var phase = new DefaultRazorOptimizationPhase(); var engine = RazorProjectEngine.CreateEmpty(b => b.Phases.Add(phase)); var codeDocument = TestRazorCodeDocument.CreateEmpty(); // Act & Assert ExceptionAssert.Throws <InvalidOperationException>( () => phase.Execute(codeDocument), $"The '{nameof(DefaultRazorOptimizationPhase)}' phase requires a '{nameof(DocumentIntermediateNode)}' " + $"provided by the '{nameof(RazorCodeDocument)}'."); }
public void Execute_ExecutesPhasesInOrder() { // Arrange var codeDocument = TestRazorCodeDocument.CreateEmpty(); // We're going to set up mocks to simulate a sequence of passes. We don't care about // what's in the nodes, we're just going to look at the identity via strict mocks. var originalNode = new DocumentIntermediateNode(); var firstPassNode = new DocumentIntermediateNode(); var secondPassNode = new DocumentIntermediateNode(); codeDocument.SetDocumentIntermediateNode(originalNode); var firstPass = new Mock <IRazorOptimizationPass>(MockBehavior.Strict); firstPass.SetupGet(m => m.Order).Returns(0); firstPass.SetupProperty(m => m.Engine); firstPass.Setup(m => m.Execute(codeDocument, originalNode)).Callback(() => { originalNode.Children.Add(firstPassNode); }); var secondPass = new Mock <IRazorOptimizationPass>(MockBehavior.Strict); secondPass.SetupGet(m => m.Order).Returns(1); secondPass.SetupProperty(m => m.Engine); secondPass.Setup(m => m.Execute(codeDocument, originalNode)).Callback(() => { // Works only when the first pass has run before this. originalNode.Children[0].Children.Add(secondPassNode); }); var phase = new DefaultRazorOptimizationPhase(); var engine = RazorProjectEngine.CreateEmpty(b => { b.Phases.Add(phase); b.Features.Add(firstPass.Object); b.Features.Add(secondPass.Object); }); // Act phase.Execute(codeDocument); // Assert Assert.Same(secondPassNode, codeDocument.GetDocumentIntermediateNode().Children[0].Children[0]); }
public void Execute_AddsSyntaxTree() { // Arrange var phase = new DefaultRazorParsingPhase(); var engine = RazorProjectEngine.CreateEmpty(builder => { builder.Phases.Add(phase); builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorLanguageVersion.Latest, fileKind: null)); }); var codeDocument = TestRazorCodeDocument.CreateEmpty(); // Act phase.Execute(codeDocument); // Assert Assert.NotNull(codeDocument.GetSyntaxTree()); }
public void Execute_ThrowsForMissingDependency_SyntaxTree() { // Arrange var phase = new DefaultRazorIntermediateNodeLoweringPhase(); var engine = RazorProjectEngine.CreateEmpty(b => { b.Phases.Add(phase); b.Features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false)); }); var codeDocument = TestRazorCodeDocument.CreateEmpty(); // Act & Assert ExceptionAssert.Throws <InvalidOperationException>( () => phase.Execute(codeDocument), $"The '{nameof(DefaultRazorIntermediateNodeLoweringPhase)}' phase requires a '{nameof(RazorSyntaxTree)}' " + $"provided by the '{nameof(RazorCodeDocument)}'."); }
public void Execute_ExecutesPhasesInOrder() { // Arrange var codeDocument = TestRazorCodeDocument.CreateEmpty(); // We're going to set up mocks to simulate a sequence of passes. We don't care about // what's in the trees, we're just going to look at the identity via strict mocks. var originalSyntaxTree = RazorSyntaxTree.Parse(codeDocument.Source); var firstPassSyntaxTree = RazorSyntaxTree.Parse(codeDocument.Source); var secondPassSyntaxTree = RazorSyntaxTree.Parse(codeDocument.Source); codeDocument.SetSyntaxTree(originalSyntaxTree); var firstPass = new Mock <IRazorSyntaxTreePass>(MockBehavior.Strict); firstPass.SetupGet(m => m.Order).Returns(0); firstPass.SetupProperty(m => m.Engine); firstPass.Setup(m => m.Execute(codeDocument, originalSyntaxTree)).Returns(firstPassSyntaxTree); var secondPass = new Mock <IRazorSyntaxTreePass>(MockBehavior.Strict); secondPass.SetupGet(m => m.Order).Returns(1); secondPass.SetupProperty(m => m.Engine); secondPass.Setup(m => m.Execute(codeDocument, firstPassSyntaxTree)).Returns(secondPassSyntaxTree); var phase = new DefaultRazorSyntaxTreePhase(); var engine = RazorProjectEngine.CreateEmpty(b => { b.Phases.Add(phase); b.Features.Add(firstPass.Object); b.Features.Add(secondPass.Object); }); // Act phase.Execute(codeDocument); // Assert Assert.Same(secondPassSyntaxTree, codeDocument.GetSyntaxTree()); }
public void OnInitialized_OrdersPassesInAscendingOrder() { // Arrange & Act var phase = new DefaultRazorOptimizationPhase(); var first = Mock.Of <IRazorOptimizationPass>(p => p.Order == 15); var second = Mock.Of <IRazorOptimizationPass>(p => p.Order == 17); var engine = RazorProjectEngine.CreateEmpty(b => { b.Phases.Add(phase); b.Features.Add(second); b.Features.Add(first); }); // Assert Assert.Collection( phase.Passes, p => Assert.Same(first, p), p => Assert.Same(second, p)); }
public void Execute_UsesConfigureParserFeatures() { // Arrange var phase = new DefaultRazorParsingPhase(); var engine = RazorProjectEngine.CreateEmpty((builder) => { builder.Phases.Add(phase); builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorLanguageVersion.Latest, fileKind: null)); builder.Features.Add(new MyParserOptionsFeature()); }); var codeDocument = TestRazorCodeDocument.CreateEmpty(); // Act phase.Execute(codeDocument); // Assert var syntaxTree = codeDocument.GetSyntaxTree(); var directive = Assert.Single(syntaxTree.Options.Directives); Assert.Equal("test", directive.Directive); }
public void Execute_CollatesSyntaxDiagnosticsFromImportDocuments() { // Arrange var phase = new DefaultRazorIntermediateNodeLoweringPhase(); var engine = RazorProjectEngine.CreateEmpty(b => { b.Phases.Add(phase); b.Features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false)); }); var codeDocument = TestRazorCodeDocument.CreateEmpty(); codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source)); codeDocument.SetImportSyntaxTrees(new[] { RazorSyntaxTree.Parse(TestRazorSourceDocument.Create("@ ")), RazorSyntaxTree.Parse(TestRazorSourceDocument.Create("<p @(")), }); var options = RazorCodeGenerationOptions.CreateDefault(); // Act phase.Execute(codeDocument); // Assert var documentNode = codeDocument.GetDocumentIntermediateNode(); Assert.Collection(documentNode.Diagnostics, diagnostic => { Assert.Equal(@"A space or line break was encountered after the ""@"" character. Only valid identifiers, keywords, comments, ""("" and ""{"" are valid at the start of a code block and they must occur immediately following ""@"" with no space in between.", diagnostic.GetMessage(CultureInfo.CurrentCulture)); }, diagnostic => { Assert.Equal(@"The explicit expression block is missing a closing "")"" character. Make sure you have a matching "")"" character for all the ""("" characters within this block, and that none of the "")"" characters are being interpreted as markup.", diagnostic.GetMessage(CultureInfo.CurrentCulture)); }); }
public void Execute_CollatesSyntaxDiagnosticsFromSourceDocument() { // Arrange var phase = new DefaultRazorIntermediateNodeLoweringPhase(); var engine = RazorProjectEngine.CreateEmpty(b => { b.Phases.Add(phase); b.Features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false)); }); var codeDocument = TestRazorCodeDocument.Create("<p class=@("); codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source)); // Act phase.Execute(codeDocument); // Assert var documentNode = codeDocument.GetDocumentIntermediateNode(); var diagnostic = Assert.Single(documentNode.Diagnostics); Assert.Equal(@"The explicit expression block is missing a closing "")"" character. Make sure you have a matching "")"" character for all the ""("" characters within this block, and that none of the "")"" characters are being interpreted as markup.", diagnostic.GetMessage(CultureInfo.CurrentCulture)); }
public void InstrumentationPass_InstrumentsHtml() { // Arrange var document = new DocumentIntermediateNode() { Options = RazorCodeGenerationOptions.CreateDefault(), }; var builder = IntermediateNodeBuilder.Create(document); builder.Push(new HtmlContentIntermediateNode() { Source = CreateSource(1), }); builder.Add(new IntermediateToken() { Content = "Hi", Kind = TokenKind.Html, Source = CreateSource(1) }); builder.Pop(); var pass = new InstrumentationPass() { Engine = RazorProjectEngine.CreateEmpty().Engine, }; // Act pass.Execute(TestRazorCodeDocument.CreateEmpty(), document); // Assert Children( document, n => BeginInstrumentation("1, 1, true", n), n => IntermediateNodeAssert.Html("Hi", n), n => EndInstrumentation(n)); }
public void Execute_ThrowsForMissingDependency_CodeTarget() { // Arrange var phase = new DefaultRazorCSharpLoweringPhase(); var engine = RazorProjectEngine.CreateEmpty(b => b.Phases.Add(phase)); var codeDocument = TestRazorCodeDocument.CreateEmpty(); codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source)); var irDocument = new DocumentIntermediateNode() { DocumentKind = "test", }; codeDocument.SetDocumentIntermediateNode(irDocument); // Act & Assert ExceptionAssert.Throws <InvalidOperationException>( () => phase.Execute(codeDocument), $"The document of kind 'test' does not have a '{nameof(CodeTarget)}'. " + $"The document classifier must set a value for '{nameof(DocumentIntermediateNode.Target)}'."); }
public void Execute_DoesNotImportNonFileScopedSinglyOccurringDirectives_Block() { // Arrange var codeBlockDirective = DirectiveDescriptor.CreateCodeBlockDirective("code", b => b.AddStringToken()); var razorBlockDirective = DirectiveDescriptor.CreateRazorBlockDirective("razor", b => b.AddStringToken()); var phase = new DefaultRazorIntermediateNodeLoweringPhase(); var engine = RazorProjectEngine.CreateEmpty(b => { b.Phases.Add(phase); b.Features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false)); b.AddDirective(codeBlockDirective); b.AddDirective(razorBlockDirective); }); var options = RazorParserOptions.Create(builder => { builder.Directives.Add(codeBlockDirective); builder.Directives.Add(razorBlockDirective); }); var importSource = TestRazorSourceDocument.Create( @"@code ""code block"" { } @razor ""razor block"" { }", filePath: "testImports.cshtml"); var codeDocument = TestRazorCodeDocument.Create("<p>NonDirective</p>"); codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source, options)); codeDocument.SetImportSyntaxTrees(new[] { RazorSyntaxTree.Parse(importSource, options) }); // Act phase.Execute(codeDocument); // Assert var documentNode = codeDocument.GetDocumentIntermediateNode(); var directives = documentNode.Children.OfType <DirectiveIntermediateNode>(); Assert.Empty(directives); }