Make single line single file scoped directives automatically import.
- Added an inner pass inside of the intermediate lowering phase to determine which directives get flowed to the final document. There were many ways to accomplish this but in order to keep the last wins mechanic for non-auto imported directives I had to let the directives get created and then removed based on if they were inherited. - Added error case if a user attempts to import a block directive with a `FileScopedSinglyOccurring` directive usage. - Added test cases that validate directives are properly inherited at the intermediate lowering phase. - Updated a few tests that had incorrect assumptions. - Left the default directive passes alone in regards to determining the "imported" directive to enable users to add their own model, inherits, etc. directives that take precedence. - Normalized the passes in the intermediate lowering phase to handle directives identically (we don't conditionally lower anymore). #1376
This commit is contained in:
parent
2d90ae47f9
commit
09ac126ecf
|
|
@ -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<DirectiveDescriptor>();
|
||||
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<string, SourceSpan?> 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
|
||||
// <input` checked="hello-world @false"`/>
|
||||
// Name=checked
|
||||
|
|
@ -783,6 +768,60 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
}
|
||||
}
|
||||
|
||||
private class ImportsVisitor : LoweringVisitor
|
||||
{
|
||||
public ImportsVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, Dictionary<string, SourceSpan?> 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<IntermediateNodeReference> Directives = new List<IntermediateNodeReference>();
|
||||
|
||||
public override void VisitDirective(DirectiveIntermediateNode node)
|
||||
{
|
||||
Directives.Add(new IntermediateNodeReference(Parent, node));
|
||||
|
||||
base.VisitDirective(node);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsMalformed(List<RazorDiagnostic> diagnostics)
|
||||
=> diagnostics.Count > 0 && diagnostics.Any(diagnostic => diagnostic.Severity == RazorDiagnosticSeverity.Error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -542,6 +542,34 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
internal static string FormatDirectiveMustExistBeforeMarkupOrCode(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DirectiveMustExistBeforeMarkupOrCode"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// Block directive '{0}' cannot be imported.
|
||||
/// </summary>
|
||||
internal static string BlockDirectiveCannotBeImported
|
||||
{
|
||||
get => GetString("BlockDirectiveCannotBeImported");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Block directive '{0}' cannot be imported.
|
||||
/// </summary>
|
||||
internal static string FormatBlockDirectiveCannotBeImported(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("BlockDirectiveCannotBeImported"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// Unreachable code. This can happen when a new {0} is introduced.
|
||||
/// </summary>
|
||||
internal static string UnexpectedDirectiveKind
|
||||
{
|
||||
get => GetString("UnexpectedDirectiveKind");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unreachable code. This can happen when a new {0} is introduced.
|
||||
/// </summary>
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -231,4 +231,10 @@
|
|||
<data name="DirectiveMustExistBeforeMarkupOrCode" xml:space="preserve">
|
||||
<value>The '{0}' directive must exist prior to markup or code.</value>
|
||||
</data>
|
||||
<data name="BlockDirectiveCannotBeImported" xml:space="preserve">
|
||||
<value>Block directive '{0}' cannot be imported.</value>
|
||||
</data>
|
||||
<data name="UnexpectedDirectiveKind" xml:space="preserve">
|
||||
<value>Unreachable code. This can happen when a new {0} is introduced.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -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("<p>Hi!</p>");
|
||||
|
|
@ -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("<p>Hi!</p>", n));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("<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 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("<p>NonDirective</p>");
|
||||
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("<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);
|
||||
}
|
||||
|
||||
[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("<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);
|
||||
}
|
||||
|
||||
[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("<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);
|
||||
}
|
||||
|
||||
[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("<p class=@(");
|
||||
codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source));
|
||||
var options = RazorCodeGenerationOptions.CreateDefault();
|
||||
|
||||
// Act
|
||||
phase.Execute(codeDocument);
|
||||
|
|
|
|||
Loading…
Reference in New Issue