Shorten Microsoft.AspNetCore.Razor.Language.Test paths (dotnet/aspnetcore-tooling#425)

* Shorten Microsoft.AspNetCore.Razor.Language.Test paths
- aspnet/AspNetCoredotnet/aspnetcore-tooling#9358 and aspnet/AspNetCoredotnet/aspnetcore-tooling#7882
- do 4046 renames
- adjust solutions to renamed directory
- special-case directory of the Microsoft.AspNetCore.Razor.Language.Test project in `TestProject`\n\nCommit migrated from 72b939d855
This commit is contained in:
Doug Bunting 2019-04-15 10:46:17 -07:00 committed by GitHub
parent 90eb9e8535
commit ae4af3d154
4047 changed files with 134377 additions and 1 deletions

View File

@ -0,0 +1,139 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Components.RenderTree;
using Xunit;
namespace Microsoft.AspNetCore.Components
{
internal static class AssertFrame
{
public static void Sequence(RenderTreeFrame frame, int? sequence = null)
{
if (sequence.HasValue)
{
Assert.Equal(sequence.Value, frame.Sequence);
}
}
public static void Text(RenderTreeFrame frame, string textContent, int? sequence = null)
{
Assert.Equal(RenderTreeFrameType.Text, frame.FrameType);
Assert.Equal(textContent, frame.TextContent);
Assert.Equal(0, frame.ElementSubtreeLength);
AssertFrame.Sequence(frame, sequence);
}
internal static void Markup(RenderTreeFrame frame, string markupContent, int? sequence = null)
{
Assert.Equal(RenderTreeFrameType.Markup, frame.FrameType);
Assert.Equal(markupContent, frame.MarkupContent);
Assert.Equal(0, frame.ElementSubtreeLength);
AssertFrame.Sequence(frame, sequence);
}
public static void Element(RenderTreeFrame frame, string elementName, int subtreeLength, int? sequence = null)
{
Assert.Equal(RenderTreeFrameType.Element, frame.FrameType);
Assert.Equal(elementName, frame.ElementName);
Assert.Equal(subtreeLength, frame.ElementSubtreeLength);
AssertFrame.Sequence(frame, sequence);
}
public static void Attribute(RenderTreeFrame frame, string attributeName, int? sequence = null)
{
Assert.Equal(RenderTreeFrameType.Attribute, frame.FrameType);
Assert.Equal(attributeName, frame.AttributeName);
AssertFrame.Sequence(frame, sequence);
}
public static void Attribute(RenderTreeFrame frame, string attributeName, string attributeValue, int? sequence = null)
{
AssertFrame.Attribute(frame, attributeName, sequence);
Assert.Equal(attributeValue, frame.AttributeValue);
}
public static void Attribute(RenderTreeFrame frame, string attributeName, Action<UIEventArgs> attributeEventHandlerValue, int? sequence = null)
{
AssertFrame.Attribute(frame, attributeName, sequence);
Assert.Equal(attributeEventHandlerValue, frame.AttributeValue);
}
public static void Attribute(RenderTreeFrame frame, string attributeName, object attributeValue, int? sequence = null)
{
AssertFrame.Attribute(frame, attributeName, sequence);
Assert.Equal(attributeValue, frame.AttributeValue);
}
public static void Attribute(RenderTreeFrame frame, string attributeName, Type valueType, int? sequence = null)
{
AssertFrame.Attribute(frame, attributeName, sequence);
Assert.IsType(valueType, frame.AttributeValue);
}
public static void Attribute(RenderTreeFrame frame, string attributeName, Action<object> attributeValidator, int? sequence = null)
{
AssertFrame.Attribute(frame, attributeName, sequence);
attributeValidator(frame.AttributeValue);
}
public static void Component<T>(RenderTreeFrame frame, int? subtreeLength = null, int? sequence = null) where T : IComponent
{
Component(frame, typeof(T).FullName, subtreeLength, sequence);
}
public static void Component(RenderTreeFrame frame, string typeName, int? subtreeLength = null, int? sequence = null)
{
Assert.Equal(RenderTreeFrameType.Component, frame.FrameType);
Assert.Equal(typeName, frame.ComponentType.FullName);
if (subtreeLength.HasValue)
{
Assert.Equal(subtreeLength.Value, frame.ComponentSubtreeLength);
}
AssertFrame.Sequence(frame, sequence);
}
public static void ComponentWithInstance<T>(RenderTreeFrame frame, int componentId, int? subtreeLength = null, int? sequence = null) where T : IComponent
{
AssertFrame.Component<T>(frame, subtreeLength, sequence);
Assert.IsType<T>(frame.Component);
Assert.Equal(componentId, frame.ComponentId);
}
public static void Region(RenderTreeFrame frame, int subtreeLength, int? sequence = null)
{
Assert.Equal(RenderTreeFrameType.Region, frame.FrameType);
Assert.Equal(subtreeLength, frame.RegionSubtreeLength);
AssertFrame.Sequence(frame, sequence);
}
public static void Whitespace(RenderTreeFrame frame, int? sequence = null)
{
Assert.Equal(RenderTreeFrameType.Text, frame.FrameType);
AssertFrame.Sequence(frame, sequence);
Assert.True(string.IsNullOrWhiteSpace(frame.TextContent));
}
public static void ElementReferenceCapture(RenderTreeFrame frame, Action<ElementRef> action, int? sequence = null)
{
Assert.Equal(RenderTreeFrameType.ElementReferenceCapture, frame.FrameType);
Assert.Same(action, frame.ElementReferenceCaptureAction);
AssertFrame.Sequence(frame, sequence);
}
public static void ComponentReferenceCapture(RenderTreeFrame frame, int? sequence = null)
{
Assert.Equal(RenderTreeFrameType.ComponentReferenceCapture, frame.FrameType);
Assert.NotNull(frame.ComponentReferenceCaptureAction);
AssertFrame.Sequence(frame, sequence);
}
public static void ComponentReferenceCapture(RenderTreeFrame frame, Action<object> action, int? sequence = null)
{
Assert.Equal(RenderTreeFrameType.ComponentReferenceCapture, frame.FrameType);
Assert.Same(action, frame.ComponentReferenceCaptureAction);
AssertFrame.Sequence(frame, sequence);
}
}
}

View File

@ -0,0 +1,279 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class BoundAttributeDescriptorExtensionsTest
{
[Fact]
public void GetPropertyName_ReturnsPropertyName()
{
// Arrange
var expectedPropertyName = "IntProperty";
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("test")
.PropertyName(expectedPropertyName)
.TypeName(typeof(int).FullName);
var descriptor = builder.Build();
// Act
var propertyName = descriptor.GetPropertyName();
// Assert
Assert.Equal(expectedPropertyName, propertyName);
}
[Fact]
public void GetPropertyName_ReturnsNullIfNoPropertyName()
{
// Arrange
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("test")
.TypeName(typeof(int).FullName);
var descriptor = builder.Build();
// Act
var propertyName = descriptor.GetPropertyName();
// Assert
Assert.Null(propertyName);
}
[Fact]
public void IsDefaultKind_ReturnsTrue_IfKindIsDefault()
{
// Arrange
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("test")
.PropertyName("IntProperty")
.TypeName(typeof(int).FullName);
var descriptor = builder.Build();
// Act
var isDefault = descriptor.IsDefaultKind();
// Assert
Assert.True(isDefault);
}
[Fact]
public void IsDefaultKind_ReturnsFalse_IfKindIsNotDefault()
{
// Arrange
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder("other-kind", "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, "other-kind");
builder
.Name("test")
.PropertyName("IntProperty")
.TypeName(typeof(int).FullName);
var descriptor = builder.Build();
// Act
var isDefault = descriptor.IsDefaultKind();
// Assert
Assert.False(isDefault);
}
[Fact]
public void ExpectsStringValue_ReturnsTrue_ForStringProperty()
{
// Arrange
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("test")
.PropertyName("BoundProp")
.TypeName(typeof(string).FullName);
var descriptor = builder.Build();
// Act
var result = descriptor.ExpectsStringValue("test");
// Assert
Assert.True(result);
}
[Fact]
public void ExpectsStringValue_ReturnsFalse_ForNonStringProperty()
{
// Arrange
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("test")
.PropertyName("BoundProp")
.TypeName(typeof(bool).FullName);
var descriptor = builder.Build();
// Act
var result = descriptor.ExpectsStringValue("test");
// Assert
Assert.False(result);
}
[Fact]
public void ExpectsStringValue_ReturnsTrue_StringIndexerAndNameMatch()
{
// Arrange
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("test")
.PropertyName("BoundProp")
.TypeName("System.Collection.Generic.IDictionary<string, string>")
.AsDictionary("prefix-test-", typeof(string).FullName);
var descriptor = builder.Build();
// Act
var result = descriptor.ExpectsStringValue("prefix-test-key");
// Assert
Assert.True(result);
}
[Fact]
public void ExpectsStringValue_ReturnsFalse_StringIndexerAndNameMismatch()
{
// Arrange
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("test")
.PropertyName("BoundProp")
.TypeName("System.Collection.Generic.IDictionary<string, string>")
.AsDictionary("prefix-test-", typeof(string).FullName);
var descriptor = builder.Build();
// Act
var result = descriptor.ExpectsStringValue("test");
// Assert
Assert.False(result);
}
[Fact]
public void ExpectsBooleanValue_ReturnsTrue_ForBooleanProperty()
{
// Arrange
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("test")
.PropertyName("BoundProp")
.TypeName(typeof(bool).FullName);
var descriptor = builder.Build();
// Act
var result = descriptor.ExpectsBooleanValue("test");
// Assert
Assert.True(result);
}
[Fact]
public void ExpectsBooleanValue_ReturnsFalse_ForNonBooleanProperty()
{
// Arrange
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("test")
.PropertyName("BoundProp")
.TypeName(typeof(int).FullName);
var descriptor = builder.Build();
// Act
var result = descriptor.ExpectsBooleanValue("test");
// Assert
Assert.False(result);
}
[Fact]
public void ExpectsBooleanValue_ReturnsTrue_BooleanIndexerAndNameMatch()
{
// Arrange
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("test")
.PropertyName("BoundProp")
.TypeName("System.Collection.Generic.IDictionary<string, bool>")
.AsDictionary("prefix-test-", typeof(bool).FullName);
var descriptor = builder.Build();
// Act
var result = descriptor.ExpectsBooleanValue("prefix-test-key");
// Assert
Assert.True(result);
}
[Fact]
public void ExpectsBooleanValue_ReturnsFalse_BooleanIndexerAndNameMismatch()
{
// Arrange
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("test")
.PropertyName("BoundProp")
.TypeName("System.Collection.Generic.IDictionary<string, bool>")
.AsDictionary("prefix-test-", typeof(bool).FullName);
var descriptor = builder.Build();
// Act
var result = descriptor.ExpectsBooleanValue("test");
// Assert
Assert.False(result);
}
}
}

View File

@ -0,0 +1,339 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{
public class CSharpCodeWriterTest
{
// The length of the newline string written by writer.WriteLine.
private static readonly int WriterNewLineLength = Environment.NewLine.Length;
public static IEnumerable<object[]> NewLines
{
get
{
return new object[][]
{
new object[] { "\r" },
new object[] { "\n" },
new object[] { "\r\n" },
};
}
}
[Fact]
public void CSharpCodeWriter_TracksPosition_WithWrite()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.Write("1234");
// Assert
var location = writer.Location;
var expected = new SourceLocation(absoluteIndex: 4, lineIndex: 0, characterIndex: 4);
Assert.Equal(expected, location);
}
[Fact]
public void CSharpCodeWriter_TracksPosition_WithIndent()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.WriteLine();
writer.Indent(size: 3);
// Assert
var location = writer.Location;
var expected = new SourceLocation(absoluteIndex: 3 + WriterNewLineLength, lineIndex: 1, characterIndex: 3);
Assert.Equal(expected, location);
}
[Fact]
public void CSharpCodeWriter_TracksPosition_WithWriteLine()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.WriteLine("1234");
// Assert
var location = writer.Location;
var expected = new SourceLocation(absoluteIndex: 4 + WriterNewLineLength, lineIndex: 1, characterIndex: 0);
Assert.Equal(expected, location);
}
[Theory]
[MemberData(nameof(NewLines))]
public void CSharpCodeWriter_TracksPosition_WithWriteLine_WithNewLineInContent(string newLine)
{
// Arrange
var writer = new CodeWriter();
// Act
writer.WriteLine("1234" + newLine + "12");
// Assert
var location = writer.Location;
var expected = new SourceLocation(
absoluteIndex: 6 + newLine.Length + WriterNewLineLength,
lineIndex: 2,
characterIndex: 0);
Assert.Equal(expected, location);
}
[Theory]
[MemberData(nameof(NewLines))]
public void CSharpCodeWriter_TracksPosition_WithWrite_WithNewlineInContent(string newLine)
{
// Arrange
var writer = new CodeWriter();
// Act
writer.Write("1234" + newLine + "123" + newLine + "12");
// Assert
var location = writer.Location;
var expected = new SourceLocation(
absoluteIndex: 9 + newLine.Length + newLine.Length,
lineIndex: 2,
characterIndex: 2);
Assert.Equal(expected, location);
}
[Fact]
public void CSharpCodeWriter_TracksPosition_WithWrite_WithNewlineInContent_RepeatedN()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.Write("1234\n\n123");
// Assert
var location = writer.Location;
var expected = new SourceLocation(
absoluteIndex: 9,
lineIndex: 2,
characterIndex: 3);
Assert.Equal(expected, location);
}
[Fact]
public void CSharpCodeWriter_TracksPosition_WithWrite_WithMixedNewlineInContent()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.Write("1234\r123\r\n12\n1");
// Assert
var location = writer.Location;
var expected = new SourceLocation(
absoluteIndex: 14,
lineIndex: 3,
characterIndex: 1);
Assert.Equal(expected, location);
}
[Fact]
public void CSharpCodeWriter_TracksPosition_WithNewline_SplitAcrossWrites()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.Write("1234\r");
var location1 = writer.Location;
writer.Write("\n");
var location2 = writer.Location;
// Assert
var expected1 = new SourceLocation(absoluteIndex: 5, lineIndex: 1, characterIndex: 0);
Assert.Equal(expected1, location1);
var expected2 = new SourceLocation(absoluteIndex: 6, lineIndex: 1, characterIndex: 0);
Assert.Equal(expected2, location2);
}
[Fact]
public void CSharpCodeWriter_TracksPosition_WithTwoNewline_SplitAcrossWrites_R()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.Write("1234\r");
var location1 = writer.Location;
writer.Write("\r");
var location2 = writer.Location;
// Assert
var expected1 = new SourceLocation(absoluteIndex: 5, lineIndex: 1, characterIndex: 0);
Assert.Equal(expected1, location1);
var expected2 = new SourceLocation(absoluteIndex: 6, lineIndex: 2, characterIndex: 0);
Assert.Equal(expected2, location2);
}
[Fact]
public void CSharpCodeWriter_TracksPosition_WithTwoNewline_SplitAcrossWrites_N()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.Write("1234\n");
var location1 = writer.Location;
writer.Write("\n");
var location2 = writer.Location;
// Assert
var expected1 = new SourceLocation(absoluteIndex: 5, lineIndex: 1, characterIndex: 0);
Assert.Equal(expected1, location1);
var expected2 = new SourceLocation(absoluteIndex: 6, lineIndex: 2, characterIndex: 0);
Assert.Equal(expected2, location2);
}
[Fact]
public void CSharpCodeWriter_TracksPosition_WithTwoNewline_SplitAcrossWrites_Reversed()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.Write("1234\n");
var location1 = writer.Location;
writer.Write("\r");
var location2 = writer.Location;
// Assert
var expected1 = new SourceLocation(absoluteIndex: 5, lineIndex: 1, characterIndex: 0);
Assert.Equal(expected1, location1);
var expected2 = new SourceLocation(absoluteIndex: 6, lineIndex: 2, characterIndex: 0);
Assert.Equal(expected2, location2);
}
[Fact]
public void CSharpCodeWriter_TracksPosition_WithNewline_SplitAcrossWrites_AtBeginning()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.Write("\r");
var location1 = writer.Location;
writer.Write("\n");
var location2 = writer.Location;
// Assert
var expected1 = new SourceLocation(absoluteIndex: 1, lineIndex: 1, characterIndex: 0);
Assert.Equal(expected1, location1);
var expected2 = new SourceLocation(absoluteIndex: 2, lineIndex: 1, characterIndex: 0);
Assert.Equal(expected2, location2);
}
[Fact]
public void WriteLineNumberDirective_UsesFilePath_FromSourceLocation()
{
// Arrange
var filePath = "some-path";
var mappingLocation = new SourceSpan(filePath, 10, 4, 3, 9);
var writer = new CodeWriter();
var expected = $"#line 5 \"{filePath}\"" + writer.NewLine;
// Act
writer.WriteLineNumberDirective(mappingLocation);
var code = writer.GenerateCode();
// Assert
Assert.Equal(expected, code);
}
[Fact]
public void WriteField_WritesFieldDeclaration()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.WriteField(new[] { "private" }, "global::System.String", "_myString");
// Assert
var output = writer.GenerateCode();
Assert.Equal("private global::System.String _myString;" + Environment.NewLine, output);
}
[Fact]
public void WriteField_WithModifiers_WritesFieldDeclaration()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.WriteField(new[] { "private", "readonly", "static" }, "global::System.String", "_myString");
// Assert
var output = writer.GenerateCode();
Assert.Equal("private readonly static global::System.String _myString;" + Environment.NewLine, output);
}
[Fact]
public void WriteAutoPropertyDeclaration_WritesPropertyDeclaration()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.WriteAutoPropertyDeclaration(new[] { "public" }, "global::System.String", "MyString");
// Assert
var output = writer.GenerateCode();
Assert.Equal("public global::System.String MyString { get; set; }" + Environment.NewLine, output);
}
[Fact]
public void WriteAutoPropertyDeclaration_WithModifiers_WritesPropertyDeclaration()
{
// Arrange
var writer = new CodeWriter();
// Act
writer.WriteAutoPropertyDeclaration(new[] { "public", "static" }, "global::System.String", "MyString");
// Assert
var output = writer.GenerateCode();
Assert.Equal("public static global::System.String MyString { get; set; }" + Environment.NewLine, output);
}
}
}

View File

@ -0,0 +1,68 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{
public class CodeTargetTest
{
[Fact]
public void CreateDefault_CreatesDefaultCodeTarget()
{
// Arrange
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var options = RazorCodeGenerationOptions.CreateDefault();
// Act
var target = CodeTarget.CreateDefault(codeDocument, options);
// Assert
Assert.IsType<DefaultCodeTarget>(target);
}
[Fact]
public void CreateDefault_CallsDelegate()
{
// Arrange
var wasCalled = false;
Action<CodeTargetBuilder> @delegate = (b) => { wasCalled = true; };
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var options = RazorCodeGenerationOptions.CreateDefault();
// Act
CodeTarget.CreateDefault(codeDocument, options, @delegate);
// Assert
Assert.True(wasCalled);
}
[Fact]
public void CreateDefault_AllowsNullDelegate()
{
// Arrange
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var options = RazorCodeGenerationOptions.CreateDefault();
// Act
CodeTarget.CreateDefault(codeDocument, options, configure: null);
// Assert (does not throw)
}
[Fact]
public void CreateEmpty_AllowsNullDelegate()
{
// Arrange
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var options = RazorCodeGenerationOptions.CreateDefault();
// Act
CodeTarget.CreateDefault(codeDocument, options, configure: null);
// Assert (does not throw)
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{
public class DefaultCodeTargetBuilderTest
{
[Fact]
public void Build_CreatesDefaultCodeTarget()
{
// Arrange
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var options = RazorCodeGenerationOptions.CreateDefault();
var builder = new DefaultCodeTargetBuilder(codeDocument, options);
var extensions = new ICodeTargetExtension[]
{
new MyExtension1(),
new MyExtension2(),
};
for (var i = 0; i < extensions.Length; i++)
{
builder.TargetExtensions.Add(extensions[i]);
}
// Act
var result = builder.Build();
// Assert
var target = Assert.IsType<DefaultCodeTarget>(result);
Assert.Equal(extensions, target.Extensions);
}
private class MyExtension1 : ICodeTargetExtension
{
}
private class MyExtension2 : ICodeTargetExtension
{
}
}
}

View File

@ -0,0 +1,174 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{
public class DefaultCodeTargetTest
{
[Fact]
public void Constructor_CreatesDefensiveCopy()
{
// Arrange
var options = RazorCodeGenerationOptions.CreateDefault();
var extensions = new ICodeTargetExtension[]
{
new MyExtension2(),
new MyExtension1(),
};
// Act
var target = new DefaultCodeTarget(options, extensions);
// Assert
Assert.NotSame(extensions, target);
}
[Fact]
public void CreateWriter_DesignTime_CreatesDesignTimeNodeWriter()
{
// Arrange
var options = RazorCodeGenerationOptions.CreateDesignTimeDefault();
var target = new DefaultCodeTarget(options, Enumerable.Empty<ICodeTargetExtension>());
// Act
var writer = target.CreateNodeWriter();
// Assert
Assert.IsType<DesignTimeNodeWriter>(writer);
}
[Fact]
public void CreateWriter_Runtime_CreatesRuntimeNodeWriter()
{
// Arrange
var options = RazorCodeGenerationOptions.CreateDefault();
var target = new DefaultCodeTarget(options, Enumerable.Empty<ICodeTargetExtension>());
// Act
var writer = target.CreateNodeWriter();
// Assert
Assert.IsType<RuntimeNodeWriter>(writer);
}
[Fact]
public void HasExtension_ReturnsTrue_WhenExtensionFound()
{
// Arrange
var options = RazorCodeGenerationOptions.CreateDefault();
var extensions = new ICodeTargetExtension[]
{
new MyExtension2(),
new MyExtension1(),
};
var target = new DefaultCodeTarget(options, extensions);
// Act
var result = target.HasExtension<MyExtension1>();
// Assert
Assert.True(result);
}
[Fact]
public void HasExtension_ReturnsFalse_WhenExtensionNotFound()
{
// Arrange
var options = RazorCodeGenerationOptions.CreateDefault();
var extensions = new ICodeTargetExtension[]
{
new MyExtension2(),
new MyExtension2(),
};
var target = new DefaultCodeTarget(options, extensions);
// Act
var result = target.HasExtension<MyExtension1>();
// Assert
Assert.False(result);
}
[Fact]
public void GetExtension_ReturnsExtension_WhenExtensionFound()
{
// Arrange
var options = RazorCodeGenerationOptions.CreateDefault();
var extensions = new ICodeTargetExtension[]
{
new MyExtension2(),
new MyExtension1(),
};
var target = new DefaultCodeTarget(options, extensions);
// Act
var result = target.GetExtension<MyExtension1>();
// Assert
Assert.Same(extensions[1], result);
}
[Fact]
public void GetExtension_ReturnsFirstMatch_WhenExtensionFound()
{
// Arrange
var options = RazorCodeGenerationOptions.CreateDefault();
var extensions = new ICodeTargetExtension[]
{
new MyExtension2(),
new MyExtension1(),
new MyExtension2(),
new MyExtension1(),
};
var target = new DefaultCodeTarget(options, extensions);
// Act
var result = target.GetExtension<MyExtension1>();
// Assert
Assert.Same(extensions[1], result);
}
[Fact]
public void GetExtension_ReturnsNull_WhenExtensionNotFound()
{
// Arrange
var options = RazorCodeGenerationOptions.CreateDefault();
var extensions = new ICodeTargetExtension[]
{
new MyExtension2(),
new MyExtension2(),
};
var target = new DefaultCodeTarget(options, extensions);
// Act
var result = target.GetExtension<MyExtension1>();
// Assert
Assert.Null(result);
}
private class MyExtension1 : ICodeTargetExtension
{
}
private class MyExtension2 : ICodeTargetExtension
{
}
}
}

View File

@ -0,0 +1,393 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{
public class DefaultDocumentWriterTest
{
[Fact] // This test covers the whole process including actual hashing.
public void WriteDocument_EndToEnd_WritesChecksumAndMarksAutoGenerated()
{
// Arrange
var document = new DocumentIntermediateNode();
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var options = RazorCodeGenerationOptions.CreateDefault();
var target = CodeTarget.CreateDefault(codeDocument, options);
var writer = new DefaultDocumentWriter(target, options);
// Act
var result = writer.WriteDocument(codeDocument, document);
// Assert
var csharp = result.GeneratedCode;
Assert.Equal(
@"#pragma checksum ""test.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""da39a3ee5e6b4b0d3255bfef95601890afd80709""
// <auto-generated/>
#pragma warning disable 1591
#pragma warning restore 1591
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDocument_SHA1_WritesChecksumAndMarksAutoGenerated()
{
// Arrange
var checksumBytes = new byte[] { (byte)'t', (byte)'e', (byte)'s', (byte)'t', };
var sourceDocument = Mock.Of<RazorSourceDocument>(d =>
d.FilePath == "test.cshtml" &&
d.GetChecksum() == checksumBytes &&
d.GetChecksumAlgorithm() == "SHA1");
var document = new DocumentIntermediateNode();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var options = RazorCodeGenerationOptions.CreateDefault();
var target = CodeTarget.CreateDefault(codeDocument, options);
var writer = new DefaultDocumentWriter(target, options);
// Act
var result = writer.WriteDocument(codeDocument, document);
// Assert
var csharp = result.GeneratedCode;
Assert.Equal(
@"#pragma checksum ""test.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""74657374""
// <auto-generated/>
#pragma warning disable 1591
#pragma warning restore 1591
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDocument_SHA256_WritesChecksumAndMarksAutoGenerated()
{
// Arrange
var checksumBytes = new byte[] { (byte)'t', (byte)'e', (byte)'s', (byte)'t', };
var sourceDocument = Mock.Of<RazorSourceDocument>(d =>
d.FilePath == "test.cshtml" &&
d.GetChecksum() == checksumBytes &&
d.GetChecksumAlgorithm() == "SHA256");
var document = new DocumentIntermediateNode();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var options = RazorCodeGenerationOptions.CreateDefault();
var target = CodeTarget.CreateDefault(codeDocument, options);
var writer = new DefaultDocumentWriter(target, options);
// Act
var result = writer.WriteDocument(codeDocument, document);
// Assert
var csharp = result.GeneratedCode;
Assert.Equal(
@"#pragma checksum ""test.cshtml"" ""{8829d00f-11b8-4213-878b-770e8597ac16}"" ""74657374""
// <auto-generated/>
#pragma warning disable 1591
#pragma warning restore 1591
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDocument_UnsupportedChecksumAlgorithm_Throws()
{
// Arrange
var checksumBytes = new byte[] { (byte)'t', (byte)'e', (byte)'s', (byte)'t', };
var sourceDocument = Mock.Of<RazorSourceDocument>(d =>
d.FilePath == "test.cshtml" &&
d.GetChecksum() == checksumBytes &&
d.GetChecksumAlgorithm() == "SHA3");
var document = new DocumentIntermediateNode();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var options = RazorCodeGenerationOptions.CreateDefault();
var target = CodeTarget.CreateDefault(codeDocument, options);
var writer = new DefaultDocumentWriter(target, options);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
{
var result = writer.WriteDocument(codeDocument, document);
});
Assert.Equal(
"The hash algorithm 'SHA3' is not supported for checksum generation. Supported algorithms are: 'SHA1 SHA256'. " +
"Set 'RazorCodeGenerationOptions.SuppressChecksum' to 'True' to suppress automatic checksum generation.",
exception.Message);
}
[Fact]
public void WriteDocument_Empty_SuppressChecksumTrue_DoesnotWriteChecksum()
{
// Arrange
var document = new DocumentIntermediateNode();
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var optionsBuilder = new DefaultRazorCodeGenerationOptionsBuilder(designTime: false)
{
SuppressChecksum = true
};
var options = optionsBuilder.Build();
var target = CodeTarget.CreateDefault(codeDocument, options);
var writer = new DefaultDocumentWriter(target, options);
// Act
var result = writer.WriteDocument(codeDocument, document);
// Assert
var csharp = result.GeneratedCode;
Assert.Equal(
@"// <auto-generated/>
#pragma warning disable 1591
#pragma warning restore 1591
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDocument_WritesNamespace()
{
// Arrange
var document = new DocumentIntermediateNode();
var builder = IntermediateNodeBuilder.Create(document);
builder.Add(new NamespaceDeclarationIntermediateNode()
{
Content = "TestNamespace",
});
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var options = RazorCodeGenerationOptions.CreateDefault();
var target = CodeTarget.CreateDefault(codeDocument, options);
var writer = new DefaultDocumentWriter(target, options);
// Act
var result = writer.WriteDocument(codeDocument, document);
// Assert
var csharp = result.GeneratedCode;
Assert.Equal(
@"#pragma checksum ""test.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""da39a3ee5e6b4b0d3255bfef95601890afd80709""
// <auto-generated/>
#pragma warning disable 1591
namespace TestNamespace
{
#line hidden
}
#pragma warning restore 1591
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDocument_WritesClass()
{
// Arrange
var document = new DocumentIntermediateNode();
var builder = IntermediateNodeBuilder.Create(document);
builder.Add(new ClassDeclarationIntermediateNode()
{
Modifiers =
{
"internal"
},
BaseType = "TestBase",
Interfaces = new List<string> { "IFoo", "IBar", },
TypeParameters = new List<TypeParameter>
{
new TypeParameter() { ParameterName = "TKey", },
new TypeParameter() { ParameterName = "TValue", },
},
ClassName = "TestClass",
});
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var options = RazorCodeGenerationOptions.CreateDefault();
var target = CodeTarget.CreateDefault(codeDocument, options);
var writer = new DefaultDocumentWriter(target, options);
// Act
var result = writer.WriteDocument(codeDocument, document);
// Assert
var csharp = result.GeneratedCode;
Assert.Equal(
@"#pragma checksum ""test.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""da39a3ee5e6b4b0d3255bfef95601890afd80709""
// <auto-generated/>
#pragma warning disable 1591
internal class TestClass<TKey, TValue> : TestBase, IFoo, IBar
{
}
#pragma warning restore 1591
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDocument_WritesMethod()
{
// Arrange
var document = new DocumentIntermediateNode();
var builder = IntermediateNodeBuilder.Create(document);
builder.Add(new MethodDeclarationIntermediateNode()
{
Modifiers =
{
"internal",
"virtual",
"async",
},
MethodName = "TestMethod",
Parameters =
{
new MethodParameter()
{
Modifiers =
{
"readonly",
"ref",
},
ParameterName = "a",
TypeName = "int",
},
new MethodParameter()
{
ParameterName = "b",
TypeName = "string",
}
},
ReturnType = "string",
});
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var options = RazorCodeGenerationOptions.CreateDefault();
var target = CodeTarget.CreateDefault(codeDocument, options);
var writer = new DefaultDocumentWriter(target, options);
// Act
var result = writer.WriteDocument(codeDocument, document);
// Assert
var csharp = result.GeneratedCode;
Assert.Equal(
@"#pragma checksum ""test.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""da39a3ee5e6b4b0d3255bfef95601890afd80709""
// <auto-generated/>
#pragma warning disable 1591
#pragma warning disable 1998
internal virtual async string TestMethod(readonly ref int a, string b)
{
}
#pragma warning restore 1998
#pragma warning restore 1591
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDocument_WritesField()
{
// Arrange
var document = new DocumentIntermediateNode();
var builder = IntermediateNodeBuilder.Create(document);
builder.Add(new FieldDeclarationIntermediateNode()
{
Modifiers =
{
"internal",
"readonly",
},
FieldName = "_foo",
FieldType = "string",
});
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var options = RazorCodeGenerationOptions.CreateDefault();
var target = CodeTarget.CreateDefault(codeDocument, options);
var writer = new DefaultDocumentWriter(target, options);
// Act
var result = writer.WriteDocument(codeDocument, document);
// Assert
var csharp = result.GeneratedCode;
Assert.Equal(
@"#pragma checksum ""test.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""da39a3ee5e6b4b0d3255bfef95601890afd80709""
// <auto-generated/>
#pragma warning disable 1591
internal readonly string _foo;
#pragma warning restore 1591
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDocument_WritesProperty()
{
// Arrange
var document = new DocumentIntermediateNode();
var builder = IntermediateNodeBuilder.Create(document);
builder.Add(new PropertyDeclarationIntermediateNode()
{
Modifiers =
{
"internal",
"virtual",
},
PropertyName = "Foo",
PropertyType = "string",
});
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var options = RazorCodeGenerationOptions.CreateDefault();
var target = CodeTarget.CreateDefault(codeDocument, options);
var writer = new DefaultDocumentWriter(target, options);
// Act
var result = writer.WriteDocument(codeDocument, document);
// Assert
var csharp = result.GeneratedCode;
Assert.Equal(
@"#pragma checksum ""test.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""da39a3ee5e6b4b0d3255bfef95601890afd80709""
// <auto-generated/>
#pragma warning disable 1591
internal virtual string Foo { get; set; }
#pragma warning restore 1591
",
csharp,
ignoreLineEndingDifferences: true);
}
}
}

View File

@ -0,0 +1,498 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{
public class DesignTimeNodeWriterTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Latest;
[Fact]
public void WriteUsingDirective_NoSource_WritesContent()
{
// Arrange
var writer = new DesignTimeNodeWriter();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new UsingDirectiveIntermediateNode()
{
Content = "System",
};
// Act
writer.WriteUsingDirective(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"using System;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteUsingDirective_WithSource_WritesContentWithLinePragmaAndMapping()
{
// Arrange
var writer = new DesignTimeNodeWriter();
var context = TestCodeRenderingContext.CreateDesignTime();
var originalSpan = new SourceSpan("test.cshtml", 0, 0, 0, 6);
var generatedSpan = new SourceSpan(null, 38 + Environment.NewLine.Length * 3, 3, 0, 6);
var expectedSourceMapping = new SourceMapping(originalSpan, generatedSpan);
var node = new UsingDirectiveIntermediateNode()
{
Content = "System",
Source = originalSpan,
};
// Act
writer.WriteUsingDirective(context, node);
// Assert
var mapping = Assert.Single(((DefaultCodeRenderingContext)context).SourceMappings);
Assert.Equal(expectedSourceMapping, mapping);
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
using System;
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpExpression_SkipsLinePragma_WithoutSource()
{
// Arrange
var writer = new DesignTimeNodeWriter();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new CSharpExpressionIntermediateNode();
var builder = IntermediateNodeBuilder.Create(node);
builder.Add(new IntermediateToken()
{
Content = "i++",
Kind = TokenKind.CSharp,
});
// Act
writer.WriteCSharpExpression(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"__o = i++;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpExpression_WritesLinePragma_WithSource()
{
// Arrange
var writer = new DesignTimeNodeWriter();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new CSharpExpressionIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 3),
};
var builder = IntermediateNodeBuilder.Create(node);
builder.Add(new IntermediateToken()
{
Content = "i++",
Kind = TokenKind.CSharp,
});
// Act
writer.WriteCSharpExpression(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
__o = i++;
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpExpression_WithExtensionNode_WritesPadding()
{
// Arrange
var writer = new DesignTimeNodeWriter();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new CSharpExpressionIntermediateNode();
var builder = IntermediateNodeBuilder.Create(node);
builder.Add(new IntermediateToken()
{
Content = "i",
Kind = TokenKind.CSharp,
});
builder.Add(new MyExtensionIntermediateNode());
builder.Add(new IntermediateToken()
{
Content = "++",
Kind = TokenKind.CSharp,
});
// Act
writer.WriteCSharpExpression(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"__o = iRender Children
++;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpExpression_WithSource_WritesPadding()
{
// Arrange
var writer = new DesignTimeNodeWriter();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new CSharpExpressionIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 8, 0, 8, 3),
};
var builder = IntermediateNodeBuilder.Create(node);
builder.Add(new IntermediateToken()
{
Content = "i",
Kind = TokenKind.CSharp,
});
builder.Add(new MyExtensionIntermediateNode());
builder.Add(new IntermediateToken()
{
Content = "++",
Kind = TokenKind.CSharp,
});
// Act
writer.WriteCSharpExpression(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
__o = iRender Children
++;
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpCode_WhitespaceContentWithSource_WritesContent()
{
// Arrange
var writer = new DesignTimeNodeWriter();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new CSharpCodeIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 3),
};
IntermediateNodeBuilder.Create(node)
.Add(new IntermediateToken()
{
Kind = TokenKind.CSharp,
Content = " "
});
// Act
writer.WriteCSharpCode(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpCode_SkipsLinePragma_WithoutSource()
{
// Arrange
var writer = new DesignTimeNodeWriter();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new CSharpCodeIntermediateNode();
IntermediateNodeBuilder.Create(node)
.Add(new IntermediateToken()
{
Kind = TokenKind.CSharp,
Content = "if (true) { }"
});
// Act
writer.WriteCSharpCode(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"if (true) { }
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpCode_WritesLinePragma_WithSource()
{
// Arrange
var writer = new DesignTimeNodeWriter();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new CSharpCodeIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 13),
};
IntermediateNodeBuilder.Create(node)
.Add(new IntermediateToken()
{
Kind = TokenKind.CSharp,
Content = "if (true) { }",
});
// Act
writer.WriteCSharpCode(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
if (true) { }
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpCode_WritesPadding_WithSource()
{
// Arrange
var writer = new DesignTimeNodeWriter();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new CSharpCodeIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 17),
};
IntermediateNodeBuilder.Create(node)
.Add(new IntermediateToken()
{
Kind = TokenKind.CSharp,
Content = " if (true) { }",
});
// Act
writer.WriteCSharpCode(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
if (true) { }
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpExpressionAttributeValue_RendersCorrectly()
{
var writer = new DesignTimeNodeWriter();
var content = "<input checked=\"hello-world @false\" />";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var documentNode = Lower(codeDocument);
var node = documentNode.Children.OfType<HtmlAttributeIntermediateNode>().Single().Children[1] as CSharpExpressionAttributeValueIntermediateNode;
var context = TestCodeRenderingContext.CreateDesignTime(source: sourceDocument);
// Act
writer.WriteCSharpExpressionAttributeValue(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
__o = false;
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpCodeAttributeValue_RendersCorrectly()
{
var writer = new DesignTimeNodeWriter();
var content = "<input checked=\"hello-world @if(@true){ }\" />";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var documentNode = Lower(codeDocument);
var node = documentNode.Children.OfType<HtmlAttributeIntermediateNode>().Single().Children[1] as CSharpCodeAttributeValueIntermediateNode;
var context = TestCodeRenderingContext.CreateDesignTime(source: sourceDocument);
// Act
writer.WriteCSharpCodeAttributeValue(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
if(@true){ }
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpCodeAttributeValue_WithExpression_RendersCorrectly()
{
var writer = new DesignTimeNodeWriter();
var content = "<input checked=\"hello-world @if(@true){ @false }\" />";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var documentNode = Lower(codeDocument);
var node = documentNode.Children.OfType<HtmlAttributeIntermediateNode>().Single().Children[1] as CSharpCodeAttributeValueIntermediateNode;
var context = TestCodeRenderingContext.CreateDesignTime(source: sourceDocument);
// Act
writer.WriteCSharpCodeAttributeValue(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
if(@true){
#line default
#line hidden
#nullable disable
Render Children
#nullable restore
#line 1 ""test.cshtml""
}
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument)
{
var projectEngine = CreateProjectEngine();
return Lower(codeDocument, projectEngine);
}
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument, RazorProjectEngine projectEngine)
{
for (var i = 0; i < projectEngine.Phases.Count; i++)
{
var phase = projectEngine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorIntermediateNodeLoweringPhase)
{
break;
}
}
var documentNode = codeDocument.GetDocumentIntermediateNode();
Assert.NotNull(documentNode);
return documentNode;
}
private class MyExtensionIntermediateNode : ExtensionIntermediateNode
{
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
public override void Accept(IntermediateNodeVisitor visitor)
{
visitor.VisitDefault(this);
}
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
{
context.CodeWriter.WriteLine("MyExtensionNode");
}
}
}
}

View File

@ -0,0 +1,48 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{
public class LiteralRuntimeNodeWriterTest
{
[Fact]
public void WriteCSharpExpression_UsesWriteLiteral_WritesLinePragma_WithSource()
{
// Arrange
var writer = new LiteralRuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new CSharpExpressionIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 3),
};
var builder = IntermediateNodeBuilder.Create(node);
builder.Add(new IntermediateToken()
{
Content = "i++",
Kind = TokenKind.CSharp,
});
// Act
writer.WriteCSharpExpression(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
WriteLiteral(i++);
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
}
}

View File

@ -0,0 +1,705 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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 Xunit;
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{
public class RuntimeNodeWriterTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Latest;
[Fact]
public void WriteUsingDirective_NoSource_WritesContent()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new UsingDirectiveIntermediateNode()
{
Content = "System",
};
// Act
writer.WriteUsingDirective(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"using System;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteUsingDirective_WithSource_WritesContentWithLinePragma()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new UsingDirectiveIntermediateNode()
{
Content = "System",
Source = new SourceSpan("test.cshtml", 0, 0, 0, 3),
};
// Act
writer.WriteUsingDirective(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
using System;
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpExpression_SkipsLinePragma_WithoutSource()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter()
{
WriteCSharpExpressionMethod = "Test",
};
var context = TestCodeRenderingContext.CreateRuntime();
var node = new CSharpExpressionIntermediateNode();
var builder = IntermediateNodeBuilder.Create(node);
builder.Add(new IntermediateToken()
{
Content = "i++",
Kind = TokenKind.CSharp,
});
// Act
writer.WriteCSharpExpression(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"Test(i++);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpExpression_WritesLinePragma_WithSource()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter()
{
WriteCSharpExpressionMethod = "Test",
};
var context = TestCodeRenderingContext.CreateRuntime();
var node = new CSharpExpressionIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 3),
};
var builder = IntermediateNodeBuilder.Create(node);
builder.Add(new IntermediateToken()
{
Content = "i++",
Kind = TokenKind.CSharp,
});
// Act
writer.WriteCSharpExpression(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
Test(i++);
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpExpression_WithExtensionNode_WritesPadding()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter()
{
WriteCSharpExpressionMethod = "Test",
};
var context = TestCodeRenderingContext.CreateRuntime();
var node = new CSharpExpressionIntermediateNode();
var builder = IntermediateNodeBuilder.Create(node);
builder.Add(new IntermediateToken()
{
Content = "i",
Kind = TokenKind.CSharp,
});
builder.Add(new MyExtensionIntermediateNode());
builder.Add(new IntermediateToken()
{
Content = "++",
Kind = TokenKind.CSharp,
});
// Act
writer.WriteCSharpExpression(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"Test(iRender Children
++);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpExpression_WithSource_WritesPadding()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter()
{
WriteCSharpExpressionMethod = "Test",
};
var context = TestCodeRenderingContext.CreateRuntime();
var node = new CSharpExpressionIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 8, 0, 8, 3),
};
var builder = IntermediateNodeBuilder.Create(node);
builder.Add(new IntermediateToken()
{
Content = "i",
Kind = TokenKind.CSharp,
});
builder.Add(new MyExtensionIntermediateNode());
builder.Add(new IntermediateToken()
{
Content = "++",
Kind = TokenKind.CSharp,
});
// Act
writer.WriteCSharpExpression(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
Test(iRender Children
++);
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpCode_WhitespaceContent_DoesNothing()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new CSharpCodeIntermediateNode();
IntermediateNodeBuilder.Create(node)
.Add(new IntermediateToken()
{
Kind = TokenKind.CSharp,
Content = " \t"
});
// Act
writer.WriteCSharpCode(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Empty(csharp);
}
[Fact]
public void WriteCSharpCode_SkipsLinePragma_WithoutSource()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new CSharpCodeIntermediateNode();
IntermediateNodeBuilder.Create(node)
.Add(new IntermediateToken()
{
Kind = TokenKind.CSharp,
Content = "if (true) { }"
});
// Act
writer.WriteCSharpCode(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"if (true) { }
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpCode_WritesLinePragma_WithSource()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new CSharpCodeIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 13),
};
IntermediateNodeBuilder.Create(node)
.Add(new IntermediateToken()
{
Kind = TokenKind.CSharp,
Content = "if (true) { }",
});
// Act
writer.WriteCSharpCode(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
if (true) { }
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpCode_WritesPadding_WithSource()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new CSharpCodeIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 17),
};
IntermediateNodeBuilder.Create(node)
.Add(new IntermediateToken()
{
Kind = TokenKind.CSharp,
Content = " if (true) { }",
});
// Act
writer.WriteCSharpCode(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
if (true) { }
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteHtmlLiteral_WithinMaxSize_WritesSingleLiteral()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
// Act
writer.WriteHtmlLiteral(context, maxStringLiteralLength: 6, "Hello");
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"WriteLiteral(""Hello"");
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteHtmlLiteral_GreaterThanMaxSize_WritesMultipleLiterals()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
// Act
writer.WriteHtmlLiteral(context, maxStringLiteralLength: 6, "Hello World");
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"WriteLiteral(""Hello "");
WriteLiteral(""World"");
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteHtmlLiteral_GreaterThanMaxSize_SingleEmojisSplit()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
// Act
writer.WriteHtmlLiteral(context, maxStringLiteralLength: 2, " 👦");
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"WriteLiteral("" "");
WriteLiteral(""👦"");
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteHtmlLiteral_GreaterThanMaxSize_SequencedZeroWithJoinedEmojisSplit()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
// Act
writer.WriteHtmlLiteral(context, maxStringLiteralLength: 6, "👩‍👩‍👧‍👧👩‍👩‍👧‍👧");
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"WriteLiteral(""👩‍👩‍"");
WriteLiteral(""👧👧"");
WriteLiteral(""👩👩"");
WriteLiteral(""👧👧"");
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteHtmlContent_RendersContentCorrectly()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new HtmlContentIntermediateNode();
node.Children.Add(new IntermediateToken()
{
Content = "SomeContent",
Kind = TokenKind.Html,
});
// Act
writer.WriteHtmlContent(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"WriteLiteral(""SomeContent"");
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteHtmlContent_LargeStringLiteral_UsesMultipleWrites()
{
// Arrange
var codeWriter = new CodeWriter();
var writer = new RuntimeNodeWriter();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new HtmlContentIntermediateNode();
node.Children.Add(new IntermediateToken()
{
Content = new string('*', 2000),
Kind = TokenKind.Html,
});
// Act
writer.WriteHtmlContent(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(string.Format(
@"WriteLiteral(@""{0}"");
WriteLiteral(@""{1}"");
", new string('*', 1024), new string('*', 976)),
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteHtmlAttribute_RendersCorrectly()
{
// Arrange
var writer = new RuntimeNodeWriter();
var content = "<input checked=\"hello-world @false\" />";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var documentNode = Lower(codeDocument);
var node = documentNode.Children.OfType<HtmlAttributeIntermediateNode>().Single();
var context = TestCodeRenderingContext.CreateRuntime();
// Act
writer.WriteHtmlAttribute(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"BeginWriteAttribute(""checked"", "" checked=\"""", 6, ""\"""", 34, 2);
Render Children
Render Children
EndWriteAttribute();
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteHtmlAttributeValue_RendersCorrectly()
{
// Arrange
var writer = new RuntimeNodeWriter();
var content = "<input checked=\"hello-world @false\" />";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var documentNode = Lower(codeDocument);
var node = documentNode.Children.OfType<HtmlAttributeIntermediateNode>().Single().Children[0] as HtmlAttributeValueIntermediateNode;
var context = TestCodeRenderingContext.CreateRuntime();
// Act
writer.WriteHtmlAttributeValue(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"WriteAttributeValue("""", 16, ""hello-world"", 16, 11, true);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpExpressionAttributeValue_RendersCorrectly()
{
// Arrange
var writer = new RuntimeNodeWriter();
var content = "<input checked=\"hello-world @false\" />";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var documentNode = Lower(codeDocument);
var node = documentNode.Children.OfType<HtmlAttributeIntermediateNode>().Single().Children[1] as CSharpExpressionAttributeValueIntermediateNode;
var context = TestCodeRenderingContext.CreateRuntime();
// Act
writer.WriteCSharpExpressionAttributeValue(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
WriteAttributeValue("" "", 27, false, 28, 6, false);
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpCodeAttributeValue_BuffersResult()
{
// Arrange
var writer = new RuntimeNodeWriter();
var content = "<input checked=\"hello-world @if(@true){ }\" />";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var documentNode = Lower(codeDocument);
var node = documentNode.Children.OfType<HtmlAttributeIntermediateNode>().Single().Children[1] as CSharpCodeAttributeValueIntermediateNode;
var context = TestCodeRenderingContext.CreateRuntime(source: sourceDocument);
// Act
writer.WriteCSharpCodeAttributeValue(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"WriteAttributeValue("" "", 27, new Microsoft.AspNetCore.Mvc.Razor.HelperResult(async(__razor_attribute_value_writer) => {
PushWriter(__razor_attribute_value_writer);
#nullable restore
#line 1 ""test.cshtml""
if(@true){ }
#line default
#line hidden
#nullable disable
PopWriter();
}
), 28, 13, false);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void BeginWriterScope_UsesSpecifiedWriter_RendersCorrectly()
{
// Arrange
var writer = new RuntimeNodeWriter()
{
PushWriterMethod = "TestPushWriter"
};
var context = TestCodeRenderingContext.CreateRuntime();
// Act
writer.BeginWriterScope(context, "MyWriter");
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"TestPushWriter(MyWriter);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void EndWriterScope_RendersCorrectly()
{
// Arrange
var writer = new RuntimeNodeWriter()
{
PopWriterMethod = "TestPopWriter"
};
var context = TestCodeRenderingContext.CreateRuntime();
// Act
writer.EndWriterScope(context);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"TestPopWriter();
",
csharp,
ignoreLineEndingDifferences: true);
}
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument)
{
var projectEngine = CreateProjectEngine();
return Lower(codeDocument, projectEngine);
}
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument, RazorProjectEngine projectEngine)
{
for (var i = 0; i < projectEngine.Phases.Count; i++)
{
var phase = projectEngine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorIntermediateNodeLoweringPhase)
{
break;
}
}
var irDocument = codeDocument.GetDocumentIntermediateNode();
Assert.NotNull(irDocument);
return irDocument;
}
private class MyExtensionIntermediateNode : ExtensionIntermediateNode
{
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
public override void Accept(IntermediateNodeVisitor visitor)
{
visitor.VisitDefault(this);
}
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,131 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{
public class TagHelperHtmlAttributeRuntimeNodeWriterTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Latest;
[Fact]
public void WriteHtmlAttributeValue_RendersCorrectly()
{
var writer = new TagHelperHtmlAttributeRuntimeNodeWriter();
var content = "<input checked=\"hello-world @false\" />";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var documentNode = Lower(codeDocument);
var node = documentNode.Children.OfType<HtmlAttributeIntermediateNode>().Single().Children[0] as HtmlAttributeValueIntermediateNode;
var context = TestCodeRenderingContext.CreateRuntime();
// Act
writer.WriteHtmlAttributeValue(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"AddHtmlAttributeValue("""", 16, ""hello-world"", 16, 11, true);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpExpressionAttributeValue_RendersCorrectly()
{
var writer = new TagHelperHtmlAttributeRuntimeNodeWriter();
var content = "<input checked=\"hello-world @false\" />";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var documentNode = Lower(codeDocument);
var node = documentNode.Children.OfType<HtmlAttributeIntermediateNode>().Single().Children[1] as CSharpExpressionAttributeValueIntermediateNode;
var context = TestCodeRenderingContext.CreateRuntime();
// Act
writer.WriteCSharpExpressionAttributeValue(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"
#nullable restore
#line 1 ""test.cshtml""
AddHtmlAttributeValue("" "", 27, false, 28, 6, false);
#line default
#line hidden
#nullable disable
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteCSharpCodeAttributeValue_BuffersResult()
{
var writer = new TagHelperHtmlAttributeRuntimeNodeWriter();
var content = "<input checked=\"hello-world @if(@true){ }\" />";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var documentNode = Lower(codeDocument);
var node = documentNode.Children.OfType<HtmlAttributeIntermediateNode>().Single().Children[1] as CSharpCodeAttributeValueIntermediateNode;
var context = TestCodeRenderingContext.CreateRuntime(source: sourceDocument);
// Act
writer.WriteCSharpCodeAttributeValue(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"AddHtmlAttributeValue("" "", 27, new Microsoft.AspNetCore.Mvc.Razor.HelperResult(async(__razor_attribute_value_writer) => {
PushWriter(__razor_attribute_value_writer);
#nullable restore
#line 1 ""test.cshtml""
if(@true){ }
#line default
#line hidden
#nullable disable
PopWriter();
}
), 28, 13, false);
",
csharp,
ignoreLineEndingDifferences: true);
}
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument)
{
var projectEngine = CreateProjectEngine();
return Lower(codeDocument, projectEngine);
}
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument, RazorProjectEngine projectEngine)
{
for (var i = 0; i < projectEngine.Phases.Count; i++)
{
var phase = projectEngine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorIntermediateNodeLoweringPhase)
{
break;
}
}
var documentNode = codeDocument.GetDocumentIntermediateNode();
Assert.NotNull(documentNode);
return documentNode;
}
}
}

View File

@ -0,0 +1,224 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
public class ComponentDocumentClassifierPassTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Latest;
[Fact]
public void Execute_SetsDocumentKind()
{
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", "Test.razor"));
codeDocument.SetFileKind(FileKinds.Component);
var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ComponentDocumentClassifierPass
{
Engine = projectEngine.Engine
};
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Assert.Equal(ComponentDocumentClassifierPass.ComponentDocumentKind, irDocument.DocumentKind);
}
[Fact]
public void ComponentDocumentClassifierPass_SetsNamespace()
{
// Arrange
var properties = new RazorSourceDocumentProperties(filePath: "/MyApp/Test.razor", relativePath: "Test.razor");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", properties));
codeDocument.SetFileKind(FileKinds.Component);
var projectEngine = CreateProjectEngine(b =>
{
b.SetRootNamespace("MyApp");
});
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ComponentDocumentClassifierPass
{
Engine = projectEngine.Engine
};
// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);
// Assert
Assert.Equal("MyApp", visitor.Namespace.Content);
}
[Fact]
public void ComponentDocumentClassifierPass_SetsClass()
{
// Arrange
var properties = new RazorSourceDocumentProperties(filePath: "/MyApp/Test.razor", relativePath: "Test.razor");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", properties));
codeDocument.SetFileKind(FileKinds.Component);
var projectEngine = CreateProjectEngine(b =>
{
b.SetRootNamespace("MyApp");
});
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ComponentDocumentClassifierPass
{
Engine = projectEngine.Engine
};
// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);
// Assert
Assert.Equal($"{CodeGenerationConstants.ComponentBase.FullTypeName}", visitor.Class.BaseType);
Assert.Equal(new[] { "public", }, visitor.Class.Modifiers);
Assert.Equal("Test", visitor.Class.ClassName);
}
[Fact]
public void ComponentDocumentClassifierPass_UsesRelativePathToGenerateTypeNameAndNamespace()
{
// Arrange
var relativePath = "/Pages/Announcements/Banner.razor";
var properties = new RazorSourceDocumentProperties(filePath: $"/MyApp{relativePath}", relativePath: relativePath);
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", properties));
codeDocument.SetFileKind(FileKinds.Component);
var projectEngine = CreateProjectEngine(b =>
{
b.SetRootNamespace("MyApp");
});
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ComponentDocumentClassifierPass
{
Engine = projectEngine.Engine
};
// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);
// Assert
Assert.Equal("Banner", visitor.Class.ClassName);
Assert.Equal("MyApp.Pages.Announcements", visitor.Namespace.Content);
}
[Fact]
public void ComponentDocumentClassifierPass_SanitizesClassName()
{
// Arrange
var properties = new RazorSourceDocumentProperties(filePath: @"x:\My.+App\path.with+invalid-chars.razor", relativePath: "path.with+invalid-chars.razor");
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", properties));
codeDocument.SetFileKind(FileKinds.Component);
var projectEngine = CreateProjectEngine(b =>
{
b.SetRootNamespace("My.+App");
});
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ComponentDocumentClassifierPass
{
Engine = projectEngine.Engine
};
// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);
// Assert
Assert.Equal("path_with_invalid_chars", visitor.Class.ClassName);
Assert.Equal("My._App", visitor.Namespace.Content);
}
[Fact]
public void ComponentDocumentClassifierPass_SetsUpMainMethod()
{
// Arrange
var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("some-content", "Test.razor"));
codeDocument.SetFileKind(FileKinds.Component);
var projectEngine = CreateProjectEngine();
var irDocument = CreateIRDocument(projectEngine, codeDocument);
var pass = new ComponentDocumentClassifierPass
{
Engine = projectEngine.Engine
};
// Act
pass.Execute(codeDocument, irDocument);
var visitor = new Visitor();
visitor.Visit(irDocument);
// Assert
Assert.Equal(CodeGenerationConstants.ComponentBase.BuildRenderTree, visitor.Method.MethodName);
Assert.Equal("void", visitor.Method.ReturnType);
Assert.Equal(new[] { "protected", "override" }, visitor.Method.Modifiers);
}
private DocumentIntermediateNode CreateIRDocument(RazorProjectEngine projectEngine, RazorCodeDocument codeDocument)
{
for (var i = 0; i < projectEngine.Phases.Count; i++)
{
var phase = projectEngine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorIntermediateNodeLoweringPhase)
{
break;
}
}
return codeDocument.GetDocumentIntermediateNode();
}
private class Visitor : IntermediateNodeWalker
{
public NamespaceDeclarationIntermediateNode Namespace { get; private set; }
public ClassDeclarationIntermediateNode Class { get; private set; }
public MethodDeclarationIntermediateNode Method { get; private set; }
public override void VisitMethodDeclaration(MethodDeclarationIntermediateNode node)
{
Method = node;
}
public override void VisitNamespaceDeclaration(NamespaceDeclarationIntermediateNode node)
{
Namespace = node;
base.VisitNamespaceDeclaration(node);
}
public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
{
Class = node;
base.VisitClassDeclaration(node);
}
}
private class TestComponentDocumentClassifierPass : ComponentDocumentClassifierPass
{
public new bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
=> base.IsMatch(codeDocument, documentNode);
}
}
}

View File

@ -0,0 +1,403 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
public class ComponentMarkupBlockPassTest
{
public ComponentMarkupBlockPassTest()
{
Pass = new ComponentMarkupBlockPass();
ProjectEngine = (DefaultRazorProjectEngine)RazorProjectEngine.Create(
RazorConfiguration.Default,
RazorProjectFileSystem.Create(Environment.CurrentDirectory),
b =>
{
if (b.Features.OfType<ComponentMarkupBlockPass>().Any())
{
b.Features.Remove(b.Features.OfType<ComponentMarkupBlockPass>().Single());
}
});
Engine = ProjectEngine.Engine;
Pass.Engine = Engine;
}
private DefaultRazorProjectEngine ProjectEngine { get; }
private RazorEngine Engine { get; }
private ComponentMarkupBlockPass Pass { get; }
[Fact]
public void Execute_RewritesHtml_Basic()
{
// Arrange
var document = CreateDocument(@"
<html>
<head cool=""beans"">
Hello, World!
</head>
</html>");
var expected = NormalizeContent(@"
<html>
<head cool=""beans"">
Hello, World!
</head>
</html>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_WithComment()
{
// Arrange
var document = CreateDocument(@"Start<!-- -->End");
var expected = NormalizeContent(@"StartEnd");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
// See: https://github.com/aspnet/AspNetCore/issues/6480
[Fact]
public void Execute_RewritesHtml_HtmlAttributePrefix()
{
// Arrange
var document = CreateDocument(@"<div class=""one two"">Hi</div>");
var expected = NormalizeContent(@"<div class=""one two"">Hi</div>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_MergesSiblings()
{
// Arrange
var document = CreateDocument(@"
<html>
@(""Hi"")<div></div>
<div></div>
<div>@(""Hi"")</div>
</html>");
var expected = NormalizeContent(@"
<div></div>
<div></div>
");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_MergesSiblings_LeftEdge()
{
// Arrange
var document = CreateDocument(@"
<html><div></div>
<div></div>
<div>@(""Hi"")</div>
</html>");
var expected = NormalizeContent(@"
<div></div>
<div></div>
");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_CSharpInAttributes()
{
// Arrange
var document = CreateDocument(@"
<html>
<head cool=""beans"" csharp=""@yes"" mixed=""hi @there"">
<div>foo</div>
</head>
</html>");
var expected = NormalizeContent("<div>foo</div>\n ");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_CSharpInBody()
{
// Arrange
var document = CreateDocument(@"
<html>
<head cool=""beans"">
<div>@foo</div>
<div>rewriteme</div>
<div>@bar</div>
</head>
</html>");
var expected = NormalizeContent("<div>rewriteme</div>\n ");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_EncodesHtmlEntities()
{
// Arrange
var document = CreateDocument(@"
<div>
&lt;span&gt;Hi&lt;/span&gt;
</div>");
var expected = NormalizeContent(@"
<div>
&lt;span&gt;Hi&lt;/span&gt;
</div>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_EmptyNonvoid()
{
// Arrange
var document = CreateDocument(@"<a href=""...""></a>");
var expected = NormalizeContent(@"<a href=""...""></a>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_Void()
{
// Arrange
var document = CreateDocument(@"<link rel=""..."" href=""...""/>");
var expected = NormalizeContent(@"<link rel=""..."" href=""..."">");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_CannotRewriteHtml_CSharpInCode()
{
// Arrange
var document = CreateDocument(@"
<html>
@if (some_bool)
{
<head cool=""beans"">
@hello
</head>
}
</html>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
Assert.Empty(documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>());
}
[Fact]
public void Execute_CannotRewriteHtml_Script()
{
// Arrange
var document = CreateDocument(@"
<html>
@if (some_bool)
{
<head cool=""beans"">
<script>...</script>
</head>
}
</html>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
Assert.Empty(documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>());
}
// The unclosed tag will have errors, so we won't rewrite it or its parent.
[Fact]
public void Execute_CannotRewriteHtml_Errors()
{
// Arrange
var document = CreateDocument(@"
<html>
<a href=""..."">
</html>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
Assert.Empty(documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>());
}
[Fact]
public void Execute_RewritesHtml_MismatchedClosingTag()
{
// Arrange
var document = CreateDocument(@"
<html>
<div>
<div>rewriteme</div>
</span>
</html>");
var expected = NormalizeContent("<div>rewriteme</div>\n ");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
private string NormalizeContent(string content)
{
// Test inputs frequently have leading space for readability.
content = content.TrimStart();
// Normalize newlines since we are testing lengths of things.
content = content.Replace("\r", "");
content = content.Replace("\n", "\r\n");
return content;
}
private RazorCodeDocument CreateDocument(string content)
{
// Normalize newlines since we are testing lengths of things.
content = content.Replace("\r", "");
content = content.Replace("\n", "\r\n");
var source = RazorSourceDocument.Create(content, "test.cshtml");
return ProjectEngine.CreateCodeDocumentCore(source, FileKinds.Component);
}
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument)
{
for (var i = 0; i < Engine.Phases.Count; i++)
{
var phase = Engine.Phases[i];
if (phase is IRazorCSharpLoweringPhase)
{
break;
}
phase.Execute(codeDocument);
}
var document = codeDocument.GetDocumentIntermediateNode();
Engine.Features.OfType<ComponentDocumentClassifierPass>().Single().Execute(codeDocument, document);
return document;
}
private class StaticTagHelperFeature : ITagHelperFeature
{
public RazorEngine Engine { get; set; }
public List<TagHelperDescriptor> TagHelpers { get; set; }
public IReadOnlyList<TagHelperDescriptor> GetDescriptors()
{
return TagHelpers;
}
}
}
}

View File

@ -0,0 +1,221 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
public class ComponentMarkupEncodingPassTest
{
public ComponentMarkupEncodingPassTest()
{
Pass = new ComponentMarkupEncodingPass();
ProjectEngine = (DefaultRazorProjectEngine)RazorProjectEngine.Create(
RazorConfiguration.Default,
RazorProjectFileSystem.Create(Environment.CurrentDirectory),
b =>
{
if (b.Features.OfType<ComponentMarkupEncodingPass>().Any())
{
b.Features.Remove(b.Features.OfType<ComponentMarkupEncodingPass>().Single());
}
});
Engine = ProjectEngine.Engine;
Pass.Engine = Engine;
}
private DefaultRazorProjectEngine ProjectEngine { get; }
private RazorEngine Engine { get; }
private ComponentMarkupEncodingPass Pass { get; }
[Fact]
public void Execute_StaticHtmlContent_RewrittenToBlock()
{
// Arrange
var document = CreateDocument(@"
<div>
&nbsp;
</div>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
Assert.Empty(documentNode.FindDescendantNodes<HtmlContentIntermediateNode>());
Assert.Single(documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>());
}
[Fact]
public void Execute_MixedHtmlContent_NoNewLineorSpecialCharacters_DoesNotSetEncoded()
{
// Arrange
var document = CreateDocument(@"
<div>The time is @DateTime.Now</div>");
var expected = NormalizeContent("The time is ");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var node = documentNode.FindDescendantNodes<HtmlContentIntermediateNode>().Single();
Assert.Equal(expected, GetHtmlContent(node));
Assert.False(node.IsEncoded());
}
[Fact]
public void Execute_MixedHtmlContent_NewLine_SetsEncoded()
{
// Arrange
var document = CreateDocument(@"
<div>
The time is @DateTime.Now</div>");
var expected = NormalizeContent(@"
The time is ");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var node = documentNode.FindDescendantNodes<HtmlContentIntermediateNode>().Single();
Assert.Equal(expected, GetHtmlContent(node));
Assert.True(node.IsEncoded());
}
[Fact]
public void Execute_MixedHtmlContent_Ampersand_SetsEncoded()
{
// Arrange
var document = CreateDocument(@"
<div>The time is &&nbsp;& @DateTime.Now</div>");
var expected = NormalizeContent("The time is &&nbsp;& ");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var node = documentNode.FindDescendantNodes<HtmlContentIntermediateNode>().Single();
Assert.Equal(expected, GetHtmlContent(node));
Assert.True(node.IsEncoded());
}
[Fact]
public void Execute_MixedHtmlContent_NonAsciiCharacter_SetsEncoded()
{
// Arrange
var document = CreateDocument(@"
<div>ThĖ tĨme is @DateTime.Now</div>");
var expected = NormalizeContent("ThĖ tĨme is ");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var node = documentNode.FindDescendantNodes<HtmlContentIntermediateNode>().Single();
Assert.Equal(expected, GetHtmlContent(node));
Assert.True(node.IsEncoded());
}
[Fact]
public void Execute_MixedHtmlContent_HTMLEntity_DoesNotSetEncoded()
{
// Arrange
var document = CreateDocument(@"
<div>The time &equals; @DateTime.Now</div>");
var expected = NormalizeContent("The time = ");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var node = documentNode.FindDescendantNodes<HtmlContentIntermediateNode>().Single();
Assert.Equal(expected, GetHtmlContent(node));
Assert.False(node.IsEncoded());
}
[Fact]
public void Execute_MixedHtmlContent_MultipleHTMLEntities_DoesNotSetEncoded()
{
// Arrange
var document = CreateDocument(@"
<div>The time &equals;&nbsp;&#61;&#0x003D; @DateTime.Now</div>");
var expected = NormalizeContent("The time =\u00A0== ");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var node = documentNode.FindDescendantNodes<HtmlContentIntermediateNode>().Single();
Assert.Equal(expected, GetHtmlContent(node));
Assert.False(node.IsEncoded());
}
private string NormalizeContent(string content)
{
// Normalize newlines since we are testing lengths of things.
content = content.Replace("\r", "");
content = content.Replace("\n", "\r\n");
return content;
}
private RazorCodeDocument CreateDocument(string content)
{
// Normalize newlines since we are testing lengths of things.
content = content.Replace("\r", "");
content = content.Replace("\n", "\r\n");
var source = RazorSourceDocument.Create(content, "test.cshtml");
return ProjectEngine.CreateCodeDocumentCore(source, FileKinds.Component);
}
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument)
{
for (var i = 0; i < Engine.Phases.Count; i++)
{
var phase = Engine.Phases[i];
if (phase is IRazorCSharpLoweringPhase)
{
break;
}
phase.Execute(codeDocument);
}
var document = codeDocument.GetDocumentIntermediateNode();
Engine.Features.OfType<ComponentDocumentClassifierPass>().Single().Execute(codeDocument, document);
return document;
}
private static string GetHtmlContent(HtmlContentIntermediateNode node)
{
var builder = new StringBuilder();
var htmlTokens = node.Children.OfType<IntermediateToken>().Where(t => t.IsHtml);
foreach (var htmlToken in htmlTokens)
{
builder.Append(htmlToken.Content);
}
return builder.ToString();
}
}
}

View File

@ -0,0 +1,126 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal static class NodeAssert
{
public static HtmlAttributeIntermediateNode Attribute(IntermediateNode node, string attributeName, string attributeValue)
{
Assert.NotNull(node);
var attributeNode = Assert.IsType<HtmlAttributeIntermediateNode>(node);
Assert.Equal(attributeName, attributeNode.AttributeName);
var attributeValueNode = Assert.IsType<HtmlAttributeValueIntermediateNode>(Assert.Single(attributeNode.Children));
var actual = new StringBuilder();
for (var i = 0; i < attributeValueNode.Children.Count; i++)
{
var token = Assert.IsType<IntermediateToken>(attributeValueNode.Children[i]);
Assert.Equal(TokenKind.Html, token.Kind);
actual.Append(token.Content);
}
Assert.Equal(attributeValue, actual.ToString());
return attributeNode;
}
public static HtmlAttributeIntermediateNode Attribute(IntermediateNodeCollection nodes, string attributeName, string attributeValue)
{
Assert.NotNull(nodes);
return Attribute(Assert.Single(nodes), attributeName, attributeValue);
}
public static HtmlContentIntermediateNode Content(IntermediateNode node, string content, bool trim = true)
{
Assert.NotNull(node);
var contentNode = Assert.IsType<HtmlContentIntermediateNode>(node);
var actual = new StringBuilder();
for (var i = 0; i < contentNode.Children.Count; i++)
{
var token = Assert.IsType<IntermediateToken>(contentNode.Children[i]);
Assert.Equal(TokenKind.Html, token.Kind);
actual.Append(token.Content);
}
Assert.Equal(content, trim ? actual.ToString().Trim() : actual.ToString());
return contentNode;
}
public static HtmlContentIntermediateNode Content(IntermediateNodeCollection nodes, string content, bool trim = true)
{
Assert.NotNull(nodes);
return Content(Assert.Single(nodes), content, trim);
}
public static HtmlAttributeIntermediateNode CSharpAttribute(IntermediateNode node, string attributeName, string attributeValue)
{
Assert.NotNull(node);
var attributeNode = Assert.IsType<HtmlAttributeIntermediateNode>(node);
Assert.Equal(attributeName, attributeNode.AttributeName);
var attributeValueNode = Assert.IsType<CSharpExpressionAttributeValueIntermediateNode>(Assert.Single(attributeNode.Children));
var actual = new StringBuilder();
for (var i = 0; i < attributeValueNode.Children.Count; i++)
{
var token = Assert.IsType<IntermediateToken>(attributeValueNode.Children[i]);
Assert.Equal(TokenKind.CSharp, token.Kind);
actual.Append(token.Content);
}
Assert.Equal(attributeValue, actual.ToString());
return attributeNode;
}
public static HtmlAttributeIntermediateNode CSharpAttribute(IntermediateNodeCollection nodes, string attributeName, string attributeValue)
{
Assert.NotNull(nodes);
return Attribute(Assert.Single(nodes), attributeName, attributeValue);
}
public static MarkupElementIntermediateNode Element(IntermediateNode node, string tagName)
{
Assert.NotNull(node);
var elementNode = Assert.IsType<MarkupElementIntermediateNode>(node);
Assert.Equal(tagName, elementNode.TagName);
return elementNode;
}
public static MarkupElementIntermediateNode Element(IntermediateNodeCollection nodes, string tagName)
{
Assert.NotNull(nodes);
return Element(Assert.Single(nodes), tagName);
}
public static HtmlContentIntermediateNode Whitespace(IntermediateNode node)
{
Assert.NotNull(node);
var contentNode = Assert.IsType<HtmlContentIntermediateNode>(node);
for (var i = 0; i < contentNode.Children.Count; i++)
{
var token = Assert.IsType<IntermediateToken>(contentNode.Children[i]);
Assert.Equal(TokenKind.Html, token.Kind);
Assert.True(string.IsNullOrWhiteSpace(token.Content));
}
return contentNode;
}
public static HtmlContentIntermediateNode Whitespace(IntermediateNodeCollection nodes)
{
Assert.NotNull(nodes);
return Whitespace(Assert.Single(nodes));
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultAllowedChildTagDescriptorBuilderTest
{
[Fact]
public void Build_DisplayNameIsName()
{
// Arrange
var builder = new DefaultAllowedChildTagDescriptorBuilder(null);
builder.Name = "foo";
// Act
var descriptor = builder.Build();
// Assert
Assert.Equal("foo", descriptor.DisplayName);
}
}
}

View File

@ -0,0 +1,47 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultBoundAttributeDescriptorBuilderTest
{
[Fact]
public void DisplayName_SetsDescriptorsDisplayName()
{
// Arrange
var expectedDisplayName = "ExpectedDisplayName";
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder.DisplayName(expectedDisplayName);
// Act
var descriptor = builder.Build();
// Assert
Assert.Equal(expectedDisplayName, descriptor.DisplayName);
}
[Fact]
public void DisplayName_DefaultsToPropertyLookingDisplayName()
{
// Arrange
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
tagHelperBuilder.TypeName("TestTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.TypeName(typeof(int).FullName)
.PropertyName("SomeProperty");
// Act
var descriptor = builder.Build();
// Assert
Assert.Equal("int TestTagHelper.SomeProperty", descriptor.DisplayName);
}
}
}

View File

@ -0,0 +1,62 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
using static Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeAssert;
namespace Microsoft.AspNetCore.Razor.Language
{
// We're purposely lean on tests here because the functionality is well covered by
// integration tests, and is mostly implemented by the base class.
public class DefaultDocumentClassifierPassTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Latest;
[Fact]
public void Execute_IgnoresDocumentsWithDocumentKind()
{
// Arrange
var documentNode = new DocumentIntermediateNode()
{
DocumentKind = "ignore",
Options = RazorCodeGenerationOptions.CreateDefault(),
};
var pass = new DefaultDocumentClassifierPass();
pass.Engine = CreateProjectEngine().Engine;
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), documentNode);
// Assert
Assert.Equal("ignore", documentNode.DocumentKind);
NoChildren(documentNode);
}
[Fact]
public void Execute_CreatesClassStructure()
{
// Arrange
var documentNode = new DocumentIntermediateNode()
{
Options = RazorCodeGenerationOptions.CreateDefault(),
};
var pass = new DefaultDocumentClassifierPass();
pass.Engine = CreateProjectEngine().Engine;
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), documentNode);
// Assert
Assert.Equal("default", documentNode.DocumentKind);
var @namespace = SingleChild<NamespaceDeclarationIntermediateNode>(documentNode);
var @class = SingleChild<ClassDeclarationIntermediateNode>(@namespace);
var method = SingleChild<MethodDeclarationIntermediateNode>(@class);
NoChildren(method);
}
}
}

View File

@ -0,0 +1,54 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class ItemCollectionTest
{
[Fact]
public void Get_MissingValueReturnsNull()
{
// Arrange
var items = new ItemCollection();
// Act
var value = items["foo"];
// Assert
Assert.Null(value);
}
[Fact]
public void GetAndSet_ReturnsValue()
{
// Arrange
var items = new ItemCollection();
var expected = "bar";
items["foo"] = expected;
// Act
var value = items["foo"];
// Assert
Assert.Same(expected, value);
}
[Fact]
public void Set_CanSetValueToNull()
{
// Arrange
var items = new ItemCollection();
items["foo"] = "bar";
// Act
items["foo"] = null;
// Assert
Assert.Null(items["foo"]);
}
}
}

View File

@ -0,0 +1,87 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorCSharpLoweringPhaseTest
{
[Fact]
public void Execute_ThrowsForMissingDependency_IRDocument()
{
// Arrange
var phase = new DefaultRazorCSharpLoweringPhase();
var engine = RazorProjectEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.CreateEmpty();
codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source));
// Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
() => phase.Execute(codeDocument),
$"The '{nameof(DefaultRazorCSharpLoweringPhase)}' phase requires a '{nameof(DocumentIntermediateNode)}' " +
$"provided by the '{nameof(RazorCodeDocument)}'.");
}
[Fact]
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)}'.");
}
[Fact]
public void Execute_CollatesIRDocumentDiagnosticsFromSourceDocument()
{
// Arrange
var phase = new DefaultRazorCSharpLoweringPhase();
var engine = RazorProjectEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.Create("<p class=@(");
var options = RazorCodeGenerationOptions.CreateDefault();
var irDocument = new DocumentIntermediateNode()
{
DocumentKind = "test",
Target = CodeTarget.CreateDefault(codeDocument, options),
Options = options,
};
var expectedDiagnostic = RazorDiagnostic.Create(
new RazorDiagnosticDescriptor("1234", () => "I am an error.", RazorDiagnosticSeverity.Error),
new SourceSpan("SomeFile.cshtml", 11, 0, 11, 1));
irDocument.Diagnostics.Add(expectedDiagnostic);
codeDocument.SetDocumentIntermediateNode(irDocument);
// Act
phase.Execute(codeDocument);
// Assert
var csharpDocument = codeDocument.GetCSharpDocument();
var diagnostic = Assert.Single(csharpDocument.Diagnostics);
Assert.Same(expectedDiagnostic, diagnostic);
}
}
}

View File

@ -0,0 +1,47 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorCodeDocumentTest
{
[Fact]
public void Ctor()
{
// Arrange
var source = TestRazorSourceDocument.Create();
var imports = new RazorSourceDocument[]
{
TestRazorSourceDocument.Create(),
};
// Act
var code = new DefaultRazorCodeDocument(source, imports);
// Assert
Assert.Same(source, code.Source);
Assert.NotNull(code.Items);
Assert.NotSame(imports, code.Imports);
Assert.Collection(imports, d => Assert.Same(imports[0], d));
}
[Fact]
public void Ctor_AllowsNullForImports()
{
// Arrange
var source = TestRazorSourceDocument.Create();
// Act
var code = new DefaultRazorCodeDocument(source, imports: null);
// Assert
Assert.Same(source, code.Source);
Assert.NotNull(code.Items);
Assert.Empty(code.Imports);
}
}
}

View File

@ -0,0 +1,217 @@
// Copyright(c) .NET Foundation.All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorDiagnosticTest
{
[Fact]
public void DefaultRazorDiagnostic_Ctor()
{
// Arrange
var descriptor = new RazorDiagnosticDescriptor("RZ0000", () => "error", RazorDiagnosticSeverity.Error);
var span = new SourceSpan("test.cs", 15, 1, 8, 5);
// Act
var diagnostic = new DefaultRazorDiagnostic(descriptor, span, new object[0]);
// Assert
Assert.Equal("RZ0000", diagnostic.Id);
Assert.Equal(RazorDiagnosticSeverity.Error, diagnostic.Severity);
Assert.Equal(span, diagnostic.Span);
}
[Fact]
public void DefaultRazorDiagnostic_GetMessage()
{
// Arrange
var descriptor = new RazorDiagnosticDescriptor("RZ0000", () => "error", RazorDiagnosticSeverity.Error);
var span = new SourceSpan("test.cs", 15, 1, 8, 5);
var diagnostic = new DefaultRazorDiagnostic(descriptor, span, new object[0]);
// Act
var result = diagnostic.GetMessage();
// Assert
Assert.Equal("error", result);
}
[Fact]
public void DefaultRazorDiagnostic_GetMessage_WithArgs()
{
// Arrange
var descriptor = new RazorDiagnosticDescriptor("RZ0000", () => "this is an {0}", RazorDiagnosticSeverity.Error);
var span = new SourceSpan("test.cs", 15, 1, 8, 5);
var diagnostic = new DefaultRazorDiagnostic(descriptor, span, new[] { "error" });
// Act
var result = diagnostic.GetMessage();
// Assert
Assert.Equal("this is an error", result);
}
[Fact]
public void DefaultRazorDiagnostic_GetMessage_WithArgs_FormatProvider()
{
// Arrange
var descriptor = new RazorDiagnosticDescriptor("RZ0000", () => "this is an {0}", RazorDiagnosticSeverity.Error);
var span = new SourceSpan("test.cs", 15, 1, 8, 5);
var diagnostic = new DefaultRazorDiagnostic(descriptor, span, new object[] { 1.3m });
// Act
var result = diagnostic.GetMessage(new CultureInfo("fr-FR"));
// Assert
Assert.Equal("this is an 1,3", result);
}
[Fact]
public void DefaultRazorDiagnostic_ToString()
{
// Arrange
var descriptor = new RazorDiagnosticDescriptor("RZ0000", () => "this is an error", RazorDiagnosticSeverity.Error);
var span = new SourceSpan("test.cs", 15, 1, 8, 5);
var diagnostic = new DefaultRazorDiagnostic(descriptor, span, new object[0]);
// Act
var result = diagnostic.ToString();
// Assert
Assert.Equal("test.cs(2,9): Error RZ0000: this is an error", result);
}
[Fact]
public void DefaultRazorDiagnostic_ToString_FormatProvider()
{
// Arrange
var descriptor = new RazorDiagnosticDescriptor("RZ0000", () => "this is an {0}", RazorDiagnosticSeverity.Error);
var span = new SourceSpan("test.cs", 15, 1, 8, 5);
var diagnostic = new DefaultRazorDiagnostic(descriptor, span, new object[] { 1.3m });
// Act
var result = ((IFormattable)diagnostic).ToString("ignored", new CultureInfo("fr-FR"));
// Assert
Assert.Equal("test.cs(2,9): Error RZ0000: this is an 1,3", result);
}
[Fact]
public void DefaultRazorDiagnostic_Equals()
{
// Arrange
var descriptor = new RazorDiagnosticDescriptor("RZ0000", () => "this is an {0}", RazorDiagnosticSeverity.Error);
var span = new SourceSpan("test.cs", 15, 1, 8, 5);
var diagnostic1 = new DefaultRazorDiagnostic(descriptor, span, new object[0]);
var diagnostic2 = new DefaultRazorDiagnostic(descriptor, span, new object[0]);
// Act
var result = diagnostic1.Equals(diagnostic2);
// Assert
Assert.True(result);
}
[Fact]
public void DefaultRazorDiagnostic_NotEquals_DifferentLocation()
{
// Arrange
var descriptor = new RazorDiagnosticDescriptor("RZ0000", () => "this is an {0}", RazorDiagnosticSeverity.Error);
var span1 = new SourceSpan("test.cs", 15, 1, 8, 5);
var span2 = new SourceSpan("test.cs", 15, 1, 8, 3);
var diagnostic1 = new DefaultRazorDiagnostic(descriptor, span1, new object[0]);
var diagnostic2 = new DefaultRazorDiagnostic(descriptor, span2, new object[0]);
// Act
var result = diagnostic1.Equals(diagnostic2);
// Assert
Assert.False(result);
}
[Fact]
public void DefaultRazorDiagnostic_NotEquals_DifferentId()
{
// Arrange
var descriptor1 = new RazorDiagnosticDescriptor("RZ0001", () => "this is an {0}", RazorDiagnosticSeverity.Error);
var descriptor2 = new RazorDiagnosticDescriptor("RZ0002", () => "this is an {0}", RazorDiagnosticSeverity.Error);
var span = new SourceSpan("test.cs", 15, 1, 8, 5);
var diagnostic1 = new DefaultRazorDiagnostic(descriptor1, span, new object[0]);
var diagnostic2 = new DefaultRazorDiagnostic(descriptor2, span, new object[0]);
// Act
var result = diagnostic1.Equals(diagnostic2);
// Assert
Assert.False(result);
}
[Fact]
public void DefaultRazorDiagnostic_HashCodesEqual()
{
// Arrange
var descriptor = new RazorDiagnosticDescriptor("RZ0000", () => "this is an {0}", RazorDiagnosticSeverity.Error);
var span = new SourceSpan("test.cs", 15, 1, 8, 5);
var diagnostic1 = new DefaultRazorDiagnostic(descriptor, span, new object[0]);
var diagnostic2 = new DefaultRazorDiagnostic(descriptor, span, new object[0]);
// Act
var result = diagnostic1.GetHashCode() == diagnostic2.GetHashCode();
// Assert
Assert.True(result);
}
[Fact]
public void DefaultRazorDiagnostic_HashCodesNotEqual_DifferentLocation()
{
// Arrange
var descriptor = new RazorDiagnosticDescriptor("RZ0000", () => "this is an {0}", RazorDiagnosticSeverity.Error);
var span1 = new SourceSpan("test.cs", 15, 1, 8, 5);
var span2 = new SourceSpan("test.cs", 15, 1, 8, 3);
var diagnostic1 = new DefaultRazorDiagnostic(descriptor, span1, new object[0]);
var diagnostic2 = new DefaultRazorDiagnostic(descriptor, span2, new object[0]);
// Act
var result = diagnostic1.GetHashCode() == diagnostic2.GetHashCode();
// Assert
Assert.False(result);
}
[Fact]
public void DefaultRazorDiagnostic_HashCodesNotEqual_DifferentId()
{
// Arrange
var descriptor1 = new RazorDiagnosticDescriptor("RZ0001", () => "this is an {0}", RazorDiagnosticSeverity.Error);
var descriptor2 = new RazorDiagnosticDescriptor("RZ0002", () => "this is an {0}", RazorDiagnosticSeverity.Error);
var span = new SourceSpan("test.cs", 15, 1, 8, 5);
var diagnostic1 = new DefaultRazorDiagnostic(descriptor1, span, new object[0]);
var diagnostic2 = new DefaultRazorDiagnostic(descriptor2, span, new object[0]);
// Act
var result = diagnostic1.GetHashCode() == diagnostic2.GetHashCode();
// Assert
Assert.False(result);
}
}
}

View File

@ -0,0 +1,102 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Testing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorDirectiveClassifierPhaseTest
{
[Fact]
public void OnInitialized_OrdersPassesInAscendingOrder()
{
// Arrange & Act
var phase = new DefaultRazorDirectiveClassifierPhase();
var first = Mock.Of<IRazorDirectiveClassifierPass>(p => p.Order == 15);
var second = Mock.Of<IRazorDirectiveClassifierPass>(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));
}
[Fact]
public void Execute_ThrowsForMissingDependency()
{
// Arrange
var phase = new DefaultRazorDirectiveClassifierPhase();
var engine = RazorProjectEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.CreateEmpty();
// Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
() => phase.Execute(codeDocument),
$"The '{nameof(DefaultRazorDirectiveClassifierPhase)}' phase requires a '{nameof(DocumentIntermediateNode)}' " +
$"provided by the '{nameof(RazorCodeDocument)}'.");
}
[Fact]
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<IRazorDirectiveClassifierPass>(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<IRazorDirectiveClassifierPass>(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 DefaultRazorDirectiveClassifierPhase();
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]);
}
}
}

View File

@ -0,0 +1,102 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Testing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorDocumentClassifierPhaseTest
{
[Fact]
public void OnInitialized_OrdersPassesInAscendingOrder()
{
// Arrange & Act
var phase = new DefaultRazorDocumentClassifierPhase();
var first = Mock.Of<IRazorDocumentClassifierPass>(p => p.Order == 15);
var second = Mock.Of<IRazorDocumentClassifierPass>(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));
}
[Fact]
public void Execute_ThrowsForMissingDependency()
{
// Arrange
var phase = new DefaultRazorDocumentClassifierPhase();
var engine = RazorProjectEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.CreateEmpty();
// Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
() => phase.Execute(codeDocument),
$"The '{nameof(DefaultRazorDocumentClassifierPhase)}' phase requires a '{nameof(DocumentIntermediateNode)}' " +
$"provided by the '{nameof(RazorCodeDocument)}'.");
}
[Fact]
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<IRazorDocumentClassifierPass>(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<IRazorDocumentClassifierPass>(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 DefaultRazorDocumentClassifierPhase();
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]);
}
}
}

View File

@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorEngineBuilderTest
{
[Fact]
public void Build_AddsFeaturesAndPhases()
{
// Arrange
var builder = new DefaultRazorEngineBuilder(designTime: false);
builder.Features.Add(Mock.Of<IRazorEngineFeature>());
builder.Features.Add(Mock.Of<IRazorEngineFeature>());
builder.Phases.Add(Mock.Of<IRazorEnginePhase>());
builder.Phases.Add(Mock.Of<IRazorEnginePhase>());
var features = builder.Features.ToArray();
var phases = builder.Phases.ToArray();
// Act
var engine = builder.Build();
// Assert
Assert.Collection(
engine.Features,
f => Assert.Same(features[0], f),
f => Assert.Same(features[1], f));
Assert.Collection(
engine.Phases,
p => Assert.Same(phases[0], p),
p => Assert.Same(phases[1], p));
}
}
}

View File

@ -0,0 +1,72 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorEngineTest
{
[Fact]
public void Ctor_InitializesPhasesAndFeatures()
{
// Arrange
var features = new IRazorEngineFeature[]
{
Mock.Of<IRazorEngineFeature>(),
Mock.Of<IRazorEngineFeature>(),
};
var phases = new IRazorEnginePhase[]
{
Mock.Of<IRazorEnginePhase>(),
Mock.Of<IRazorEnginePhase>(),
};
// Act
var engine = new DefaultRazorEngine(features, phases);
// Assert
for (var i = 0; i < features.Length; i++)
{
Assert.Same(engine, features[i].Engine);
}
for (var i = 0; i < phases.Length; i++)
{
Assert.Same(engine, phases[i].Engine);
}
}
[Fact]
public void Process_CallsAllPhases()
{
// Arrange
var features = new IRazorEngineFeature[]
{
Mock.Of<IRazorEngineFeature>(),
Mock.Of<IRazorEngineFeature>(),
};
var phases = new IRazorEnginePhase[]
{
Mock.Of<IRazorEnginePhase>(),
Mock.Of<IRazorEnginePhase>(),
};
var engine = new DefaultRazorEngine(features, phases);
var document = TestRazorCodeDocument.CreateEmpty();
// Act
engine.Process(document);
// Assert
for (var i = 0; i < phases.Length; i++)
{
var mock = Mock.Get(phases[i]);
mock.Verify(p => p.Execute(document), Times.Once());
}
}
}
}

View File

@ -0,0 +1,541 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Moq;
using Xunit;
using static Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeAssert;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorIntermediateNodeLoweringPhaseIntegrationTest
{
[Fact]
public void Lower_SetsOptions_Defaults()
{
// Arrange
var codeDocument = TestRazorCodeDocument.CreateEmpty();
// Act
var documentNode = Lower(codeDocument);
// Assert
Assert.NotNull(documentNode.Options);
Assert.False(documentNode.Options.DesignTime);
Assert.Equal(4, documentNode.Options.IndentSize);
Assert.False(documentNode.Options.IndentWithTabs);
}
[Fact]
public void Lower_SetsOptions_RunsConfigureCallbacks()
{
// Arrange
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var callback = new Mock<IConfigureRazorCodeGenerationOptionsFeature>();
callback
.Setup(c => c.Configure(It.IsAny<RazorCodeGenerationOptionsBuilder>()))
.Callback<RazorCodeGenerationOptionsBuilder>(o =>
{
o.IndentSize = 17;
o.IndentWithTabs = true;
o.SuppressChecksum = true;
});
// Act
var documentNode = Lower(
codeDocument,
builder: b =>
{
b.Features.Add(callback.Object);
},
designTime: true);
// Assert
Assert.NotNull(documentNode.Options);
Assert.True(documentNode.Options.DesignTime);
Assert.Equal(17, documentNode.Options.IndentSize);
Assert.True(documentNode.Options.IndentWithTabs);
Assert.True(documentNode.Options.SuppressChecksum);
}
[Fact]
public void Lower_HelloWorld()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("Hello, World!");
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(documentNode,
n => Html("Hello, World!", n));
}
[Fact]
public void Lower_HtmlWithDataDashAttributes()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"
<html>
<body>
<span data-val=""@Hello"" />
</body>
</html>");
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(documentNode,
n => Html(
@"
<html>
<body>
<span data-val=""", n),
n => CSharpExpression("Hello", n),
n => Html(@""" />
</body>
</html>", n));
}
[Fact]
public void Lower_HtmlWithConditionalAttributes()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"
<html>
<body>
<span val=""@Hello World"" />
</body>
</html>");
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(documentNode,
n => Html(
@"
<html>
<body>
<span", n),
n => ConditionalAttribute(
prefix: " val=\"",
name: "val",
suffix: "\"",
node: n,
valueValidators: new Action<IntermediateNode>[]
{
value => CSharpExpressionAttributeValue(string.Empty, "Hello", value),
value => LiteralAttributeValue(" ", "World", value)
}),
n => Html(@" />
</body>
</html>", n));
}
[Fact]
public void Lower_WithFunctions()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@functions { public int Foo { get; set; }}");
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(documentNode,
n => Directive(
"functions",
n,
c => Assert.IsType<CSharpCodeIntermediateNode>(c)));
}
[Fact]
public void Lower_WithUsing()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@using System");
var expectedSourceLocation = new SourceSpan(codeDocument.Source.FilePath, 1, 0, 1, 12);
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(documentNode,
n =>
{
Using("System", n);
Assert.Equal(expectedSourceLocation, n.Source);
});
}
[Fact]
public void Lower_TagHelpers()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly
<span val=""@Hello World""></span>");
var tagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "span",
typeName: "SpanTagHelper",
assemblyName: "TestAssembly")
};
// Act
var documentNode = Lower(codeDocument, tagHelpers: tagHelpers);
// Assert
Children(documentNode,
n => Directive(
SyntaxConstants.CSharp.AddTagHelperKeyword,
n,
v => DirectiveToken(DirectiveTokenKind.String, "*, TestAssembly", v)),
n => TagHelper(
"span",
TagMode.StartTagAndEndTag,
tagHelpers,
n,
c => Assert.IsType<TagHelperBodyIntermediateNode>(c),
c => TagHelperHtmlAttribute(
"val",
AttributeStructure.DoubleQuotes,
c,
v => CSharpExpressionAttributeValue(string.Empty, "Hello", v),
v => LiteralAttributeValue(" ", "World", v))));
}
[Fact]
public void Lower_TagHelpers_WithPrefix()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly
@tagHelperPrefix cool:
<cool:span val=""@Hello World""></cool:span>");
var tagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "span",
typeName: "SpanTagHelper",
assemblyName: "TestAssembly")
};
// Act
var documentNode = Lower(codeDocument, tagHelpers: tagHelpers);
// Assert
Children(documentNode,
n => Directive(
SyntaxConstants.CSharp.AddTagHelperKeyword,
n,
v => DirectiveToken(DirectiveTokenKind.String, "*, TestAssembly", v)),
n => Directive(
SyntaxConstants.CSharp.TagHelperPrefixKeyword,
n,
v => DirectiveToken(DirectiveTokenKind.String, "cool:", v)),
n => TagHelper(
"span", // Note: this is span not cool:span
TagMode.StartTagAndEndTag,
tagHelpers,
n,
c => Assert.IsType<TagHelperBodyIntermediateNode>(c),
c => TagHelperHtmlAttribute(
"val",
AttributeStructure.DoubleQuotes,
c,
v => CSharpExpressionAttributeValue(string.Empty, "Hello", v),
v => LiteralAttributeValue(" ", "World", v))));
}
[Fact]
public void Lower_TagHelper_InSection()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly
@section test {
<span val=""@Hello World""></span>
}");
var tagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "span",
typeName: "SpanTagHelper",
assemblyName: "TestAssembly")
};
// Act
var documentNode = Lower(codeDocument, tagHelpers: tagHelpers);
// Assert
Children(
documentNode,
n => Directive(
SyntaxConstants.CSharp.AddTagHelperKeyword,
n,
v => DirectiveToken(DirectiveTokenKind.String, "*, TestAssembly", v)),
n => Directive(
"section",
n,
c1 => DirectiveToken(DirectiveTokenKind.Member, "test", c1),
c1 => Html(Environment.NewLine, c1),
c1 => TagHelper(
"span",
TagMode.StartTagAndEndTag,
tagHelpers,
c1,
c2 => Assert.IsType<TagHelperBodyIntermediateNode>(c2),
c2 => TagHelperHtmlAttribute(
"val",
AttributeStructure.DoubleQuotes,
c2,
v => CSharpExpressionAttributeValue(string.Empty, "Hello", v),
v => LiteralAttributeValue(" ", "World", v))),
c1 => Html(Environment.NewLine, c1)));
}
[Fact]
public void Lower_TagHelpersWithBoundAttribute()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"@addTagHelper *, TestAssembly
<input bound='foo' />");
var tagHelpers = new[]
{
CreateTagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("bound")
.PropertyName("FooProp")
.TypeName("System.String"),
})
};
// Act
var documentNode = Lower(codeDocument, tagHelpers: tagHelpers);
// Assert
Children(
documentNode,
n => Directive(
SyntaxConstants.CSharp.AddTagHelperKeyword,
n,
v => DirectiveToken(DirectiveTokenKind.String, "*, TestAssembly", v)),
n => TagHelper(
"input",
TagMode.SelfClosing,
tagHelpers,
n,
c => Assert.IsType<TagHelperBodyIntermediateNode>(c),
c => SetTagHelperProperty(
"bound",
"FooProp",
AttributeStructure.SingleQuotes,
c,
v => Html("foo", v))));
}
[Fact]
public void Lower_WithImports_Using()
{
// Arrange
var source = TestRazorSourceDocument.Create(@"@using System.Threading.Tasks
<p>Hi!</p>");
var imports = new[]
{
TestRazorSourceDocument.Create("@using System.Globalization"),
TestRazorSourceDocument.Create("@using System.Text"),
};
var codeDocument = TestRazorCodeDocument.Create(source, imports);
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(
documentNode,
n => Using("System.Globalization", n),
n => Using("System.Text", n),
n => Using("System.Threading.Tasks", n),
n => Html("<p>Hi!</p>", n));
}
[Fact]
public void Lower_WithImports_AllowsIdenticalNamespacesInPrimaryDocument()
{
// Arrange
var source = TestRazorSourceDocument.Create(@"@using System.Threading.Tasks
@using System.Threading.Tasks");
var imports = new[]
{
TestRazorSourceDocument.Create("@using System.Threading.Tasks"),
};
var codeDocument = TestRazorCodeDocument.Create(source, imports);
// Act
var documentNode = Lower(codeDocument);
// Assert
Children(
documentNode,
n => Using("System.Threading.Tasks", n),
n => Using("System.Threading.Tasks", n));
}
[Fact]
public void Lower_WithMultipleImports_SingleLineFileScopedSinglyOccurring()
{
// Arrange
var source = TestRazorSourceDocument.Create("<p>Hi!</p>");
var imports = new[]
{
TestRazorSourceDocument.Create("@test value1"),
TestRazorSourceDocument.Create("@test value2"),
};
var codeDocument = TestRazorCodeDocument.Create(source, imports);
// Act
var documentNode = Lower(codeDocument, b =>
{
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, "value2", c)),
n => Html("<p>Hi!</p>", n));
}
[Fact]
public void Lower_WithImports_IgnoresBlockDirective()
{
// Arrange
var source = TestRazorSourceDocument.Create("<p>Hi!</p>");
var imports = new[]
{
TestRazorSourceDocument.Create("@block token { }"),
};
var codeDocument = TestRazorCodeDocument.Create(source, imports);
// Act
var documentNode = Lower(codeDocument, b =>
{
b.AddDirective(DirectiveDescriptor.CreateDirective("block", DirectiveKind.RazorBlock, d => d.AddMemberToken()));
});
// Assert
Children(
documentNode,
n => Html("<p>Hi!</p>", n));
}
private DocumentIntermediateNode Lower(
RazorCodeDocument codeDocument,
Action<RazorProjectEngineBuilder> builder = null,
IEnumerable<TagHelperDescriptor> tagHelpers = null,
bool designTime = false)
{
tagHelpers = tagHelpers ?? new TagHelperDescriptor[0];
Action<RazorProjectEngineBuilder> configureEngine = b =>
{
builder?.Invoke(b);
SectionDirective.Register(b);
b.AddTagHelpers(tagHelpers);
b.Features.Add(new DesignTimeOptionsFeature(designTime));
};
var projectEngine = RazorProjectEngine.Create(configureEngine);
for (var i = 0; i < projectEngine.Phases.Count; i++)
{
var phase = projectEngine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorIntermediateNodeLoweringPhase)
{
break;
}
}
var documentNode = codeDocument.GetDocumentIntermediateNode();
Assert.NotNull(documentNode);
return documentNode;
}
private static TagHelperDescriptor CreateTagHelperDescriptor(
string tagName,
string typeName,
string assemblyName,
IEnumerable<Action<BoundAttributeDescriptorBuilder>> attributes = null)
{
var builder = TagHelperDescriptorBuilder.Create(typeName, assemblyName);
builder.TypeName(typeName);
if (attributes != null)
{
foreach (var attributeBuilder in attributes)
{
builder.BoundAttributeDescriptor(attributeBuilder);
}
}
builder.TagMatchingRuleDescriptor(ruleBuilder => ruleBuilder.RequireTagName(tagName));
var descriptor = builder.Build();
return descriptor;
}
private class DesignTimeOptionsFeature : IConfigureRazorParserOptionsFeature, IConfigureRazorCodeGenerationOptionsFeature
{
private bool _designTime;
public DesignTimeOptionsFeature(bool designTime)
{
_designTime = designTime;
}
public int Order { get; }
public RazorEngine Engine { get; set; }
public void Configure(RazorParserOptionsBuilder options)
{
options.SetDesignTime(_designTime);
}
public void Configure(RazorCodeGenerationOptionsBuilder options)
{
options.SetDesignTime(_designTime);
}
}
}
}

View File

@ -0,0 +1,298 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
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 = 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("<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 = 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);
}
[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 = 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 importSource1 = TestRazorSourceDocument.Create("@custom \"hello\"", filePath: "import1.cshtml");
var importSource2 = TestRazorSourceDocument.Create("@custom \"world\"", filePath: "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 = 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);
}
[Fact]
public void Execute_ErrorsForCodeBlockFileScopedSinglyOccurringDirectives()
{
// Arrange
var directive = DirectiveDescriptor.CreateCodeBlockDirective("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);
}
[Fact]
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);
}
[Fact]
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)}'.");
}
[Fact]
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());
}
[Fact]
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());
},
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());
});
}
}
}

View File

@ -0,0 +1,102 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Testing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorOptimizationPhaseTest
{
[Fact]
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));
}
[Fact]
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)}'.");
}
[Fact]
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]);
}
}
}

View File

@ -0,0 +1,93 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorParsingPhaseTest
{
[Fact]
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());
}
[Fact]
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);
}
[Fact]
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); });
}
private class MyParserOptionsFeature : RazorEngineFeatureBase, IConfigureRazorParserOptionsFeature
{
public int Order { get; }
public void Configure(RazorParserOptionsBuilder options)
{
options.Directives.Add(DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine));
}
}
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorProjectEngineBuilderTest
{
[Fact]
public void Build_AddsFeaturesToRazorEngine()
{
// Arrange
var builder = new DefaultRazorProjectEngineBuilder(RazorConfiguration.Default, Mock.Of<RazorProjectFileSystem>());
builder.Features.Add(Mock.Of<IRazorEngineFeature>());
builder.Features.Add(Mock.Of<IRazorEngineFeature>());
builder.Features.Add(Mock.Of<IRazorProjectEngineFeature>());
var features = builder.Features.ToArray();
// Act
var projectEngine = builder.Build();
// Assert
Assert.Collection(projectEngine.Engine.Features,
feature => Assert.Same(features[0], feature),
feature => Assert.Same(features[1], feature));
}
[Fact]
public void Build_AddsPhasesToRazorEngine()
{
// Arrange
var builder = new DefaultRazorProjectEngineBuilder(RazorConfiguration.Default, Mock.Of<RazorProjectFileSystem>());
builder.Phases.Add(Mock.Of<IRazorEnginePhase>());
builder.Phases.Add(Mock.Of<IRazorEnginePhase>());
var phases = builder.Phases.ToArray();
// Act
var projectEngine = builder.Build();
// Assert
Assert.Collection(projectEngine.Engine.Phases,
phase => Assert.Same(phases[0], phase),
phase => Assert.Same(phases[1], phase));
}
[Fact]
public void Build_CreatesProjectEngineWithFileSystem()
{
// Arrange
var fileSystem = Mock.Of<RazorProjectFileSystem>();
var builder = new DefaultRazorProjectEngineBuilder(RazorConfiguration.Default, fileSystem);
// Act
var projectEngine = builder.Build();
// Assert
Assert.Same(fileSystem, projectEngine.FileSystem);
}
}
}

View File

@ -0,0 +1,351 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorProjectEngineIntegrationTest
{
[Fact]
public void Process_SetsOptions_Runtime()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
var parserOptions = codeDocument.GetParserOptions();
Assert.False(parserOptions.DesignTime);
var codeGenerationOptions = codeDocument.GetCodeGenerationOptions();
Assert.False(codeGenerationOptions.DesignTime);
Assert.False(codeGenerationOptions.SuppressChecksum);
Assert.False(codeGenerationOptions.SuppressMetadataAttributes);
}
[Fact]
public void ProcessDesignTime_SetsOptions_DesignTime()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.ProcessDesignTime(projectItem);
// Assert
var parserOptions = codeDocument.GetParserOptions();
Assert.True(parserOptions.DesignTime);
var codeGenerationOptions = codeDocument.GetCodeGenerationOptions();
Assert.True(codeGenerationOptions.DesignTime);
Assert.True(codeGenerationOptions.SuppressChecksum);
Assert.True(codeGenerationOptions.SuppressMetadataAttributes);
}
[Fact]
public void Process_GetsImportsFromFeature()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var testImport = Mock.Of<RazorProjectItem>(i => i.Read() == new MemoryStream() && i.FilePath == "testvalue" && i.Exists == true);
var importFeature = new Mock<IImportProjectFeature>();
importFeature
.Setup(feature => feature.GetImports(It.IsAny<RazorProjectItem>()))
.Returns(new[] { testImport });
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty, builder =>
{
builder.SetImportFeature(importFeature.Object);
});
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
var import = Assert.Single(codeDocument.Imports);
Assert.Equal("testvalue", import.FilePath);
}
[Fact]
public void Process_GetsImportsFromFeature_MultipleFeatures()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var testImport1 = Mock.Of<RazorProjectItem>(i => i.Read() == new MemoryStream() && i.FilePath == "testvalue1" && i.Exists == true);
var importFeature1 = new Mock<IImportProjectFeature>();
importFeature1
.Setup(feature => feature.GetImports(It.IsAny<RazorProjectItem>()))
.Returns(new[] { testImport1 });
var testImport2 = Mock.Of<RazorProjectItem>(i => i.Read() == new MemoryStream() && i.FilePath == "testvalue2" && i.Exists == true);
var importFeature2 = new Mock<IImportProjectFeature>();
importFeature2
.Setup(feature => feature.GetImports(It.IsAny<RazorProjectItem>()))
.Returns(new[] { testImport2 });
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty, builder =>
{
builder.Features.Add(importFeature1.Object);
builder.Features.Add(importFeature2.Object);
});
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
Assert.Collection(codeDocument.Imports,
i => Assert.Equal("testvalue1", i.FilePath),
i => Assert.Equal("testvalue2", i.FilePath));
}
[Fact]
public void Process_GeneratesCodeDocumentWithValidCSharpDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
var csharpDocument = codeDocument.GetCSharpDocument();
Assert.NotNull(csharpDocument);
Assert.Empty(csharpDocument.Diagnostics);
}
[Fact]
public void Process_WithImportsAndTagHelpers_SetsOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var importItem = new TestRazorProjectItem("_import.cshtml");
var expectedImports = new[] { RazorSourceDocument.ReadFrom(importItem) };
var expectedTagHelpers = new[]
{
TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly").Build(),
TagHelperDescriptorBuilder.Create("Test2TagHelper", "TestAssembly").Build(),
};
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(RazorSourceDocument.ReadFrom(projectItem), "test", expectedImports, expectedTagHelpers);
// Assert
var tagHelpers = codeDocument.GetTagHelpers();
Assert.Same(expectedTagHelpers, tagHelpers);
Assert.Equal(expectedImports, codeDocument.Imports);
}
[Fact]
public void Process_WithFileKind_SetsOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(RazorSourceDocument.ReadFrom(projectItem), "test", Array.Empty<RazorSourceDocument>(), tagHelpers: null);
// Assert
var actual = codeDocument.GetFileKind();
Assert.Equal("test", actual);
}
[Fact]
public void Process_WithNullTagHelpers_SetsOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(RazorSourceDocument.ReadFrom(projectItem), "test", Array.Empty<RazorSourceDocument>(), tagHelpers: null);
// Assert
var tagHelpers = codeDocument.GetTagHelpers();
Assert.Null(tagHelpers);
}
[Fact]
public void Process_SetsNullTagHelpersOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
var tagHelpers = codeDocument.GetTagHelpers();
Assert.Null(tagHelpers);
}
[Fact]
public void Process_SetsInferredFileKindOnCodeDocument_MvcFile()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
var actual = codeDocument.GetFileKind();
Assert.Same(FileKinds.Legacy, actual);
}
[Fact]
public void Process_SetsInferredFileKindOnCodeDocument_Component()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.razor");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
var actual = codeDocument.GetFileKind();
Assert.Same(FileKinds.Component, actual);
}
[Fact]
public void Process_WithNullImports_SetsEmptyListOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.Process(RazorSourceDocument.ReadFrom(projectItem), "test", importSources: null, tagHelpers: null);
// Assert
Assert.Empty(codeDocument.Imports);
}
[Fact]
public void ProcessDesignTime_WithImportsAndTagHelpers_SetsOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var importItem = new TestRazorProjectItem("_import.cshtml");
var expectedImports = new[] { RazorSourceDocument.ReadFrom(importItem) };
var expectedTagHelpers = new[]
{
TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly").Build(),
TagHelperDescriptorBuilder.Create("Test2TagHelper", "TestAssembly").Build(),
};
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.ProcessDesignTime(RazorSourceDocument.ReadFrom(projectItem), "test", expectedImports, expectedTagHelpers);
// Assert
var tagHelpers = codeDocument.GetTagHelpers();
Assert.Same(expectedTagHelpers, tagHelpers);
Assert.Equal(expectedImports, codeDocument.Imports);
}
[Fact]
public void ProcessDesignTime_WithNullTagHelpers_SetsOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.ProcessDesignTime(RazorSourceDocument.ReadFrom(projectItem), "test", Array.Empty<RazorSourceDocument>(), tagHelpers: null);
// Assert
var tagHelpers = codeDocument.GetTagHelpers();
Assert.Null(tagHelpers);
}
[Fact]
public void ProcessDesignTime_SetsInferredFileKindOnCodeDocument_MvcFile()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.ProcessDesignTime(projectItem);
// Assert
var actual = codeDocument.GetFileKind();
Assert.Same(FileKinds.Legacy, actual);
}
[Fact]
public void ProcessDesignTime_SetsInferredFileKindOnCodeDocument_Component()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.razor");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.ProcessDesignTime(projectItem);
// Assert
var actual = codeDocument.GetFileKind();
Assert.Same(FileKinds.Component, actual);
}
[Fact]
public void ProcessDesignTime_SetsNullTagHelpersOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.ProcessDesignTime(projectItem);
// Assert
var tagHelpers = codeDocument.GetTagHelpers();
Assert.Null(tagHelpers);
}
[Fact]
public void ProcessDesignTime_WithNullImports_SetsEmptyListOnCodeDocument()
{
// Arrange
var projectItem = new TestRazorProjectItem("Index.cshtml");
var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, TestRazorProjectFileSystem.Empty);
// Act
var codeDocument = projectEngine.ProcessDesignTime(RazorSourceDocument.ReadFrom(projectItem), "test", importSources: null, tagHelpers: null);
// Assert
Assert.Empty(codeDocument.Imports);
}
}
}

View File

@ -0,0 +1,62 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorProjectEngineTest
{
[Fact]
public void GetImportSourceDocuments_DoesNotIncludeNonExistentItems()
{
// Arrange
var existingItem = new TestRazorProjectItem("Index.cshtml");
var nonExistentItem = Mock.Of<RazorProjectItem>(item => item.Exists == false);
var items = new[] { existingItem, nonExistentItem };
// Act
var sourceDocuments = DefaultRazorProjectEngine.GetImportSourceDocuments(items);
// Assert
var sourceDocument = Assert.Single(sourceDocuments);
Assert.Equal(existingItem.FilePath, sourceDocument.FilePath);
}
[Fact]
public void GetImportSourceDocuments_UnreadableItem_Throws()
{
// Arrange
var projectItem = new Mock<RazorProjectItem>(MockBehavior.Strict);
projectItem.SetupGet(p => p.Exists).Returns(true);
projectItem.SetupGet(p => p.PhysicalPath).Returns("path/to/file.cshtml");
projectItem.Setup(p => p.Read()).Throws(new IOException("Couldn't read file."));
var items = new[] { projectItem.Object };
// Act & Assert
var exception = Assert.Throws<IOException>(() => DefaultRazorProjectEngine.GetImportSourceDocuments(items));
Assert.Equal("Couldn't read file.", exception.Message);
}
[Fact]
public void GetImportSourceDocuments_WithSuppressExceptions_UnreadableItem_DoesNotThrow()
{
// Arrange
var projectItem = new Mock<RazorProjectItem>(MockBehavior.Strict);
projectItem.SetupGet(p => p.Exists).Returns(true);
projectItem.SetupGet(p => p.PhysicalPath).Returns("path/to/file.cshtml");
projectItem.SetupGet(p => p.FilePath).Returns("path/to/file.cshtml");
projectItem.SetupGet(p => p.RelativePhysicalPath).Returns("path/to/file.cshtml");
projectItem.Setup(p => p.Read()).Throws(new IOException("Couldn't read file."));
var items = new[] { projectItem.Object };
// Act
var sourceDocuments = DefaultRazorProjectEngine.GetImportSourceDocuments(items, suppressExceptions: true);
// Assert - Does not throw
Assert.Empty(sourceDocuments);
}
}
}

View File

@ -0,0 +1,298 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorProjectFileSystemTest
{
private static string TestFolder { get; } = Path.Combine(
TestProject.GetProjectDirectory(typeof(DefaultRazorProjectFileSystemTest)),
"TestFiles",
"DefaultRazorProjectFileSystem");
[Theory]
[InlineData(null)]
[InlineData("")]
public void NormalizeAndEnsureValidPath_ThrowsIfPathIsNullOrEmpty(string path)
{
// Arrange
var fileSystem = new TestRazorProjectFileSystem("C:/some/test/path/root");
// Act and Assert
ExceptionAssert.ThrowsArgumentNullOrEmptyString(() => fileSystem.NormalizeAndEnsureValidPath(path), "path");
}
[Fact]
public void NormalizeAndEnsureValidPath_NormalizesToAbsolutePath()
{
// Arrange
var fileSystem = new TestRazorProjectFileSystem("C:/some/test/path/root");
// Act
var absolutePath = fileSystem.NormalizeAndEnsureValidPath("file.cshtml");
// Assert
Assert.Equal("C:/some/test/path/root/file.cshtml", absolutePath);
}
[Fact]
public void NormalizeAndEnsureValidPath_FileFromNetworkShare__WindowsStyle_NormalizesToAbsolutePath()
{
// Arrange
var fileSystem = new TestRazorProjectFileSystem("//some/network/share/root");
// Act
var absolutePath = fileSystem.NormalizeAndEnsureValidPath("\\\\some\\network\\share\\root\\file.cshtml");
// Assert
Assert.Equal("//some/network/share/root/file.cshtml", absolutePath);
}
[Fact]
public void NormalizeAndEnsureValidPath_FileFromNetworkShare_UnixStyle_NormalizesToAbsolutePath()
{
// Arrange
var fileSystem = new TestRazorProjectFileSystem("//some/network/share/root");
// Act
var absolutePath = fileSystem.NormalizeAndEnsureValidPath("//some/network/share/root/file.cshtml");
// Assert
Assert.Equal("//some/network/share/root/file.cshtml", absolutePath);
}
[Fact]
public void NormalizeAndEnsureValidPath_NormalizesToAbsolutePathWithoutForwardSlash()
{
// Arrange
var fileSystem = new TestRazorProjectFileSystem("C:/some/test/path/root");
// Act
var absolutePath = fileSystem.NormalizeAndEnsureValidPath("/file.cshtml");
// Assert
Assert.Equal("C:/some/test/path/root/file.cshtml", absolutePath);
}
[Fact]
public void NormalizeAndEnsureValidPath_NormalizesToForwardSlashes()
{
// Arrange
var fileSystem = new TestRazorProjectFileSystem(@"C:\some\test\path\root");
// Act
var absolutePath = fileSystem.NormalizeAndEnsureValidPath(@"something\file.cshtml");
// Assert
Assert.Equal("C:/some/test/path/root/something/file.cshtml", absolutePath);
}
[Fact]
public void EnumerateItems_DiscoversAllCshtmlFiles()
{
// Arrange
var fileSystem = new DefaultRazorProjectFileSystem(TestFolder);
// Act
var items = fileSystem.EnumerateItems("/");
// Assert
Assert.Collection(
items.OrderBy(f => f.FilePath, StringComparer.Ordinal),
item =>
{
Assert.Equal("/Home.cshtml", item.FilePath);
Assert.Equal("/", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "Home.cshtml"), item.PhysicalPath);
Assert.Equal("Home.cshtml", item.RelativePhysicalPath);
},
item =>
{
Assert.Equal("/Views/About/About.cshtml", item.FilePath);
Assert.Equal("/", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "Views", "About", "About.cshtml"), item.PhysicalPath);
Assert.Equal(Path.Combine("Views", "About", "About.cshtml"), item.RelativePhysicalPath);
},
item =>
{
Assert.Equal("/Views/Home/Index.cshtml", item.FilePath);
Assert.Equal("/", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "Views", "Home", "Index.cshtml"), item.PhysicalPath);
Assert.Equal(Path.Combine("Views", "Home", "Index.cshtml"), item.RelativePhysicalPath);
},
item =>
{
Assert.Equal("/Views/Home/_ViewImports.cshtml", item.FilePath);
Assert.Equal("/", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "Views", "Home", "_ViewImports.cshtml"), item.PhysicalPath);
Assert.Equal(Path.Combine("Views", "Home", "_ViewImports.cshtml"), item.RelativePhysicalPath);
},
item =>
{
Assert.Equal("/Views/_ViewImports.cshtml", item.FilePath);
Assert.Equal("/", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "Views", "_ViewImports.cshtml"), item.PhysicalPath);
Assert.Equal(Path.Combine("Views", "_ViewImports.cshtml"), item.RelativePhysicalPath);
},
item =>
{
Assert.Equal("/_ViewImports.cshtml", item.FilePath);
Assert.Equal("/", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "_ViewImports.cshtml"), item.PhysicalPath);
Assert.Equal("_ViewImports.cshtml", item.RelativePhysicalPath);
});
}
[Fact]
public void EnumerateItems_DiscoversAllCshtmlFiles_UnderSpecifiedBasePath()
{
// Arrange
var fileSystem = new DefaultRazorProjectFileSystem(TestFolder);
// Act
var items = fileSystem.EnumerateItems("/Views");
// Assert
Assert.Collection(
items.OrderBy(f => f.FilePath, StringComparer.Ordinal),
item =>
{
Assert.Equal("/About/About.cshtml", item.FilePath);
Assert.Equal("/Views", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "Views", "About", "About.cshtml"), item.PhysicalPath);
Assert.Equal(Path.Combine("About", "About.cshtml"), item.RelativePhysicalPath);
},
item =>
{
Assert.Equal("/Home/Index.cshtml", item.FilePath);
Assert.Equal("/Views", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "Views", "Home", "Index.cshtml"), item.PhysicalPath);
Assert.Equal(Path.Combine("Home", "Index.cshtml"), item.RelativePhysicalPath);
},
item =>
{
Assert.Equal("/Home/_ViewImports.cshtml", item.FilePath);
Assert.Equal("/Views", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "Views", "Home", "_ViewImports.cshtml"), item.PhysicalPath);
Assert.Equal(Path.Combine("Home", "_ViewImports.cshtml"), item.RelativePhysicalPath);
},
item =>
{
Assert.Equal("/_ViewImports.cshtml", item.FilePath);
Assert.Equal("/Views", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "Views", "_ViewImports.cshtml"), item.PhysicalPath);
Assert.Equal(Path.Combine("_ViewImports.cshtml"), item.RelativePhysicalPath);
});
}
[Fact]
public void EnumerateItems_ReturnsEmptySequence_WhenBasePathDoesNotExist()
{
// Arrange
var fileSystem = new DefaultRazorProjectFileSystem(TestFolder);
// Act
var items = fileSystem.EnumerateItems("/Does-Not-Exist");
// Assert
Assert.Empty(items);
}
[Fact]
public void FindHierarchicalItems_FindsItemsWithMatchingNames()
{
// Arrange
var fileSystem = new DefaultRazorProjectFileSystem(TestFolder);
// Act
var items = fileSystem.FindHierarchicalItems("/Views/Home/Index.cshtml", "_ViewImports.cshtml");
// Assert
Assert.Collection(
items,
item =>
{
Assert.Equal("/Views/Home/_ViewImports.cshtml", item.FilePath);
Assert.Equal("/", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "Views", "Home", "_ViewImports.cshtml"), item.PhysicalPath);
Assert.Equal(Path.Combine("Views", "Home", "_ViewImports.cshtml"), item.RelativePhysicalPath);
},
item =>
{
Assert.Equal("/Views/_ViewImports.cshtml", item.FilePath);
Assert.Equal("/", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "Views", "_ViewImports.cshtml"), item.PhysicalPath);
Assert.Equal(Path.Combine("Views", "_ViewImports.cshtml"), item.RelativePhysicalPath);
},
item =>
{
Assert.Equal("/_ViewImports.cshtml", item.FilePath);
Assert.Equal("/", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "_ViewImports.cshtml"), item.PhysicalPath);
Assert.Equal("_ViewImports.cshtml", item.RelativePhysicalPath);
});
}
[Fact]
public void GetItem_ReturnsFileFromDisk()
{
// Arrange
var filePath = "/Views/About/About.cshtml";
var fileSystem = new DefaultRazorProjectFileSystem(TestFolder);
// Act
var item = fileSystem.GetItem(filePath, fileKind: null);
// Assert
Assert.True(item.Exists);
Assert.Equal(filePath, item.FilePath);
Assert.Equal("/", item.BasePath);
Assert.Equal(Path.Combine(TestFolder, "Views", "About", "About.cshtml"), item.PhysicalPath);
Assert.Equal(Path.Combine("Views", "About", "About.cshtml"), item.RelativePhysicalPath);
}
[Fact]
public void GetItem_ReturnsNotFoundResult()
{
// Arrange
var path = "/NotFound.cshtml";
var fileSystem = new DefaultRazorProjectFileSystem(TestFolder);
// Act
var item = fileSystem.GetItem(path, fileKind: null);
// Assert
Assert.False(item.Exists);
}
[Fact]
public void GetItem_MismatchedRootPath_Throws()
{
// Arrange
var rootPath = "//some/network/share/root";
var fileSystem = new TestRazorProjectFileSystem(rootPath);
var path = "\\\\some\\other\\network\\share\\root\\file.cshtml";
// Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
() => fileSystem.GetItem(path, fileKind: null),
$"The file '{path.Replace('\\', '/')}' is not a descendent of the base path '{rootPath}'.");
}
private class TestRazorProjectFileSystem : DefaultRazorProjectFileSystem
{
public TestRazorProjectFileSystem(string root) : base(root)
{
}
public new string NormalizeAndEnsureValidPath(string path) => base.NormalizeAndEnsureValidPath(path);
}
}
}

View File

@ -0,0 +1,101 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorProjectItemTest
{
private static string TestFolder { get; } = Path.Combine(
TestProject.GetProjectDirectory(typeof(DefaultRazorProjectItemTest)),
"TestFiles",
"DefaultRazorProjectFileSystem");
[Fact]
public void DefaultRazorProjectItem_SetsProperties()
{
// Arrange
var fileInfo = new FileInfo(Path.Combine(TestFolder, "Home.cshtml"));
// Act
var projectItem = new DefaultRazorProjectItem("/", "/Home.cshtml", "Home.cshtml", "test", fileInfo);
// Assert
Assert.Equal("/Home.cshtml", projectItem.FilePath);
Assert.Equal("/", projectItem.BasePath);
Assert.True(projectItem.Exists);
Assert.Equal("Home.cshtml", projectItem.FileName);
Assert.Equal("test", projectItem.FileKind);
Assert.Equal(fileInfo.FullName, projectItem.PhysicalPath);
Assert.Equal("Home.cshtml", projectItem.RelativePhysicalPath);
}
[Fact]
public void DefaultRazorProjectItem_InfersFileKind_Component()
{
// Arrange
var fileInfo = new FileInfo(Path.Combine(TestFolder, "Home.cshtml"));
// Act
var projectItem = new DefaultRazorProjectItem("/", "/Home.razor", "Home.cshtml", fileKind: null, fileInfo);
// Assert
Assert.Equal(FileKinds.Component, projectItem.FileKind);
}
[Fact]
public void DefaultRazorProjectItem_InfersFileKind_Legacy()
{
// Arrange
var fileInfo = new FileInfo(Path.Combine(TestFolder, "Home.cshtml"));
// Act
var projectItem = new DefaultRazorProjectItem("/", "/Home.cshtml", "Home.cshtml", fileKind: null, fileInfo);
// Assert
Assert.Equal(FileKinds.Legacy, projectItem.FileKind);
}
[Fact]
public void DefaultRazorProjectItem_InfersFileKind_Null()
{
// Arrange
var fileInfo = new FileInfo(Path.Combine(TestFolder, "Home.cshtml"));
// Act
var projectItem = new DefaultRazorProjectItem("/", filePath: null, "Home.cshtml", fileKind: null, fileInfo);
// Assert
Assert.Null(projectItem.FileKind);
}
[Fact]
public void Exists_ReturnsFalseWhenFileDoesNotExist()
{
// Arrange
var fileInfo = new FileInfo(Path.Combine(TestFolder, "Views", "FileDoesNotExist.cshtml"));
// Act
var projectItem = new DefaultRazorProjectItem("/Views", "/FileDoesNotExist.cshtml", Path.Combine("Views", "FileDoesNotExist.cshtml"), "test", fileInfo);
// Assert
Assert.False(projectItem.Exists);
}
[Fact]
public void Read_ReturnsReadStream()
{
// Arrange
var fileInfo = new FileInfo(Path.Combine(TestFolder, "Home.cshtml"));
var projectItem = new DefaultRazorProjectItem("/", "/Home.cshtml", "Home.cshtml", "test", fileInfo);
// Act
var stream = projectItem.Read();
// Assert
Assert.Equal("home-content", new StreamReader(stream).ReadToEnd());
}
}
}

View File

@ -0,0 +1,94 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Testing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorSyntaxTreePhaseTest
{
[Fact]
public void OnInitialized_OrdersPassesInAscendingOrder()
{
// Arrange & Act
var phase = new DefaultRazorSyntaxTreePhase();
var first = Mock.Of<IRazorSyntaxTreePass>(p => p.Order == 15);
var second = Mock.Of<IRazorSyntaxTreePass>(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));
}
[Fact]
public void Execute_ThrowsForMissingDependency()
{
// Arrange
var phase = new DefaultRazorSyntaxTreePhase();
var engine = RazorProjectEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.CreateEmpty();
// Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
() => phase.Execute(codeDocument),
$"The '{nameof(DefaultRazorSyntaxTreePhase)}' phase requires a '{nameof(RazorSyntaxTree)}' " +
$"provided by the '{nameof(RazorCodeDocument)}'.");
}
[Fact]
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());
}
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRequiredAttributeDescriptorBuilderTest
{
[Fact]
public void Build_DisplayNameIsName_NameComparisonFullMatch()
{
// Arrange
var builder = new DefaultRequiredAttributeDescriptorBuilder();
builder
.Name("asp-action")
.NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch);
// Act
var descriptor = builder.Build();
// Assert
Assert.Equal("asp-action", descriptor.DisplayName);
}
[Fact]
public void Build_DisplayNameIsNameWithDots_NameComparisonPrefixMatch()
{
// Arrange
var builder = new DefaultRequiredAttributeDescriptorBuilder();
builder
.Name("asp-route-")
.NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch);
// Act
var descriptor = builder.Build();
// Assert
Assert.Equal("asp-route-...", descriptor.DisplayName);
}
}
}

View File

@ -0,0 +1,122 @@
// Copyright(c) .NET Foundation.All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DirectiveDescriptorBuilderExtensionsTest
{
[Fact]
public void AddMemberToken_AddsToken()
{
// Arrange & Act
var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddMemberToken());
// Assert
var token = Assert.Single(descriptor.Tokens);
Assert.Equal(DirectiveTokenKind.Member, token.Kind);
Assert.False(token.Optional);
Assert.Null(token.Name);
Assert.Null(token.Description);
}
[Fact]
public void AddNamespaceToken_AddsToken()
{
// Arrange & Act
var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddNamespaceToken("Name", "Description"));
// Assert
var token = Assert.Single(descriptor.Tokens);
Assert.Equal(DirectiveTokenKind.Namespace, token.Kind);
Assert.False(token.Optional);
Assert.Equal("Name", token.Name);
Assert.Equal("Description", token.Description);
}
[Fact]
public void AddStringToken_AddsToken()
{
// Arrange & Act
var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddStringToken());
// Assert
var token = Assert.Single(descriptor.Tokens);
Assert.Equal(DirectiveTokenKind.String, token.Kind);
Assert.False(token.Optional);
Assert.Null(token.Name);
Assert.Null(token.Description);
}
[Fact]
public void AddTypeToken_AddsToken()
{
// Arrange & Act
var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddTypeToken("Name", "Description"));
// Assert
var token = Assert.Single(descriptor.Tokens);
Assert.Equal(DirectiveTokenKind.Type, token.Kind);
Assert.False(token.Optional);
Assert.Equal("Name", token.Name);
Assert.Equal("Description", token.Description);
}
[Fact]
public void AddOptionalTypeToken_AddsToken()
{
// Arrange & Act
var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddOptionalTypeToken());
// Assert
var token = Assert.Single(descriptor.Tokens);
Assert.Equal(DirectiveTokenKind.Type, token.Kind);
Assert.True(token.Optional);
Assert.Null(token.Name);
Assert.Null(token.Description);
}
[Fact]
public void AddOptionalMemberToken_AddsToken()
{
// Arrange & Act
var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddOptionalMemberToken("Name", "Description"));
// Assert
var token = Assert.Single(descriptor.Tokens);
Assert.Equal(DirectiveTokenKind.Member, token.Kind);
Assert.True(token.Optional);
Assert.Equal("Name", token.Name);
Assert.Equal("Description", token.Description);
}
[Fact]
public void AddOptionalNamespaceToken_AddsToken()
{
// Arrange & Act
var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddOptionalNamespaceToken());
// Assert
var token = Assert.Single(descriptor.Tokens);
Assert.Equal(DirectiveTokenKind.Namespace, token.Kind);
Assert.True(token.Optional);
Assert.Null(token.Name);
Assert.Null(token.Description);
}
[Fact]
public void AddOptionalStringToken_AddsToken()
{
// Arrange & Act
var descriptor = DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddOptionalStringToken("Name", "Description"));
// Assert
var token = Assert.Single(descriptor.Tokens);
Assert.Equal(DirectiveTokenKind.String, token.Kind);
Assert.True(token.Optional);
Assert.Equal("Name", token.Name);
Assert.Equal("Description", token.Description);
}
}
}

View File

@ -0,0 +1,150 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DirectiveDescriptorTest
{
[Fact]
public void CreateDirective_CreatesDirective_WithProvidedKind()
{
// Arrange & Act
var directive = DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine);
// Assert
Assert.Equal("test", directive.Directive);
Assert.Equal(DirectiveKind.SingleLine, directive.Kind);
}
[Fact]
public void CreateDirective_WithConfigure_CreatesDirective_WithProvidedKind()
{
// Arrange
var called = false;
Action<IDirectiveDescriptorBuilder> configure = b => { called = true; };
// Act
var directive = DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine, configure);
// Assert
Assert.Equal("test", directive.Directive);
Assert.Equal(DirectiveKind.SingleLine, directive.Kind);
Assert.True(called);
}
[Fact]
public void CreateSingleLineDirective_CreatesSingleLineDirective()
{
// Arrange & Act
var directive = DirectiveDescriptor.CreateSingleLineDirective("test");
// Assert
Assert.Equal("test", directive.Directive);
Assert.Equal(DirectiveKind.SingleLine, directive.Kind);
}
[Fact]
public void CreateSingleLineDirective_WithConfigure_CreatesSingleLineDirective()
{
// Arrange
var called = false;
Action<IDirectiveDescriptorBuilder> configure = b => { called = true; };
// Act
var directive = DirectiveDescriptor.CreateSingleLineDirective("test", configure);
// Assert
Assert.Equal("test", directive.Directive);
Assert.Equal(DirectiveKind.SingleLine, directive.Kind);
Assert.True(called);
}
[Fact]
public void CreateRazorBlockDirective_CreatesRazorBlockDirective()
{
// Arrange & Act
var directive = DirectiveDescriptor.CreateRazorBlockDirective("test");
// Assert
Assert.Equal("test", directive.Directive);
Assert.Equal(DirectiveKind.RazorBlock, directive.Kind);
}
[Fact]
public void CreateRazorBlockDirective_WithConfigure_CreatesRazorBlockDirective()
{
// Arrange
var called = false;
Action<IDirectiveDescriptorBuilder> configure = b => { called = true; };
// Act
var directive = DirectiveDescriptor.CreateRazorBlockDirective("test", configure);
// Assert
Assert.Equal("test", directive.Directive);
Assert.Equal(DirectiveKind.RazorBlock, directive.Kind);
Assert.True(called);
}
[Fact]
public void CreateCodeBlockDirective_CreatesCodeBlockDirective()
{
// Arrange & Act
var directive = DirectiveDescriptor.CreateCodeBlockDirective("test");
// Assert
Assert.Equal("test", directive.Directive);
Assert.Equal(DirectiveKind.CodeBlock, directive.Kind);
}
[Fact]
public void CreateCodeBlockDirective_WithConfigure_CreatesCodeBlockDirective()
{
// Arrange
var called = false;
Action<IDirectiveDescriptorBuilder> configure = b => { called = true; };
// Act
var directive = DirectiveDescriptor.CreateCodeBlockDirective("test", configure);
// Assert
Assert.Equal("test", directive.Directive);
Assert.Equal(DirectiveKind.CodeBlock, directive.Kind);
Assert.True(called);
}
[Fact]
public void Build_ValidatesDirectiveKeyword_EmptyIsInvalid()
{
// Arrange & Act
var ex = Assert.Throws<InvalidOperationException>(() => DirectiveDescriptor.CreateSingleLineDirective(""));
// Assert
Assert.Equal("Invalid directive keyword ''. Directives must have a non-empty keyword that consists only of letters.", ex.Message);
}
[Fact]
public void Build_ValidatesDirectiveKeyword_InvalidCharacter()
{
// Arrange & Act
var ex = Assert.Throws<InvalidOperationException>(() => DirectiveDescriptor.CreateSingleLineDirective("test_directive"));
// Assert
Assert.Equal("Invalid directive keyword 'test_directive'. Directives must have a non-empty keyword that consists only of letters.", ex.Message);
}
[Fact]
public void Build_ValidatesDirectiveName_NonOptionalTokenFollowsOptionalToken()
{
// Arrange & Act
var ex = Assert.Throws<InvalidOperationException>(
() => DirectiveDescriptor.CreateSingleLineDirective("test", b => { b.AddOptionalMemberToken(); b.AddMemberToken(); }));
// Assert
Assert.Equal("A non-optional directive token cannot follow an optional directive token.", ex.Message);
}
}
}

View File

@ -0,0 +1,135 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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 Xunit;
using static Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeAssert;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DirectiveRemovalOptimizationPassTest
{
[Fact]
public void Execute_Custom_RemovesDirectiveNodeFromDocument()
{
// Arrange
var content = "@custom \"Hello\"";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var defaultEngine = RazorProjectEngine.Create(b =>
{
b.AddDirective(DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, d => d.AddStringToken()));
}).Engine;
var documentNode = Lower(codeDocument, defaultEngine);
var pass = new DirectiveRemovalOptimizationPass()
{
Engine = defaultEngine,
};
// Act
pass.Execute(codeDocument, documentNode);
// Assert
Children(documentNode,
node => Assert.IsType<NamespaceDeclarationIntermediateNode>(node));
var @namespace = documentNode.Children[0];
Children(@namespace,
node => Assert.IsType<ClassDeclarationIntermediateNode>(node));
var @class = @namespace.Children[0];
var method = SingleChild<MethodDeclarationIntermediateNode>(@class);
Assert.Empty(method.Children);
}
[Fact]
public void Execute_MultipleCustomDirectives_RemovesDirectiveNodesFromDocument()
{
// Arrange
var content = "@custom \"Hello\"" + Environment.NewLine + "@custom \"World\"";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var defaultEngine = RazorProjectEngine.Create(b =>
{
b.AddDirective(DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, d => d.AddStringToken()));
}).Engine;
var documentNode = Lower(codeDocument, defaultEngine);
var pass = new DirectiveRemovalOptimizationPass()
{
Engine = defaultEngine,
};
// Act
pass.Execute(codeDocument, documentNode);
// Assert
Children(documentNode,
node => Assert.IsType<NamespaceDeclarationIntermediateNode>(node));
var @namespace = documentNode.Children[0];
Children(@namespace,
node => Assert.IsType<ClassDeclarationIntermediateNode>(node));
var @class = @namespace.Children[0];
var method = SingleChild<MethodDeclarationIntermediateNode>(@class);
Assert.Empty(method.Children);
}
[Fact]
public void Execute_DirectiveWithError_PreservesDiagnosticsAndRemovesDirectiveNodeFromDocument()
{
// Arrange
var content = "@custom \"Hello\"";
var expectedDiagnostic = RazorDiagnostic.Create(new RazorDiagnosticDescriptor("RZ9999", () => "Some diagnostic message.", RazorDiagnosticSeverity.Error), SourceSpan.Undefined);
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var defaultEngine = RazorProjectEngine.Create(b =>
{
b.AddDirective(DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, d => d.AddStringToken()));
}).Engine;
var documentNode = Lower(codeDocument, defaultEngine);
// Add the diagnostic to the directive node.
var directiveNode = documentNode.FindDescendantNodes<DirectiveIntermediateNode>().Single();
directiveNode.Diagnostics.Add(expectedDiagnostic);
var pass = new DirectiveRemovalOptimizationPass()
{
Engine = defaultEngine,
};
// Act
pass.Execute(codeDocument, documentNode);
// Assert
var diagnostic = Assert.Single(documentNode.Diagnostics);
Assert.Equal(expectedDiagnostic, diagnostic);
Children(documentNode,
node => Assert.IsType<NamespaceDeclarationIntermediateNode>(node));
var @namespace = documentNode.Children[0];
Children(@namespace,
node => Assert.IsType<ClassDeclarationIntermediateNode>(node));
var @class = @namespace.Children[0];
var method = SingleChild<MethodDeclarationIntermediateNode>(@class);
Assert.Empty(method.Children);
}
private static DocumentIntermediateNode Lower(RazorCodeDocument codeDocument, RazorEngine engine)
{
for (var i = 0; i < engine.Phases.Count; i++)
{
var phase = engine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorDirectiveClassifierPhase)
{
break;
}
}
var documentNode = codeDocument.GetDocumentIntermediateNode();
Assert.NotNull(documentNode);
return documentNode;
}
}
}

View File

@ -0,0 +1,86 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Test
{
public class DirectiveTokenEditHandlerTest
{
[Theory]
[InlineData(0, 4, "")] // "Namespace"
[InlineData(4, 0, "Other")] // "SomeOtherNamespace"
[InlineData(0, 4, "Other")] // "OtherNamespace"
public void CanAcceptChange_ProvisionallyAcceptsNonWhitespaceChanges(int index, int length, string newText)
{
// Arrange
var directiveTokenHandler = new TestDirectiveTokenEditHandler();
directiveTokenHandler.AcceptedCharacters = AcceptedCharactersInternal.NonWhitespace;
var target = GetSyntaxNode(directiveTokenHandler, "SomeNamespace");
var sourceChange = new SourceChange(index, length, newText);
// Act
var result = directiveTokenHandler.CanAcceptChange(target, sourceChange);
// Assert
Assert.Equal(PartialParseResultInternal.Accepted | PartialParseResultInternal.Provisional, result);
}
[Theory]
[InlineData(4, 1, "")] // "SomeNamespace"
[InlineData(9, 0, " ")] // "Some Name space"
[InlineData(9, 5, " Space")] // "Some Name Space"
public void CanAcceptChange_RejectsWhitespaceChanges(int index, int length, string newText)
{
// Arrange
var directiveTokenHandler = new TestDirectiveTokenEditHandler();
directiveTokenHandler.AcceptedCharacters = AcceptedCharactersInternal.NonWhitespace;
var target = GetSyntaxNode(directiveTokenHandler, "Some Namespace");
var sourceChange = new SourceChange(index, length, newText);
// Act
var result = directiveTokenHandler.CanAcceptChange(target, sourceChange);
// Assert
Assert.Equal(PartialParseResultInternal.Rejected, result);
}
private static CSharpStatementLiteralSyntax GetSyntaxNode(DirectiveTokenEditHandler editHandler, string content)
{
var builder = SyntaxListBuilder<SyntaxToken>.Create();
var tokens = CSharpLanguageCharacteristics.Instance.TokenizeString(content).ToArray();
foreach (var token in tokens)
{
builder.Add((SyntaxToken)token.CreateRed());
}
var node = SyntaxFactory.CSharpStatementLiteral(builder.ToList());
var context = new SpanContext(SpanChunkGenerator.Null, editHandler);
return node.WithSpanContext(context);
}
private class TestDirectiveTokenEditHandler : DirectiveTokenEditHandler
{
public TestDirectiveTokenEditHandler() : base(content => TestTokenizer(content))
{
}
public new PartialParseResultInternal CanAcceptChange(SyntaxNode target, SourceChange change)
=> base.CanAcceptChange(target, change);
internal static IEnumerable<Syntax.InternalSyntax.SyntaxToken> TestTokenizer(string str)
{
yield return Syntax.InternalSyntax.SyntaxFactory.Token(SyntaxKind.Marker, str);
}
}
}
}

View File

@ -0,0 +1,295 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
using static Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeAssert;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DocumentClassifierPassBaseTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Latest;
public RazorEngine Engine => CreateProjectEngine().Engine;
[Fact]
public void Execute_HasDocumentKind_IgnoresDocument()
{
// Arrange
var documentNode = new DocumentIntermediateNode()
{
DocumentKind = "ignore",
Options = RazorCodeGenerationOptions.CreateDefault(),
};
var pass = new TestDocumentClassifierPass();
pass.Engine = Engine;
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), documentNode);
// Assert
Assert.Equal("ignore", documentNode.DocumentKind);
NoChildren(documentNode);
}
[Fact]
public void Execute_NoMatch_IgnoresDocument()
{
// Arrange
var documentNode = new DocumentIntermediateNode()
{
Options = RazorCodeGenerationOptions.CreateDefault(),
};
var pass = new TestDocumentClassifierPass()
{
Engine = Engine,
ShouldMatch = false,
};
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), documentNode);
// Assert
Assert.Null(documentNode.DocumentKind);
NoChildren(documentNode);
}
[Fact]
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);
}
[Fact]
public void Execute_Match_SetsDocumentType_AndCreatesStructure()
{
// Arrange
var documentNode = new DocumentIntermediateNode()
{
Options = RazorCodeGenerationOptions.CreateDefault(),
};
var pass = new TestDocumentClassifierPass();
pass.Engine = Engine;
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), documentNode);
// Assert
Assert.Equal("test", documentNode.DocumentKind);
Assert.NotNull(documentNode.Target);
var @namespace = SingleChild<NamespaceDeclarationIntermediateNode>(documentNode);
var @class = SingleChild<ClassDeclarationIntermediateNode>(@namespace);
var method = SingleChild<MethodDeclarationIntermediateNode>(@class);
NoChildren(method);
}
[Fact]
public void Execute_AddsUsingsToNamespace()
{
// Arrange
var documentNode = new DocumentIntermediateNode()
{
Options = RazorCodeGenerationOptions.CreateDefault(),
};
var builder = IntermediateNodeBuilder.Create(documentNode);
builder.Add(new UsingDirectiveIntermediateNode());
var pass = new TestDocumentClassifierPass();
pass.Engine = Engine;
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), documentNode);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIntermediateNode>(documentNode);
Children(
@namespace,
n => Assert.IsType<UsingDirectiveIntermediateNode>(n),
n => Assert.IsType<ClassDeclarationIntermediateNode>(n));
}
[Fact]
public void Execute_AddsTheRestToMethod()
{
// Arrange
var documentNode = new DocumentIntermediateNode()
{
Options = RazorCodeGenerationOptions.CreateDefault(),
};
var builder = IntermediateNodeBuilder.Create(documentNode);
builder.Add(new HtmlContentIntermediateNode());
builder.Add(new CSharpCodeIntermediateNode());
var pass = new TestDocumentClassifierPass();
pass.Engine = Engine;
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), documentNode);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIntermediateNode>(documentNode);
var @class = SingleChild<ClassDeclarationIntermediateNode>(@namespace);
var method = SingleChild<MethodDeclarationIntermediateNode>(@class);
Children(
method,
n => Assert.IsType<HtmlContentIntermediateNode>(n),
n => Assert.IsType<CSharpCodeIntermediateNode>(n));
}
[Fact]
public void Execute_CanInitializeDefaults()
{
// Arrange
var documentNode = new DocumentIntermediateNode()
{
Options = RazorCodeGenerationOptions.CreateDefault(),
};
var builder = IntermediateNodeBuilder.Create(documentNode);
builder.Add(new HtmlContentIntermediateNode());
builder.Add(new CSharpCodeIntermediateNode());
var pass = new TestDocumentClassifierPass()
{
Engine = Engine,
Namespace = "TestNamespace",
Class = "TestClass",
Method = "TestMethod",
};
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), documentNode);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIntermediateNode>(documentNode);
Assert.Equal("TestNamespace", @namespace.Content);
var @class = SingleChild<ClassDeclarationIntermediateNode>(@namespace);
Assert.Equal("TestClass", @class.ClassName);
var method = SingleChild<MethodDeclarationIntermediateNode>(@class);
Assert.Equal("TestMethod", method.MethodName);
}
[Fact]
public void Execute_AddsPrimaryAnnotations()
{
// Arrange
var documentNode = new DocumentIntermediateNode()
{
Options = RazorCodeGenerationOptions.CreateDefault(),
};
var builder = IntermediateNodeBuilder.Create(documentNode);
builder.Add(new HtmlContentIntermediateNode());
builder.Add(new CSharpCodeIntermediateNode());
var pass = new TestDocumentClassifierPass()
{
Engine = Engine,
Namespace = "TestNamespace",
Class = "TestClass",
Method = "TestMethod",
};
// Act
pass.Execute(TestRazorCodeDocument.CreateEmpty(), documentNode);
// Assert
var @namespace = SingleChild<NamespaceDeclarationIntermediateNode>(documentNode);
AnnotationEquals(@namespace, CommonAnnotations.PrimaryNamespace);
var @class = SingleChild<ClassDeclarationIntermediateNode>(@namespace);
AnnotationEquals(@class, CommonAnnotations.PrimaryClass);
var method = SingleChild<MethodDeclarationIntermediateNode>(@class);
AnnotationEquals(method, CommonAnnotations.PrimaryMethod);
}
private class TestDocumentClassifierPass : DocumentClassifierPassBase
{
public override int Order => IntermediateNodePassBase.DefaultFeatureOrder;
public bool ShouldMatch { get; set; } = true;
public Action<CodeTargetBuilder> CodeTargetCallback { get; set; }
public string Namespace { get; set; }
public string Class { get; set; }
public string Method { get; set; }
protected override string DocumentKind => "test";
protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
{
return ShouldMatch;
}
protected override void OnDocumentStructureCreated(
RazorCodeDocument codeDocument,
NamespaceDeclarationIntermediateNode @namespace,
ClassDeclarationIntermediateNode @class,
MethodDeclarationIntermediateNode method)
{
@namespace.Content = Namespace;
@class.ClassName = Class;
@method.MethodName = Method;
}
protected override void ConfigureTarget(CodeTargetBuilder builder)
{
CodeTargetCallback?.Invoke(builder);
}
}
private class MyExtension1 : ICodeTargetExtension
{
}
private class MyExtension2 : ICodeTargetExtension
{
}
}
}

View File

@ -0,0 +1,74 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
public class DefaultMetadataIdentifierFeatureTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Latest;
[Fact]
public void GetIdentifier_ReturnsNull_ForNullRelativePath()
{
// Arrange
var sourceDocument = RazorSourceDocument.Create("content", new RazorSourceDocumentProperties("Test.cshtml", null));
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var feature = new DefaultMetadataIdentifierFeature()
{
Engine = CreateProjectEngine().Engine,
};
// Act
var result = feature.GetIdentifier(codeDocument, sourceDocument);
// Assert
Assert.Null(result);
}
[Fact]
public void GetIdentifier_ReturnsNull_ForEmptyRelativePath()
{
// Arrange
var sourceDocument = RazorSourceDocument.Create("content", new RazorSourceDocumentProperties("Test.cshtml", string.Empty));
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var feature = new DefaultMetadataIdentifierFeature()
{
Engine = CreateProjectEngine().Engine,
};
// Act
var result = feature.GetIdentifier(codeDocument, sourceDocument);
// Assert
Assert.Null(result);
}
[Theory]
[InlineData("Test.cshtml", "/Test.cshtml")]
[InlineData("/Test.cshtml", "/Test.cshtml")]
[InlineData("\\Test.cshtml", "/Test.cshtml")]
[InlineData("\\About\\Test.cshtml", "/About/Test.cshtml")]
[InlineData("\\About\\Test\\cshtml", "/About/Test/cshtml")]
public void GetIdentifier_SanitizesRelativePath(string relativePath, string expected)
{
// Arrange
var sourceDocument = RazorSourceDocument.Create("content", new RazorSourceDocumentProperties("Test.cshtml", relativePath));
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var feature = new DefaultMetadataIdentifierFeature()
{
Engine = CreateProjectEngine().Engine,
};
// Act
var result = feature.GetIdentifier(codeDocument, sourceDocument);
// Assert
Assert.Equal(expected, result);
}
}
}

View File

@ -0,0 +1,128 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
public class DefaultTagHelperOptimizationPassTest
{
[Fact]
public void DefaultTagHelperOptimizationPass_Execute_ReplacesChildren()
{
// Arrange
var codeDocument = CreateDocument(@"
@addTagHelper TestTagHelper, TestAssembly
<p foo=""17"" attr=""value"">");
var tagHelpers = new[]
{
TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly")
.TypeName("TestTagHelper")
.BoundAttributeDescriptor(attribute => attribute
.Name("Foo")
.TypeName("System.Int32")
.PropertyName("FooProp"))
.TagMatchingRuleDescriptor(rule => rule.RequireTagName("p"))
.Build()
};
var engine = CreateEngine(tagHelpers);
var pass = new DefaultTagHelperOptimizationPass()
{
Engine = engine
};
var irDocument = CreateIRDocument(engine, codeDocument);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
var @class = irDocument.FindPrimaryClass();
Assert.IsType<DefaultTagHelperRuntimeIntermediateNode>(@class.Children[0]);
var fieldDeclaration = Assert.IsType<FieldDeclarationIntermediateNode>(@class.Children[1]);
Assert.Equal(bool.TrueString, fieldDeclaration.Annotations[CommonAnnotations.DefaultTagHelperExtension.TagHelperField]);
Assert.Equal("__TestTagHelper", fieldDeclaration.FieldName);
Assert.Equal("global::TestTagHelper", fieldDeclaration.FieldType);
Assert.Equal("private", fieldDeclaration.Modifiers.First());
var tagHelper = FindTagHelperNode(irDocument);
Assert.Equal(5, tagHelper.Children.Count);
var body = Assert.IsType<DefaultTagHelperBodyIntermediateNode>(tagHelper.Children[0]);
Assert.Equal("p", body.TagName);
Assert.Equal(TagMode.StartTagAndEndTag, body.TagMode);
var create = Assert.IsType<DefaultTagHelperCreateIntermediateNode>(tagHelper.Children[1]);
Assert.Equal("__TestTagHelper", create.FieldName);
Assert.Equal("TestTagHelper", create.TypeName);
Assert.Equal(tagHelpers[0], create.TagHelper, TagHelperDescriptorComparer.CaseSensitive);
var property = Assert.IsType<DefaultTagHelperPropertyIntermediateNode>(tagHelper.Children[2]);
Assert.Equal("foo", property.AttributeName);
Assert.Equal(AttributeStructure.DoubleQuotes, property.AttributeStructure);
Assert.Equal(tagHelpers[0].BoundAttributes[0], property.BoundAttribute, BoundAttributeDescriptorComparer.CaseSensitive);
Assert.Equal("__TestTagHelper", property.FieldName);
Assert.False(property.IsIndexerNameMatch);
Assert.Equal("FooProp", property.PropertyName);
Assert.Equal(tagHelpers[0], property.TagHelper, TagHelperDescriptorComparer.CaseSensitive);
var htmlAttribute = Assert.IsType<DefaultTagHelperHtmlAttributeIntermediateNode>(tagHelper.Children[3]);
Assert.Equal("attr", htmlAttribute.AttributeName);
Assert.Equal(AttributeStructure.DoubleQuotes, htmlAttribute.AttributeStructure);
Assert.IsType<DefaultTagHelperExecuteIntermediateNode>(tagHelper.Children[4]);
}
private RazorCodeDocument CreateDocument(string content)
{
var source = RazorSourceDocument.Create(content, "test.cshtml");
return RazorCodeDocument.Create(source);
}
private RazorEngine CreateEngine(params TagHelperDescriptor[] tagHelpers)
{
return RazorProjectEngine.Create(b =>
{
b.Features.Add(new TestTagHelperFeature(tagHelpers));
}).Engine;
}
private DocumentIntermediateNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument)
{
for (var i = 0; i < engine.Phases.Count; i++)
{
var phase = engine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorDirectiveClassifierPhase)
{
break;
}
}
return codeDocument.GetDocumentIntermediateNode();
}
private TagHelperIntermediateNode FindTagHelperNode(IntermediateNode node)
{
var visitor = new TagHelperNodeVisitor();
visitor.Visit(node);
return visitor.Node;
}
private class TagHelperNodeVisitor : IntermediateNodeWalker
{
public TagHelperIntermediateNode Node { get; set; }
public override void VisitTagHelper(TagHelperIntermediateNode node)
{
Node = node;
}
}
}
}

View File

@ -0,0 +1,247 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
public class DesignTimeDirectiveTargetExtensionTest
{
[Fact]
public void WriteDesignTimeDirective_NoChildren_WritesEmptyMethod_WithPragma()
{
// Arrange
var extension = new DesignTimeDirectiveTargetExtension();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new DesignTimeDirectiveIntermediateNode();
// Act
extension.WriteDesignTimeDirective(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"#pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() {
}
#pragma warning restore 219
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDesignTimeDirective_WithTypeToken_WritesLambda()
{
// Arrange
var extension = new DesignTimeDirectiveTargetExtension();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new DesignTimeDirectiveIntermediateNode();
var token = new DirectiveTokenIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 5),
Content = "System.String",
DirectiveToken = DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.Type),
};
node.Children.Add(token);
// Act
extension.WriteDesignTimeDirective(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"#pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() {
((System.Action)(() => {
#nullable restore
#line 1 ""test.cshtml""
System.String __typeHelper = default(System.String);
#line default
#line hidden
#nullable disable
}
))();
}
#pragma warning restore 219
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDesignTimeDirective_WithNamespaceToken_WritesLambda()
{
// Arrange
var extension = new DesignTimeDirectiveTargetExtension();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new DesignTimeDirectiveIntermediateNode();
var token = new DirectiveTokenIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 5),
Content = "System.Collections.Generic",
DirectiveToken = DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.Namespace),
};
node.Children.Add(token);
// Act
extension.WriteDesignTimeDirective(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"#pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() {
((System.Action)(() => {
#nullable restore
#line 1 ""test.cshtml""
global::System.Object __typeHelper = nameof(System.Collections.Generic);
#line default
#line hidden
#nullable disable
}
))();
}
#pragma warning restore 219
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDesignTimeDirective_WithMemberToken_WritesLambda()
{
// Arrange
var extension = new DesignTimeDirectiveTargetExtension();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new DesignTimeDirectiveIntermediateNode();
var token = new DirectiveTokenIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 5),
Content = "Foo",
DirectiveToken = DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.Member),
};
node.Children.Add(token);
// Act
extension.WriteDesignTimeDirective(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"#pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() {
((System.Action)(() => {
#nullable restore
#line 1 ""test.cshtml""
global::System.Object Foo = null;
#line default
#line hidden
#nullable disable
}
))();
}
#pragma warning restore 219
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDesignTimeDirective_WithStringToken_WritesLambda()
{
// Arrange
var extension = new DesignTimeDirectiveTargetExtension();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new DesignTimeDirectiveIntermediateNode();
var token = new DirectiveTokenIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 5),
Content = "Value",
DirectiveToken = DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.String),
};
var tokenWithQuotedContent = new DirectiveTokenIntermediateNode()
{
Source = new SourceSpan("test.cshtml", 0, 0, 0, 5),
Content = "\"Value\"",
DirectiveToken = DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.String),
};
node.Children.Add(token);
node.Children.Add(tokenWithQuotedContent);
// Act
extension.WriteDesignTimeDirective(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"#pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() {
((System.Action)(() => {
#nullable restore
#line 1 ""test.cshtml""
global::System.Object __typeHelper = ""Value"";
#line default
#line hidden
#nullable disable
}
))();
((System.Action)(() => {
#nullable restore
#line 1 ""test.cshtml""
global::System.Object __typeHelper = ""Value"";
#line default
#line hidden
#nullable disable
}
))();
}
#pragma warning restore 219
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteDesignTimeDirective_ChildrenWithNoSource_WritesEmptyMethod_WithPragma()
{
// Arrange
var extension = new DesignTimeDirectiveTargetExtension();
var context = TestCodeRenderingContext.CreateDesignTime();
var node = new DesignTimeDirectiveIntermediateNode();
var token = new DirectiveTokenIntermediateNode()
{
Content = "Value",
DirectiveToken = DirectiveTokenDescriptor.CreateToken(DirectiveTokenKind.String),
};
node.Children.Add(token);
// Act
extension.WriteDesignTimeDirective(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"#pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() {
}
#pragma warning restore 219
",
csharp,
ignoreLineEndingDifferences: true);
}
}
}

View File

@ -0,0 +1,98 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
using static Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeAssert;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
public class FunctionsDirectivePassTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Latest;
[Fact]
public void Execute_SkipsDocumentWithNoClassNode()
{
// Arrange
var projectEngine = CreateProjectEngine();
var pass = new FunctionsDirectivePass()
{
Engine = projectEngine.Engine,
};
var sourceDocument = TestRazorSourceDocument.Create("@functions { var value = true; }");
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = new DocumentIntermediateNode();
irDocument.Children.Add(new DirectiveIntermediateNode() { Directive = FunctionsDirective.Directive, });
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Children(
irDocument,
node => Assert.IsType<DirectiveIntermediateNode>(node));
}
[Fact]
public void Execute_AddsStatementsToClassLevel()
{
// Arrange
var projectEngine = CreateProjectEngine();
var pass = new FunctionsDirectivePass()
{
Engine = projectEngine.Engine,
};
var sourceDocument = TestRazorSourceDocument.Create("@functions { var value = true; }");
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = Lower(codeDocument, projectEngine);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Children(
irDocument,
node => Assert.IsType<NamespaceDeclarationIntermediateNode>(node));
var @namespace = irDocument.Children[0];
Children(
@namespace,
node => Assert.IsType<ClassDeclarationIntermediateNode>(node));
var @class = @namespace.Children[0];
Children(
@class,
node => Assert.IsType<MethodDeclarationIntermediateNode>(node),
node => CSharpCode(" var value = true; ", node));
var method = @class.Children[0];
Children(
method,
node => Assert.IsType<DirectiveIntermediateNode>(node));
}
private static DocumentIntermediateNode Lower(RazorCodeDocument codeDocument, RazorProjectEngine projectEngine)
{
for (var i = 0; i < projectEngine.Phases.Count; i++)
{
var phase = projectEngine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorDocumentClassifierPhase)
{
break;
}
}
var irDocument = codeDocument.GetDocumentIntermediateNode();
Assert.NotNull(irDocument);
return irDocument;
}
}
}

View File

@ -0,0 +1,91 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
using static Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeAssert;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
public class InheritsDirectivePassTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Latest;
[Fact]
public void Execute_SkipsDocumentWithNoClassNode()
{
// Arrange
var engine = CreateEngine();
var pass = new InheritsDirectivePass()
{
Engine = engine,
};
var sourceDocument = TestRazorSourceDocument.Create("@inherits Hello<World[]>");
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = new DocumentIntermediateNode();
irDocument.Children.Add(new DirectiveIntermediateNode() { Directive = FunctionsDirective.Directive, });
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Children(
irDocument,
node => Assert.IsType<DirectiveIntermediateNode>(node));
}
[Fact]
public void Execute_Inherits_SetsClassDeclarationBaseType()
{
// Arrange
var engine = CreateEngine();
var pass = new InheritsDirectivePass()
{
Engine = engine,
};
var content = "@inherits Hello<World[]>";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = Lower(codeDocument, engine);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Children(
irDocument,
node => Assert.IsType<NamespaceDeclarationIntermediateNode>(node));
var @namespace = irDocument.Children[0];
Children(
@namespace,
node => Assert.IsType<ClassDeclarationIntermediateNode>(node));
var @class = (ClassDeclarationIntermediateNode)@namespace.Children[0];
Assert.Equal("Hello<World[]>", @class.BaseType);
}
private static DocumentIntermediateNode Lower(RazorCodeDocument codeDocument, RazorEngine engine)
{
for (var i = 0; i < engine.Phases.Count; i++)
{
var phase = engine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorDocumentClassifierPhase)
{
break;
}
}
var irDocument = codeDocument.GetDocumentIntermediateNode();
Assert.NotNull(irDocument);
return irDocument;
}
}
}

View File

@ -0,0 +1,392 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.Components;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
using static Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeAssert;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
public class MetadataAttributePassTest
{
[Fact]
public void Execute_NullCodeGenerationOptions_Noops()
{
// Arrange
var engine = CreateEngine();
var pass = new MetadataAttributePass()
{
Engine = engine,
};
var sourceDocument = TestRazorSourceDocument.Create();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = new DocumentIntermediateNode();
// Act
pass.Execute(codeDocument, irDocument);
// Assert
NoChildren(irDocument);
}
[Fact]
public void Execute_SuppressMetadataAttributes_Noops()
{
// Arrange
var engine = CreateEngine();
var pass = new MetadataAttributePass()
{
Engine = engine,
};
var sourceDocument = TestRazorSourceDocument.Create();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = new DocumentIntermediateNode()
{
Options = RazorCodeGenerationOptions.Create(o =>
{
o.SuppressMetadataAttributes = true;
}),
};
// Act
pass.Execute(codeDocument, irDocument);
// Assert
NoChildren(irDocument);
}
[Fact]
public void Execute_ComponentDocumentKind_Noops()
{
// Arrange
var engine = CreateEngine();
var pass = new MetadataAttributePass()
{
Engine = engine,
};
var sourceDocument = TestRazorSourceDocument.Create();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = new DocumentIntermediateNode()
{
DocumentKind = ComponentDocumentClassifierPass.ComponentDocumentKind,
Options = RazorCodeGenerationOptions.Create(o =>
{
o.SuppressMetadataAttributes = true;
}),
};
// Act
pass.Execute(codeDocument, irDocument);
// Assert
NoChildren(irDocument);
}
[Fact]
public void Execute_NoNamespaceSet_Noops()
{
// Arrange
var engine = CreateEngine();
var pass = new MetadataAttributePass()
{
Engine = engine,
};
var sourceDocument = TestRazorSourceDocument.Create();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = new DocumentIntermediateNode()
{
DocumentKind = "test",
Options = RazorCodeGenerationOptions.Create((o) => { }),
};
var builder = IntermediateNodeBuilder.Create(irDocument);
var @namespace = new NamespaceDeclarationIntermediateNode
{
Annotations =
{
[CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace,
},
};
builder.Push(@namespace);
var @class = new ClassDeclarationIntermediateNode
{
Annotations =
{
[CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass,
},
ClassName = "Test",
};
builder.Add(@class);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
SingleChild<NamespaceDeclarationIntermediateNode>(irDocument);
}
[Fact]
public void Execute_NoClassNameSet_Noops()
{
// Arrange
var engine = CreateEngine();
var pass = new MetadataAttributePass()
{
Engine = engine,
};
var sourceDocument = TestRazorSourceDocument.Create();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = new DocumentIntermediateNode()
{
DocumentKind = "test",
Options = RazorCodeGenerationOptions.Create((o) => { }),
};
var builder = IntermediateNodeBuilder.Create(irDocument);
var @namespace = new NamespaceDeclarationIntermediateNode
{
Annotations =
{
[CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace,
},
Content = "Some.Namespace"
};
builder.Push(@namespace);
var @class = new ClassDeclarationIntermediateNode
{
Annotations =
{
[CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass,
},
};
builder.Add(@class);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
SingleChild<NamespaceDeclarationIntermediateNode>(irDocument);
}
[Fact]
public void Execute_NoDocumentKind_Noops()
{
// Arrange
var engine = CreateEngine();
var pass = new MetadataAttributePass()
{
Engine = engine,
};
var sourceDocument = TestRazorSourceDocument.Create();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = new DocumentIntermediateNode();
var builder = IntermediateNodeBuilder.Create(irDocument);
var @namespace = new NamespaceDeclarationIntermediateNode
{
Annotations =
{
[CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace,
},
Content = "Some.Namespace"
};
builder.Push(@namespace);
var @class = new ClassDeclarationIntermediateNode
{
Annotations =
{
[CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass,
},
ClassName = "Test",
};
builder.Add(@class);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
SingleChild<NamespaceDeclarationIntermediateNode>(irDocument);
}
[Fact]
public void Execute_NoIdentifier_Noops()
{
// Arrange
var engine = CreateEngine();
var pass = new MetadataAttributePass()
{
Engine = engine,
};
var sourceDocument = TestRazorSourceDocument.Create("", new RazorSourceDocumentProperties(null, null));
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = new DocumentIntermediateNode()
{
DocumentKind = "test",
Options = RazorCodeGenerationOptions.Create((o) => { }),
};
var builder = IntermediateNodeBuilder.Create(irDocument);
var @namespace = new NamespaceDeclarationIntermediateNode
{
Annotations =
{
[CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace,
},
Content = "Some.Namespace"
};
builder.Push(@namespace);
var @class = new ClassDeclarationIntermediateNode
{
Annotations =
{
[CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass,
},
ClassName = "Test",
};
builder.Add(@class);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
SingleChild<NamespaceDeclarationIntermediateNode>(irDocument);
}
[Fact]
public void Execute_HasRequiredInfo_AddsItemAndSourceChecksum()
{
// Arrange
var engine = CreateEngine();
var pass = new MetadataAttributePass()
{
Engine = engine,
};
var sourceDocument = TestRazorSourceDocument.Create("", new RazorSourceDocumentProperties(null, "Foo\\Bar.cshtml"));
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = new DocumentIntermediateNode()
{
DocumentKind = "test",
Options = RazorCodeGenerationOptions.Create((o) => { }),
};
var builder = IntermediateNodeBuilder.Create(irDocument);
var @namespace = new NamespaceDeclarationIntermediateNode
{
Annotations =
{
[CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace,
},
Content = "Some.Namespace"
};
builder.Push(@namespace);
var @class = new ClassDeclarationIntermediateNode
{
Annotations =
{
[CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass,
},
ClassName = "Test",
};
builder.Add(@class);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Assert.Equal(2, irDocument.Children.Count);
var item = Assert.IsType<RazorCompiledItemAttributeIntermediateNode>(irDocument.Children[0]);
Assert.Equal("/Foo/Bar.cshtml", item.Identifier);
Assert.Equal("test", item.Kind);
Assert.Equal("Some.Namespace.Test", item.TypeName);
Assert.Equal(2, @namespace.Children.Count);
var checksum = Assert.IsType<RazorSourceChecksumAttributeIntermediateNode>(@namespace.Children[0]);
Assert.NotNull(checksum.Checksum); // Not verifying the checksum here
Assert.Equal("SHA1", checksum.ChecksumAlgorithm);
Assert.Equal("/Foo/Bar.cshtml", checksum.Identifier);
}
[Fact]
public void Execute_HasRequiredInfo_AndImport_AddsItemAndSourceChecksum()
{
// Arrange
var engine = CreateEngine();
var pass = new MetadataAttributePass()
{
Engine = engine,
};
var sourceDocument = TestRazorSourceDocument.Create("", new RazorSourceDocumentProperties(null, "Foo\\Bar.cshtml"));
var import = TestRazorSourceDocument.Create("@using System", new RazorSourceDocumentProperties(null, "Foo\\Import.cshtml"));
var codeDocument = RazorCodeDocument.Create(sourceDocument, new[] { import, });
var irDocument = new DocumentIntermediateNode()
{
DocumentKind = "test",
Options = RazorCodeGenerationOptions.Create((o) => { }),
};
var builder = IntermediateNodeBuilder.Create(irDocument);
var @namespace = new NamespaceDeclarationIntermediateNode
{
Annotations =
{
[CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace,
},
Content = "Some.Namespace"
};
builder.Push(@namespace);
var @class = new ClassDeclarationIntermediateNode
{
Annotations =
{
[CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass,
},
ClassName = "Test",
};
builder.Add(@class);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Assert.Equal(2, irDocument.Children.Count);
var item = Assert.IsType<RazorCompiledItemAttributeIntermediateNode>(irDocument.Children[0]);
Assert.Equal("/Foo/Bar.cshtml", item.Identifier);
Assert.Equal("test", item.Kind);
Assert.Equal("Some.Namespace.Test", item.TypeName);
Assert.Equal(3, @namespace.Children.Count);
var checksum = Assert.IsType<RazorSourceChecksumAttributeIntermediateNode>(@namespace.Children[0]);
Assert.NotNull(checksum.Checksum); // Not verifying the checksum here
Assert.Equal("SHA1", checksum.ChecksumAlgorithm);
Assert.Equal("/Foo/Bar.cshtml", checksum.Identifier);
checksum = Assert.IsType<RazorSourceChecksumAttributeIntermediateNode>(@namespace.Children[1]);
Assert.NotNull(checksum.Checksum); // Not verifying the checksum here
Assert.Equal("SHA1", checksum.ChecksumAlgorithm);
Assert.Equal("/Foo/Import.cshtml", checksum.Identifier);
}
private static RazorEngine CreateEngine()
{
return RazorProjectEngine.Create(b =>
{
b.Features.Add(new DefaultMetadataIdentifierFeature());
}).Engine;
}
}
}

View File

@ -0,0 +1,123 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
public class MetadataAttributeTargetExtensionTest
{
[Fact]
public void WriteRazorCompiledItemAttribute_RendersCorrectly()
{
// Arrange
var extension = new MetadataAttributeTargetExtension()
{
CompiledItemAttributeName = "global::TestItem",
};
var context = TestCodeRenderingContext.CreateRuntime();
var node = new RazorCompiledItemAttributeIntermediateNode()
{
TypeName = "Foo.Bar",
Kind = "test",
Identifier = "Foo/Bar",
};
// Act
extension.WriteRazorCompiledItemAttribute(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"[assembly: global::TestItem(typeof(Foo.Bar), @""test"", @""Foo/Bar"")]
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteRazorSourceChecksumAttribute_RendersCorrectly()
{
// Arrange
var extension = new MetadataAttributeTargetExtension()
{
SourceChecksumAttributeName = "global::TestChecksum",
};
var context = TestCodeRenderingContext.CreateRuntime();
var node = new RazorSourceChecksumAttributeIntermediateNode()
{
ChecksumAlgorithm = "SHA1",
Checksum = new byte[] { (byte)'t', (byte)'e', (byte)'s', (byte)'t', },
Identifier = "Foo/Bar",
};
// Act
extension.WriteRazorSourceChecksumAttribute(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"[global::TestChecksum(@""SHA1"", @""74657374"", @""Foo/Bar"")]
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteRazorCompiledItemAttributeMetadata_RendersCorrectly()
{
// Arrange
var extension = new MetadataAttributeTargetExtension()
{
CompiledItemMetadataAttributeName = "global::TestItemMetadata",
};
var context = TestCodeRenderingContext.CreateRuntime();
var node = new RazorCompiledItemMetadataAttributeIntermediateNode
{
Key = "key",
Value = "value",
};
// Act
extension.WriteRazorCompiledItemMetadataAttribute(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode().Trim();
Assert.Equal(
"[global::TestItemMetadata(\"key\", \"value\")]",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteRazorCompiledItemAttributeMetadata_EscapesKeysAndValuesCorrectly()
{
// Arrange
var extension = new MetadataAttributeTargetExtension()
{
CompiledItemMetadataAttributeName = "global::TestItemMetadata",
};
var context = TestCodeRenderingContext.CreateRuntime();
var node = new RazorCompiledItemMetadataAttributeIntermediateNode
{
Key = "\"test\" key",
Value = @"""test"" value",
};
// Act
extension.WriteRazorCompiledItemMetadataAttribute(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode().Trim();
Assert.Equal(
"[global::TestItemMetadata(\"\\\"test\\\" key\", \"\\\"test\\\" value\")]",
csharp,
ignoreLineEndingDifferences: true);
}
}
}

View File

@ -0,0 +1,276 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
public class PreallocatedAttributeTargetExtensionTest
{
[Fact]
public void WriteTagHelperHtmlAttributeValue_RendersCorrectly()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new PreallocatedTagHelperHtmlAttributeValueIntermediateNode()
{
AttributeName = "Foo",
Value = "Bar",
AttributeStructure = AttributeStructure.DoubleQuotes,
VariableName = "MyProp"
};
// Act
extension.WriteTagHelperHtmlAttributeValue(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"private static readonly global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute MyProp = new global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute(""Foo"", new global::Microsoft.AspNetCore.Html.HtmlString(""Bar""), global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperHtmlAttributeValue_Minimized_RendersCorrectly()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new PreallocatedTagHelperHtmlAttributeValueIntermediateNode()
{
AttributeName = "Foo",
Value = "Bar",
AttributeStructure = AttributeStructure.Minimized,
VariableName = "_tagHelper1"
};
// Act
extension.WriteTagHelperHtmlAttributeValue(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"private static readonly global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute _tagHelper1 = new global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute(""Foo"");
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperHtmlAttribute_RendersCorrectly()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new PreallocatedTagHelperHtmlAttributeIntermediateNode()
{
VariableName = "_tagHelper1"
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperHtmlAttribute(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"__tagHelperExecutionContext.AddHtmlAttribute(_tagHelper1);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperPropertyValue_RendersCorrectly()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var node = new PreallocatedTagHelperPropertyValueIntermediateNode()
{
AttributeName = "Foo",
Value = "Bar",
AttributeStructure = AttributeStructure.DoubleQuotes,
VariableName = "_tagHelper1",
};
// Act
extension.WriteTagHelperPropertyValue(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"private static readonly global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute _tagHelper1 = new global::Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute(""Foo"", ""Bar"", global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteTagHelperProperty_RendersCorrectly()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "FooTagHelper", "Test");
tagHelperBuilder.TypeName("FooTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("Foo")
.TypeName("System.String")
.PropertyName("FooProp");
var descriptor = builder.Build();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new PreallocatedTagHelperPropertyIntermediateNode()
{
AttributeName = descriptor.Name,
BoundAttribute = descriptor,
FieldName = "__FooTagHelper",
PropertyName = "FooProp",
VariableName = "_tagHelper1",
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"__FooTagHelper.FooProp = (string)_tagHelper1.Value;
__tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteSetPreallocatedTagHelperProperty_IndexerAttribute_RendersCorrectly()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "FooTagHelper", "Test");
tagHelperBuilder.TypeName("FooTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("Foo")
.TypeName("System.Collections.Generic.Dictionary<System.String, System.String>")
.AsDictionaryAttribute("pre-", "System.String")
.PropertyName("FooProp");
var descriptor = builder.Build();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new PreallocatedTagHelperPropertyIntermediateNode()
{
AttributeName = "pre-Foo",
FieldName = "__FooTagHelper",
VariableName = "_tagHelper1",
BoundAttribute = descriptor,
IsIndexerNameMatch = true,
PropertyName = "FooProp",
TagHelper = tagHelperBuilder.Build(),
};
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"if (__FooTagHelper.FooProp == null)
{
throw new InvalidOperationException(InvalidTagHelperIndexerAssignment(""pre-Foo"", ""FooTagHelper"", ""FooProp""));
}
__FooTagHelper.FooProp[""Foo""] = (string)_tagHelper1.Value;
__tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact]
public void WriteSetPreallocatedTagHelperProperty_IndexerAttribute_MultipleValues()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "FooTagHelper", "Test");
tagHelperBuilder.TypeName("FooTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("Foo")
.TypeName("System.Collections.Generic.Dictionary<System.String, System.String>")
.AsDictionaryAttribute("pre-", "System.String")
.PropertyName("FooProp");
var boundAttribute = builder.Build();
var tagHelper = tagHelperBuilder.Build();
var tagHelperNode = new TagHelperIntermediateNode();
var node1 = new PreallocatedTagHelperPropertyIntermediateNode()
{
AttributeName = "pre-Bar",
FieldName = "__FooTagHelper",
VariableName = "_tagHelper0s",
BoundAttribute = boundAttribute,
IsIndexerNameMatch = true,
PropertyName = "FooProp",
TagHelper = tagHelper,
};
var node2 = new PreallocatedTagHelperPropertyIntermediateNode()
{
AttributeName = "pre-Foo",
FieldName = "__FooTagHelper",
VariableName = "_tagHelper1",
BoundAttribute = boundAttribute,
IsIndexerNameMatch = true,
PropertyName = "FooProp",
TagHelper = tagHelper,
};
tagHelperNode.Children.Add(node1);
tagHelperNode.Children.Add(node2);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node2);
// Assert
var csharp = context.CodeWriter.GenerateCode();
Assert.Equal(
@"__FooTagHelper.FooProp[""Foo""] = (string)_tagHelper1.Value;
__tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
",
csharp,
ignoreLineEndingDifferences: true);
}
private static void Push(CodeRenderingContext context, TagHelperIntermediateNode node)
{
((DefaultCodeRenderingContext)context).AncestorsInternal.Push(node);
}
}
}

View File

@ -0,0 +1,106 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
using static Microsoft.AspNetCore.Razor.Language.Intermediate.IntermediateNodeAssert;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
public class SectionDirectivePassTest
{
[Fact]
public void Execute_SkipsDocumentWithNoClassNode()
{
// Arrange
var projectEngine = CreateProjectEngine();
var pass = new SectionDirectivePass()
{
Engine = projectEngine.Engine,
};
var sourceDocument = TestRazorSourceDocument.Create("@section Header { <p>Hello World</p> }");
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = new DocumentIntermediateNode();
irDocument.Children.Add(new DirectiveIntermediateNode() { Directive = SectionDirective.Directive, });
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Children(
irDocument,
node => Assert.IsType<DirectiveIntermediateNode>(node));
}
[Fact]
public void Execute_WrapsStatementInSectionNode()
{
// Arrange
var projectEngine = CreateProjectEngine();
var pass = new SectionDirectivePass()
{
Engine = projectEngine.Engine,
};
var content = "@section Header { <p>Hello World</p> }";
var sourceDocument = TestRazorSourceDocument.Create(content);
var codeDocument = RazorCodeDocument.Create(sourceDocument);
var irDocument = Lower(codeDocument, projectEngine);
// Act
pass.Execute(codeDocument, irDocument);
// Assert
Children(
irDocument,
node => Assert.IsType<NamespaceDeclarationIntermediateNode>(node));
var @namespace = irDocument.Children[0];
Children(
@namespace,
node => Assert.IsType<ClassDeclarationIntermediateNode>(node));
var @class = @namespace.Children[0];
var method = SingleChild<MethodDeclarationIntermediateNode>(@class);
Children(
method,
node => Assert.IsType<DirectiveIntermediateNode>(node),
node => Assert.IsType<SectionIntermediateNode>(node));
var section = method.Children[1] as SectionIntermediateNode;
Assert.Equal("Header", section.SectionName);
Children(section, c => Html(" <p>Hello World</p> ", c));
}
private static RazorProjectEngine CreateProjectEngine()
{
return RazorProjectEngine.Create(b =>
{
SectionDirective.Register(b);
});
}
private static DocumentIntermediateNode Lower(RazorCodeDocument codeDocument, RazorProjectEngine projectEngine)
{
for (var i = 0; i < projectEngine.Phases.Count; i++)
{
var phase = projectEngine.Phases[i];
phase.Execute(codeDocument);
if (phase is IRazorDocumentClassifierPhase)
{
break;
}
}
var irDocument = codeDocument.GetDocumentIntermediateNode();
Assert.NotNull(irDocument);
return irDocument;
}
}
}

View File

@ -0,0 +1,80 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
public class SectionTargetExtensionTest
{
[Fact]
public void WriteSection_WritesSectionCode()
{
// Arrange
var node = new SectionIntermediateNode()
{
Children =
{
new CSharpExpressionIntermediateNode(),
},
SectionName = "MySection"
};
var extension = new SectionTargetExtension()
{
SectionMethodName = "CreateSection"
};
var context = TestCodeRenderingContext.CreateRuntime();
// Act
extension.WriteSection(context, node);
// Assert
var expected = @"CreateSection(""MySection"", async() => {
Render Children
}
);
";
var output = context.CodeWriter.GenerateCode();
Assert.Equal(expected, output);
}
[Fact]
public void WriteSection_WritesSectionCode_DesignTime()
{
// Arrange
var node = new SectionIntermediateNode()
{
Children =
{
new CSharpExpressionIntermediateNode(),
},
SectionName = "MySection"
};
var extension = new SectionTargetExtension()
{
SectionMethodName = "CreateSection"
};
var context = TestCodeRenderingContext.CreateDesignTime();
// Act
extension.WriteSection(context, node);
// Assert
var expected = @"CreateSection(""MySection"", async(__razor_section_writer) => {
Render Children
}
);
";
var output = context.CodeWriter.GenerateCode();
Assert.Equal(expected, output);
}
}
}

View File

@ -0,0 +1,51 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Extensions
{
public class TemplateTargetExtensionTest
{
[Fact]
public void WriteTemplate_WritesTemplateCode()
{
// Arrange
var node = new TemplateIntermediateNode()
{
Children =
{
new CSharpExpressionIntermediateNode()
}
};
var extension = new TemplateTargetExtension()
{
TemplateTypeName = "global::TestTemplate"
};
var nodeWriter = new RuntimeNodeWriter()
{
PushWriterMethod = "TestPushWriter",
PopWriterMethod = "TestPopWriter"
};
var context = TestCodeRenderingContext.CreateRuntime(nodeWriter: nodeWriter);
// Act
extension.WriteTemplate(context, node);
// Assert
var expected = @"item => new global::TestTemplate(async(__razor_template_writer) => {
TestPushWriter(__razor_template_writer);
Render Children
TestPopWriter();
}
)";
var output = context.CodeWriter.GenerateCode();
Assert.Equal(expected, output);
}
}
}

View File

@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class HtmlConventionsTest
{
public static TheoryData HtmlConversionData
{
get
{
return new TheoryData<string, string>
{
{ "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 = HtmlConventions.ToHtmlCase(input);
// Assert
Assert.Equal(output, expectedOutput);
}
}
}

View File

@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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.Legacy;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class HtmlNodeOptimizationPassTest
{
[Fact]
public void Execute_RewritesWhitespace()
{
// Assert
var content = Environment.NewLine + " @true";
var sourceDocument = TestRazorSourceDocument.Create(content);
var originalTree = RazorSyntaxTree.Parse(sourceDocument);
var pass = new HtmlNodeOptimizationPass();
var codeDocument = RazorCodeDocument.Create(sourceDocument);
// Act
var outputTree = pass.Execute(codeDocument, originalTree);
// Assert
var document = Assert.IsType<RazorDocumentSyntax>(outputTree.Root);
var block = Assert.IsType<MarkupBlockSyntax>(document.Document);
Assert.Equal(4, block.Children.Count);
var whitespace = Assert.IsType<MarkupTextLiteralSyntax>(block.Children[1]);
Assert.True(whitespace.GetContent().All(char.IsWhiteSpace));
}
}
}

View File

@ -0,0 +1,57 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class BasicIntegrationTest : IntegrationTestBase
{
[Fact]
public void Empty()
{
// Arrange
var projectEngine = CreateProjectEngine();
var projectItem = CreateProjectItemFromFile();
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode());
}
[Fact]
public void HelloWorld()
{
// Arrange
var projectEngine = CreateProjectEngine();
var projectItem = CreateProjectItemFromFile();
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode());
}
[Fact]
public void CustomDirective()
{
// Arrange
var projectEngine = CreateProjectEngine(b =>
{
b.AddDirective(DirectiveDescriptor.CreateDirective("test", DirectiveKind.SingleLine));
});
var projectItem = CreateProjectItemFromFile();
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode());
}
}
}

View File

@ -0,0 +1,96 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Components;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class ComponentBindIntegrationTest : RazorIntegrationTestBase
{
internal override string FileKind => FileKinds.Component;
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void BindDuplicates_ReportsDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
[BindElement(""div"", ""value"", ""myvalue2"", ""myevent2"")]
[BindElement(""div"", ""value"", ""myvalue"", ""myevent"")]
public static class BindAttributes
{
}
}"));
// Act
var result = CompileToCSharp(@"
<div bind-value=""@ParentValue"" />
@functions {
public string ParentValue { get; set; } = ""hi"";
}");
// Assert
var diagnostic = Assert.Single(result.Diagnostics);
Assert.Equal("RZ9989", diagnostic.Id);
Assert.Equal(
"The attribute 'bind-value' was matched by multiple bind attributes. Duplicates:" + Environment.NewLine +
"Test.BindAttributes" + Environment.NewLine +
"Test.BindAttributes",
diagnostic.GetMessage());
}
[Fact]
public void BindFallback_InvalidSyntax_TooManyParts()
{
// Arrange & Act
var generated = CompileToCSharp(@"
<input type=""text"" bind-first-second-third=""Text"" />
@functions {
public string Text { get; set; } = ""text"";
}");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9991", diagnostic.Id);
}
[Fact]
public void BindFallback_InvalidSyntax_TrailingDash()
{
// Arrange & Act
var generated = CompileToCSharp(@"
<input type=""text"" bind-first-=""Text"" />
@functions {
public string Text { get; set; } = ""text"";
}");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9991", diagnostic.Id);
}
[Fact]
public void Bind_InvalidUseOfDirective_DoesNotThrow()
{
var generated = CompileToCSharp(@"
<input type=""text"" bind=""@page"" />
@functions {
public string page { get; set; } = ""text"";
}");
// Assert
Assert.Collection(
generated.Diagnostics,
d => Assert.Equal("RZ2005", d.Id),
d => Assert.Equal("RZ1011", d.Id));
}
}
}

View File

@ -0,0 +1,249 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Razor.Language.Components;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class ComponentChildContentIntegrationTest : RazorIntegrationTestBase
{
private readonly CSharpSyntaxTree RenderChildContentComponent = Parse(@"
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
namespace Test
{
public class RenderChildContent : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddContent(0, ChildContent);
}
[Parameter]
RenderFragment ChildContent { get; set; }
}
}
");
private readonly CSharpSyntaxTree RenderChildContentStringComponent = Parse(@"
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
namespace Test
{
public class RenderChildContentString : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddContent(0, ChildContent, Value);
}
[Parameter]
RenderFragment<string> ChildContent { get; set; }
[Parameter]
string Value { get; set; }
}
}
");
private readonly CSharpSyntaxTree RenderMultipleChildContent = Parse(@"
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
namespace Test
{
public class RenderMultipleChildContent : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddContent(0, Header, Name);
builder.AddContent(1, ChildContent, Value);
builder.AddContent(2, Footer);
}
[Parameter]
string Name { get; set; }
[Parameter]
RenderFragment<string> Header { get; set; }
[Parameter]
RenderFragment<string> ChildContent { get; set; }
[Parameter]
RenderFragment Footer { get; set; }
[Parameter]
string Value { get; set; }
}
}
");
internal override string FileKind => FileKinds.Component;
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void ChildContent_AttributeAndBody_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
// Act
var generated = CompileToCSharp(@"
@{ RenderFragment<string> template = @<div>@context.ToLowerInvariant()</div>; }
<RenderChildContent ChildContent=""@template.WithValue(""HI"")"">
Some Content
</RenderChildContent>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(ComponentDiagnosticFactory.ChildContentSetByAttributeAndBody.Id, diagnostic.Id);
}
[Fact]
public void ChildContent_AttributeAndExplicitChildContent_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
// Act
var generated = CompileToCSharp(@"
@{ RenderFragment<string> template = @<div>@context.ToLowerInvariant()</div>; }
<RenderChildContent ChildContent=""@template.WithValue(""HI"")"">
<ChildContent>
Some Content
</ChildContent>
</RenderChildContent>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(ComponentDiagnosticFactory.ChildContentSetByAttributeAndBody.Id, diagnostic.Id);
}
[Fact]
public void ChildContent_ExplicitChildContent_UnrecogizedContent_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
// Act
var generated = CompileToCSharp(@"
<RenderChildContent>
<ChildContent>
</ChildContent>
@somethingElse
</RenderChildContent>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(ComponentDiagnosticFactory.ChildContentMixedWithExplicitChildContent.Id, diagnostic.Id);
Assert.Equal(
"Unrecognized child content inside component 'RenderChildContent'. The component 'RenderChildContent' accepts " +
"child content through the following top-level items: 'ChildContent'.",
diagnostic.GetMessage());
}
[Fact]
public void ChildContent_ExplicitChildContent_UnrecogizedElement_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
// Act
var generated = CompileToCSharp(@"
<RenderChildContent>
<ChildContent>
</ChildContent>
<UnrecognizedChildContent></UnrecognizedChildContent>
</RenderChildContent>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(ComponentDiagnosticFactory.ChildContentMixedWithExplicitChildContent.Id, diagnostic.Id);
}
[Fact]
public void ChildContent_ExplicitChildContent_UnrecogizedAttribute_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentComponent);
// Act
var generated = CompileToCSharp(@"
<RenderChildContent>
<ChildContent attr>
</ChildContent>
</RenderChildContent>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(ComponentDiagnosticFactory.ChildContentHasInvalidAttribute.Id, diagnostic.Id);
}
[Fact]
public void ChildContent_ExplicitChildContent_InvalidParameterName_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
// Act
var generated = CompileToCSharp(@"
<RenderChildContentString>
<ChildContent Context=""@(""HI"")"">
</ChildContent>
</RenderChildContentString>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(ComponentDiagnosticFactory.ChildContentHasInvalidParameter.Id, diagnostic.Id);
}
[Fact]
public void ChildContent_ExplicitChildContent_RepeatedParameterName_GeneratesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
// Act
var generated = CompileToCSharp(@"
<RenderChildContentString>
<ChildContent>
<RenderChildContentString>
<ChildContent Context=""context"">
</ChildContent>
</RenderChildContentString>
</ChildContent>
</RenderChildContentString>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(ComponentDiagnosticFactory.ChildContentRepeatedParameterName.Id, diagnostic.Id);
Assert.Equal(
"The child content element 'ChildContent' of component 'RenderChildContentString' uses the same parameter name ('context') as enclosing child content " +
"element 'ChildContent' of component 'RenderChildContentString'. Specify the parameter name like: '<ChildContent Context=\"another_name\"> to resolve the ambiguity",
diagnostic.GetMessage());
}
[Fact]
public void ChildContent_ContextParameterNameOnComponent_Invalid_ProducesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(RenderChildContentStringComponent);
// Act
var generated = CompileToCSharp(@"
<RenderChildContentString Context=""@Foo()"">
</RenderChildContentString>");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(ComponentDiagnosticFactory.ChildContentHasInvalidParameterOnComponent.Id, diagnostic.Id);
Assert.Equal(
"Invalid parameter name. The parameter name attribute 'Context' on component 'RenderChildContentString' can only include literal text.",
diagnostic.GetMessage());
}
}
}

View File

@ -0,0 +1,175 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
using System.Text;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class ComponentDeclarationRazorIntegrationTest : RazorIntegrationTestBase
{
public ComponentDeclarationRazorIntegrationTest()
{
// Include this assembly to use types defined in tests.
BaseCompilation = DefaultBaseCompilation.AddReferences(MetadataReference.CreateFromFile(GetType().Assembly.Location));
}
internal override CSharpCompilation BaseCompilation { get; }
internal override string FileKind => FileKinds.Component;
internal override bool DeclarationOnly => true;
[Fact]
public void DeclarationConfiguration_IncludesFunctions()
{
// Arrange & Act
var component = CompileToComponent(@"
@functions {
public string Value { get; set; }
}");
// Assert
var property = component.GetType().GetProperty("Value");
Assert.NotNull(property);
Assert.Same(typeof(string), property.PropertyType);
}
[Fact]
public void DeclarationConfiguration_IncludesInject()
{
// Arrange & Act
var component = CompileToComponent(@"
@inject string Value
");
// Assert
var property = component.GetType().GetProperty("Value", BindingFlags.NonPublic | BindingFlags.Instance);
Assert.NotNull(property);
Assert.Same(typeof(string), property.PropertyType);
}
[Fact]
public void DeclarationConfiguration_IncludesUsings()
{
// Arrange & Act
var component = CompileToComponent(@"
@using System.Text
@inject StringBuilder Value
");
// Assert
var property = component.GetType().GetProperty("Value", BindingFlags.NonPublic | BindingFlags.Instance);
Assert.NotNull(property);
Assert.Same(typeof(StringBuilder), property.PropertyType);
}
[Fact]
public void DeclarationConfiguration_IncludesInherits()
{
// Arrange & Act
var component = CompileToComponent($@"
@inherits {FullTypeName<BaseClass>()}
");
// Assert
Assert.Same(typeof(BaseClass), component.GetType().BaseType);
}
[Fact]
public void DeclarationConfiguration_IncludesImplements()
{
// Arrange & Act
var component = CompileToComponent($@"
@implements {FullTypeName<IDoCoolThings>()}
");
// Assert
var type = component.GetType();
Assert.Contains(typeof(IDoCoolThings), component.GetType().GetInterfaces());
}
[Fact] // Regression test for https://github.com/aspnet/Blazor/issues/453
public void DeclarationConfiguration_FunctionsBlockHasLineMappings_MappingsApplyToError()
{
// Arrange & Act 1
var generated = CompileToCSharp(@"
@functions {
public StringBuilder Builder { get; set; }
}
");
// Assert 1
AssertSourceEquals(@"
// <auto-generated/>
#pragma warning disable 1591
#pragma warning disable 0414
#pragma warning disable 0649
#pragma warning disable 0169
namespace Test
{
#line hidden
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public class TestComponent : Microsoft.AspNetCore.Components.ComponentBase
{
#pragma warning disable 1998
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder)
{
}
#pragma warning restore 1998
#nullable restore
#line 1 ""x:\dir\subdir\Test\TestComponent.cshtml""
public StringBuilder Builder { get; set; }
#line default
#line hidden
#nullable disable
}
}
#pragma warning restore 1591
", generated);
// Act 2
var assembly = CompileToAssembly(generated, throwOnFailure: false);
// Assert 2
var diagnostic = Assert.Single(assembly.Diagnostics);
// This error should map to line 2 of the generated file, the test
// says 1 because Roslyn's line/column data structures are 0-based.
var position = diagnostic.Location.GetMappedLineSpan();
Assert.EndsWith(".cshtml", position.Path);
Assert.Equal(1, position.StartLinePosition.Line);
}
public class BaseClass : IComponent
{
public void Init(RenderHandle renderHandle)
{
}
protected virtual void BuildRenderTree(RenderTreeBuilder builder)
{
}
public void SetParameters(ParameterCollection parameters)
{
}
}
public interface IDoCoolThings
{
}
}
}

View File

@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class ComponentDesignTimeCodeGenerationTest : ComponentCodeGenerationTestBase
{
internal override bool DesignTime => true;
}
}

View File

@ -0,0 +1,111 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class ComponentDiagnosticRazorIntegrationTest : RazorIntegrationTestBase
{
internal override string FileKind => FileKinds.Component;
[Fact]
public void RejectsEndTagWithNoStartTag()
{
// Arrange/Act
var result = CompileToCSharp(
"Line1\nLine2\nLine3</mytag>");
// Assert
Assert.Collection(result.Diagnostics,
item =>
{
Assert.Equal("RZ9981", item.Id);
Assert.Equal("Unexpected closing tag 'mytag' with no matching start tag.", item.GetMessage());
});
}
// This used to be a sugar syntax for lambdas, but we don't support that anymore
[Fact]
public void OldCodeBlockAttributeSyntax_ReportsError()
{
// Arrange/Act
var generated = CompileToCSharp(@"
<elem attr=@{ DidInvokeCode = true; } />
@functions {
public bool DidInvokeCode { get; set; } = false;
}");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9979", diagnostic.Id);
Assert.NotNull(diagnostic.GetMessage());
}
[Fact]
public void RejectsScriptTag()
{
// Arrange/Act
var result = CompileToCSharp(@"Hello
<div>
<script src='anything'>
something
</script>
</div>
Goodbye");
// Assert
Assert.Collection(result.Diagnostics,
item =>
{
Assert.Equal("RZ9992", item.Id);
Assert.Equal("Script tags should not be placed inside components because they cannot be updated dynamically. To fix this, move the script tag to the 'index.html' file or another static location. For more information see https://go.microsoft.com/fwlink/?linkid=872131", item.GetMessage());
Assert.Equal(2, item.Span.LineIndex);
Assert.Equal(4, item.Span.CharacterIndex);
});
}
[Fact]
public void RejectsTagHelperDirectives()
{
// Arrange/Act
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
}
}
"));
var result = CompileToCSharp(@"
@addTagHelper *, TestAssembly
@tagHelperPrefix th
<MyComponent />
");
// Assert
Assert.Collection(result.Diagnostics,
item =>
{
Assert.Equal("RZ9978", item.Id);
Assert.Equal("The directives @addTagHelper, @removeTagHelper and @tagHelperPrefix are not valid in a component document. " +
"Use '@using <namespace>' directive instead.", item.GetMessage());
Assert.Equal(0, item.Span.LineIndex);
Assert.Equal(0, item.Span.CharacterIndex);
},
item =>
{
Assert.Equal("RZ9978", item.Id);
Assert.Equal("The directives @addTagHelper, @removeTagHelper and @tagHelperPrefix are not valid in a component document. " +
"Use '@using <namespace>' directive instead.", item.GetMessage());
Assert.Equal(1, item.Span.LineIndex);
Assert.Equal(0, item.Span.CharacterIndex);
});
}
}
}

View File

@ -0,0 +1,150 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Layouts;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
// Integration tests for component directives
public class ComponentDirectiveIntegrationTest : RazorIntegrationTestBase
{
public ComponentDirectiveIntegrationTest()
{
// Include this assembly to use types defined in tests.
BaseCompilation = DefaultBaseCompilation.AddReferences(MetadataReference.CreateFromFile(GetType().Assembly.Location));
}
internal override CSharpCompilation BaseCompilation { get; }
internal override string FileKind => FileKinds.Component;
[Fact]
public void ComponentsDoNotHaveLayoutAttributeByDefault()
{
// Arrange/Act
var component = CompileToComponent($"Hello");
// Assert
Assert.Null(component.GetType().GetCustomAttribute<LayoutAttribute>());
}
[Fact]
public void SupportsLayoutDeclarations()
{
// Arrange/Act
var testComponentTypeName = FullTypeName<TestLayout>();
var component = CompileToComponent(
$"@layout {testComponentTypeName}\n" +
$"Hello");
// Assert
var layoutAttribute = component.GetType().GetCustomAttribute<LayoutAttribute>();
Assert.NotNull(layoutAttribute);
Assert.Equal(typeof(TestLayout), layoutAttribute.LayoutType);
}
[Fact]
public void SupportsImplementsDeclarations()
{
// Arrange/Act
var testInterfaceTypeName = FullTypeName<ITestInterface>();
var component = CompileToComponent(
$"@implements {testInterfaceTypeName}\n" +
$"Hello");
// Assert
Assert.IsAssignableFrom<ITestInterface>(component);
}
[Fact]
public void SupportsMultipleImplementsDeclarations()
{
// Arrange/Act
var testInterfaceTypeName = FullTypeName<ITestInterface>();
var testInterfaceTypeName2 = FullTypeName<ITestInterface2>();
var component = CompileToComponent(
$"@implements {testInterfaceTypeName}\n" +
$"@implements {testInterfaceTypeName2}\n" +
$"Hello");
// Assert
Assert.IsAssignableFrom<ITestInterface>(component);
Assert.IsAssignableFrom<ITestInterface2>(component);
}
[Fact]
public void SupportsInheritsDirective()
{
// Arrange/Act
var testBaseClassTypeName = FullTypeName<TestBaseClass>();
var component = CompileToComponent(
$"@inherits {testBaseClassTypeName}" + Environment.NewLine +
$"Hello");
// Assert
Assert.IsAssignableFrom<TestBaseClass>(component);
}
[Fact]
public void SupportsInjectDirective()
{
// Arrange/Act 1: Compilation
var componentType = CompileToComponent(
$"@inject {FullTypeName<IMyService1>()} MyService1\n" +
$"@inject {FullTypeName<IMyService2>()} MyService2\n" +
$"Hello from @MyService1 and @MyService2").GetType();
// Assert 1: Compiled type has correct properties
var propertyFlags = BindingFlags.Instance | BindingFlags.NonPublic;
var injectableProperties = componentType.GetProperties(propertyFlags)
.Where(p => p.GetCustomAttribute<InjectAttribute>() != null);
Assert.Collection(injectableProperties.OrderBy(p => p.Name),
property =>
{
Assert.Equal("MyService1", property.Name);
Assert.Equal(typeof(IMyService1), property.PropertyType);
Assert.False(property.GetMethod.IsPublic);
Assert.False(property.SetMethod.IsPublic);
},
property =>
{
Assert.Equal("MyService2", property.Name);
Assert.Equal(typeof(IMyService2), property.PropertyType);
Assert.False(property.GetMethod.IsPublic);
Assert.False(property.SetMethod.IsPublic);
});
}
public class TestLayout : IComponent
{
[Parameter]
RenderFragment Body { get; set; }
public void Init(RenderHandle renderHandle)
{
}
public void SetParameters(ParameterCollection parameters)
{
}
}
public interface ITestInterface { }
public interface ITestInterface2 { }
public class TestBaseClass : ComponentBase { }
public interface IMyService1 { }
public interface IMyService2 { }
public class MyService1Impl : IMyService1 { }
public class MyService2Impl : IMyService2 { }
}
}

View File

@ -0,0 +1,121 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.Components;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class ComponentDiscoveryIntegrationTest : RazorIntegrationTestBase
{
internal override string FileKind => FileKinds.Component;
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void ComponentDiscovery_CanFindComponent_DefinedinCSharp()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
}
}
"));
// Act
var result = CompileToCSharp(string.Empty);
// Assert
var bindings = result.CodeDocument.GetTagHelperContext();
Assert.Contains(bindings.TagHelpers, t => t.Name == "Test.MyComponent");
}
[Fact]
public void ComponentDiscovery_CanFindComponent_WithNamespace_DefinedinCSharp()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test.AnotherNamespace
{
public class MyComponent : ComponentBase
{
}
}
"));
// Act
var result = CompileToCSharp(string.Empty);
// Assert
var bindings = result.CodeDocument.GetTagHelperContext();
Assert.Contains(bindings.TagHelpers, t =>
{
return t.Name == "Test.AnotherNamespace.MyComponent" &&
t.IsComponentFullyQualifiedNameMatch();
});
Assert.DoesNotContain(bindings.TagHelpers, t =>
{
return t.Name == "Test.AnotherNamespace.MyComponent" &&
!t.IsComponentFullyQualifiedNameMatch();
});
}
[Fact]
public void ComponentDiscovery_CanFindComponent_DefinedinCshtml()
{
// Arrange
// Act
var result = CompileToCSharp("UniqueName.cshtml", string.Empty);
// Assert
var bindings = result.CodeDocument.GetTagHelperContext();
Assert.Contains(bindings.TagHelpers, t => t.Name == "Test.UniqueName");
}
[Fact]
public void ComponentDiscovery_CanFindComponent_WithTypeParameter()
{
// Arrange
// Act
var result = CompileToCSharp("UniqueName.cshtml", @"
@typeparam TItem
@functions {
[Parameter] TItem Item { get; set; }
}");
// Assert
var bindings = result.CodeDocument.GetTagHelperContext();
Assert.Contains(bindings.TagHelpers, t => t.Name == "Test.UniqueName<TItem>");
}
[Fact]
public void ComponentDiscovery_CanFindComponent_WithMultipleTypeParameters()
{
// Arrange
// Act
var result = CompileToCSharp("UniqueName.cshtml", @"
@typeparam TItem1
@typeparam TItem2
@typeparam TItem3
@functions {
[Parameter] TItem1 Item { get; set; }
}");
// Assert
var bindings = result.CodeDocument.GetTagHelperContext();
Assert.Contains(bindings.TagHelpers, t => t.Name == "Test.UniqueName<TItem1, TItem2, TItem3>");
}
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
// Integration tests focused on file path handling for class/namespace names
public class ComponentFilePathIntegrationTest : RazorIntegrationTestBase
{
internal override string FileKind => FileKinds.Component;
[Fact]
public void FileNameIsInvalidClassName_SanitizesInvalidClassName()
{
// Arrange
// Act
var result = CompileToAssembly("Filename with spaces.cshtml", "");
// Assert
Assert.Empty(result.Diagnostics);
var type = Assert.Single(result.Assembly.GetTypes());
Assert.Equal(DefaultRootNamespace, type.Namespace);
Assert.Equal("Filename_with_spaces", type.Name);
}
[Theory]
[InlineData("ItemAtRoot.cs", "Test", "ItemAtRoot")]
[InlineData("Dir1\\MyFile.cs", "Test.Dir1", "MyFile")]
[InlineData("Dir1\\Dir2\\MyFile.cs", "Test.Dir1.Dir2", "MyFile")]
public void CreatesClassWithCorrectNameAndNamespace(string relativePath, string expectedNamespace, string expectedClassName)
{
// Arrange
relativePath = relativePath.Replace('\\', Path.DirectorySeparatorChar);
// Act
var result = CompileToAssembly(relativePath, "");
// Assert
Assert.Empty(result.Diagnostics);
var type = Assert.Single(result.Assembly.GetTypes());
Assert.Equal(expectedNamespace, type.Namespace);
Assert.Equal(expectedClassName, type.Name);
}
}
}

View File

@ -0,0 +1,124 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Razor.Language.Components;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class ComponentGenericTypeIntegrationTest : RazorIntegrationTestBase
{
private readonly CSharpSyntaxTree GenericContextComponent = Parse(@"
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
namespace Test
{
public class GenericContext<TItem> : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
var items = (IReadOnlyList<TItem>)Items ?? Array.Empty<TItem>();
for (var i = 0; i < items.Count; i++)
{
if (ChildContent == null)
{
builder.AddContent(i, Items[i]);
}
else
{
builder.AddContent(i, ChildContent, new Context() { Index = i, Item = items[i], });
}
}
}
[Parameter]
List<TItem> Items { get; set; }
[Parameter]
RenderFragment<Context> ChildContent { get; set; }
public class Context
{
public int Index { get; set; }
public TItem Item { get; set; }
}
}
}
");
private readonly CSharpSyntaxTree MultipleGenericParameterComponent = Parse(@"
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.RenderTree;
namespace Test
{
public class MultipleGenericParameter<TItem1, TItem2, TItem3> : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.AddContent(0, Item1);
builder.AddContent(1, Item2);
builder.AddContent(2, Item3);
}
[Parameter]
TItem1 Item1 { get; set; }
[Parameter]
TItem2 Item2 { get; set; }
[Parameter]
TItem3 Item3 { get; set; }
}
}
");
internal override string FileKind => FileKinds.Component;
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void GenericComponent_WithoutAnyTypeParameters_TriggersDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(GenericContextComponent);
// Act
var generated = CompileToCSharp(@"
<GenericContext />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(ComponentDiagnosticFactory.GenericComponentTypeInferenceUnderspecified.Id, diagnostic.Id);
Assert.Equal(
"The type of component 'GenericContext' cannot be inferred based on the values provided. Consider " +
"specifying the type arguments directly using the following attributes: 'TItem'.",
diagnostic.GetMessage());
}
[Fact]
public void GenericComponent_WithMissingTypeParameters_TriggersDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(MultipleGenericParameterComponent);
// Act
var generated = CompileToCSharp(@"
<MultipleGenericParameter TItem1=int />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Same(ComponentDiagnosticFactory.GenericComponentMissingTypeArgument.Id, diagnostic.Id);
Assert.Equal(
"The component 'MultipleGenericParameter' is missing required type arguments. " +
"Specify the missing types using the attributes: 'TItem2', 'TItem3'.",
diagnostic.GetMessage());
}
}
}

View File

@ -0,0 +1,191 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class ComponentImportsIntegrationTest : RazorIntegrationTestBase
{
internal override string FileKind => FileKinds.ComponentImport;
[Fact]
public void NoErrorsForUsingStatements()
{
// Arrange/Act
var result = CompileToCSharp("_Imports.razor", @"
@using System.Text
@using System.Reflection
@* This is allowed in imports *@
");
// Assert
Assert.Empty(result.Diagnostics);
}
[Fact]
public void NoErrorsForRazorComments()
{
// Arrange/Act
var result = CompileToCSharp("_Imports.razor", @"
@* This is allowed in imports *@
");
// Assert
Assert.Empty(result.Diagnostics);
}
[Fact]
public void NoErrorsForSupportedDirectives()
{
// Arrange/Act
var result = CompileToCSharp("_Imports.razor", @"
@inject FooService Foo
@typeparam TItem
@implements ISomeInterface
@inherits SomeNamespace.SomeBaseType
");
// Assert
Assert.Empty(result.Diagnostics);
}
[Fact]
public void ErrorsForPageDirective()
{
// Arrange/Act
var result = CompileToCSharp("_Imports.razor", @"
@page ""/""
");
// Assert
Assert.Collection(result.Diagnostics,
item =>
{
Assert.Equal("RZ9987", item.Id);
Assert.Equal(@"The '@page' directive specified in _Imports.razor file will not be imported. The directive must appear at the top of each Razor file", item.GetMessage());
});
}
[Fact]
public void ErrorsForTagHelperDirectives()
{
// Arrange/Act
var result = CompileToCSharp("_Imports.razor", @"
@addTagHelper *, TestAssembly
@removeTagHelper *, TestAssembly
@tagHelperPrefix th:
");
// Assert
Assert.Collection(result.Diagnostics,
item =>
{
Assert.Equal("RZ9978", item.Id);
Assert.Equal(0, item.Span.LineIndex);
Assert.Equal(@"The directives @addTagHelper, @removeTagHelper and @tagHelperPrefix are not valid in a component document. Use '@using <namespace>' directive instead.", item.GetMessage());
},
item =>
{
Assert.Equal("RZ9978", item.Id);
Assert.Equal(1, item.Span.LineIndex);
Assert.Equal(@"The directives @addTagHelper, @removeTagHelper and @tagHelperPrefix are not valid in a component document. Use '@using <namespace>' directive instead.", item.GetMessage());
},
item =>
{
Assert.Equal("RZ9978", item.Id);
Assert.Equal(2, item.Span.LineIndex);
Assert.Equal(@"The directives @addTagHelper, @removeTagHelper and @tagHelperPrefix are not valid in a component document. Use '@using <namespace>' directive instead.", item.GetMessage());
});
}
[Fact]
public void ErrorsForFunctionsDirective()
{
// Arrange/Act
var result = CompileToCSharp("_Imports.razor", @"
@functions {
public class Test
{
}
}
");
// Assert
Assert.Collection(result.Diagnostics,
item =>
{
Assert.Equal("RZ10003", item.Id);
Assert.Equal(@"Markup, code and block directives are not valid in component imports.", item.GetMessage());
});
}
[Fact]
public void ErrorsForSectionDirective()
{
// Arrange/Act
var result = CompileToCSharp("_Imports.razor", @"
@section Foo {
}
");
// Assert
Assert.Collection(result.Diagnostics,
item =>
{
Assert.Equal("RZ10003", item.Id);
Assert.Equal(@"Markup, code and block directives are not valid in component imports.", item.GetMessage());
});
}
[Fact]
public void ErrorsForMarkup()
{
// Arrange/Act
var result = CompileToCSharp("_Imports.razor", @"
<div>asdf</div>
");
// Assert
Assert.Collection(result.Diagnostics,
item =>
{
Assert.Equal("RZ10003", item.Id);
Assert.Equal(@"Markup, code and block directives are not valid in component imports.", item.GetMessage());
});
}
[Fact]
public void ErrorsForCode()
{
// Arrange/Act
var result = CompileToCSharp("_Imports.razor", @"
@Foo
@(Bar)
@{
var x = Foo;
}");
// Assert
Assert.Collection(result.Diagnostics,
item =>
{
Assert.Equal("RZ10003", item.Id);
Assert.Equal(0, item.Span.LineIndex);
Assert.Equal(@"Markup, code and block directives are not valid in component imports.", item.GetMessage());
},
item =>
{
Assert.Equal("RZ10003", item.Id);
Assert.Equal(1, item.Span.LineIndex);
Assert.Equal(@"Markup, code and block directives are not valid in component imports.", item.GetMessage());
},
item =>
{
Assert.Equal("RZ10003", item.Id);
Assert.Equal(2, item.Span.LineIndex);
Assert.Equal(@"Markup, code and block directives are not valid in component imports.", item.GetMessage());
});
}
}
}

View File

@ -0,0 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class ComponentRuntimeCodeGenerationTest : ComponentCodeGenerationTestBase
{
}
}

View File

@ -0,0 +1,127 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class ComponentTemplateIntegrationTest : RazorIntegrationTestBase
{
internal override string FileKind => FileKinds.Component;
// Razor doesn't parse this as a template, we don't need much special handling for
// it because it will just be invalid in general.
[Fact]
public void Template_ImplicitExpressionInMarkupAttribute_CreatesDiagnostic()
{
// Arrange
// Act
var generated = CompileToCSharp(@"<div attr=""@<div></div>"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ1005", diagnostic.Id);
}
[Fact]
public void Template_ExplicitExpressionInMarkupAttribute_CreatesDiagnostic()
{
// Arrange
// Act
var generated = CompileToCSharp(@"<div attr=""@(@<div></div>)"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9994", diagnostic.Id);
}
// Razor doesn't parse this as a template, we don't need much special handling for
// it because it will just be invalid in general.
[Fact]
public void Template_ImplicitExpressionInComponentAttribute_CreatesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
}
}
"));
// Act
var generated = CompileToCSharp(@"<MyComponent attr=""@<div></div>"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ1005", diagnostic.Id);
}
[Fact]
public void Template_ExplicitExpressionInComponentAttribute_CreatesDiagnostic()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
}
}
"));
// Act
var generated = CompileToCSharp(@"<MyComponent attr=""@(@<div></div>)"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9994", diagnostic.Id);
}
[Fact]
public void Template_ExplicitExpressionInRef_CreatesDiagnostic()
{
// Arrange
// Act
var generated = CompileToCSharp(@"<div ref=""@(@<div></div>)"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9994", diagnostic.Id);
}
[Fact]
public void Template_ExplicitExpressionInBind_CreatesDiagnostic()
{
// Arrange
// Act
var generated = CompileToCSharp(@"<input type=""text"" bind=""@(@<div></div>)"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9994", diagnostic.Id);
}
[Fact]
public void Template_ExplicitExpressionInEventHandler_CreatesDiagnostic()
{
// Arrange
// Act
var generated = CompileToCSharp(@"<input type=""text"" onchange=""@(@<div></div>)"" />");
// Assert
var diagnostic = Assert.Single(generated.Diagnostics);
Assert.Equal("RZ9994", diagnostic.Id);
}
}
}

View File

@ -0,0 +1,130 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using Xunit;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
// Similar to design time code generation tests, but goes a character at a time.
// Don't add many of these since they are slow - instead add features to existing
// tests here, and use these as smoke tests, not for detailed regression testing.
public class ComponentTypingTest : RazorIntegrationTestBase
{
internal override bool DesignTime => true;
internal override string FileKind => FileKinds.Component;
internal override bool UseTwoPhaseCompilation => true;
[Fact]
public void DoSomeTyping()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter] int Value { get; set; }
[Parameter] Action<int> ValueChanged { get; set; }
[Parameter] string AnotherValue { get; set; }
}
public class ModelState
{
public Action<string> Bind(Func<string, string> func) => throw null;
}
}
"));
var text = @"
<div>
<MyComponent bind-Value=""myValue"" AnotherValue=""hi""/>
<input type=""text"" bind=""@this.ModelState.Bind(x => x)"" />
<button ref=""_button"" onsubmit=""@FormSubmitted"">Click me</button>
</div>
<MyComponent
IntProperty=""123""
BoolProperty=""true""
StringProperty=""My string""
ObjectProperty=""new SomeType()""/>
@functions {
Test.ModelState ModelState { get; set; }
}";
for (var i = 0; i <= text.Length; i++)
{
try
{
CompileToCSharp(text.Substring(0, i), throwOnFailure: false);
}
catch (Exception ex)
{
throw new XunitException($@"
Code generation failed on iteration {i} with source text:
{text.Substring(0, i)}
Exception:
{ex}
");
}
}
}
[Fact] // Regression test for #1068
public void Regression_1068()
{
// Arrange
// Act
var generated = CompileToCSharp(@"
<input type=""text"" bind="" />
@functions {
Test.ModelState ModelState { get; set; }
}
", throwOnFailure: false);
// Assert
}
[Fact]
public void MalformedAttributeContent()
{
// Act
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using System;
using Microsoft.AspNetCore.Components;
namespace Test
{
public class MyComponent : ComponentBase
{
[Parameter] int Value { get; set; }
[Parameter] Action<int> ValueChanged { get; set; }
[Parameter] string AnotherValue { get; set; }
}
public class ModelState
{
public Action<string> Bind(Func<string, string> func) => throw null;
}
}
"));
var generated = CompileToCSharp(@"
<MyComponent Value=10 Something=@for
<button disabled=@form.IsSubmitting type=""submit"" class=""btn btn-primary mt-3 mr-3 has-spinner @(form.IsSubmitting ? ""active"" :"""")"" onclick=@(async () => await SaveAsync(false))>
@functions {
Test.ModelState ModelState { get; set; }
}", throwOnFailure: false);
// Assert - does not throw
}
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
// Extensible directives only have codegen for design time, so we're only testing that.
public class ExtensibleDirectiveTest : IntegrationTestBase
{
public ExtensibleDirectiveTest()
: base(generateBaselines: null)
{
}
[Fact]
public void NamespaceToken()
{
// Arrange
var engine = CreateProjectEngine(builder =>
{
builder.ConfigureDocumentClassifier();
builder.AddDirective(DirectiveDescriptor.CreateDirective("custom", DirectiveKind.SingleLine, b => b.AddNamespaceToken()));
});
var projectItem = CreateProjectItemFromFile();
// Act
var codeDocument = engine.ProcessDesignTime(projectItem);
// Assert
AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode());
AssertCSharpDocumentMatchesBaseline(codeDocument.GetCSharpDocument());
AssertSourceMappingsMatchBaseline(codeDocument);
}
}
}

View File

@ -0,0 +1,36 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class HtmlAttributeIntegrationTest : IntegrationTestBase
{
[Fact]
public void HtmlWithDataDashAttribute()
{
// Arrange
var projectItem = CreateProjectItemFromFile();
// Act
var compiled = CompileToCSharp(projectItem);
// Assert
AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode());
}
[Fact]
public void HtmlWithConditionalAttribute()
{
// Arrange
var projectItem = CreateProjectItemFromFile();
// Act
var compiled = CompileToCSharp(projectItem);
// Assert
AssertDocumentNodeMatchesBaseline(compiled.CodeDocument.GetDocumentIntermediateNode());
}
}
}

View File

@ -0,0 +1,126 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class TagHelpersIntegrationTest : IntegrationTestBase
{
[Fact]
public void SimpleTagHelpers()
{
// Arrange
var descriptors = new[]
{
CreateTagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "TestAssembly")
};
var projectEngine = CreateProjectEngine(builder => builder.AddTagHelpers(descriptors));
var projectItem = CreateProjectItemFromFile();
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode());
}
[Fact]
public void TagHelpersWithBoundAttributes()
{
// Arrange
var descriptors = new[]
{
CreateTagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("bound")
.PropertyName("FooProp")
.TypeName("System.String"),
})
};
var projectEngine = CreateProjectEngine(builder => builder.AddTagHelpers(descriptors));
var projectItem = CreateProjectItemFromFile();
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode());
}
[Fact]
public void NestedTagHelpers()
{
// Arrange
var descriptors = new[]
{
CreateTagHelperDescriptor(
tagName: "p",
typeName: "PTagHelper",
assemblyName: "TestAssembly"),
CreateTagHelperDescriptor(
tagName: "form",
typeName: "FormTagHelper",
assemblyName: "TestAssembly"),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("value")
.PropertyName("FooProp")
.TypeName("System.String"),
})
};
var projectEngine = CreateProjectEngine(builder => builder.AddTagHelpers(descriptors));
var projectItem = CreateProjectItemFromFile();
// Act
var codeDocument = projectEngine.Process(projectItem);
// Assert
var syntaxTree = codeDocument.GetSyntaxTree();
var irTree = codeDocument.GetDocumentIntermediateNode();
AssertDocumentNodeMatchesBaseline(codeDocument.GetDocumentIntermediateNode());
}
private static TagHelperDescriptor CreateTagHelperDescriptor(
string tagName,
string typeName,
string assemblyName,
IEnumerable<Action<BoundAttributeDescriptorBuilder>> attributes = null)
{
var builder = TagHelperDescriptorBuilder.Create(typeName, assemblyName);
builder.TypeName(typeName);
if (attributes != null)
{
foreach (var attributeBuilder in attributes)
{
builder.BoundAttributeDescriptor(attributeBuilder);
}
}
builder.TagMatchingRuleDescriptor(ruleBuilder => ruleBuilder.RequireTagName(tagName));
var descriptor = builder.Build();
return descriptor;
}
}
}

View File

@ -0,0 +1,637 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
public class TestTagHelperDescriptors
{
public static IEnumerable<TagHelperDescriptor> SimpleTagHelperDescriptors
{
get
{
return new[]
{
CreateTagHelperDescriptor(
tagName: "span",
typeName: "SpanTagHelper",
assemblyName: "TestAssembly"),
CreateTagHelperDescriptor(
tagName: "div",
typeName: "DivTagHelper",
assemblyName: "TestAssembly"),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("value")
.PropertyName("FooProp")
.TypeName("System.String"),
builder => builder
.Name("bound")
.PropertyName("BoundProp")
.TypeName("System.String"),
builder => builder
.Name("age")
.PropertyName("AgeProp")
.TypeName("System.Int32"),
})
};
}
}
public static IEnumerable<TagHelperDescriptor> MinimizedBooleanTagHelperDescriptors
{
get
{
return new[]
{
CreateTagHelperDescriptor(
tagName: "span",
typeName: "SpanTagHelper",
assemblyName: "TestAssembly"),
CreateTagHelperDescriptor(
tagName: "div",
typeName: "DivTagHelper",
assemblyName: "TestAssembly"),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("value")
.PropertyName("FooProp")
.TypeName("System.String"),
builder => builder
.Name("bound")
.PropertyName("BoundProp")
.TypeName("System.Boolean"),
builder => builder
.Name("age")
.PropertyName("AgeProp")
.TypeName("System.Int32"),
})
};
}
}
public static IEnumerable<TagHelperDescriptor> CssSelectorTagHelperDescriptors
{
get
{
var inputTypePropertyInfo = typeof(TestType).GetRuntimeProperty("Type");
var inputCheckedPropertyInfo = typeof(TestType).GetRuntimeProperty("Checked");
return new[]
{
CreateTagHelperDescriptor(
tagName: "a",
typeName: "TestNamespace.ATagHelper",
assemblyName: "TestAssembly",
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder
.RequireAttributeDescriptor(attribute => attribute
.Name("href")
.NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch)
.Value("~/")
.ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.FullMatch)),
}),
CreateTagHelperDescriptor(
tagName: "a",
typeName: "TestNamespace.ATagHelperMultipleSelectors",
assemblyName: "TestAssembly",
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder
.RequireAttributeDescriptor(attribute => attribute
.Name("href")
.NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch)
.Value("~/")
.ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch))
.RequireAttributeDescriptor(attribute => attribute
.Name("href")
.NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch)
.Value("?hello=world")
.ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.SuffixMatch)),
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo),
},
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder
.RequireAttributeDescriptor(attribute => attribute
.Name("type")
.NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch)
.Value("text")
.ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.FullMatch)),
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper2",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo),
},
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder
.RequireAttributeDescriptor(attribute => attribute
.Name("ty")
.NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.PrefixMatch)),
}),
CreateTagHelperDescriptor(
tagName: "*",
typeName: "TestNamespace.CatchAllTagHelper",
assemblyName: "TestAssembly",
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder
.RequireAttributeDescriptor(attribute => attribute
.Name("href")
.NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch)
.Value("~/")
.ValueComparisonMode(RequiredAttributeDescriptor.ValueComparisonMode.PrefixMatch)),
}),
CreateTagHelperDescriptor(
tagName: "*",
typeName: "TestNamespace.CatchAllTagHelper2",
assemblyName: "TestAssembly",
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder
.RequireAttributeDescriptor(attribute => attribute
.Name("type")
.NameComparisonMode(RequiredAttributeDescriptor.NameComparisonMode.FullMatch)),
}),
};
}
}
public static IEnumerable<TagHelperDescriptor> EnumTagHelperDescriptors
{
get
{
return new[]
{
CreateTagHelperDescriptor(
tagName: "*",
typeName: "TestNamespace.CatchAllTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("catch-all")
.PropertyName("CatchAll")
.AsEnum()
.TypeName($"{typeof(TestTagHelperDescriptors).FullName}.{nameof(MyEnum)}"),
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("value")
.PropertyName("Value")
.AsEnum()
.TypeName($"{typeof(TestTagHelperDescriptors).FullName}.{nameof(MyEnum)}"),
}),
};
}
}
public static IEnumerable<TagHelperDescriptor> SymbolBoundTagHelperDescriptors
{
get
{
return new[]
{
CreateTagHelperDescriptor(
tagName: "*",
typeName: "TestNamespace.CatchAllTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("[item]")
.PropertyName("ListItems")
.TypeName("System.Collections.Generic.List<string>"),
builder => builder
.Name("[(item)]")
.PropertyName("ArrayItems")
.TypeName(typeof(string[]).FullName),
builder => builder
.Name("(click)")
.PropertyName("Event1")
.TypeName(typeof(Action).FullName),
builder => builder
.Name("(^click)")
.PropertyName("Event2")
.TypeName(typeof(Action).FullName),
builder => builder
.Name("*something")
.PropertyName("StringProperty1")
.TypeName(typeof(string).FullName),
builder => builder
.Name("#local")
.PropertyName("StringProperty2")
.TypeName(typeof(string).FullName),
},
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder.RequireAttributeDescriptor(attribute => attribute.Name("bound")),
}),
};
}
}
public static IEnumerable<TagHelperDescriptor> MinimizedTagHelpers_Descriptors
{
get
{
return new[]
{
CreateTagHelperDescriptor(
tagName: "*",
typeName: "TestNamespace.CatchAllTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("catchall-bound-string")
.PropertyName("BoundRequiredString")
.TypeName(typeof(string).FullName),
},
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder.RequireAttributeDescriptor(attribute => attribute.Name("catchall-unbound-required")),
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("input-bound-required-string")
.PropertyName("BoundRequiredString")
.TypeName(typeof(string).FullName),
builder => builder
.Name("input-bound-string")
.PropertyName("BoundString")
.TypeName(typeof(string).FullName),
},
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder
.RequireAttributeDescriptor(attribute => attribute.Name("input-bound-required-string"))
.RequireAttributeDescriptor(attribute => attribute.Name("input-unbound-required")),
}),
CreateTagHelperDescriptor(
tagName: "div",
typeName: "DivTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("boundbool")
.PropertyName("BoundBoolProp")
.TypeName(typeof(bool).FullName),
builder => builder
.Name("booldict")
.PropertyName("BoolDictProp")
.TypeName("System.Collections.Generic.IDictionary<string, bool>")
.AsDictionaryAttribute("booldict-prefix-", typeof(bool).FullName),
}),
};
}
}
public static IEnumerable<TagHelperDescriptor> DynamicAttributeTagHelpers_Descriptors
{
get
{
return new[]
{
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("bound")
.PropertyName("Bound")
.TypeName(typeof(string).FullName)
}),
};
}
}
public static IEnumerable<TagHelperDescriptor> DuplicateTargetTagHelperDescriptors
{
get
{
var typePropertyInfo = typeof(TestType).GetRuntimeProperty("Type");
var checkedPropertyInfo = typeof(TestType).GetRuntimeProperty("Checked");
return new[]
{
CreateTagHelperDescriptor(
tagName: "*",
typeName: "TestNamespace.CatchAllTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", typePropertyInfo),
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "checked", checkedPropertyInfo),
},
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder.RequireAttributeDescriptor(attribute => attribute.Name("type")),
builder => builder.RequireAttributeDescriptor(attribute => attribute.Name("checked"))
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", typePropertyInfo),
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "checked", checkedPropertyInfo),
},
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder.RequireAttributeDescriptor(attribute => attribute.Name("type")),
builder => builder.RequireAttributeDescriptor(attribute => attribute.Name("checked"))
})
};
}
}
public static IEnumerable<TagHelperDescriptor> AttributeTargetingTagHelperDescriptors
{
get
{
var inputTypePropertyInfo = typeof(TestType).GetRuntimeProperty("Type");
var inputCheckedPropertyInfo = typeof(TestType).GetRuntimeProperty("Checked");
return new[]
{
CreateTagHelperDescriptor(
tagName: "p",
typeName: "TestNamespace.PTagHelper",
assemblyName: "TestAssembly",
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder.RequireAttributeDescriptor(attribute => attribute.Name("class")),
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo),
},
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder.RequireAttributeDescriptor(attribute => attribute.Name("type")),
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper2",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo),
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "checked", inputCheckedPropertyInfo),
},
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder
.RequireAttributeDescriptor(attribute => attribute.Name("type"))
.RequireAttributeDescriptor(attribute => attribute.Name("checked")),
}),
CreateTagHelperDescriptor(
tagName: "*",
typeName: "TestNamespace.CatchAllTagHelper",
assemblyName: "TestAssembly",
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder.RequireAttributeDescriptor(attribute => attribute.Name("catchAll")),
}),
};
}
}
public static IEnumerable<TagHelperDescriptor> PrefixedAttributeTagHelperDescriptors
{
get
{
return new[]
{
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper1",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("int-prefix-grabber")
.PropertyName("IntProperty")
.TypeName(typeof(int).FullName),
builder => builder
.Name("int-dictionary")
.PropertyName("IntDictionaryProperty")
.TypeName("System.Collections.Generic.IDictionary<string, int>")
.AsDictionaryAttribute("int-prefix-", typeof(int).FullName),
builder => builder
.Name("string-prefix-grabber")
.PropertyName("StringProperty")
.TypeName(typeof(string).FullName),
builder => builder
.Name("string-dictionary")
.PropertyName("StringDictionaryProperty")
.TypeName("Namespace.DictionaryWithoutParameterlessConstructor<string, string>")
.AsDictionaryAttribute("string-prefix-", typeof(string).FullName),
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper2",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => builder
.Name("int-dictionary")
.PropertyName("IntDictionaryProperty")
.TypeName(typeof(int).FullName)
.AsDictionaryAttribute("int-prefix-", typeof(int).FullName),
builder => builder
.Name("string-dictionary")
.PropertyName("StringDictionaryProperty")
.TypeName("Namespace.DictionaryWithoutParameterlessConstructor<string, string>")
.AsDictionaryAttribute("string-prefix-", typeof(string).FullName),
}),
};
}
}
public static IEnumerable<TagHelperDescriptor> TagHelpersInSectionDescriptors
{
get
{
var propertyInfo = typeof(TestType).GetRuntimeProperty("BoundProperty");
return new[]
{
CreateTagHelperDescriptor(
tagName: "MyTagHelper",
typeName: "TestNamespace.MyTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "BoundProperty", propertyInfo),
}),
CreateTagHelperDescriptor(
tagName: "NestedTagHelper",
typeName: "TestNamespace.NestedTagHelper",
assemblyName: "TestAssembly"),
};
}
}
public static IEnumerable<TagHelperDescriptor> DefaultPAndInputTagHelperDescriptors
{
get
{
var pAgePropertyInfo = typeof(TestType).GetRuntimeProperty("Age");
var inputTypePropertyInfo = typeof(TestType).GetRuntimeProperty("Type");
var checkedPropertyInfo = typeof(TestType).GetRuntimeProperty("Checked");
return new[]
{
CreateTagHelperDescriptor(
tagName: "p",
typeName: "TestNamespace.PTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "age", pAgePropertyInfo),
},
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder.RequireTagStructure(TagStructure.NormalOrSelfClosing)
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo),
},
ruleBuilders: new Action<TagMatchingRuleDescriptorBuilder>[]
{
builder => builder.RequireTagStructure(TagStructure.WithoutEndTag)
}),
CreateTagHelperDescriptor(
tagName: "input",
typeName: "TestNamespace.InputTagHelper2",
assemblyName: "TestAssembly",
attributes: new Action<BoundAttributeDescriptorBuilder>[]
{
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "type", inputTypePropertyInfo),
builder => BuildBoundAttributeDescriptorFromPropertyInfo(builder, "checked", checkedPropertyInfo),
}),
};
}
}
private static TagHelperDescriptor CreateTagHelperDescriptor(
string tagName,
string typeName,
string assemblyName,
IEnumerable<Action<BoundAttributeDescriptorBuilder>> attributes = null,
IEnumerable<Action<TagMatchingRuleDescriptorBuilder>> ruleBuilders = null)
{
var builder = TagHelperDescriptorBuilder.Create(typeName, assemblyName);
builder.TypeName(typeName);
if (attributes != null)
{
foreach (var attributeBuilder in attributes)
{
builder.BoundAttributeDescriptor(attributeBuilder);
}
}
if (ruleBuilders != null)
{
foreach (var ruleBuilder in ruleBuilders)
{
builder.TagMatchingRuleDescriptor(innerRuleBuilder =>
{
innerRuleBuilder.RequireTagName(tagName);
ruleBuilder(innerRuleBuilder);
});
}
}
else
{
builder.TagMatchingRuleDescriptor(ruleBuilder => ruleBuilder.RequireTagName(tagName));
}
var descriptor = builder.Build();
return descriptor;
}
private static void BuildBoundAttributeDescriptorFromPropertyInfo(
BoundAttributeDescriptorBuilder builder,
string name,
PropertyInfo propertyInfo)
{
builder
.Name(name)
.PropertyName(propertyInfo.Name)
.TypeName(propertyInfo.PropertyType.FullName);
if (propertyInfo.PropertyType.GetTypeInfo().IsEnum)
{
builder.AsEnum();
}
}
private class TestType
{
public int Age { get; set; }
public string Type { get; set; }
public bool Checked { get; set; }
public string BoundProperty { get; set; }
}
public enum MyEnum
{
MyValue,
MySecondValue
}
}
}

View File

@ -0,0 +1,217 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public class DefaultRazorIntermediateNodeBuilderTest
{
[Fact]
public void Ctor_CreatesEmptyBuilder()
{
// Arrange & Act
var builder = new DefaultRazorIntermediateNodeBuilder();
var current = builder.Current;
// Assert
Assert.Null(current);
}
[Fact]
public void Push_WhenEmpty_AddsNode()
{
// Arrange
var builder = new DefaultRazorIntermediateNodeBuilder();
var node = new BasicIntermediateNode();
// Act
builder.Push(node);
// Assert
Assert.Same(node, builder.Current);
}
[Fact]
public void Push_WhenNonEmpty_SetsUpChild()
{
// Arrange
var builder = new DefaultRazorIntermediateNodeBuilder();
var parent = new BasicIntermediateNode();
builder.Push(parent);
var node = new BasicIntermediateNode();
// Act
builder.Push(node);
// Assert
Assert.Same(node, builder.Current);
Assert.Collection(parent.Children, n => Assert.Same(node, n));
}
[Fact]
public void Pop_ThrowsWhenEmpty()
{
// Arrange
var builder = new DefaultRazorIntermediateNodeBuilder();
// Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
() => builder.Pop(),
"The 'Pop' operation is not valid when the builder is empty.");
}
[Fact]
public void Pop_SingleNodeDepth_RemovesAndReturnsNode()
{
// Arrange
var builder = new DefaultRazorIntermediateNodeBuilder();
var node = new BasicIntermediateNode();
builder.Push(node);
// Act
var result = builder.Pop();
// Assert
Assert.Same(node, result);
Assert.Null(builder.Current);
}
[Fact]
public void Pop_MultipleNodeDepth_RemovesAndReturnsNode()
{
// Arrange
var builder = new DefaultRazorIntermediateNodeBuilder();
var parent = new BasicIntermediateNode();
builder.Push(parent);
var node = new BasicIntermediateNode();
builder.Push(node);
// Act
var result = builder.Pop();
// Assert
Assert.Same(node, result);
Assert.Same(parent, builder.Current);
}
[Fact]
public void Add_AddsToChildrenAndSetsParent()
{
// Arrange
var builder = new DefaultRazorIntermediateNodeBuilder();
var parent = new BasicIntermediateNode();
builder.Push(parent);
var node = new BasicIntermediateNode();
// Act
builder.Add(node);
// Assert
Assert.Same(parent, builder.Current);
Assert.Collection(parent.Children, n => Assert.Same(node, n));
}
[Fact]
public void Insert_AddsToChildren_EmptyCollection()
{
// Arrange
var builder = new DefaultRazorIntermediateNodeBuilder();
var parent = new BasicIntermediateNode();
builder.Push(parent);
var node = new BasicIntermediateNode();
// Act
builder.Insert(0, node);
// Assert
Assert.Same(parent, builder.Current);
Assert.Collection(parent.Children, n => Assert.Same(node, n));
}
[Fact]
public void Insert_AddsToChildren_NonEmpyCollection()
{
// Arrange
var builder = new DefaultRazorIntermediateNodeBuilder();
var parent = new BasicIntermediateNode();
builder.Push(parent);
var child = new BasicIntermediateNode();
builder.Add(child);
var node = new BasicIntermediateNode();
// Act
builder.Insert(0, node);
// Assert
Assert.Same(parent, builder.Current);
Assert.Collection(parent.Children, n => Assert.Same(node, n), n => Assert.Same(child, n));
}
[Fact]
public void Insert_AddsToChildren_NonEmpyCollection_AtEnd()
{
// Arrange
var builder = new DefaultRazorIntermediateNodeBuilder();
var parent = new BasicIntermediateNode();
builder.Push(parent);
var child = new BasicIntermediateNode();
builder.Add(child);
var node = new BasicIntermediateNode();
// Act
builder.Insert(1, node);
// Assert
Assert.Same(parent, builder.Current);
Assert.Collection(parent.Children, n => Assert.Same(child, n), n => Assert.Same(node, n));
}
[Fact]
public void Build_PopsMultipleLevels()
{
// Arrange
var builder = new DefaultRazorIntermediateNodeBuilder();
var document = new DocumentIntermediateNode();
builder.Push(document);
var node = new BasicIntermediateNode();
builder.Push(node);
// Act
var result = builder.Build();
// Assert
Assert.Same(document, result);
Assert.Null(builder.Current);
}
private class BasicIntermediateNode : IntermediateNode
{
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
public override void Accept(IntermediateNodeVisitor visitor)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,113 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public class DocumentIntermediateNodeExtensionsTest
{
[Fact]
public void FindPrimaryClass_FindsClassWithAnnotation()
{
// Arrange
var document = new DocumentIntermediateNode();
var @class = new ClassDeclarationIntermediateNode();
@class.Annotations[CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass;
var builder = IntermediateNodeBuilder.Create(document);
builder.Add(@class);
// Act
var result = document.FindPrimaryClass();
// Assert
Assert.Same(@class, result);
}
[Fact]
public void FindPrimaryMethod_FindsMethodWithAnnotation()
{
// Arrange
var document = new DocumentIntermediateNode();
var method = new MethodDeclarationIntermediateNode();
method.Annotations[CommonAnnotations.PrimaryMethod] = CommonAnnotations.PrimaryMethod;
var builder = IntermediateNodeBuilder.Create(document);
builder.Add(method);
// Act
var result = document.FindPrimaryMethod();
// Assert
Assert.Same(method, result);
}
[Fact]
public void FindPrimaryNamespace_FindsNamespaceWithAnnotation()
{
// Arrange
var document = new DocumentIntermediateNode();
var @namespace = new NamespaceDeclarationIntermediateNode();
@namespace.Annotations[CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace;
var builder = IntermediateNodeBuilder.Create(document);
builder.Add(@namespace);
// Act
var result = document.FindPrimaryNamespace();
// Assert
Assert.Same(@namespace, result);
}
[Fact]
public void FindDirectiveReferences_FindsMatchingDirectives()
{
// Arrange
var directive = DirectiveDescriptor.CreateSingleLineDirective("test");
var directive2 = DirectiveDescriptor.CreateSingleLineDirective("test");
var document = new DocumentIntermediateNode();
var @namespace = new NamespaceDeclarationIntermediateNode();
var builder = IntermediateNodeBuilder.Create(document);
builder.Push(@namespace);
var match1 = new DirectiveIntermediateNode()
{
Directive = directive,
};
builder.Add(match1);
var nonMatch = new DirectiveIntermediateNode()
{
Directive = directive2,
};
builder.Add(nonMatch);
var match2 = new DirectiveIntermediateNode()
{
Directive = directive,
};
builder.Add(match2);
// Act
var results = document.FindDirectiveReferences(directive);
// Assert
Assert.Collection(
results,
r =>
{
Assert.Same(@namespace, r.Parent);
Assert.Same(match1, r.Node);
},
r =>
{
Assert.Same(@namespace, r.Parent);
Assert.Same(match2, r.Node);
});
}
}
}

View File

@ -0,0 +1,92 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
// These tests cover the methods on ExtensionIntermediateNode that are used to implement visitors
// that special case an extension node.
public class ExtensionIntermediateNodeTest
{
[Fact]
public void Accept_CallsStandardVisitExtension_ForStandardVisitor()
{
// Arrange
var node = new TestExtensionIntermediateNode();
var visitor = new StandardVisitor();
// Act
node.Accept(visitor);
// Assert
Assert.True(visitor.WasStandardMethodCalled);
Assert.False(visitor.WasSpecificMethodCalled);
}
[Fact]
public void Accept_CallsSpecialVisitExtension_ForSpecialVisitor()
{
// Arrange
var node = new TestExtensionIntermediateNode();
var visitor = new SpecialVisitor();
// Act
node.Accept(visitor);
// Assert
Assert.False(visitor.WasStandardMethodCalled);
Assert.True(visitor.WasSpecificMethodCalled);
}
private class TestExtensionIntermediateNode : ExtensionIntermediateNode
{
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
public override void Accept(IntermediateNodeVisitor visitor)
{
// This is the standard visitor boilerplate for an extension node.
AcceptExtensionNode<TestExtensionIntermediateNode>(this, visitor);
}
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
{
throw new NotImplementedException();
}
}
private class StandardVisitor : IntermediateNodeVisitor
{
public bool WasStandardMethodCalled { get; private set; }
public bool WasSpecificMethodCalled { get; private set; }
public override void VisitExtension(ExtensionIntermediateNode node)
{
WasStandardMethodCalled = true;
}
public void VisitExtension(TestExtensionIntermediateNode node)
{
WasSpecificMethodCalled = true;
}
}
private class SpecialVisitor : IntermediateNodeVisitor, IExtensionIntermediateNodeVisitor<TestExtensionIntermediateNode>
{
public bool WasStandardMethodCalled { get; private set; }
public bool WasSpecificMethodCalled { get; private set; }
public override void VisitExtension(ExtensionIntermediateNode node)
{
WasStandardMethodCalled = true;
}
public void VisitExtension(TestExtensionIntermediateNode node)
{
WasSpecificMethodCalled = true;
}
}
}
}

View File

@ -0,0 +1,510 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public class IntermediateNodeReferenceTest
{
[Fact]
public void InsertAfter_SingleNode_AddsNodeAfterNode()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var node2 = new BasicIntermediateNode("Node2");
var node3 = new BasicIntermediateNode("Node3");
parent.Children.Add(node1);
parent.Children.Add(node3);
var reference = new IntermediateNodeReference(parent, node1);
// Act
reference.InsertAfter(node2);
// Assert
Assert.Equal(new[] { node1, node2, node3, }, parent.Children);
}
[Fact]
public void InsertAfter_SingleNode_AddsNodeAfterNode_AtEnd()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var node2 = new BasicIntermediateNode("Node2");
var node3 = new BasicIntermediateNode("Node3");
parent.Children.Add(node1);
parent.Children.Add(node2);
var reference = new IntermediateNodeReference(parent, node2);
// Act
reference.InsertAfter(node3);
// Assert
Assert.Equal(new[] { node1, node2, node3, }, parent.Children);
}
[Fact]
public void InsertAfter_MultipleNodes_AddsNodesAfterNode()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var node2 = new BasicIntermediateNode("Node2");
var node3 = new BasicIntermediateNode("Node3");
var node4 = new BasicIntermediateNode("Node4");
parent.Children.Add(node1);
parent.Children.Add(node4);
var reference = new IntermediateNodeReference(parent, node1);
// Act
reference.InsertAfter(new[] { node2, node3 });
// Assert
Assert.Equal(new[] { node1, node2, node3, node4, }, parent.Children);
}
[Fact]
public void InsertAfter_MultipleNodes_AddsNodesAfterNode_AtEnd()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var node2 = new BasicIntermediateNode("Node2");
var node3 = new BasicIntermediateNode("Node3");
var node4 = new BasicIntermediateNode("Node4");
parent.Children.Add(node1);
parent.Children.Add(node2);
var reference = new IntermediateNodeReference(parent, node2);
// Act
reference.InsertAfter(new[] { node3, node4 });
// Assert
Assert.Equal(new[] { node1, node2, node3, node4, }, parent.Children);
}
[Fact]
public void InsertBefore_SingleNode_AddsNodeBeforeNode()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var node2 = new BasicIntermediateNode("Node2");
var node3 = new BasicIntermediateNode("Node3");
parent.Children.Add(node1);
parent.Children.Add(node3);
var reference = new IntermediateNodeReference(parent, node3);
// Act
reference.InsertBefore(node2);
// Assert
Assert.Equal(new[] { node1, node2, node3, }, parent.Children);
}
[Fact]
public void InsertBefore_SingleNode_AddsNodeBeforeNode_AtBeginning()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var node2 = new BasicIntermediateNode("Node2");
var node3 = new BasicIntermediateNode("Node3");
parent.Children.Add(node2);
parent.Children.Add(node3);
var reference = new IntermediateNodeReference(parent, node2);
// Act
reference.InsertBefore(node1);
// Assert
Assert.Equal(new[] { node1, node2, node3, }, parent.Children);
}
[Fact]
public void InsertBefore_MultipleNodes_AddsNodesBeforeNode()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var node2 = new BasicIntermediateNode("Node2");
var node3 = new BasicIntermediateNode("Node3");
var node4 = new BasicIntermediateNode("Node4");
parent.Children.Add(node1);
parent.Children.Add(node4);
var reference = new IntermediateNodeReference(parent, node4);
// Act
reference.InsertBefore(new[] { node2, node3 });
// Assert
Assert.Equal(new[] { node1, node2, node3, node4, }, parent.Children);
}
[Fact]
public void InsertAfter_MultipleNodes_AddsNodesBeforeNode_AtBeginning()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var node2 = new BasicIntermediateNode("Node2");
var node3 = new BasicIntermediateNode("Node3");
var node4 = new BasicIntermediateNode("Node4");
parent.Children.Add(node3);
parent.Children.Add(node4);
var reference = new IntermediateNodeReference(parent, node3);
// Act
reference.InsertBefore(new[] { node1, node2 });
// Assert
Assert.Equal(new[] { node1, node2, node3, node4, }, parent.Children);
}
[Fact]
public void Remove_RemovesNode()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var node2 = new BasicIntermediateNode("Node2");
var node3 = new BasicIntermediateNode("Node3");
parent.Children.Add(node1);
parent.Children.Add(node3);
parent.Children.Add(node2);
var reference = new IntermediateNodeReference(parent, node3);
// Act
reference.Remove();
// Assert
Assert.Equal(new[] { node1, node2,}, parent.Children);
}
[Fact]
public void Replace_ReplacesNode()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var node2 = new BasicIntermediateNode("Node2");
var node3 = new BasicIntermediateNode("Node3");
var node4 = new BasicIntermediateNode("Node4");
parent.Children.Add(node1);
parent.Children.Add(node4);
parent.Children.Add(node3);
var reference = new IntermediateNodeReference(parent, node4);
// Act
reference.Replace(node2);
// Assert
Assert.Equal(new[] { node1, node2, node3, }, parent.Children);
}
[Fact]
public void InsertAfter_SingleNode_ThrowsForReferenceNotInitialized()
{
// Arrange
var reference = new IntermediateNodeReference();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertAfter(new BasicIntermediateNode("_")));
Assert.Equal("The reference is invalid. References initialized with the default constructor cannot modify nodes.", exception.Message);
}
[Fact]
public void InsertAfter_MulipleNodes_ThrowsForReferenceNotInitialized()
{
// Arrange
var reference = new IntermediateNodeReference();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertAfter(new[] { new BasicIntermediateNode("_") }));
Assert.Equal("The reference is invalid. References initialized with the default constructor cannot modify nodes.", exception.Message);
}
[Fact]
public void InsertBefore_SingleNode_ThrowsForReferenceNotInitialized()
{
// Arrange
var reference = new IntermediateNodeReference();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertBefore(new BasicIntermediateNode("_")));
Assert.Equal("The reference is invalid. References initialized with the default constructor cannot modify nodes.", exception.Message);
}
[Fact]
public void InsertBefore_MulipleNodes_ThrowsForReferenceNotInitialized()
{
// Arrange
var reference = new IntermediateNodeReference();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertBefore(new[] { new BasicIntermediateNode("_") }));
Assert.Equal("The reference is invalid. References initialized with the default constructor cannot modify nodes.", exception.Message);
}
[Fact]
public void Remove_ThrowsForReferenceNotInitialized()
{
// Arrange
var reference = new IntermediateNodeReference();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.Remove());
Assert.Equal("The reference is invalid. References initialized with the default constructor cannot modify nodes.", exception.Message);
}
[Fact]
public void Replace_ThrowsForReferenceNotInitialized()
{
// Arrange
var reference = new IntermediateNodeReference();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.Replace(new BasicIntermediateNode("_")));
Assert.Equal("The reference is invalid. References initialized with the default constructor cannot modify nodes.", exception.Message);
}
[Fact]
public void InsertAfter_SingleNode_ThrowsForReadOnlyCollection()
{
// Arrange
var parent = new BasicIntermediateNode("Parent", IntermediateNodeCollection.ReadOnly);
var node1 = new BasicIntermediateNode("Node1");
var reference = new IntermediateNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertAfter(new BasicIntermediateNode("_")));
Assert.Equal("The node 'Parent' has a read-only child collection and cannot be modified.", exception.Message);
}
[Fact]
public void InsertAfter_MulipleNodes_ThrowsForReadOnlyCollection()
{
// Arrange
var parent = new BasicIntermediateNode("Parent", IntermediateNodeCollection.ReadOnly);
var node1 = new BasicIntermediateNode("Node1");
var reference = new IntermediateNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertAfter(new[] { new BasicIntermediateNode("_") }));
Assert.Equal("The node 'Parent' has a read-only child collection and cannot be modified.", exception.Message);
}
[Fact]
public void InsertBefore_SingleNode_ThrowsForReadOnlyCollection()
{
// Arrange
var parent = new BasicIntermediateNode("Parent", IntermediateNodeCollection.ReadOnly);
var node1 = new BasicIntermediateNode("Node1");
var reference = new IntermediateNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertBefore(new BasicIntermediateNode("_")));
Assert.Equal("The node 'Parent' has a read-only child collection and cannot be modified.", exception.Message);
}
[Fact]
public void InsertBefore_MulipleNodes_ThrowsForReadOnlyCollection()
{
// Arrange
var parent = new BasicIntermediateNode("Parent", IntermediateNodeCollection.ReadOnly);
var node1 = new BasicIntermediateNode("Node1");
var reference = new IntermediateNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertBefore(new[] { new BasicIntermediateNode("_") }));
Assert.Equal("The node 'Parent' has a read-only child collection and cannot be modified.", exception.Message);
}
[Fact]
public void Remove_ThrowsForReadOnlyCollection()
{
// Arrange
var parent = new BasicIntermediateNode("Parent", IntermediateNodeCollection.ReadOnly);
var node1 = new BasicIntermediateNode("Node1");
var reference = new IntermediateNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.Remove());
Assert.Equal("The node 'Parent' has a read-only child collection and cannot be modified.", exception.Message);
}
[Fact]
public void Replace_ThrowsForReadOnlyCollection()
{
// Arrange
var parent = new BasicIntermediateNode("Parent", IntermediateNodeCollection.ReadOnly);
var node1 = new BasicIntermediateNode("Node1");
var reference = new IntermediateNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.Replace(new BasicIntermediateNode("_")));
Assert.Equal("The node 'Parent' has a read-only child collection and cannot be modified.", exception.Message);
}
[Fact]
public void InsertAfter_SingleNode_ThrowsForNodeNotFound()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var reference = new IntermediateNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertAfter(new BasicIntermediateNode("_")));
Assert.Equal("The reference is invalid. The node 'Node1' could not be found as a child of 'Parent'.", exception.Message);
}
[Fact]
public void InsertAfter_MulipleNodes_ThrowsForNodeNotFound()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var reference = new IntermediateNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertAfter(new[] { new BasicIntermediateNode("_") }));
Assert.Equal("The reference is invalid. The node 'Node1' could not be found as a child of 'Parent'.", exception.Message);
}
[Fact]
public void InsertBefore_SingleNode_ThrowsForNodeNotFound()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var reference = new IntermediateNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertBefore(new BasicIntermediateNode("_")));
Assert.Equal("The reference is invalid. The node 'Node1' could not be found as a child of 'Parent'.", exception.Message);
}
[Fact]
public void InsertBefore_MulipleNodes_ThrowsForNodeNotFound()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var reference = new IntermediateNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertBefore(new[] { new BasicIntermediateNode("_") }));
Assert.Equal("The reference is invalid. The node 'Node1' could not be found as a child of 'Parent'.", exception.Message);
}
[Fact]
public void Remove_ThrowsForNodeNotFound()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var reference = new IntermediateNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.Remove());
Assert.Equal("The reference is invalid. The node 'Node1' could not be found as a child of 'Parent'.", exception.Message);
}
[Fact]
public void Replace_ThrowsForNodeNotFound()
{
// Arrange
var parent = new BasicIntermediateNode("Parent");
var node1 = new BasicIntermediateNode("Node1");
var reference = new IntermediateNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.Replace(new BasicIntermediateNode("_")));
Assert.Equal("The reference is invalid. The node 'Node1' could not be found as a child of 'Parent'.", exception.Message);
}
private class BasicIntermediateNode : IntermediateNode
{
public BasicIntermediateNode(string name)
: this(name, new IntermediateNodeCollection())
{
Name = name;
}
public BasicIntermediateNode(string name, IntermediateNodeCollection children)
{
Name = name;
Children = children;
}
public string Name { get; }
public override IntermediateNodeCollection Children { get; }
public override void Accept(IntermediateNodeVisitor visitor)
{
throw new System.NotImplementedException();
}
public override string ToString() => Name;
}
}
}

View File

@ -0,0 +1,140 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public class IntermediateNodeWalkerTest
{
[Fact]
public void IntermediateNodeWalker_Visit_TraversesEntireGraph()
{
// Arrange
var walker = new DerivedIntermediateNodeWalker();
var nodes = new IntermediateNode[]
{
new BasicIntermediateNode("Root"),
new BasicIntermediateNode("Root->A"),
new BasicIntermediateNode("Root->B"),
new BasicIntermediateNode("Root->B->1"),
new BasicIntermediateNode("Root->B->2"),
new BasicIntermediateNode("Root->C"),
};
var builder = new DefaultRazorIntermediateNodeBuilder();
builder.Push(nodes[0]);
builder.Add(nodes[1]);
builder.Push(nodes[2]);
builder.Add(nodes[3]);
builder.Add(nodes[4]);
builder.Pop();
builder.Add(nodes[5]);
var root = builder.Pop();
// Act
walker.Visit(root);
// Assert
Assert.Equal(nodes, walker.Visited.ToArray());
}
[Fact]
public void IntermediateNodeWalker_Visit_SetsParentAndAncestors()
{
// Arrange
var walker = new DerivedIntermediateNodeWalker();
var nodes = new IntermediateNode[]
{
new BasicIntermediateNode("Root"),
new BasicIntermediateNode("Root->A"),
new BasicIntermediateNode("Root->B"),
new BasicIntermediateNode("Root->B->1"),
new BasicIntermediateNode("Root->B->2"),
new BasicIntermediateNode("Root->C"),
};
var ancestors = new Dictionary<string, string[]>()
{
{ "Root", new string[]{ } },
{ "Root->A", new string[] { "Root" } },
{ "Root->B", new string[] { "Root" } },
{ "Root->B->1", new string[] { "Root->B", "Root" } },
{ "Root->B->2", new string[] { "Root->B", "Root" } },
{ "Root->C", new string[] { "Root" } },
};
walker.OnVisiting = (n) =>
{
Assert.Equal(ancestors[((BasicIntermediateNode)n).Name], walker.Ancestors.Cast<BasicIntermediateNode>().Select(b => b.Name));
Assert.Equal(ancestors[((BasicIntermediateNode)n).Name].FirstOrDefault(), ((BasicIntermediateNode)walker.Parent)?.Name);
};
var builder = new DefaultRazorIntermediateNodeBuilder();
builder.Push(nodes[0]);
builder.Add(nodes[1]);
builder.Push(nodes[2]);
builder.Add(nodes[3]);
builder.Add(nodes[4]);
builder.Pop();
builder.Add(nodes[5]);
var root = builder.Pop();
// Act & Assert
walker.Visit(root);
}
private class DerivedIntermediateNodeWalker : IntermediateNodeWalker
{
public new IReadOnlyList<IntermediateNode> Ancestors => base.Ancestors;
public new IntermediateNode Parent => base.Parent;
public List<IntermediateNode> Visited { get; } = new List<IntermediateNode>();
public Action<IntermediateNode> OnVisiting { get; set; }
public override void VisitDefault(IntermediateNode node)
{
Visited.Add(node);
OnVisiting?.Invoke(node);
base.VisitDefault(node);
}
public virtual void VisitBasic(BasicIntermediateNode node)
{
VisitDefault(node);
}
}
private class BasicIntermediateNode : IntermediateNode
{
public BasicIntermediateNode(string name)
{
Name = name;
}
public string Name { get; }
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
public override void Accept(IntermediateNodeVisitor visitor)
{
((DerivedIntermediateNodeWalker)visitor).VisitBasic(this);
}
public override string ToString()
{
return Name;
}
}
}
}

View File

@ -0,0 +1,172 @@
using System.IO;
using System.Text;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Test
{
public class LargeTextSourceDocumentTest
{
private const int ChunkTestLength = 10;
[Fact]
public void GetChecksum_ReturnsCopiedChecksum()
{
// Arrange
var contentString = "Hello World";
var stream = TestRazorSourceDocument.CreateStreamContent(contentString);
var reader = new StreamReader(stream, detectEncodingFromByteOrderMarks: true);
var document = new LargeTextSourceDocument(reader, 5, Encoding.UTF8, RazorSourceDocumentProperties.Default);
// Act
var firstChecksum = document.GetChecksum();
var secondChecksum = document.GetChecksum();
// Assert
Assert.Equal(firstChecksum, secondChecksum);
Assert.NotSame(firstChecksum, secondChecksum);
}
[Fact]
public void GetChecksum_ComputesCorrectChecksum_UTF8()
{
// Arrange
var contentString = "Hello World";
var stream = TestRazorSourceDocument.CreateStreamContent(contentString);
var reader = new StreamReader(stream, detectEncodingFromByteOrderMarks: true);
var document = new LargeTextSourceDocument(reader, 5, Encoding.UTF8, RazorSourceDocumentProperties.Default);
var expectedChecksum = new byte[] { 10, 77, 85, 168, 215, 120, 229, 2, 47, 171, 112, 25, 119, 197, 216, 64, 187, 196, 134, 208 };
// Act
var checksum = document.GetChecksum();
// Assert
Assert.Equal(expectedChecksum, checksum);
}
[Fact]
public void GetChecksum_ComputesCorrectChecksum_UTF32()
{
// Arrange
var contentString = "Hello World";
var stream = TestRazorSourceDocument.CreateStreamContent(contentString, Encoding.UTF32);
var reader = new StreamReader(stream, detectEncodingFromByteOrderMarks: true);
var document = new LargeTextSourceDocument(reader, 5, Encoding.UTF32, RazorSourceDocumentProperties.Default);
var expectedChecksum = new byte[] { 108, 172, 130, 171, 42, 19, 155, 176, 211, 80, 224, 121, 169, 133, 25, 134, 48, 228, 199, 141 };
// Act
var checksum = document.GetChecksum();
// Assert
Assert.Equal(expectedChecksum, checksum);
}
[Theory]
[InlineData(ChunkTestLength - 1)]
[InlineData(ChunkTestLength)]
[InlineData(ChunkTestLength + 1)]
[InlineData(ChunkTestLength * 2 - 1)]
[InlineData(ChunkTestLength * 2)]
[InlineData(ChunkTestLength * 2 + 1)]
public void Indexer_ProvidesCharacterAccessToContent(int contentLength)
{
// Arrange
var content = new char[contentLength];
for (var i = 0; i < contentLength - 1; i++)
{
content[i] = 'a';
}
content[contentLength - 1] = 'b';
var contentString = new string(content);
var stream = TestRazorSourceDocument.CreateStreamContent(new string(content));
var reader = new StreamReader(stream, true);
var document = new LargeTextSourceDocument(reader, ChunkTestLength, Encoding.UTF8, RazorSourceDocumentProperties.Default);
// Act
var output = new char[contentLength];
for (var i = 0; i < document.Length; i++)
{
output[i] = document[i];
}
var outputString = new string(output);
// Assert
Assert.Equal(contentLength, document.Length);
Assert.Equal(contentString, outputString);
}
[Theory]
[InlineData("test.cshtml")]
[InlineData(null)]
public void FilePath(string filePath)
{
// Arrange
var stream = TestRazorSourceDocument.CreateStreamContent("abc");
var reader = new StreamReader(stream, true);
// Act
var document = new LargeTextSourceDocument(reader, ChunkTestLength, Encoding.UTF8, new RazorSourceDocumentProperties(filePath: filePath, relativePath: null));
// Assert
Assert.Equal(filePath, document.FilePath);
}
[Theory]
[InlineData("test.cshtml")]
[InlineData(null)]
public void RelativePath(string relativePath)
{
// Arrange
var stream = TestRazorSourceDocument.CreateStreamContent("abc");
var reader = new StreamReader(stream, true);
// Act
var document = new LargeTextSourceDocument(reader, ChunkTestLength, Encoding.UTF8, new RazorSourceDocumentProperties(filePath: null, relativePath: relativePath));
// Assert
Assert.Equal(relativePath, document.RelativePath);
}
[Fact]
public void Lines()
{
// Arrange
var stream = TestRazorSourceDocument.CreateStreamContent("abc\ndef\nghi");
var reader = new StreamReader(stream, true);
// Act
var document = new LargeTextSourceDocument(reader, ChunkTestLength, Encoding.UTF8, RazorSourceDocumentProperties.Default);
// Assert
Assert.Equal(3, document.Lines.Count);
}
[Theory]
[InlineData("", 0, 0, 0)] // Nothing to copy
[InlineData("a", 0, 100, 1)] // Destination index different from start
[InlineData("j", ChunkTestLength - 1, 0, 1)] // One char just before the chunk limit
[InlineData("k", ChunkTestLength, 0, 1)] // One char one the chunk limit
[InlineData("l", ChunkTestLength + 1, 0, 1)] // One char just after the chunk limit
[InlineData("jk", ChunkTestLength - 1, 0, 2)] // Two char that are on both chunk sides
[InlineData("abcdefghijklmnopqrstuvwxy", 0, 100, 25)] // Everything except the last
[InlineData("abcdefghijklmnopqrstuvwxyz", 0, 0, 26)] // Copy all
[InlineData("xyz", 23, 0, 3)] // The last chars
public void CopyTo(string expected, int sourceIndex, int destinationIndex, int count)
{
// Arrange
var stream = TestRazorSourceDocument.CreateStreamContent("abcdefghijklmnopqrstuvwxyz");
var reader = new StreamReader(stream, true);
var document = new LargeTextSourceDocument(reader, ChunkTestLength, Encoding.UTF8, RazorSourceDocumentProperties.Default);
// Act
var destination = new char[1000];
document.CopyTo(sourceIndex, destination, destinationIndex, count);
// Assert
var copy = new string(destination, destinationIndex, count);
Assert.Equal(expected, copy);
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
using System.IO;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public static class BaselineWriter
{
private static object baselineLock = new object();
[Conditional("GENERATE_BASELINES")]
public static void WriteBaseline(string baselineFile, string output)
{
var root = RecursiveFind("Razor.sln", Path.GetFullPath("."));
var baselinePath = Path.Combine(root, baselineFile);
// Serialize writes to minimize contention for file handles and directory access.
lock (baselineLock)
{
// Update baseline
using (var stream = File.Open(baselinePath, FileMode.Create, FileAccess.Write))
{
using (var writer = new StreamWriter(stream))
{
writer.Write(output);
}
}
}
}
private static string RecursiveFind(string path, string start)
{
var test = Path.Combine(start, path);
if (File.Exists(test))
{
return start;
}
else
{
return RecursiveFind(path, new DirectoryInfo(start).Parent.FullName);
}
}
}
}

View File

@ -0,0 +1,53 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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.Extensions;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpAutoCompleteTest : ParserTestBase
{
[Fact]
public void FunctionsDirectiveAutoCompleteAtEOF()
{
// Arrange, Act & Assert
ParseDocumentTest("@functions{", new[] { FunctionsDirective.Directive });
}
[Fact]
public void SectionDirectiveAutoCompleteAtEOF()
{
// Arrange, Act & Assert
ParseDocumentTest("@section Header {", new[] { SectionDirective.Directive });
}
[Fact]
public void VerbatimBlockAutoCompleteAtEOF()
{
ParseDocumentTest("@{");
}
[Fact]
public void FunctionsDirectiveAutoCompleteAtStartOfFile()
{
// Arrange, Act & Assert
ParseDocumentTest("@functions{" + Environment.NewLine + "foo", new[] { FunctionsDirective.Directive });
}
[Fact]
public void SectionDirectiveAutoCompleteAtStartOfFile()
{
// Arrange, Act & Assert
ParseDocumentTest("@section Header {" + Environment.NewLine + "<p>Foo</p>", new[] { SectionDirective.Directive });
}
[Fact]
public void VerbatimBlockAutoCompleteAtStartOfFile()
{
ParseDocumentTest("@{" + Environment.NewLine + "<p></p>");
}
}
}

View File

@ -0,0 +1,719 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpBlockTest : ParserTestBase
{
[Fact]
public void LocalFunctionsWithRazor()
{
ParseDocumentTest(
@"@{
void Foo()
{
var time = DateTime.Now
<strong>Hello the time is @time</strong>
}
}");
}
[Fact]
public void LocalFunctionsWithGenerics()
{
ParseDocumentTest(
@"@{
void Foo()
{
<strong>Hello the time is @{ DisplayCount(new List<string>()); }</strong>
}
void DisplayCount<T>(List<T> something)
{
<text>The count is something.Count</text>
}
}");
}
[Fact]
public void NestedCodeBlockWithCSharpAt()
{
ParseDocumentTest("@{ if (true) { var val = @x; if (val != 3) { } } }");
}
[Fact]
public void NestedCodeBlockWithMarkupSetsDotAsMarkup()
{
ParseDocumentTest("@if (true) { @if(false) { <div>@something.</div> } }");
}
[Fact]
public void BalancingBracketsIgnoresStringLiteralCharactersAndBrackets()
{
// BalancingBracketsIgnoresStringLiteralCharactersAndBracketsInsideSingleLineComments
ParseDocumentTest(@"@if(foo) {
// bar } "" baz '
zoop();
}");
}
[Fact]
public void NestedCodeBlockWithAtDoesntCauseError()
{
ParseDocumentTest("@if (true) { @if(false) { } }");
}
[Fact]
public void BalancingBracketsIgnoresStringLiteralCharactersAndBracketsInsideBlockComments()
{
ParseDocumentTest(
@"@if(foo) {
/* bar } "" */ ' baz } '
zoop();
}");
}
[Fact]
public void SkipsExprThenBalancesBracesIfFirstIdentifierIsForKeyword()
{
// ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsForKeyword
ParseDocumentTest(
"@for(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }");
}
[Fact]
public void SkipsExprThenBalancesBracesIfFirstIdentifierIsForeachKeyword()
{
// ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsForeachKeyword
ParseDocumentTest(
"@foreach(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }");
}
[Fact]
public void SkipsExprThenBalancesBracesIfFirstIdentifierIsWhileKeyword()
{
// ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsWhileKeyword
ParseDocumentTest(
"@while(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }");
}
[Fact]
public void SkipsExprThenBalancesIfFirstIdentifierIsUsingFollowedByParen()
{
// ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsUsingKeywordFollowedByParen
ParseDocumentTest(
"@using(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }");
}
[Fact]
public void SupportsUsingsNestedWithinOtherBlocks()
{
ParseDocumentTest(
"@if(foo) { using(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); } }");
}
[Fact]
public void SkipsExprThenBalancesBracesIfFirstIdentifierIsIfKeywordWithNoElseBranches()
{
// ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsIfKeywordWithNoElseBranches
ParseDocumentTest(
"@if(int i = 0; i < 10; new Foo { Bar = \"baz\" }) { Debug.WriteLine(@\"foo } bar\"); }");
}
[Fact]
public void AllowsEmptyBlockStatement()
{
ParseDocumentTest("@if(false) { }");
}
[Fact]
public void TerminatesParenBalancingAtEOF()
{
ParseDocumentTest("@Html.En(code()");
}
[Fact]
public void SupportsBlockCommentBetweenIfAndElseClause()
{
ParseDocumentTest(
"@if(foo) { bar(); } /* Foo */ /* Bar */ else { baz(); }");
}
[Fact]
public void SupportsRazorCommentBetweenIfAndElseClause()
{
RunRazorCommentBetweenClausesTest(
"@if(foo) { bar(); } ", " else { baz(); }");
}
[Fact]
public void SupportsBlockCommentBetweenElseIfAndElseClause()
{
ParseDocumentTest(
"@if(foo) { bar(); } else if(bar) { baz(); } /* Foo */ /* Bar */ else { biz(); }");
}
[Fact]
public void SupportsRazorCommentBetweenElseIfAndElseClause()
{
RunRazorCommentBetweenClausesTest(
"@if(foo) { bar(); } else if(bar) { baz(); } ", " else { baz(); }");
}
[Fact]
public void SupportsBlockCommentBetweenIfAndElseIfClause()
{
ParseDocumentTest(
"if(foo) { bar(); } /* Foo */ /* Bar */ else if(bar) { baz(); }");
}
[Fact]
public void SupportsRazorCommentBetweenIfAndElseIfClause()
{
RunRazorCommentBetweenClausesTest("@if(foo) { bar(); } ", " else if(bar) { baz(); }");
}
[Fact]
public void SupportsLineCommentBetweenIfAndElseClause()
{
ParseDocumentTest(@"@if(foo) { bar(); }
// Foo
// Bar
else { baz(); }");
}
[Fact]
public void SupportsLineCommentBetweenElseIfAndElseClause()
{
ParseDocumentTest(@"@if(foo) { bar(); } else if(bar) { baz(); }
// Foo
// Bar
else { biz(); }");
}
[Fact]
public void SupportsLineCommentBetweenIfAndElseIfClause()
{
ParseDocumentTest(@"@if(foo) { bar(); }
// Foo
// Bar
else if(bar) { baz(); }");
}
[Fact]
public void ParsesElseIfBranchesOfIfStatement()
{
const string ifStatement = @"@if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""foo } bar"");
}";
const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""bar } baz"");
}";
const string document = ifStatement + elseIfBranch;
ParseDocumentTest(document);
}
[Fact]
public void ParsesMultipleElseIfBranchesOfIfStatement()
{
const string ifStatement = @"@if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""foo } bar"");
}";
const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""bar } baz"");
}";
const string document = ifStatement + elseIfBranch + elseIfBranch + elseIfBranch + elseIfBranch;
ParseDocumentTest(document);
}
[Fact]
public void ParsesMultipleElseIfBranchesOfIfStatementFollowedByOneElseBranch()
{
const string ifStatement = @"@if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""foo } bar"");
}";
const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""bar } baz"");
}";
const string elseBranch = @" else { Debug.WriteLine(@""bar } baz""); }";
const string document = ifStatement + elseIfBranch + elseIfBranch + elseBranch;
ParseDocumentTest(document);
}
[Fact]
public void StopsParsingCodeAfterElseBranch()
{
const string ifStatement = @"@if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""foo } bar"");
}";
const string elseIfBranch = @" else if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""bar } baz"");
}";
const string elseBranch = @" else { Debug.WriteLine(@""bar } baz""); }";
const string document = ifStatement + elseIfBranch + elseBranch + elseIfBranch;
ParseDocumentTest(document);
}
[Fact]
public void StopsParsingIfIfStatementNotFollowedByElse()
{
const string document = @"@if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""foo } bar"");
}";
ParseDocumentTest(document);
}
[Fact]
public void AcceptsElseIfWithNoCondition()
{
// We don't want to be a full C# parser - If the else if is missing it's condition, the C# compiler
// can handle that, we have all the info we need to keep parsing
const string ifBranch = @"@if(int i = 0; i < 10; new Foo { Bar = ""baz"" }) {
Debug.WriteLine(@""foo } bar"");
}";
const string elseIfBranch = @" else if { foo(); }";
const string document = ifBranch + elseIfBranch;
ParseDocumentTest(document);
}
[Fact]
public void CorrectlyParsesDoWhileBlock()
{
ParseDocumentTest(
"@do { var foo = bar; } while(foo != bar);");
}
[Fact]
public void CorrectlyParsesDoWhileBlockMissingSemicolon()
{
ParseDocumentTest("@do { var foo = bar; } while(foo != bar)");
}
[Fact]
public void CorrectlyParsesDoWhileBlockMissingWhileCondition()
{
ParseDocumentTest("@do { var foo = bar; } while");
}
[Fact]
public void CorrectlyParsesDoWhileBlockMissingWhileConditionWithSemicolon()
{
ParseDocumentTest(
"@do { var foo = bar; } while;");
}
[Fact]
public void CorrectlyParsesDoWhileBlockMissingWhileClauseEntirely()
{
ParseDocumentTest("@do { var foo = bar; } narf;");
}
[Fact]
public void SupportsBlockCommentBetweenDoAndWhileClause()
{
ParseDocumentTest(
"@do { var foo = bar; } /* Foo */ /* Bar */ while(true);");
}
[Fact]
public void SupportsLineCommentBetweenDoAndWhileClause()
{
ParseDocumentTest(@"@do { var foo = bar; }
// Foo
// Bar
while(true);");
}
[Fact]
public void SupportsRazorCommentBetweenDoAndWhileClause()
{
RunRazorCommentBetweenClausesTest(
"@do { var foo = bar; } ", " while(true);");
}
[Fact]
public void CorrectlyParsesMarkupInDoWhileBlock()
{
ParseDocumentTest("@do { var foo = bar; <p>Foo</p> foo++; } while (foo<bar>);");
}
[Fact]
public void SkipsExprThenBalancesBracesIfFirstIdentifierIsSwitchKeyword()
{
// ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsSwitchKeyword
ParseDocumentTest(@"@switch(foo) {
case 0:
break;
case 1:
{
break;
}
case 2:
return;
default:
return;
}");
}
[Fact]
public void ThenBalancesBracesIfFirstIdentifierIsLockKeyword()
{
// ParseBlockSkipsParenthesisedExpressionAndThenBalancesBracesIfFirstIdentifierIsLockKeyword
ParseDocumentTest(
"@lock(foo) { Debug.WriteLine(@\"foo } bar\"); }");
}
[Fact]
public void HasErrorsIfNamespaceImportMissingSemicolon()
{
ParseDocumentTest(
"@using Foo.Bar.Baz");
}
[Fact]
public void HasErrorsIfNamespaceAliasMissingSemicolon()
{
ParseDocumentTest(
"@using Foo.Bar.Baz = FooBarBaz");
}
[Fact]
public void ParsesNamespaceImportWithSemicolonForUsingKeywordIfIsInValidFormat()
{
ParseDocumentTest(
"@using Foo.Bar.Baz;");
}
[Fact]
public void DoesntCaptureWhitespaceAfterUsing()
{
ParseDocumentTest("@using Foo ");
}
[Fact]
public void CapturesNewlineAfterUsing()
{
ParseDocumentTest($"@using Foo{Environment.NewLine}");
}
[Fact]
public void ParsesNamespaceAliasWithSemicolonForUsingKeywordIfIsInValidFormat()
{
ParseDocumentTest(
"@using FooBarBaz = FooBarBaz;");
}
[Fact]
public void TerminatesUsingKeywordAtEOFAndOutputsFileCodeBlock()
{
ParseDocumentTest("@using ");
}
[Fact]
public void TerminatesSingleLineCommentAtEndOfFile()
{
const string document = "@foreach(var f in Foo) { // foo bar baz";
ParseDocumentTest(document);
}
[Fact]
public void TerminatesBlockCommentAtEndOfFile()
{
const string document = "@foreach(var f in Foo) { /* foo bar baz";
ParseDocumentTest(document);
}
[Fact]
public void TerminatesSingleSlashAtEndOfFile()
{
const string document = "@foreach(var f in Foo) { / foo bar baz";
ParseDocumentTest(document);
}
[Fact]
public void SupportsBlockCommentBetweenTryAndFinallyClause()
{
ParseDocumentTest("@try { bar(); } /* Foo */ /* Bar */ finally { baz(); }");
}
[Fact]
public void SupportsRazorCommentBetweenTryAndFinallyClause()
{
RunRazorCommentBetweenClausesTest("@try { bar(); } ", " finally { biz(); }");
}
[Fact]
public void SupportsBlockCommentBetweenCatchAndFinallyClause()
{
ParseDocumentTest(
"@try { bar(); } catch(bar) { baz(); } /* Foo */ /* Bar */ finally { biz(); }");
}
[Fact]
public void SupportsRazorCommentBetweenCatchAndFinallyClause()
{
RunRazorCommentBetweenClausesTest(
"@try { bar(); } catch(bar) { baz(); } ", " finally { biz(); }");
}
[Fact]
public void SupportsBlockCommentBetweenTryAndCatchClause()
{
ParseDocumentTest("@try { bar(); } /* Foo */ /* Bar */ catch(bar) { baz(); }");
}
[Fact]
public void SupportsRazorCommentBetweenTryAndCatchClause()
{
RunRazorCommentBetweenClausesTest("@try { bar(); }", " catch(bar) { baz(); }");
}
[Fact]
public void SupportsLineCommentBetweenTryAndFinallyClause()
{
ParseDocumentTest(@"@try { bar(); }
// Foo
// Bar
finally { baz(); }");
}
[Fact]
public void SupportsLineCommentBetweenCatchAndFinallyClause()
{
ParseDocumentTest(@"@try { bar(); } catch(bar) { baz(); }
// Foo
// Bar
finally { biz(); }");
}
[Fact]
public void SupportsLineCommentBetweenTryAndCatchClause()
{
ParseDocumentTest(@"@try { bar(); }
// Foo
// Bar
catch(bar) { baz(); }");
}
[Fact]
public void SupportsTryStatementWithNoAdditionalClauses()
{
ParseDocumentTest("@try { var foo = new { } }");
}
[Fact]
public void SupportsMarkupWithinTryClause()
{
RunSimpleWrappedMarkupTest(
prefix: "@try {",
markup: " <p>Foo</p> ",
suffix: "}");
}
[Fact]
public void SupportsTryStatementWithOneCatchClause()
{
ParseDocumentTest("@try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } }");
}
[Fact]
public void SupportsMarkupWithinCatchClause()
{
RunSimpleWrappedMarkupTest(
prefix: "@try { var foo = new { } } catch(Foo Bar Baz) {",
markup: " <p>Foo</p> ",
suffix: "}");
}
[Fact]
public void SupportsTryStatementWithMultipleCatchClause()
{
ParseDocumentTest(
"@try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) " +
"{ var foo = new { } } catch(Foo Bar Baz) { var foo = new { } }");
}
[Fact]
public void SupportsExceptionLessCatchClauses()
{
ParseDocumentTest("@try { var foo = new { } } catch { var foo = new { } }");
}
[Fact]
public void SupportsMarkupWithinAdditionalCatchClauses()
{
RunSimpleWrappedMarkupTest(
prefix: "@try { var foo = new { } } catch(Foo Bar Baz) { var foo = new { } } catch(Foo Bar Baz) " +
"{ var foo = new { } } catch(Foo Bar Baz) {",
markup: " <p>Foo</p> ",
suffix: "}");
}
[Fact]
public void SupportsTryStatementWithFinallyClause()
{
ParseDocumentTest("@try { var foo = new { } } finally { var foo = new { } }");
}
[Fact]
public void SupportsMarkupWithinFinallyClause()
{
RunSimpleWrappedMarkupTest(
prefix: "@try { var foo = new { } } finally {",
markup: " <p>Foo</p> ",
suffix: "}");
}
[Fact]
public void StopsParsingCatchClausesAfterFinallyBlock()
{
var content = "@try { var foo = new { } } finally { var foo = new { } }";
ParseDocumentTest(content + " catch(Foo Bar Baz) { }");
}
[Fact]
public void DoesNotAllowMultipleFinallyBlocks()
{
var content = "@try { var foo = new { } } finally { var foo = new { } }";
ParseDocumentTest(content + " finally { }");
}
[Fact]
public void AcceptsTrailingDotIntoImplicitExpressionWhenEmbeddedInCode()
{
// Arrange
ParseDocumentTest("@if(foo) { @foo. }");
}
[Fact]
public void ParsesExpressionOnSwitchCharacterFollowedByOpenParen()
{
// Arrange
ParseDocumentTest("@if(foo) { @(foo + bar) }");
}
[Fact]
public void ParsesExpressionOnSwitchCharacterFollowedByIdentifierStart()
{
// Arrange
ParseDocumentTest("@if(foo) { @foo[4].bar() }");
}
[Fact]
public void TreatsDoubleAtSignAsEscapeSequenceIfAtStatementStart()
{
// Arrange
ParseDocumentTest("@if(foo) { @@class.Foo() }");
}
[Fact]
public void TreatsAtSignsAfterFirstPairAsPartOfCSharpStatement()
{
// Arrange
ParseDocumentTest("@if(foo) { @@@@class.Foo() }");
}
[Fact]
public void DoesNotParseOnSwitchCharacterNotFollowedByOpenAngleOrColon()
{
// ParseBlockDoesNotParseMarkupStatementOrExpressionOnSwitchCharacterNotFollowedByOpenAngleOrColon
// Arrange
ParseDocumentTest("@if(foo) { @\"Foo\".ToString(); }");
}
[Fact]
public void ParsersCanNestRecursively()
{
// Arrange
ParseDocumentTest("@foreach(var c in db.Categories) {" + Environment.NewLine
+ " <div>" + Environment.NewLine
+ " <h1>@c.Name</h1>" + Environment.NewLine
+ " <ul>" + Environment.NewLine
+ " @foreach(var p in c.Products) {" + Environment.NewLine
+ " <li><a href=\"@Html.ActionUrl(\"Products\", \"Detail\", new { id = p.Id })\">@p.Name</a></li>" + Environment.NewLine
+ " }" + Environment.NewLine
+ " </ul>" + Environment.NewLine
+ " </div>" + Environment.NewLine
+ " }");
}
[Fact]
public void WithDoubleTransitionInAttributeValue_DoesNotThrow()
{
var input = "@{<span foo='@@' />}";
ParseDocumentTest(input);
}
[Fact]
public void WithDoubleTransitionAtEndOfAttributeValue_DoesNotThrow()
{
var input = "@{<span foo='abc@@' />}";
ParseDocumentTest(input);
}
[Fact]
public void WithDoubleTransitionAtBeginningOfAttributeValue_DoesNotThrow()
{
var input = "@{<span foo='@@def' />}";
ParseDocumentTest(input);
}
[Fact]
public void WithDoubleTransitionBetweenAttributeValue_DoesNotThrow()
{
var input = "@{<span foo='abc @@ def' />}";
ParseDocumentTest(input);
}
[Fact]
public void WithDoubleTransitionWithExpressionBlock_DoesNotThrow()
{
var input = "@{<span foo='@@@(2+3)' bar='@(2+3)@@@DateTime.Now' baz='@DateTime.Now@@' bat='@DateTime.Now @@' zoo='@@@DateTime.Now' />}";
ParseDocumentTest(input);
}
[Fact]
public void WithDoubleTransitionInEmail_DoesNotThrow()
{
var input = "@{<span foo='abc@def.com abc@@def.com @@' />}";
ParseDocumentTest(input);
}
[Fact]
public void WithDoubleTransitionInRegex_DoesNotThrow()
{
var input = @"@{<span foo=""/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@@[a-z0-9]([a-z0-9-]*[a-z0-9])?\.([a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i"" />}";
ParseDocumentTest(input);
}
[Fact]
public void WithDoubleTransition_EndOfFile_Throws()
{
ParseDocumentTest("@{<span foo='@@");
}
[Fact]
public void WithUnexpectedTransitionsInAttributeValue_Throws()
{
ParseDocumentTest("@{<span foo='@ @' />}");
}
private void RunRazorCommentBetweenClausesTest(string preComment, string postComment, AcceptedCharactersInternal acceptedCharacters = AcceptedCharactersInternal.Any)
{
ParseDocumentTest(preComment + "@* Foo *@ @* Bar *@" + postComment);
}
private void RunSimpleWrappedMarkupTest(string prefix, string markup, string suffix)
{
ParseDocumentTest(prefix + markup + suffix);
}
}
}

View File

@ -0,0 +1,215 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Test.Legacy
{
public class CSharpCodeParserTest
{
public static TheoryData InvalidTagHelperPrefixData
{
get
{
var directiveLocation = new SourceLocation(1, 2, 3);
RazorDiagnostic InvalidPrefixError(int length, char character, string prefix)
{
return RazorDiagnosticFactory.CreateParsing_InvalidTagHelperPrefixValue(
new SourceSpan(directiveLocation, length), SyntaxConstants.CSharp.TagHelperPrefixKeyword, character, prefix);
}
return new TheoryData<string, SourceLocation, IEnumerable<RazorDiagnostic>>
{
{
"th ",
directiveLocation,
new[]
{
InvalidPrefixError(3, ' ', "th "),
}
},
{
"th\t",
directiveLocation,
new[]
{
InvalidPrefixError(3, '\t', "th\t"),
}
},
{
"th" + Environment.NewLine,
directiveLocation,
new[]
{
InvalidPrefixError(2 + Environment.NewLine.Length, Environment.NewLine[0], "th" + Environment.NewLine),
}
},
{
" th ",
directiveLocation,
new[]
{
InvalidPrefixError(4, ' ', " th "),
}
},
{
"@",
directiveLocation,
new[]
{
InvalidPrefixError(1, '@', "@"),
}
},
{
"t@h",
directiveLocation,
new[]
{
InvalidPrefixError(3, '@', "t@h"),
}
},
{
"!",
directiveLocation,
new[]
{
InvalidPrefixError(1, '!', "!"),
}
},
{
"!th",
directiveLocation,
new[]
{
InvalidPrefixError(3, '!', "!th"),
}
},
};
}
}
[Theory]
[MemberData(nameof(InvalidTagHelperPrefixData))]
public void ValidateTagHelperPrefix_ValidatesPrefix(
string directiveText,
SourceLocation directiveLocation,
object expectedErrors)
{
// Arrange
var expectedDiagnostics = (IEnumerable<RazorDiagnostic>)expectedErrors;
var source = TestRazorSourceDocument.Create();
var options = RazorParserOptions.CreateDefault();
var context = new ParserContext(source, options);
var parser = new CSharpCodeParser(context);
var diagnostics = new List<RazorDiagnostic>();
// Act
parser.ValidateTagHelperPrefix(directiveText, directiveLocation, diagnostics);
// Assert
Assert.Equal(expectedDiagnostics, diagnostics);
}
[Theory]
[InlineData("foo,assemblyName", 4)]
[InlineData("foo, assemblyName", 5)]
[InlineData(" foo, assemblyName", 8)]
[InlineData(" foo , assemblyName", 11)]
[InlineData("foo, assemblyName", 8)]
[InlineData(" foo , assemblyName ", 14)]
public void ParseAddOrRemoveDirective_CalculatesAssemblyLocationInLookupText(string text, int assemblyLocation)
{
// Arrange
var source = TestRazorSourceDocument.Create();
var options = RazorParserOptions.CreateDefault();
var context = new ParserContext(source, options);
var parser = new CSharpCodeParser(context);
var directive = new CSharpCodeParser.ParsedDirective()
{
DirectiveText = text,
};
var diagnostics = new List<RazorDiagnostic>();
var expected = new SourceLocation(assemblyLocation, 0, assemblyLocation);
// Act
var result = parser.ParseAddOrRemoveDirective(directive, SourceLocation.Zero, diagnostics);
// Assert
Assert.Empty(diagnostics);
Assert.Equal("foo", result.TypePattern);
Assert.Equal("assemblyName", result.AssemblyName);
}
[Theory]
[InlineData("", 1)]
[InlineData("*,", 2)]
[InlineData("?,", 2)]
[InlineData(",", 1)]
[InlineData(",,,", 3)]
[InlineData("First, ", 7)]
[InlineData("First , ", 8)]
[InlineData(" ,Second", 8)]
[InlineData(" , Second", 9)]
[InlineData("SomeType,", 9)]
[InlineData("SomeAssembly", 12)]
[InlineData("First,Second,Third", 18)]
public void ParseAddOrRemoveDirective_CreatesErrorIfInvalidLookupText_DoesNotThrow(string directiveText, int errorLength)
{
// Arrange
var source = TestRazorSourceDocument.Create();
var options = RazorParserOptions.CreateDefault();
var context = new ParserContext(source, options);
var parser = new CSharpCodeParser(context);
var directive = new CSharpCodeParser.ParsedDirective()
{
DirectiveText = directiveText
};
var diagnostics = new List<RazorDiagnostic>();
var expectedError = RazorDiagnosticFactory.CreateParsing_InvalidTagHelperLookupText(
new SourceSpan(new SourceLocation(1, 2, 3), errorLength), directiveText);
// Act
var result = parser.ParseAddOrRemoveDirective(directive, new SourceLocation(1, 2, 3), diagnostics);
// Assert
Assert.Same(directive, result);
var error = Assert.Single(diagnostics);
Assert.Equal(expectedError, error);
}
[Fact]
public void TagHelperPrefixDirective_DuplicatesCauseError()
{
// Arrange
var expectedDiagnostic = RazorDiagnosticFactory.CreateParsing_DuplicateDirective(
new SourceSpan(null, 22 + Environment.NewLine.Length, 1, 0, 16), "tagHelperPrefix");
var source = TestRazorSourceDocument.Create(
@"@tagHelperPrefix ""th:""
@tagHelperPrefix ""th""",
filePath: null);
// Act
var document = RazorSyntaxTree.Parse(source);
// Assert
var erroredNode = document.Root.DescendantNodes().Last(n => n.GetSpanContext()?.ChunkGenerator is TagHelperPrefixDirectiveChunkGenerator);
var chunkGenerator = Assert.IsType<TagHelperPrefixDirectiveChunkGenerator>(erroredNode.GetSpanContext().ChunkGenerator);
var diagnostic = Assert.Single(chunkGenerator.Diagnostics);
Assert.Equal(expectedDiagnostic, diagnostic);
}
}
}

View File

@ -0,0 +1,345 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpErrorTest : ParserTestBase
{
[Fact]
public void HandlesQuotesAfterTransition()
{
ParseDocumentTest("@\"");
}
[Fact]
public void WithHelperDirectiveProducesError()
{
ParseDocumentTest("@helper fooHelper { }");
}
[Fact]
public void WithNestedCodeBlockProducesError()
{
ParseDocumentTest("@if { @{} }");
}
[Fact]
public void CapturesWhitespaceToEOLInInvalidUsingStmtAndTreatsAsFileCode()
{
// ParseBlockCapturesWhitespaceToEndOfLineInInvalidUsingStatementAndTreatsAsFileCode
ParseDocumentTest(
"@using " + Environment.NewLine + Environment.NewLine);
}
[Fact]
public void MethodOutputsOpenCurlyAsCodeSpanIfEofFoundAfterOpenCurlyBrace()
{
ParseDocumentTest("@{");
}
[Fact]
public void MethodOutputsZeroLengthCodeSpanIfStatementBlockEmpty()
{
ParseDocumentTest("@{}");
}
[Fact]
public void MethodProducesErrorIfNewlineFollowsTransition()
{
ParseDocumentTest("@" + Environment.NewLine);
}
[Fact]
public void MethodProducesErrorIfWhitespaceBetweenTransitionAndBlockStartInEmbeddedExpr()
{
// ParseBlockMethodProducesErrorIfWhitespaceBetweenTransitionAndBlockStartInEmbeddedExpression
ParseDocumentTest("@{" + Environment.NewLine
+ " @ {}" + Environment.NewLine
+ "}");
}
[Fact]
public void MethodProducesErrorIfEOFAfterTransitionInEmbeddedExpression()
{
ParseDocumentTest("@{" + Environment.NewLine
+ " @");
}
[Fact]
public void MethodParsesNothingIfFirstCharacterIsNotIdentifierStartOrParenOrBrace()
{
ParseDocumentTest("@!!!");
}
[Fact]
public void ShouldReportErrorAndTerminateAtEOFIfIfParenInExplicitExprUnclosed()
{
// ParseBlockShouldReportErrorAndTerminateAtEOFIfIfParenInExplicitExpressionUnclosed
ParseDocumentTest("@(foo bar" + Environment.NewLine
+ "baz");
}
[Fact]
public void ShouldReportErrorAndTerminateAtMarkupIfIfParenInExplicitExprUnclosed()
{
// ParseBlockShouldReportErrorAndTerminateAtMarkupIfIfParenInExplicitExpressionUnclosed
ParseDocumentTest("@(foo bar" + Environment.NewLine
+ "<html>" + Environment.NewLine
+ "baz" + Environment.NewLine
+ "</html");
}
[Fact]
public void CorrectlyHandlesInCorrectTransitionsIfImplicitExpressionParensUnclosed()
{
ParseDocumentTest("@Href(" + Environment.NewLine
+ "<h1>@Html.Foo(Bar);</h1>" + Environment.NewLine);
}
[Fact]
// Test for fix to Dev10 884975 - Incorrect Error Messaging
public void ShouldReportErrorAndTerminateAtEOFIfParenInImplicitExprUnclosed()
{
// ParseBlockShouldReportErrorAndTerminateAtEOFIfParenInImplicitExpressionUnclosed
ParseDocumentTest("@Foo(Bar(Baz)" + Environment.NewLine
+ "Biz" + Environment.NewLine
+ "Boz");
}
[Fact]
// Test for fix to Dev10 884975 - Incorrect Error Messaging
public void ShouldReportErrorAndTerminateAtMarkupIfParenInImplicitExpressionUnclosed()
{
// ParseBlockShouldReportErrorAndTerminateAtMarkupIfParenInImplicitExpressionUnclosed
ParseDocumentTest("@Foo(Bar(Baz)" + Environment.NewLine
+ "Biz" + Environment.NewLine
+ "<html>" + Environment.NewLine
+ "Boz" + Environment.NewLine
+ "</html>");
}
[Fact]
// Test for fix to Dev10 884975 - Incorrect Error Messaging
public void ShouldReportErrorAndTerminateAtEOFIfBracketInImplicitExpressionUnclosed()
{
// ParseBlockShouldReportErrorAndTerminateAtEOFIfBracketInImplicitExpressionUnclosed
ParseDocumentTest("@Foo[Bar[Baz]" + Environment.NewLine
+ "Biz" + Environment.NewLine
+ "Boz");
}
[Fact]
// Test for fix to Dev10 884975 - Incorrect Error Messaging
public void ShouldReportErrorAndTerminateAtMarkupIfBracketInImplicitExprUnclosed()
{
// ParseBlockShouldReportErrorAndTerminateAtMarkupIfBracketInImplicitExpressionUnclosed
ParseDocumentTest("@Foo[Bar[Baz]" + Environment.NewLine
+ "Biz" + Environment.NewLine
+ "<b>" + Environment.NewLine
+ "Boz" + Environment.NewLine
+ "</b>");
}
// Simple EOF handling errors:
[Fact]
public void ReportsErrorIfExplicitCodeBlockUnterminatedAtEOF()
{
ParseDocumentTest("@{ var foo = bar; if(foo != null) { bar(); } ");
}
[Fact]
public void ReportsErrorIfClassBlockUnterminatedAtEOF()
{
ParseDocumentTest(
"@functions { var foo = bar; if(foo != null) { bar(); } ",
new[] { FunctionsDirective.Directive });
}
[Fact]
public void ReportsErrorIfIfBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("@if");
}
[Fact]
public void ReportsErrorIfElseBlockUnterminatedAtEOF()
{
ParseDocumentTest("@if(foo) { baz(); } else { var foo = bar; if(foo != null) { bar(); } ");
}
[Fact]
public void ReportsErrorIfElseIfBlockUnterminatedAtEOF()
{
ParseDocumentTest("@if(foo) { baz(); } else if { var foo = bar; if(foo != null) { bar(); } ");
}
[Fact]
public void ReportsErrorIfDoBlockUnterminatedAtEOF()
{
ParseDocumentTest("@do { var foo = bar; if(foo != null) { bar(); } ");
}
[Fact]
public void ReportsErrorIfTryBlockUnterminatedAtEOF()
{
ParseDocumentTest("@try { var foo = bar; if(foo != null) { bar(); } ");
}
[Fact]
public void ReportsErrorIfCatchBlockUnterminatedAtEOF()
{
ParseDocumentTest("@try { baz(); } catch(Foo) { var foo = bar; if(foo != null) { bar(); } ");
}
[Fact]
public void ReportsErrorIfFinallyBlockUnterminatedAtEOF()
{
ParseDocumentTest("@try { baz(); } finally { var foo = bar; if(foo != null) { bar(); } ");
}
[Fact]
public void ReportsErrorIfForBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("@for");
}
[Fact]
public void ReportsErrorIfForeachBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("@foreach");
}
[Fact]
public void ReportsErrorIfWhileBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("@while");
}
[Fact]
public void ReportsErrorIfSwitchBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("@switch");
}
[Fact]
public void ReportsErrorIfLockBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("@lock");
}
[Fact]
public void ReportsErrorIfUsingBlockUnterminatedAtEOF()
{
RunUnterminatedSimpleKeywordBlock("@using");
}
[Fact]
public void RequiresControlFlowStatementsToHaveBraces()
{
ParseDocumentTest("@if(foo) <p>Bar</p> else if(bar) <p>Baz</p> else <p>Boz</p>");
}
[Fact]
public void IncludesUnexpectedCharacterInSingleStatementControlFlowStatementError()
{
ParseDocumentTest("@if(foo)) { var bar = foo; }");
}
[Fact]
public void OutputsErrorIfAtSignFollowedByLessThanSignAtStatementStart()
{
ParseDocumentTest("@if(foo) { @<p>Bar</p> }");
}
[Fact]
public void TerminatesIfBlockAtEOLWhenRecoveringFromMissingCloseParen()
{
ParseDocumentTest("@if(foo bar" + Environment.NewLine
+ "baz");
}
[Fact]
public void TerminatesForeachBlockAtEOLWhenRecoveringFromMissingCloseParen()
{
ParseDocumentTest("@foreach(foo bar" + Environment.NewLine
+ "baz");
}
[Fact]
public void TerminatesWhileClauseInDoStmtAtEOLWhenRecoveringFromMissingCloseParen()
{
ParseDocumentTest("@do { } while(foo bar" + Environment.NewLine
+ "baz");
}
[Fact]
public void TerminatesUsingBlockAtEOLWhenRecoveringFromMissingCloseParen()
{
ParseDocumentTest("@using(foo bar" + Environment.NewLine
+ "baz");
}
[Fact]
public void ResumesIfStatementAfterOpenParen()
{
ParseDocumentTest("@if(" + Environment.NewLine
+ "else { <p>Foo</p> }");
}
[Fact]
public void TerminatesNormalCSharpStringsAtEOLIfEndQuoteMissing()
{
ParseDocumentTest("@if(foo) {" + Environment.NewLine
+ " var p = \"foo bar baz" + Environment.NewLine
+ ";" + Environment.NewLine
+ "}");
}
[Fact]
public void TerminatesNormalStringAtEndOfFile()
{
ParseDocumentTest("@if(foo) { var foo = \"blah blah blah blah blah");
}
[Fact]
public void TerminatesVerbatimStringAtEndOfFile()
{
ParseDocumentTest("@if(foo) { var foo = @\"blah " + Environment.NewLine
+ "blah; " + Environment.NewLine
+ "<p>Foo</p>" + Environment.NewLine
+ "blah " + Environment.NewLine
+ "blah");
}
[Fact]
public void CorrectlyParsesMarkupIncorrectyAssumedToBeWithinAStatement()
{
ParseDocumentTest("@if(foo) {" + Environment.NewLine
+ " var foo = \"foo bar baz" + Environment.NewLine
+ " <p>Foo is @foo</p>" + Environment.NewLine
+ "}");
}
[Fact]
public void CorrectlyParsesAtSignInDelimitedBlock()
{
ParseDocumentTest("@(Request[\"description\"] ?? @photo.Description)");
}
[Fact]
public void CorrectlyRecoversFromMissingCloseParenInExpressionWithinCode()
{
ParseDocumentTest("@{string.Format(<html></html>}");
}
private void RunUnterminatedSimpleKeywordBlock(string keyword)
{
ParseDocumentTest(
keyword + " (foo) { var foo = bar; if(foo != null) { bar(); } ");
}
}
}

View File

@ -0,0 +1,76 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpExplicitExpressionTest : ParserTestBase
{
[Fact]
public void ShouldOutputZeroLengthCodeSpanIfExplicitExpressionIsEmpty()
{
ParseDocumentTest("@()");
}
[Fact]
public void ShouldOutputZeroLengthCodeSpanIfEOFOccursAfterStartOfExplicitExpr()
{
// ParseBlockShouldOutputZeroLengthCodeSpanIfEOFOccursAfterStartOfExplicitExpression
ParseDocumentTest("@(");
}
[Fact]
public void ShouldAcceptEscapedQuoteInNonVerbatimStrings()
{
ParseDocumentTest("@(\"\\\"\")");
}
[Fact]
public void ShouldAcceptEscapedQuoteInVerbatimStrings()
{
ParseDocumentTest("@(@\"\"\"\")");
}
[Fact]
public void ShouldAcceptMultipleRepeatedEscapedQuoteInVerbatimStrings()
{
ParseDocumentTest("@(@\"\"\"\"\"\")");
}
[Fact]
public void ShouldAcceptMultiLineVerbatimStrings()
{
ParseDocumentTest(@"@(@""" + Environment.NewLine
+ @"Foo" + Environment.NewLine
+ @"Bar" + Environment.NewLine
+ @"Baz" + Environment.NewLine
+ @""")");
}
[Fact]
public void ShouldAcceptMultipleEscapedQuotesInNonVerbatimStrings()
{
ParseDocumentTest("@(\"\\\"hello, world\\\"\")");
}
[Fact]
public void ShouldAcceptMultipleEscapedQuotesInVerbatimStrings()
{
ParseDocumentTest("@(@\"\"\"hello, world\"\"\")");
}
[Fact]
public void ShouldAcceptConsecutiveEscapedQuotesInNonVerbatimStrings()
{
ParseDocumentTest("@(\"\\\"\\\"\")");
}
[Fact]
public void ShouldAcceptConsecutiveEscapedQuotesInVerbatimStrings()
{
ParseDocumentTest("@(@\"\"\"\"\"\")");
}
}
}

View File

@ -0,0 +1,136 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpFunctionsTest : ParserTestBase
{
[Fact]
public void MarkupInFunctionsBlock_DoesNotParseWhenNotSupported()
{
ParseDocumentTest(
RazorLanguageVersion.Version_2_1,
@"
@functions {
void Announcment(string message)
{
<h3>@message</h3>
}
}
", new[] { FunctionsDirective.Directive, }, designTime: false);
}
[Fact]
public void MarkupInFunctionsBlock_ParsesMarkupInsideMethod()
{
ParseDocumentTest(
RazorLanguageVersion.Version_3_0,
@"
@functions {
void Announcment(string message)
{
<h3>@message</h3>
}
}
", new[] { FunctionsDirective.Directive, }, designTime: false);
}
// This will parse correctly in Razor, but will generate invalid C#.
[Fact]
public void MarkupInFunctionsBlock_ParsesMarkupWithExpressionsMethod()
{
ParseDocumentTest(
RazorLanguageVersion.Version_3_0,
@"
@functions {
void Announcment(string message) => <h3>@message</h3>
}
", new[] { FunctionsDirective.Directive, }, designTime: false);
}
[Fact]
public void MarkupInFunctionsBlock_DoesNotParseMarkupInString()
{
ParseDocumentTest(
RazorLanguageVersion.Version_3_0,
@"
@functions {
void Announcment(string message) => ""<h3>@message</h3>"";
}
", new[] { FunctionsDirective.Directive, }, designTime: false);
}
[Fact]
public void MarkupInFunctionsBlock_DoesNotParseMarkupInVerbatimString()
{
ParseDocumentTest(
RazorLanguageVersion.Version_3_0,
@"
@functions {
void Announcment(string message) => @""<h3>@message</h3>"";
}
", new[] { FunctionsDirective.Directive, }, designTime: false);
}
[Fact]
public void MarkupInFunctionsBlock_CanContainCurlyBraces()
{
ParseDocumentTest(
RazorLanguageVersion.Version_3_0,
@"
@functions {
void Announcment(string message)
{
<div>
@if (message.Length > 0)
{
<p>@message.Length</p>
}
</div>
}
}
", new[] { FunctionsDirective.Directive, }, designTime: false);
}
[Fact]
public void MarkupInFunctionsBlock_MarkupCanContainTemplate()
{
ParseDocumentTest(
RazorLanguageVersion.Version_3_0,
@"
@functions {
void Announcment(string message)
{
<div>
@if (message.Length > 0)
{
Repeat(@<p>@message.Length</p>);
}
</div>
}
}
", new[] { FunctionsDirective.Directive, }, designTime: false);
}
[Fact]
public void ReservedKeywordsInFunctionsBlock_WithMarkup()
{
ParseDocumentTest(
RazorLanguageVersion.Version_3_0,
@"
@functions {
class Person
{
public void DoSomething()
{
<p>Just do it!</p>
}
}
}
", new[] { FunctionsDirective.Directive, }, designTime: false);
}
}
}

View File

@ -0,0 +1,374 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpImplicitExpressionTest : ParserTestBase
{
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket1()
{
// Act & Assert
ParseDocumentTest("@val??[");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket2()
{
// Act & Assert
ParseDocumentTest("@val??[0");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket3()
{
// Act & Assert
ParseDocumentTest("@val?[");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket4()
{
// Act & Assert
ParseDocumentTest("@val?(");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket5()
{
// Act & Assert
ParseDocumentTest("@val?[more");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket6()
{
// Act & Assert
ParseDocumentTest("@val?[0]");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket7()
{
// Act & Assert
ParseDocumentTest("@val?[<p>");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket8()
{
// Act & Assert
ParseDocumentTest("@val?[more.<p>");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket9()
{
// Act & Assert
ParseDocumentTest("@val??[more<p>");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket10()
{
// Act & Assert
ParseDocumentTest("@val?[-1]?");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket11()
{
// Act & Assert
ParseDocumentTest("@val?[abc]?[def");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket12()
{
// Act & Assert
ParseDocumentTest("@val?[abc]?[2]");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket13()
{
// Act & Assert
ParseDocumentTest("@val?[abc]?.more?[def]");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket14()
{
// Act & Assert
ParseDocumentTest("@val?[abc]?.more?.abc");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket15()
{
// Act & Assert
ParseDocumentTest("@val?[null ?? true]");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Bracket16()
{
// Act & Assert
ParseDocumentTest("@val?[abc?.gef?[-1]]");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot1()
{
// Act & Assert
ParseDocumentTest("@val?");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot2()
{
// Act & Assert
ParseDocumentTest("@val??");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot3()
{
// Act & Assert
ParseDocumentTest("@val??more");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot4()
{
// Act & Assert
ParseDocumentTest("@val?!");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot5()
{
// Act & Assert
ParseDocumentTest("@val?.");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot6()
{
// Act & Assert
ParseDocumentTest("@val??.");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot7()
{
// Act & Assert
ParseDocumentTest("@val?.(abc)");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot8()
{
// Act & Assert
ParseDocumentTest("@val?.<p>");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot9()
{
// Act & Assert
ParseDocumentTest("@val?.more");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot10()
{
// Act & Assert
ParseDocumentTest("@val?.more<p>");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot11()
{
// Act & Assert
ParseDocumentTest("@val??.more<p>");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot12()
{
// Act & Assert
ParseDocumentTest("@val?.more(false)?.<p>");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot13()
{
// Act & Assert
ParseDocumentTest("@val?.more(false)?.abc");
}
[Fact]
public void ParsesNullConditionalOperatorImplicitExpression_Dot14()
{
// Act & Assert
ParseDocumentTest("@val?.more(null ?? true)?.abc");
}
[Fact]
public void NestedImplicitExpression()
{
ParseDocumentTest("if (true) { @foo }");
}
[Fact]
public void AcceptsNonEnglishCharactersThatAreValidIdentifiers()
{
ParseDocumentTest("@हळूँजद॔.");
}
[Fact]
public void OutputsZeroLengthCodeSpanIfInvalidCharacterFollowsTransition()
{
ParseDocumentTest("@/");
}
[Fact]
public void OutputsZeroLengthCodeSpanIfEOFOccursAfterTransition()
{
ParseDocumentTest("@");
}
[Fact]
public void SupportsSlashesWithinComplexImplicitExpressions()
{
ParseDocumentTest("@DataGridColumn.Template(\"Years of Service\", e => (int)Math.Round((DateTime.Now - dt).TotalDays / 365))");
}
[Fact]
public void ParsesSingleIdentifierAsImplicitExpression()
{
ParseDocumentTest("@foo");
}
[Fact]
public void DoesNotAcceptSemicolonIfExpressionTerminatedByWhitespace()
{
ParseDocumentTest("@foo ;");
}
[Fact]
public void IgnoresSemicolonAtEndOfSimpleImplicitExpression()
{
ParseDocumentTest("@foo;");
}
[Fact]
public void ParsesDottedIdentifiersAsImplicitExpression()
{
ParseDocumentTest("@foo.bar.baz");
}
[Fact]
public void IgnoresSemicolonAtEndOfDottedIdentifiers()
{
ParseDocumentTest("@foo.bar.baz;");
}
[Fact]
public void DoesNotIncludeDotAtEOFInImplicitExpression()
{
ParseDocumentTest("@foo.bar.");
}
[Fact]
public void DoesNotIncludeDotFollowedByInvalidIdentifierCharInImplicitExpr1()
{
// ParseBlockMethodDoesNotIncludeDotFollowedByInvalidIdentifierCharacterInImplicitExpression1
ParseDocumentTest("@foo.bar.0");
}
[Fact]
public void DoesNotIncludeDotFollowedByInvalidIdentifierCharInImplicitExpr2()
{
// ParseBlockMethodDoesNotIncludeDotFollowedByInvalidIdentifierCharacterInImplicitExpression2
ParseDocumentTest("@foo.bar.</p>");
}
[Fact]
public void DoesNotIncludeSemicolonAfterDot()
{
ParseDocumentTest("@foo.bar.;");
}
[Fact]
public void TerminatesAfterIdentifierUnlessFollowedByDotOrParenInImplicitExpr()
{
// ParseBlockMethodTerminatesAfterIdentifierUnlessFollowedByDotOrParenInImplicitExpression
ParseDocumentTest("@foo.bar</p>");
}
[Fact]
public void ProperlyParsesParenthesesAndBalancesThemInImplicitExpression()
{
ParseDocumentTest(@"@foo().bar(""bi\""z"", 4)(""chained method; call"").baz(@""bo""""z"", '\'', () => { return 4; }, (4+5+new { foo = bar[4] }))");
}
[Fact]
public void ProperlyParsesBracketsAndBalancesThemInImplicitExpression()
{
ParseDocumentTest(@"@foo.bar[4 * (8 + 7)][""fo\""o""].baz");
}
[Fact]
public void TerminatesImplicitExpressionAtHtmlEndTag()
{
ParseDocumentTest("@foo().bar.baz</p>zoop");
}
[Fact]
public void TerminatesImplicitExpressionAtHtmlStartTag()
{
ParseDocumentTest("@foo().bar.baz<p>zoop");
}
[Fact]
public void TerminatesImplicitExprBeforeDotIfDotNotFollowedByIdentifierStartChar()
{
// ParseBlockTerminatesImplicitExpressionBeforeDotIfDotNotFollowedByIdentifierStartCharacter
ParseDocumentTest("@foo().bar.baz.42");
}
[Fact]
public void StopsBalancingParenthesesAtEOF()
{
ParseDocumentTest("@foo(()");
}
[Fact]
public void TerminatesImplicitExpressionIfCloseParenFollowedByAnyWhiteSpace()
{
ParseDocumentTest("@foo.bar() (baz)");
}
[Fact]
public void TerminatesImplicitExpressionIfIdentifierFollowedByAnyWhiteSpace()
{
ParseDocumentTest("@foo .bar() (baz)");
}
[Fact]
public void TerminatesImplicitExpressionAtLastValidPointIfDotFollowedByWhitespace()
{
ParseDocumentTest("@foo. bar() (baz)");
}
[Fact]
public void OutputExpressionIfModuleTokenNotFollowedByBrace()
{
ParseDocumentTest("@module.foo()");
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpLanguageCharacteristicsTest
{
[Fact]
public void GetSample_RightShiftAssign_ReturnsCorrectToken()
{
// Arrange & Act
var token = CSharpLanguageCharacteristics.Instance.GetSample(SyntaxKind.RightShiftAssign);
// Assert
Assert.Equal(">>=", token);
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpNestedStatementsTest : ParserTestBase
{
[Fact]
public void NestedSimpleStatement()
{
ParseDocumentTest("@while(true) { foo(); }");
}
[Fact]
public void NestedKeywordStatement()
{
ParseDocumentTest("@while(true) { for(int i = 0; i < 10; i++) { foo(); } }");
}
[Fact]
public void NestedCodeBlock()
{
ParseDocumentTest("@while(true) { { { { foo(); } } } }");
}
[Fact]
public void NestedImplicitExpression()
{
ParseDocumentTest("@while(true) { @foo }");
}
[Fact]
public void NestedExplicitExpression()
{
ParseDocumentTest("@while(true) { @(foo) }");
}
[Fact]
public void NestedMarkupBlock()
{
ParseDocumentTest("@while(true) { <p>Hello</p> }");
}
}
}

View File

@ -0,0 +1,134 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpRazorCommentsTest : ParserTestBase
{
[Fact]
public void UnterminatedRazorComment()
{
ParseDocumentTest("@*");
}
[Fact]
public void EmptyRazorComment()
{
ParseDocumentTest("@**@");
}
[Fact]
public void RazorCommentInImplicitExpressionMethodCall()
{
ParseDocumentTest("@foo(" + Environment.NewLine
+ "@**@" + Environment.NewLine);
}
[Fact]
public void UnterminatedRazorCommentInImplicitExpressionMethodCall()
{
ParseDocumentTest("@foo(@*");
}
[Fact]
public void RazorMultilineCommentInBlock()
{
ParseDocumentTest(@"
@{
@*
This is a comment
*@
}
");
}
[Fact]
public void RazorCommentInVerbatimBlock()
{
ParseDocumentTest("@{" + Environment.NewLine
+ " <text" + Environment.NewLine
+ " @**@" + Environment.NewLine
+ "}");
}
[Fact]
public void RazorCommentInOpeningTagBlock()
{
ParseDocumentTest("<text @* razor comment *@></text>");
}
[Fact]
public void RazorCommentInClosingTagBlock()
{
ParseDocumentTest("<text></text @* razor comment *@>");
}
[Fact]
public void UnterminatedRazorCommentInVerbatimBlock()
{
ParseDocumentTest("@{@*");
}
[Fact]
public void RazorCommentInMarkup()
{
ParseDocumentTest(
"<p>" + Environment.NewLine
+ "@**@" + Environment.NewLine
+ "</p>");
}
[Fact]
public void MultipleRazorCommentInMarkup()
{
ParseDocumentTest(
"<p>" + Environment.NewLine
+ " @**@ " + Environment.NewLine
+ "@**@" + Environment.NewLine
+ "</p>");
}
[Fact]
public void MultipleRazorCommentsInSameLineInMarkup()
{
ParseDocumentTest(
"<p>" + Environment.NewLine
+ "@**@ @**@" + Environment.NewLine
+ "</p>");
}
[Fact]
public void RazorCommentsSurroundingMarkup()
{
ParseDocumentTest(
"<p>" + Environment.NewLine
+ "@* hello *@ content @* world *@" + Environment.NewLine
+ "</p>");
}
[Fact]
public void RazorCommentBetweenCodeBlockAndMarkup()
{
ParseDocumentTest(
"@{ }" + Environment.NewLine +
"@* Hello World *@" + Environment.NewLine +
"<div>Foo</div>"
);
}
[Fact]
public void RazorCommentWithExtraNewLineInMarkup()
{
ParseDocumentTest(
"<p>" + Environment.NewLine + Environment.NewLine
+ "@* content *@" + Environment.NewLine
+ "@*" + Environment.NewLine
+ "content" + Environment.NewLine
+ "*@" + Environment.NewLine + Environment.NewLine
+ "</p>");
}
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpReservedWordsTest : ParserTestBase
{
[Fact]
public void ReservedWord()
{
ParseDocumentTest("@namespace");
}
[Fact]
private void ReservedWordIsCaseSensitive()
{
ParseDocumentTest("@NameSpace");
}
}
}

View File

@ -0,0 +1,290 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpSectionTest : ParserTestBase
{
[Fact]
public void CapturesNewlineImmediatelyFollowing()
{
ParseDocumentTest(
"@section" + Environment.NewLine,
new[] { SectionDirective.Directive });
}
[Fact]
public void CapturesWhitespaceToEndOfLineInSectionStatementMissingOpenBrace()
{
ParseDocumentTest(
"@section Foo " + Environment.NewLine + " ",
new[] { SectionDirective.Directive });
}
[Fact]
public void CapturesWhitespaceToEndOfLineInSectionStatementMissingName()
{
ParseDocumentTest(
"@section " + Environment.NewLine + " ",
new[] { SectionDirective.Directive });
}
[Fact]
public void IgnoresSectionUnlessAllLowerCase()
{
ParseDocumentTest(
"@Section foo",
new[] { SectionDirective.Directive });
}
[Fact]
public void ReportsErrorAndTerminatesSectionBlockIfKeywordNotFollowedByIdentifierStartChar()
{
// ParseSectionBlockReportsErrorAndTerminatesSectionBlockIfKeywordNotFollowedByIdentifierStartCharacter
ParseDocumentTest(
"@section 9 { <p>Foo</p> }",
new[] { SectionDirective.Directive });
}
[Fact]
public void ReportsErrorAndTerminatesSectionBlockIfNameNotFollowedByOpenBrace()
{
// ParseSectionBlockReportsErrorAndTerminatesSectionBlockIfNameNotFollowedByOpenBrace
ParseDocumentTest(
"@section foo-bar { <p>Foo</p> }",
new[] { SectionDirective.Directive });
}
[Fact]
public void ParserOutputsErrorOnNestedSections()
{
ParseDocumentTest(
"@section foo { @section bar { <p>Foo</p> } }",
new[] { SectionDirective.Directive });
}
[Fact]
public void ParserOutputsErrorOnMultipleNestedSections()
{
ParseDocumentTest(
"@section foo { @section bar { <p>Foo</p> @section baz { } } }",
new[] { SectionDirective.Directive });
}
[Fact]
public void ParserDoesNotOutputErrorOtherNestedDirectives()
{
// This isn't a real scenario but we just want to verify we don't show misleading errors.
ParseDocumentTest(
"@section foo { @inherits Bar }",
new[] { SectionDirective.Directive, InheritsDirective.Directive });
}
[Fact]
public void HandlesEOFAfterOpenBrace()
{
ParseDocumentTest(
"@section foo {",
new[] { SectionDirective.Directive });
}
[Fact]
public void HandlesEOFAfterOpenContent1()
{
ParseDocumentTest(
"@section foo { ",
new[] { SectionDirective.Directive });
}
[Fact]
public void HandlesEOFAfterOpenContent2()
{
ParseDocumentTest(
"@section foo {\n",
new[] { SectionDirective.Directive });
}
[Fact]
public void HandlesEOFAfterOpenContent3()
{
ParseDocumentTest(
"@section foo {abc",
new[] { SectionDirective.Directive });
}
[Fact]
public void HandlesEOFAfterOpenContent4()
{
ParseDocumentTest(
"@section foo {\n abc",
new[] { SectionDirective.Directive });
}
[Fact]
public void HandlesUnterminatedSection()
{
ParseDocumentTest(
"@section foo { <p>Foo{}</p>",
new[] { SectionDirective.Directive });
}
[Fact]
public void HandlesUnterminatedSectionWithNestedIf()
{
// Arrange
var newLine = Environment.NewLine;
var spaces = " ";
// Act & Assert
ParseDocumentTest(
string.Format(
"@section Test{0}{{{0}{1}@if(true){0}{1}{{{0}{1}{1}<p>Hello World</p>{0}{1}}}",
newLine,
spaces),
new[] { SectionDirective.Directive });
}
[Fact]
public void ReportsErrorAndAcceptsWhitespaceToEOLIfSectionNotFollowedByOpenBrace()
{
// ParseSectionBlockReportsErrorAndAcceptsWhitespaceToEndOfLineIfSectionNotFollowedByOpenBrace
ParseDocumentTest(
"@section foo " + Environment.NewLine,
new[] { SectionDirective.Directive });
}
[Fact]
public void AcceptsOpenBraceMultipleLinesBelowSectionName()
{
ParseDocumentTest(
"@section foo "
+ Environment.NewLine
+ Environment.NewLine
+ Environment.NewLine
+ Environment.NewLine
+ Environment.NewLine
+ Environment.NewLine
+ "{" + Environment.NewLine
+ "<p>Foo</p>" + Environment.NewLine
+ "}",
new[] { SectionDirective.Directive });
}
[Fact]
public void ParsesNamedSectionCorrectly()
{
ParseDocumentTest(
"@section foo { <p>Foo</p> }",
new[] { SectionDirective.Directive });
}
[Fact]
public void DoesNotRequireSpaceBetweenSectionNameAndOpenBrace()
{
ParseDocumentTest(
"@section foo{ <p>Foo</p> }",
new[] { SectionDirective.Directive });
}
[Fact]
public void BalancesBraces()
{
ParseDocumentTest(
"@section foo { <script>(function foo() { return 1; })();</script> }",
new[] { SectionDirective.Directive });
}
[Fact]
public void AllowsBracesInCSharpExpression()
{
ParseDocumentTest(
"@section foo { I really want to render a close brace, so here I go: @(\"}\") }",
new[] { SectionDirective.Directive });
}
[Fact]
public void SectionIsCorrectlyTerminatedWhenCloseBraceImmediatelyFollowsCodeBlock()
{
ParseDocumentTest(
"@section Foo {" + Environment.NewLine
+ "@if(true) {" + Environment.NewLine
+ "}" + Environment.NewLine
+ "}",
new[] { SectionDirective.Directive });
}
[Fact]
public void SectionCorrectlyTerminatedWhenCloseBraceFollowsCodeBlockNoWhitespace()
{
// SectionIsCorrectlyTerminatedWhenCloseBraceImmediatelyFollowsCodeBlockNoWhitespace
ParseDocumentTest(
"@section Foo {" + Environment.NewLine
+ "@if(true) {" + Environment.NewLine
+ "}}",
new[] { SectionDirective.Directive });
}
[Fact]
public void CorrectlyTerminatesWhenCloseBraceImmediatelyFollowsMarkup()
{
ParseDocumentTest(
"@section foo {something}",
new[] { SectionDirective.Directive });
}
[Fact]
public void ParsesComment()
{
ParseDocumentTest(
"@section s {<!-- -->}",
new[] { SectionDirective.Directive });
}
// This was a user reported bug (codeplex #710), the section parser wasn't handling
// comments.
[Fact]
public void ParsesCommentWithDelimiters()
{
ParseDocumentTest(
"@section s {<!-- > \" '-->}",
new[] { SectionDirective.Directive });
}
[Fact]
public void CommentRecoversFromUnclosedTag()
{
ParseDocumentTest(
"@section s {" + Environment.NewLine + "<a" + Environment.NewLine + "<!-- > \" '-->}",
new[] { SectionDirective.Directive });
}
[Fact]
public void ParsesXmlProcessingInstruction()
{
ParseDocumentTest(
"@section s { <? xml bleh ?>}",
new[] { SectionDirective.Directive });
}
[Fact]
public void _WithDoubleTransition1()
{
ParseDocumentTest("@section s {<span foo='@@' />}", new[] { SectionDirective.Directive });
}
[Fact]
public void _WithDoubleTransition2()
{
ParseDocumentTest("@section s {<span foo='@DateTime.Now @@' />}", new[] { SectionDirective.Directive });
}
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpSpecialBlockTest : ParserTestBase
{
[Fact]
public void NonKeywordStatementInCodeBlockIsHandledCorrectly()
{
ParseDocumentTest(
@"@{
List<dynamic> photos = gallery.Photo.ToList();
}");
}
[Fact]
public void BalancesBracesOutsideStringsIfFirstCharIsBraceAndReturnsSpanOfTypeCode()
{
// ParseBlockBalancesBracesOutsideStringsIfFirstCharacterIsBraceAndReturnsSpanOfTypeCode
ParseDocumentTest("@{foo\"b}ar\" if(condition) { string.Format(\"{0}\"); } }");
}
[Fact]
public void BalancesParensOutsideStringsIfFirstCharIsParenAndReturnsSpanOfTypeExpr()
{
// ParseBlockBalancesParensOutsideStringsIfFirstCharacterIsParenAndReturnsSpanOfTypeExpression
ParseDocumentTest("@(foo\"b)ar\" if(condition) { string.Format(\"{0}\"); } )");
}
[Fact]
public void ParseBlockIgnoresSingleSlashAtStart()
{
ParseDocumentTest("@/ foo");
}
[Fact]
public void ParseBlockTerminatesSingleLineCommentAtEndOfLine()
{
ParseDocumentTest(
"@if(!false) {" + Environment.NewLine +
" // Foo" + Environment.NewLine +
"\t<p>A real tag!</p>" + Environment.NewLine +
"}");
}
}
}

View File

@ -0,0 +1,247 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
// Basic Tests for C# Statements:
// * Basic case for each statement
// * Basic case for ALL clauses
// This class DOES NOT contain
// * Error cases
// * Tests for various types of nested statements
// * Comment tests
public class CSharpStatementTest : ParserTestBase
{
[Fact]
public void ForStatement()
{
ParseDocumentTest("@for(int i = 0; i++; i < length) { foo(); }");
}
[Fact]
public void ForEachStatement()
{
ParseDocumentTest("@foreach(var foo in bar) { foo(); }");
}
[Fact]
public void AwaitForEachStatement()
{
ParseDocumentTest("@await foreach(var foo in bar) { foo(); }");
}
[Fact]
public void MalformedAwaitForEachStatement()
{
ParseDocumentTest("@await foreach(var foo in bar { foo(); ");
}
[Fact]
public void WhileStatement()
{
ParseDocumentTest("@while(true) { foo(); }");
}
[Fact]
public void SwitchStatement()
{
ParseDocumentTest("@switch(foo) { foo(); }");
}
[Fact]
public void LockStatement()
{
ParseDocumentTest("@lock(baz) { foo(); }");
}
[Fact]
public void IfStatement()
{
ParseDocumentTest("@if(true) { foo(); }");
}
[Fact]
public void ElseIfClause()
{
ParseDocumentTest("@if(true) { foo(); } else if(false) { foo(); } else if(!false) { foo(); }");
}
[Fact]
public void ElseClause()
{
ParseDocumentTest("@if(true) { foo(); } else { foo(); }");
}
[Fact]
public void TryStatement()
{
ParseDocumentTest("@try { foo(); }");
}
[Fact]
public void CatchClause()
{
ParseDocumentTest("@try { foo(); } catch(IOException ioex) { handleIO(); } catch(Exception ex) { handleOther(); }");
}
[Fact]
public void ExceptionFilter_TryCatchWhenComplete_SingleLine()
{
ParseDocumentTest("@try { someMethod(); } catch(Exception) when (true) { handleIO(); }");
}
[Fact]
public void ExceptionFilter_TryCatchWhenFinallyComplete_SingleLine()
{
ParseDocumentTest("@try { A(); } catch(Exception) when (true) { B(); } finally { C(); }");
}
[Fact]
public void ExceptionFilter_TryCatchWhenCatchWhenComplete_SingleLine()
{
ParseDocumentTest("@try { A(); } catch(Exception) when (true) { B(); } catch(IOException) when (false) { C(); }");
}
[Fact]
public void ExceptionFilter_MultiLine()
{
ParseDocumentTest(
@"@try
{
A();
}
catch(Exception) when (true)
{
B();
}
catch(IOException) when (false)
{
C();
}");
}
[Fact]
public void ExceptionFilter_NestedTryCatchWhen()
{
ParseDocumentTest("@{try { someMethod(); } catch(Exception) when (true) { handleIO(); }}");
}
[Fact]
public void ExceptionFilter_IncompleteTryCatchWhen()
{
ParseDocumentTest("@try { someMethod(); } catch(Exception) when");
}
[Fact]
public void ExceptionFilter_IncompleteTryWhen()
{
ParseDocumentTest("@try { someMethod(); } when");
}
[Fact]
public void ExceptionFilter_IncompleteTryCatchNoBodyWhen()
{
ParseDocumentTest("@try { someMethod(); } catch(Exception) when { anotherMethod(); }");
}
[Fact]
public void ExceptionFilter_IncompleteTryCatchWhenNoBodies()
{
ParseDocumentTest("@try { someMethod(); } catch(Exception) when (true)");
}
[Fact]
public void ExceptionFilterError_TryCatchWhen_InCompleteCondition()
{
ParseDocumentTest("@try { someMethod(); } catch(Exception) when (");
}
[Fact]
public void ExceptionFilterError_TryCatchWhen_InCompleteBody()
{
ParseDocumentTest("@try { someMethod(); } catch(Exception) when (true) {");
}
[Fact]
public void FinallyClause()
{
ParseDocumentTest("@try { foo(); } finally { Dispose(); }");
}
[Fact]
public void Using_VariableDeclaration_Simple()
{
ParseDocumentTest("@{ using var foo = someDisposable; }");
}
[Fact]
public void Using_VariableDeclaration_Complex()
{
ParseDocumentTest("@{ using Some.Disposable.TypeName foo = GetDisposable<Some.Disposable.TypeName>(() => { using var bar = otherDisposable; }); }");
}
[Fact]
public void StaticUsing_NoUsing()
{
ParseDocumentTest("@using static");
}
[Fact]
public void StaticUsing_SingleIdentifier()
{
ParseDocumentTest("@using static System");
}
[Fact]
public void StaticUsing_MultipleIdentifiers()
{
ParseDocumentTest("@using static System.Console");
}
[Fact]
public void StaticUsing_GlobalPrefix()
{
ParseDocumentTest("@using static global::System.Console");
}
[Fact]
public void StaticUsing_Complete_Spaced()
{
ParseDocumentTest("@using static global::System.Console ");
}
[Fact]
public void UsingStatement()
{
ParseDocumentTest("@using(var foo = new Foo()) { foo.Bar(); }");
}
[Fact]
public void UsingTypeAlias()
{
ParseDocumentTest("@using StringDictionary = System.Collections.Generic.Dictionary<string, string>");
}
[Fact]
public void UsingNamespaceImport()
{
ParseDocumentTest("@using System.Text.Encoding.ASCIIEncoding");
}
[Fact]
public void DoStatement()
{
ParseDocumentTest("@do { foo(); } while(true);");
}
[Fact]
public void NonBlockKeywordTreatedAsImplicitExpression()
{
ParseDocumentTest("@is foo");
}
}
}

View File

@ -0,0 +1,92 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpTemplateTest : ParserTestBase
{
[Fact]
public void HandlesSingleLineTemplate()
{
ParseDocumentTest("@{ var foo = @: bar" + Environment.NewLine + "; }");
}
[Fact]
public void HandlesSingleLineImmediatelyFollowingStatementChar()
{
ParseDocumentTest("@{i@: bar" + Environment.NewLine + "}");
}
[Fact]
public void HandlesSimpleTemplateInExplicitExpressionParens()
{
ParseDocumentTest("@(Html.Repeat(10, @<p>Foo #@item</p>))");
}
[Fact]
public void HandlesSimpleTemplateInImplicitExpressionParens()
{
ParseDocumentTest("@Html.Repeat(10, @<p>Foo #@item</p>)");
}
[Fact]
public void HandlesTwoTemplatesInImplicitExpressionParens()
{
ParseDocumentTest("@Html.Repeat(10, @<p>Foo #@item</p>, @<p>Foo #@item</p>)");
}
[Fact]
public void ProducesErrorButCorrectlyParsesNestedTemplateInImplicitExprParens()
{
// ParseBlockProducesErrorButCorrectlyParsesNestedTemplateInImplicitExpressionParens
ParseDocumentTest("@Html.Repeat(10, @<p>Foo #@Html.Repeat(10, @<p>@item</p>)</p>)");
}
[Fact]
public void HandlesSimpleTemplateInStatementWithinCodeBlock()
{
ParseDocumentTest("@foreach(foo in Bar) { Html.ExecuteTemplate(foo, @<p>Foo #@item</p>); }");
}
[Fact]
public void HandlesTwoTemplatesInStatementWithinCodeBlock()
{
ParseDocumentTest("@foreach(foo in Bar) { Html.ExecuteTemplate(foo, @<p>Foo #@item</p>, @<p>Foo #@item</p>); }");
}
[Fact]
public void ProducesErrorButCorrectlyParsesNestedTemplateInStmtWithinCodeBlock()
{
// ParseBlockProducesErrorButCorrectlyParsesNestedTemplateInStatementWithinCodeBlock
ParseDocumentTest("@foreach(foo in Bar) { Html.ExecuteTemplate(foo, @<p>Foo #@Html.Repeat(10, @<p>@item</p>)</p>); }");
}
[Fact]
public void HandlesSimpleTemplateInStatementWithinStatementBlock()
{
ParseDocumentTest("@{ var foo = bar; Html.ExecuteTemplate(foo, @<p>Foo #@item</p>); }");
}
[Fact]
public void HandlessTwoTemplatesInStatementWithinStatementBlock()
{
ParseDocumentTest("@{ var foo = bar; Html.ExecuteTemplate(foo, @<p>Foo #@item</p>, @<p>Foo #@item</p>); }");
}
[Fact]
public void ProducesErrorButCorrectlyParsesNestedTemplateInStmtWithinStmtBlock()
{
// ParseBlockProducesErrorButCorrectlyParsesNestedTemplateInStatementWithinStatementBlock
ParseDocumentTest("@{ var foo = bar; Html.ExecuteTemplate(foo, @<p>Foo #@Html.Repeat(10, @<p>@item</p>)</p>); }");
}
[Fact]
public void _WithDoubleTransition_DoesNotThrow()
{
ParseDocumentTest("@{ var foo = bar; Html.ExecuteTemplate(foo, @<p foo='@@'>Foo #@item</p>); }");
}
}
}

View File

@ -0,0 +1,218 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class CSharpToMarkupSwitchTest : ParserTestBase
{
[Fact]
public void SingleAngleBracketDoesNotCauseSwitchIfOuterBlockIsTerminated()
{
ParseDocumentTest("@{ List< }");
}
[Fact]
public void GivesSpacesToCodeOnAtTagTemplateTransitionInDesignTimeMode()
{
ParseDocumentTest("@Foo( @<p>Foo</p> )", designTime: true);
}
[Fact]
public void GivesSpacesToCodeOnAtColonTemplateTransitionInDesignTimeMode()
{
ParseDocumentTest("@Foo( " + Environment.NewLine
+ "@:<p>Foo</p> " + Environment.NewLine
+ ")", designTime: true);
}
[Fact]
public void GivesSpacesToCodeOnTagTransitionInDesignTimeMode()
{
ParseDocumentTest("@{" + Environment.NewLine
+ " <p>Foo</p> " + Environment.NewLine
+ "}", designTime: true);
}
[Fact]
public void GivesSpacesToCodeOnInvalidAtTagTransitionInDesignTimeMode()
{
ParseDocumentTest("@{" + Environment.NewLine
+ " @<p>Foo</p> " + Environment.NewLine
+ "}", designTime: true);
}
[Fact]
public void GivesSpacesToCodeOnAtColonTransitionInDesignTimeMode()
{
ParseDocumentTest("@{" + Environment.NewLine
+ " @:<p>Foo</p> " + Environment.NewLine
+ "}", designTime: true);
}
[Fact]
public void ShouldSupportSingleLineMarkupContainingStatementBlock()
{
ParseDocumentTest("@Repeat(10," + Environment.NewLine
+ " @: @{}" + Environment.NewLine
+ ")");
}
[Fact]
public void ShouldSupportMarkupWithoutPreceedingWhitespace()
{
ParseDocumentTest("@foreach(var file in files){" + Environment.NewLine
+ Environment.NewLine
+ Environment.NewLine
+ "@:Baz" + Environment.NewLine
+ "<br/>" + Environment.NewLine
+ "<a>Foo</a>" + Environment.NewLine
+ "@:Bar" + Environment.NewLine
+ "}");
}
[Fact]
public void GivesAllWhitespaceOnSameLineWithTrailingNewLineToMarkupExclPreceedingNewline()
{
// ParseBlockGivesAllWhitespaceOnSameLineExcludingPreceedingNewlineButIncludingTrailingNewLineToMarkup
ParseDocumentTest("@if(foo) {" + Environment.NewLine
+ " var foo = \"After this statement there are 10 spaces\"; " + Environment.NewLine
+ " <p>" + Environment.NewLine
+ " Foo" + Environment.NewLine
+ " @bar" + Environment.NewLine
+ " </p>" + Environment.NewLine
+ " @:Hello!" + Environment.NewLine
+ " var biz = boz;" + Environment.NewLine
+ "}");
}
[Fact]
public void AllowsMarkupInIfBodyWithBraces()
{
ParseDocumentTest("@if(foo) { <p>Bar</p> } else if(bar) { <p>Baz</p> } else { <p>Boz</p> }");
}
[Fact]
public void AllowsMarkupInIfBodyWithBracesWithinCodeBlock()
{
ParseDocumentTest("@{ if(foo) { <p>Bar</p> } else if(bar) { <p>Baz</p> } else { <p>Boz</p> } }");
}
[Fact]
public void SupportsMarkupInCaseAndDefaultBranchesOfSwitch()
{
// Arrange
ParseDocumentTest("@switch(foo) {" + Environment.NewLine
+ " case 0:" + Environment.NewLine
+ " <p>Foo</p>" + Environment.NewLine
+ " break;" + Environment.NewLine
+ " case 1:" + Environment.NewLine
+ " <p>Bar</p>" + Environment.NewLine
+ " return;" + Environment.NewLine
+ " case 2:" + Environment.NewLine
+ " {" + Environment.NewLine
+ " <p>Baz</p>" + Environment.NewLine
+ " <p>Boz</p>" + Environment.NewLine
+ " }" + Environment.NewLine
+ " default:" + Environment.NewLine
+ " <p>Biz</p>" + Environment.NewLine
+ "}");
}
[Fact]
public void SupportsMarkupInCaseAndDefaultBranchesOfSwitchInCodeBlock()
{
// Arrange
ParseDocumentTest("@{ switch(foo) {" + Environment.NewLine
+ " case 0:" + Environment.NewLine
+ " <p>Foo</p>" + Environment.NewLine
+ " break;" + Environment.NewLine
+ " case 1:" + Environment.NewLine
+ " <p>Bar</p>" + Environment.NewLine
+ " return;" + Environment.NewLine
+ " case 2:" + Environment.NewLine
+ " {" + Environment.NewLine
+ " <p>Baz</p>" + Environment.NewLine
+ " <p>Boz</p>" + Environment.NewLine
+ " }" + Environment.NewLine
+ " default:" + Environment.NewLine
+ " <p>Biz</p>" + Environment.NewLine
+ "} }");
}
[Fact]
public void ParsesMarkupStatementOnOpenAngleBracket()
{
ParseDocumentTest("@for(int i = 0; i < 10; i++) { <p>Foo</p> }");
}
[Fact]
public void ParsesMarkupStatementOnOpenAngleBracketInCodeBlock()
{
ParseDocumentTest("@{ for(int i = 0; i < 10; i++) { <p>Foo</p> } }");
}
[Fact]
public void ParsesMarkupStatementOnSwitchCharacterFollowedByColon()
{
// Arrange
ParseDocumentTest("@if(foo) { @:Bar" + Environment.NewLine
+ "} zoop");
}
[Fact]
public void ParsesMarkupStatementOnSwitchCharacterFollowedByDoubleColon()
{
// Arrange
ParseDocumentTest("@if(foo) { @::Sometext" + Environment.NewLine
+ "}");
}
[Fact]
public void ParsesMarkupStatementOnSwitchCharacterFollowedByTripleColon()
{
// Arrange
ParseDocumentTest("@if(foo) { @:::Sometext" + Environment.NewLine
+ "}");
}
[Fact]
public void ParsesMarkupStatementOnSwitchCharacterFollowedByColonInCodeBlock()
{
// Arrange
ParseDocumentTest("@{ if(foo) { @:Bar" + Environment.NewLine
+ "} } zoop");
}
[Fact]
public void CorrectlyReturnsFromMarkupBlockWithPseudoTag()
{
ParseDocumentTest("@if (i > 0) { <text>;</text> }");
}
[Fact]
public void CorrectlyReturnsFromMarkupBlockWithPseudoTagInCodeBlock()
{
ParseDocumentTest("@{ if (i > 0) { <text>;</text> } }");
}
[Fact]
public void SupportsAllKindsOfImplicitMarkupInCodeBlock()
{
ParseDocumentTest("@{" + Environment.NewLine
+ " if(true) {" + Environment.NewLine
+ " @:Single Line Markup" + Environment.NewLine
+ " }" + Environment.NewLine
+ " foreach (var p in Enumerable.Range(1, 10)) {" + Environment.NewLine
+ " <text>The number is @p</text>" + Environment.NewLine
+ " }" + Environment.NewLine
+ " if(!false) {" + Environment.NewLine
+ " <p>A real tag!</p>" + Environment.NewLine
+ " }" + Environment.NewLine
+ "}");
}
}
}

Some files were not shown because too many files have changed in this diff Show More