diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs index 3e65c31776..48394b103c 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs @@ -73,6 +73,8 @@ namespace Microsoft.AspNetCore.Razor.Language builder.Insert(i++, @using); } + ImportDirectives(document); + // The document should contain all errors that currently exist in the system. This involves // adding the errors from the primary and imported syntax trees. @@ -96,6 +98,56 @@ namespace Microsoft.AspNetCore.Razor.Language codeDocument.SetDocumentIntermediateNode(document); } + private void ImportDirectives(DocumentIntermediateNode document) + { + var visitor = new DirectiveVisitor(); + visitor.VisitDocument(document); + + var seenDirectives = new HashSet(); + for (var i = visitor.Directives.Count - 1; i >= 0; i--) + { + var reference = visitor.Directives[i]; + var directive = (DirectiveIntermediateNode)reference.Node; + var descriptor = directive.Descriptor; + var seenDirective = !seenDirectives.Add(descriptor); + var imported = ReferenceEquals(directive.Annotations[CommonAnnotations.Imported], CommonAnnotations.Imported); + + if (!imported) + { + continue; + } + + switch (descriptor.Kind) + { + case DirectiveKind.SingleLine: + if (seenDirective && descriptor.Usage == DirectiveUsage.FileScopedSinglyOccurring) + { + // This directive has been overridden, it should be removed from the document. + + break; + } + + continue; + case DirectiveKind.RazorBlock: + case DirectiveKind.CodeBlock: + if (descriptor.Usage == DirectiveUsage.FileScopedSinglyOccurring) + { + // A block directive cannot be imported. + + document.Diagnostics.Add( + RazorDiagnosticFactory.CreateDirective_BlockDirectiveCannotBeImported(descriptor.Directive)); + } + break; + default: + throw new InvalidOperationException(Resources.FormatUnexpectedDirectiveKind(typeof(DirectiveKind).FullName)); + } + + // Overridden and invalid imported directives make it to here. They should be removed from the document. + + reference.Remove(); + } + } + private RazorCodeGenerationOptions CreateCodeGenerationOptions() { var builder = new DefaultRazorCodeGenerationOptionsBuilder(); @@ -122,6 +174,50 @@ namespace Microsoft.AspNetCore.Razor.Language public string FilePath { get; set; } + public override void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span) + { + _builder.Add(new DirectiveTokenIntermediateNode() + { + Content = span.Content, + Descriptor = chunkGenerator.Descriptor, + Source = BuildSourceSpanFromNode(span), + }); + } + + public override void VisitDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block) + { + IntermediateNode directiveNode; + if (IsMalformed(chunkGenerator.Diagnostics)) + { + directiveNode = new MalformedDirectiveIntermediateNode() + { + Name = chunkGenerator.Descriptor.Directive, + Descriptor = chunkGenerator.Descriptor, + Source = BuildSourceSpanFromNode(block), + }; + } + else + { + directiveNode = new DirectiveIntermediateNode() + { + Name = chunkGenerator.Descriptor.Directive, + Descriptor = chunkGenerator.Descriptor, + Source = BuildSourceSpanFromNode(block), + }; + } + + for (var i = 0; i < chunkGenerator.Diagnostics.Count; i++) + { + directiveNode.Diagnostics.Add(chunkGenerator.Diagnostics[i]); + } + + _builder.Push(directiveNode); + + VisitDefault(block); + + _builder.Pop(); + } + public override void VisitImportSpan(AddImportChunkGenerator chunkGenerator, Span span) { var namespaceImport = chunkGenerator.Namespace.Trim(); @@ -264,73 +360,6 @@ namespace Microsoft.AspNetCore.Razor.Language } } - private class ImportsVisitor : LoweringVisitor - { - // Imports only supports usings and single-line directives. We only want to include directive tokens - // when we're inside a single line directive. Also single line directives can't nest which makes - // this simple. - private bool _insideLineDirective; - - public ImportsVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, Dictionary namespaces) - : base(document, builder, namespaces) - { - } - - public override void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span) - { - if (_insideLineDirective) - { - _builder.Add(new DirectiveTokenIntermediateNode() - { - Content = span.Content, - Descriptor = chunkGenerator.Descriptor, - Source = BuildSourceSpanFromNode(span), - }); - } - } - - public override void VisitDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block) - { - if (chunkGenerator.Descriptor.Kind == DirectiveKind.SingleLine) - { - _insideLineDirective = true; - - IntermediateNode directiveNode; - if (IsMalformed(chunkGenerator.Diagnostics)) - { - directiveNode = new MalformedDirectiveIntermediateNode() - { - Name = chunkGenerator.Descriptor.Directive, - Descriptor = chunkGenerator.Descriptor, - Source = BuildSourceSpanFromNode(block), - }; - } - else - { - directiveNode = new DirectiveIntermediateNode() - { - Name = chunkGenerator.Descriptor.Directive, - Descriptor = chunkGenerator.Descriptor, - Source = BuildSourceSpanFromNode(block), - }; - } - - for (var i = 0; i < chunkGenerator.Diagnostics.Count; i++) - { - directiveNode.Diagnostics.Add(chunkGenerator.Diagnostics[i]); - } - - _builder.Push(directiveNode); - - base.VisitDirectiveBlock(chunkGenerator, block); - - _builder.Pop(); - - _insideLineDirective = false; - } - } - } - private class MainSourceVisitor : LoweringVisitor { private DeclareTagHelperFieldsIntermediateNode _tagHelperFields; @@ -342,50 +371,6 @@ namespace Microsoft.AspNetCore.Razor.Language _tagHelperPrefix = tagHelperPrefix; } - public override void VisitDirectiveToken(DirectiveTokenChunkGenerator chunkGenerator, Span span) - { - _builder.Add(new DirectiveTokenIntermediateNode() - { - Content = span.Content, - Descriptor = chunkGenerator.Descriptor, - Source = BuildSourceSpanFromNode(span), - }); - } - - public override void VisitDirectiveBlock(DirectiveChunkGenerator chunkGenerator, Block block) - { - IntermediateNode directiveNode; - if (IsMalformed(chunkGenerator.Diagnostics)) - { - directiveNode = new MalformedDirectiveIntermediateNode() - { - Name = chunkGenerator.Descriptor.Directive, - Descriptor = chunkGenerator.Descriptor, - Source = BuildSourceSpanFromNode(block), - }; - } - else - { - directiveNode = new DirectiveIntermediateNode() - { - Name = chunkGenerator.Descriptor.Directive, - Descriptor = chunkGenerator.Descriptor, - Source = BuildSourceSpanFromNode(block), - }; - } - - for (var i = 0; i < chunkGenerator.Diagnostics.Count; i++) - { - directiveNode.Diagnostics.Add(chunkGenerator.Diagnostics[i]); - } - - _builder.Push(directiveNode); - - VisitDefault(block); - - _builder.Pop(); - } - // Example // // Name=checked @@ -783,6 +768,60 @@ namespace Microsoft.AspNetCore.Razor.Language } } + private class ImportsVisitor : LoweringVisitor + { + public ImportsVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, Dictionary namespaces) + : base(document, new ImportBuilder(builder), namespaces) + { + } + + private class ImportBuilder : IntermediateNodeBuilder + { + private readonly IntermediateNodeBuilder _innerBuilder; + + public ImportBuilder(IntermediateNodeBuilder innerBuilder) + { + _innerBuilder = innerBuilder; + } + + public override IntermediateNode Current => _innerBuilder.Current; + + public override void Add(IntermediateNode node) + { + node.Annotations[CommonAnnotations.Imported] = CommonAnnotations.Imported; + _innerBuilder.Add(node); + } + + public override IntermediateNode Build() => _innerBuilder.Build(); + + public override void Insert(int index, IntermediateNode node) + { + node.Annotations[CommonAnnotations.Imported] = CommonAnnotations.Imported; + _innerBuilder.Insert(index, node); + } + + public override IntermediateNode Pop() => _innerBuilder.Pop(); + + public override void Push(IntermediateNode node) + { + node.Annotations[CommonAnnotations.Imported] = CommonAnnotations.Imported; + _innerBuilder.Push(node); + } + } + } + + private class DirectiveVisitor : IntermediateNodeWalker + { + public List Directives = new List(); + + public override void VisitDirective(DirectiveIntermediateNode node) + { + Directives.Add(new IntermediateNodeReference(Parent, node)); + + base.VisitDirective(node); + } + } + private static bool IsMalformed(List diagnostics) => diagnostics.Count > 0 && diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error); } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CommonAnnotations.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CommonAnnotations.cs index b721520bfe..cccd97fb85 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CommonAnnotations.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CommonAnnotations.cs @@ -5,6 +5,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { internal static class CommonAnnotations { + public static readonly object Imported = "Imported"; + public static readonly object PrimaryClass = "PrimaryClass"; public static readonly object PrimaryMethod = "PrimaryMethod"; diff --git a/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs index f03e6bd554..54aeab2417 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Properties/Resources.Designer.cs @@ -542,6 +542,34 @@ namespace Microsoft.AspNetCore.Razor.Language internal static string FormatDirectiveMustExistBeforeMarkupOrCode(object p0) => string.Format(CultureInfo.CurrentCulture, GetString("DirectiveMustExistBeforeMarkupOrCode"), p0); + /// + /// Block directive '{0}' cannot be imported. + /// + internal static string BlockDirectiveCannotBeImported + { + get => GetString("BlockDirectiveCannotBeImported"); + } + + /// + /// Block directive '{0}' cannot be imported. + /// + internal static string FormatBlockDirectiveCannotBeImported(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("BlockDirectiveCannotBeImported"), p0); + + /// + /// Unreachable code. This can happen when a new {0} is introduced. + /// + internal static string UnexpectedDirectiveKind + { + get => GetString("UnexpectedDirectiveKind"); + } + + /// + /// Unreachable code. This can happen when a new {0} is introduced. + /// + internal static string FormatUnexpectedDirectiveKind(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UnexpectedDirectiveKind"), p0); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs index b0e44ee42f..52d7bb2997 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs @@ -13,6 +13,16 @@ namespace Microsoft.AspNetCore.Razor.Language // General Errors ID Offset = 0 + public static readonly RazorDiagnosticDescriptor Directive_BlockDirectiveCannotBeImported = + new RazorDiagnosticDescriptor( + $"{DiagnosticPrefix}0000", + () => Resources.BlockDirectiveCannotBeImported, + RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateDirective_BlockDirectiveCannotBeImported(string directive) + { + return RazorDiagnostic.Create(Directive_BlockDirectiveCannotBeImported, SourceSpan.Undefined, directive); + } + #endregion #region Language Errors diff --git a/src/Microsoft.AspNetCore.Razor.Language/Resources.resx b/src/Microsoft.AspNetCore.Razor.Language/Resources.resx index 3298994894..b0b16e933f 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Resources.resx +++ b/src/Microsoft.AspNetCore.Razor.Language/Resources.resx @@ -231,4 +231,10 @@ The '{0}' directive must exist prior to markup or code. + + Block directive '{0}' cannot be imported. + + + Unreachable code. This can happen when a new {0} is introduced. + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIntermediateNodeLoweringPhaseIntegrationTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIntermediateNodeLoweringPhaseIntegrationTest.cs index 10beea43c6..6024fa1bdb 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIntermediateNodeLoweringPhaseIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIntermediateNodeLoweringPhaseIntegrationTest.cs @@ -380,7 +380,7 @@ namespace Microsoft.AspNetCore.Razor.Language } [Fact] - public void Lower_WithImports_Directive() + public void Lower_WithMultipleImports_SingleLineFileScopedSinglyOccurring() { // Arrange var source = TestRazorSourceDocument.Create("

Hi!

"); @@ -395,13 +395,19 @@ namespace Microsoft.AspNetCore.Razor.Language // Act var documentNode = Lower(codeDocument, b => { - b.AddDirective(DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine, d => d.AddMemberToken())); + b.AddDirective(DirectiveDescriptor.CreateDirective( + "test", + DirectiveKind.SingleLine, + builder => + { + builder.AddMemberToken(); + builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; + })); }); // Assert Children( documentNode, - n => Directive("test", n, c => DirectiveToken(DirectiveTokenKind.Member, "value1", c)), n => Directive("test", n, c => DirectiveToken(DirectiveTokenKind.Member, "value2", c)), n => Html("

Hi!

", n)); } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIntermediateNodeLoweringPhaseTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIntermediateNodeLoweringPhaseTest.cs index 63e9b87435..e613c42b03 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIntermediateNodeLoweringPhaseTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIntermediateNodeLoweringPhaseTest.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; +using Microsoft.AspNetCore.Razor.Language.Intermediate; using Microsoft.AspNetCore.Testing; using Xunit; @@ -9,6 +11,198 @@ namespace Microsoft.AspNetCore.Razor.Language { public class DefaultRazorIntermediateNodeLoweringPhaseTest { + [Fact] + public void Execute_AutomaticallyImportsSingleLineSinglyOccurringDirective() + { + // Arrange + var directive = DirectiveDescriptor.CreateSingleLineDirective( + "custom", + builder => + { + builder.AddStringToken(); + builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; + }); + var phase = new DefaultRazorIntermediateNodeLoweringPhase(); + var engine = RazorEngine.CreateEmpty(b => + { + b.Phases.Add(phase); + b.AddDirective(directive); + }); + var options = RazorParserOptions.Create(new[] { directive }, designTime: false); + var importSource = TestRazorSourceDocument.Create("@custom \"hello\"", fileName: "import.cshtml"); + var codeDocument = TestRazorCodeDocument.Create("

NonDirective

"); + 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("\"hello\"", stringToken.Content); + } + + [Fact] + public void Execute_AutomaticallyOverridesImportedSingleLineSinglyOccurringDirective_MainDocument() + { + // Arrange + var directive = DirectiveDescriptor.CreateSingleLineDirective( + "custom", + builder => + { + builder.AddStringToken(); + builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; + }); + var phase = new DefaultRazorIntermediateNodeLoweringPhase(); + var engine = RazorEngine.CreateEmpty(b => + { + b.Phases.Add(phase); + b.AddDirective(directive); + }); + var options = RazorParserOptions.Create(new[] { directive }, designTime: false); + var importSource = TestRazorSourceDocument.Create("@custom \"hello\"", fileName: "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); + } + + [Fact] + public void Execute_AutomaticallyOverridesImportedSingleLineSinglyOccurringDirective_MultipleImports() + { + // Arrange + var directive = DirectiveDescriptor.CreateSingleLineDirective( + "custom", + builder => + { + builder.AddStringToken(); + builder.Usage = DirectiveUsage.FileScopedSinglyOccurring; + }); + var phase = new DefaultRazorIntermediateNodeLoweringPhase(); + var engine = RazorEngine.CreateEmpty(b => + { + b.Phases.Add(phase); + b.AddDirective(directive); + }); + var options = RazorParserOptions.Create(new[] { directive }, designTime: false); + var importSource1 = TestRazorSourceDocument.Create("@custom \"hello\"", fileName: "import1.cshtml"); + var importSource2 = TestRazorSourceDocument.Create("@custom \"world\"", fileName: "import2.cshtml"); + var codeDocument = TestRazorCodeDocument.Create("

NonDirective

"); + codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source, options)); + codeDocument.SetImportSyntaxTrees(new[] { RazorSyntaxTree.Parse(importSource1, options), RazorSyntaxTree.Parse(importSource2, 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); + } + + [Fact] + 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 = RazorEngine.CreateEmpty(b => + { + b.Phases.Add(phase); + b.AddDirective(codeBlockDirective); + b.AddDirective(razorBlockDirective); + }); + var options = RazorParserOptions.Create(new[] { codeBlockDirective, razorBlockDirective }, designTime: false); + var importSource = TestRazorSourceDocument.Create( +@"@code ""code block"" { } +@razor ""razor block"" { }", + fileName: "testImports.cshtml"); + var codeDocument = TestRazorCodeDocument.Create("

NonDirective

"); + 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(); + Assert.Empty(directives); + } + + [Fact] + public void Execute_ErrorsForCodeBlockFileScopedSinglyOccurringDirectives() + { + // Arrange + var directive = DirectiveDescriptor.CreateCodeBlockDirective("custom", b => b.Usage = DirectiveUsage.FileScopedSinglyOccurring); + var phase = new DefaultRazorIntermediateNodeLoweringPhase(); + var engine = RazorEngine.CreateEmpty(b => + { + b.Phases.Add(phase); + b.AddDirective(directive); + }); + var options = RazorParserOptions.Create(new[] { directive }, designTime: false); + var importSource = TestRazorSourceDocument.Create("@custom { }", fileName: "import.cshtml"); + var codeDocument = TestRazorCodeDocument.Create("

NonDirective

"); + 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(); + Assert.Empty(directives); + var diagnostic = Assert.Single(documentNode.GetAllDiagnostics()); + Assert.Equal(expectedDiagnostic, diagnostic); + } + + [Fact] + public void Execute_ErrorsForRazorBlockFileScopedSinglyOccurringDirectives() + { + // Arrange + var directive = DirectiveDescriptor.CreateRazorBlockDirective("custom", b => b.Usage = DirectiveUsage.FileScopedSinglyOccurring); + var phase = new DefaultRazorIntermediateNodeLoweringPhase(); + var engine = RazorEngine.CreateEmpty(b => + { + b.Phases.Add(phase); + b.AddDirective(directive); + }); + var options = RazorParserOptions.Create(new[] { directive }, designTime: false); + var importSource = TestRazorSourceDocument.Create("@custom { }", fileName: "import.cshtml"); + var codeDocument = TestRazorCodeDocument.Create("

NonDirective

"); + 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(); + Assert.Empty(directives); + var diagnostic = Assert.Single(documentNode.GetAllDiagnostics()); + Assert.Equal(expectedDiagnostic, diagnostic); + } + [Fact] public void Execute_ThrowsForMissingDependency_SyntaxTree() { @@ -34,7 +228,6 @@ namespace Microsoft.AspNetCore.Razor.Language var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase)); var codeDocument = TestRazorCodeDocument.Create("