|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language;
public class DefaultRazorCSharpLoweringPhaseTest : RazorProjectEngineTestBase
{
protected override RazorLanguageVersion Version => RazorLanguageVersion.Latest;
[Fact]
public void Execute_ThrowsForMissingDependency_IRDocument()
{
// Arrange
var codeDocument = ProjectEngine.CreateEmptyCodeDocument();
codeDocument = codeDocument.WithSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source));
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument));
Assert.Equal(
$"The '{nameof(DefaultRazorCSharpLoweringPhase)}' phase requires a '{nameof(DocumentIntermediateNode)}' " +
$"provided by the '{nameof(RazorCodeDocument)}'.",
exception.Message);
}
[Fact]
public void Execute_ThrowsForMissingDependency_CodeTarget()
{
// Arrange
var codeDocument = ProjectEngine.CreateEmptyCodeDocument();
codeDocument = codeDocument.WithSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source));
var documentNode = new DocumentIntermediateNode()
{
DocumentKind = "test",
};
codeDocument = codeDocument.WithDocumentNode(documentNode);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument));
Assert.Equal(
$"The document of kind 'test' does not have a '{nameof(CodeTarget)}'. " +
$"The document classifier must set a value for '{nameof(DocumentIntermediateNode.Target)}'.",
exception.Message);
}
[Fact]
public void Execute_CollatesIRDocumentDiagnosticsFromSourceDocument()
{
// Arrange
var codeDocument = ProjectEngine.CreateCodeDocument("<p class=@(");
codeDocument = codeDocument.WithSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source));
var documentNode = new DocumentIntermediateNode()
{
DocumentKind = "test",
Target = CodeTarget.CreateDefault(codeDocument),
Options = codeDocument.CodeGenerationOptions
};
var expectedDiagnostic = RazorDiagnostic.Create(
new RazorDiagnosticDescriptor("1234", "I am an error.", RazorDiagnosticSeverity.Error),
new SourceSpan("SomeFile.cshtml", 11, 0, 11, 1));
documentNode.AddDiagnostic(expectedDiagnostic);
codeDocument = codeDocument.WithDocumentNode(documentNode);
// Act
codeDocument = ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument);
// Assert
var csharpDocument = codeDocument.GetRequiredCSharpDocument();
var diagnostic = Assert.Single(csharpDocument.Diagnostics);
Assert.Same(expectedDiagnostic, diagnostic);
}
[Fact] // This test covers the whole process including actual hashing.
public void Execute_EndToEnd_WritesChecksumAndMarksAutoGenerated()
{
// Arrange
var codeDocument = ProjectEngine.CreateEmptyCodeDocument();
var documentNode = new DocumentIntermediateNode()
{
Target = CodeTarget.CreateDefault(codeDocument)
};
codeDocument = codeDocument.WithDocumentNode(documentNode);
// Act
codeDocument = ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument);
var result = codeDocument.GetRequiredCSharpDocument();
// Assert
var csharp = result.Text.ToString();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"""
#pragma checksum "test.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "f1945cd6c19e56b3c1c78943ef5ec18116907a4ca1efc40a57d48ab1db7adfc5"
// <auto-generated/>
#pragma warning disable 1591
#pragma warning restore 1591
""",
csharp);
}
[Fact]
public void Execute_SHA1_WritesChecksumAndMarksAutoGenerated()
{
// Arrange
var sourceText = SourceText.From("", checksumAlgorithm: SourceHashAlgorithm.Sha1);
var source = RazorSourceDocument.Create(sourceText, RazorSourceDocumentProperties.Create("test.cshtml", null));
var codeDocument = ProjectEngine.CreateCodeDocument(source);
var documentNode = new DocumentIntermediateNode()
{
Target = CodeTarget.CreateDefault(codeDocument)
};
codeDocument = codeDocument.WithDocumentNode(documentNode);
// Act
codeDocument = ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument);
var result = codeDocument.GetRequiredCSharpDocument();
// Assert
var csharp = result.Text.ToString();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"""
#pragma checksum "test.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "da39a3ee5e6b4b0d3255bfef95601890afd80709"
// <auto-generated/>
#pragma warning disable 1591
#pragma warning restore 1591
""",
csharp);
}
[Fact]
public void WriteDocument_SHA256_WritesChecksumAndMarksAutoGenerated()
{
// Arrange
var sourceText = SourceText.From("", checksumAlgorithm: SourceHashAlgorithm.Sha256);
var source = RazorSourceDocument.Create(sourceText, RazorSourceDocumentProperties.Create("test.cshtml", null));
var codeDocument = ProjectEngine.CreateCodeDocument(source);
var documentNode = new DocumentIntermediateNode()
{
Target = CodeTarget.CreateDefault(codeDocument)
};
codeDocument = codeDocument.WithDocumentNode(documentNode);
// Act
codeDocument = ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument);
var result = codeDocument.GetRequiredCSharpDocument();
// Assert
var csharp = result.Text.ToString();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"""
#pragma checksum "test.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
// <auto-generated/>
#pragma warning disable 1591
#pragma warning restore 1591
""",
csharp);
}
[Fact]
public void Execute_Empty_SuppressChecksumTrue_DoesnotWriteChecksum()
{
// Arrange
var projectEngine = CreateProjectEngine(builder =>
{
builder.ConfigureCodeGenerationOptions(builder =>
{
builder.SuppressChecksum = true;
});
});
var codeDocument = projectEngine.CreateEmptyCodeDocument();
var documentNode = new DocumentIntermediateNode()
{
Target = CodeTarget.CreateDefault(codeDocument)
};
codeDocument = codeDocument.WithDocumentNode(documentNode);
// Act
codeDocument = ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument);
var result = codeDocument.GetRequiredCSharpDocument();
// Assert
var csharp = result.Text.ToString();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
@"// <auto-generated/>
#pragma warning disable 1591
#pragma warning restore 1591
",
csharp);
}
[Fact]
public void Execute_WritesNamespace()
{
// Arrange
var codeDocument = ProjectEngine.CreateEmptyCodeDocument();
var documentNode = new DocumentIntermediateNode()
{
Target = CodeTarget.CreateDefault(codeDocument)
};
codeDocument = codeDocument.WithDocumentNode(documentNode);
var builder = IntermediateNodeBuilder.Create(documentNode);
builder.Add(new NamespaceDeclarationIntermediateNode()
{
Name = "TestNamespace",
});
// Act
codeDocument = ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument);
var result = codeDocument.GetRequiredCSharpDocument();
// Assert
var csharp = result.Text.ToString();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"""
#pragma checksum "test.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "f1945cd6c19e56b3c1c78943ef5ec18116907a4ca1efc40a57d48ab1db7adfc5"
// <auto-generated/>
#pragma warning disable 1591
namespace TestNamespace
{
#line hidden
}
#pragma warning restore 1591
""",
csharp);
}
[Fact]
public void Execute_WritesClass()
{
// Arrange
var codeDocument = ProjectEngine.CreateEmptyCodeDocument();
var documentNode = new DocumentIntermediateNode()
{
Target = CodeTarget.CreateDefault(codeDocument)
};
codeDocument = codeDocument.WithDocumentNode(documentNode);
var builder = IntermediateNodeBuilder.Create(documentNode);
builder.Add(new ClassDeclarationIntermediateNode()
{
Modifiers = ["internal"],
BaseType = new BaseTypeWithModel("TestBase"),
Interfaces = [IntermediateNodeFactory.CSharpToken("IFoo"), IntermediateNodeFactory.CSharpToken("IBar")],
TypeParameters = [
new("TKey"),
new("TValue")
],
Name = "TestClass"
});
// Act
codeDocument = ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument);
var result = codeDocument.GetRequiredCSharpDocument();
// Assert
var csharp = result.Text.ToString();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"""
#pragma checksum "test.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "f1945cd6c19e56b3c1c78943ef5ec18116907a4ca1efc40a57d48ab1db7adfc5"
// <auto-generated/>
#pragma warning disable 1591
internal class TestClass<TKey,TValue> : TestBase, IFoo, IBar
{
}
#pragma warning restore 1591
""",
csharp);
}
[Fact]
public void Execute_WithNullableContext_WritesClass()
{
// Arrange
var codeDocument = ProjectEngine.CreateEmptyCodeDocument();
var documentNode = new DocumentIntermediateNode()
{
Target = CodeTarget.CreateDefault(codeDocument)
};
codeDocument = codeDocument.WithDocumentNode(documentNode);
var builder = IntermediateNodeBuilder.Create(documentNode);
builder.Add(new ClassDeclarationIntermediateNode()
{
Modifiers = ["internal"],
BaseType = new BaseTypeWithModel("TestBase"),
Interfaces = [IntermediateNodeFactory.CSharpToken("IFoo"), IntermediateNodeFactory.CSharpToken("IBar")],
TypeParameters = [
new("TKey"),
new("TValue")
],
Name = "TestClass",
NullableContext = true
});
// Act
codeDocument = ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument);
var result = codeDocument.GetRequiredCSharpDocument();
// Assert
var csharp = result.Text.ToString();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"""
#pragma checksum "test.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "f1945cd6c19e56b3c1c78943ef5ec18116907a4ca1efc40a57d48ab1db7adfc5"
// <auto-generated/>
#pragma warning disable 1591
#nullable restore
internal class TestClass<TKey,TValue> : TestBase, IFoo, IBar
#nullable disable
{
}
#pragma warning restore 1591
""",
csharp);
}
[Fact]
public void Execute_WritesClass_ConstrainedGenericTypeParameters()
{
// Arrange
var codeDocument = ProjectEngine.CreateEmptyCodeDocument();
var documentNode = new DocumentIntermediateNode()
{
Target = CodeTarget.CreateDefault(codeDocument)
};
codeDocument = codeDocument.WithDocumentNode(documentNode);
var builder = IntermediateNodeBuilder.Create(documentNode);
builder.Add(new ClassDeclarationIntermediateNode()
{
Modifiers = ["internal"],
BaseType = new BaseTypeWithModel("TestBase"),
Interfaces = [IntermediateNodeFactory.CSharpToken("IFoo"), IntermediateNodeFactory.CSharpToken("IBar")],
TypeParameters = [
new("TKey", "where TKey : class"),
new("TValue", "where TValue : class")
],
Name = "TestClass"
});
// Act
codeDocument = ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument);
var result = codeDocument.GetRequiredCSharpDocument();
// Assert
var csharp = result.Text.ToString();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"""
#pragma checksum "test.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "f1945cd6c19e56b3c1c78943ef5ec18116907a4ca1efc40a57d48ab1db7adfc5"
// <auto-generated/>
#pragma warning disable 1591
internal class TestClass<TKey,TValue> : TestBase, IFoo, IBar
where TKey : class
where TValue : class
{
}
#pragma warning restore 1591
""",
csharp);
}
[Fact]
public void Execute_WritesMethod()
{
// Arrange
var codeDocument = ProjectEngine.CreateEmptyCodeDocument();
var documentNode = new DocumentIntermediateNode()
{
Target = CodeTarget.CreateDefault(codeDocument)
};
codeDocument = codeDocument.WithDocumentNode(documentNode);
var builder = IntermediateNodeBuilder.Create(documentNode);
builder.Add(new MethodDeclarationIntermediateNode()
{
Modifiers = ["internal", "virtual", "async"],
Name = "TestMethod",
Parameters = [
new(name: "a", type: "int", modifiers: ["readonly", "ref"]),
new(name: "b", type: "string")
],
ReturnType = "string"
});
// Act
codeDocument = ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument);
var result = codeDocument.GetRequiredCSharpDocument();
// Assert
var csharp = result.Text.ToString();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"""
#pragma checksum "test.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "f1945cd6c19e56b3c1c78943ef5ec18116907a4ca1efc40a57d48ab1db7adfc5"
// <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);
}
[Fact]
public void Execute_WritesField()
{
// Arrange
var codeDocument = ProjectEngine.CreateEmptyCodeDocument();
var documentNode = new DocumentIntermediateNode()
{
Target = CodeTarget.CreateDefault(codeDocument)
};
codeDocument = codeDocument.WithDocumentNode(documentNode);
var builder = IntermediateNodeBuilder.Create(documentNode);
builder.Add(new FieldDeclarationIntermediateNode()
{
Modifiers = ["internal", "readonly"],
Name = "_foo",
Type = "string",
});
// Act
codeDocument = ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument);
var result = codeDocument.GetRequiredCSharpDocument();
// Assert
var csharp = result.Text.ToString();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"""
#pragma checksum "test.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "f1945cd6c19e56b3c1c78943ef5ec18116907a4ca1efc40a57d48ab1db7adfc5"
// <auto-generated/>
#pragma warning disable 1591
internal readonly string _foo;
#pragma warning restore 1591
""",
csharp);
}
[Fact]
public void Execute_WritesProperty()
{
// Arrange
var codeDocument = ProjectEngine.CreateEmptyCodeDocument();
var documentNode = new DocumentIntermediateNode()
{
Target = CodeTarget.CreateDefault(codeDocument)
};
codeDocument = codeDocument.WithDocumentNode(documentNode);
var builder = IntermediateNodeBuilder.Create(documentNode);
builder.Add(new PropertyDeclarationIntermediateNode()
{
Modifiers = ["internal", "virtual"],
Name = "Foo",
Type = IntermediateNodeFactory.CSharpToken("string"),
ExpressionBody = "default"
});
// Act
codeDocument = ProjectEngine.ExecutePhase<DefaultRazorCSharpLoweringPhase>(codeDocument);
var result = codeDocument.GetRequiredCSharpDocument();
// Assert
var csharp = result.Text.ToString();
AssertEx.AssertEqualToleratingWhitespaceDifferences(
"""
#pragma checksum "test.cshtml" "{8829d00f-11b8-4213-878b-770e8597ac16}" "f1945cd6c19e56b3c1c78943ef5ec18116907a4ca1efc40a57d48ab1db7adfc5"
// <auto-generated/>
#pragma warning disable 1591
internal virtual string Foo => default;
#pragma warning restore 1591
""",
csharp);
}
}
|