Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fuse/malformed inherits model impl #11045

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,77 @@ @model SomeType
Assert.Equal($"TModel = global::System.Object", usingNode.Content);
}

[Fact]
public void ModelDirective_Uses_IncompleteGenericType()
{
// Arrange
var codeDocument = CreateDocument(@"
@model Type1<
");
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);

// Act
var result = ModelDirective.GetModelType(irDocument);

// Assert
Assert.Equal("""
Type1<

""", result);
}

[Fact]
public void ModelDirective_Uses_IncompleteGenericType2()
{
// Arrange
var codeDocument = CreateDocument(@"
@model Type1<object
");
var engine = CreateRuntimeEngine();
var irDocument = CreateIRDocument(engine, codeDocument);

// Act
var result = ModelDirective.GetModelType(irDocument);

// Assert
Assert.Equal("""
Type1<object

""", result);
}

[Fact]
public void ModelDirectivePass_Execute_ReplacesTModelInBaseType_WithComment()
{
// Arrange
var codeDocument = CreateDocument(@"
@inherits BaseType<TModel>
@model /*comment*/ Type1
");

var engine = CreateRuntimeEngine();
var pass = new ModelDirective.Pass()
{
Engine = engine,
};

var irDocument = CreateIRDocument(engine, codeDocument);

// Act
pass.Execute(codeDocument, irDocument);

// Assert
var @class = FindClassNode(irDocument);
var baseType = @class.BaseType;

Assert.Equal("BaseType", baseType.BaseType.Content);
Assert.NotNull(baseType.BaseType.Source);

Assert.Equal("Type1", baseType.ModelType.Content);
Assert.NotNull(baseType.ModelType.Source);
}

private RazorCodeDocument CreateDocument(string content)
{
var source = RazorSourceDocument.Create(content, "test.cshtml");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5649,6 +5649,79 @@ @inherits BaseComponent
CompileToAssembly(generated);
}

[IntegrationTestFact, WorkItem("https://github.com/dotnet/razor/issues/10963")]
public void InheritsDirective_IncompleteType()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse("""
namespace Test;

public abstract class BaseComponent<T> : Microsoft.AspNetCore.Components.ComponentBase
{
}
"""));

// Act
var generated = CompileToCSharp("""
@inherits
""",
// (22,33): error CS0115: 'TestComponent.BuildRenderTree(RenderTreeBuilder)': no suitable method found to override
// protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
Diagnostic(ErrorCode.ERR_OverrideNotExpected, "BuildRenderTree").WithArguments("Test.TestComponent.BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder)").WithLocation(22, 33)
);

// Assert
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);

AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
CompileToAssembly(generated,
DesignTime
? []
: [
// (18,33): error CS0115: 'TestComponent.BuildRenderTree(RenderTreeBuilder)': no suitable method found to override
// protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
Diagnostic(ErrorCode.ERR_OverrideNotExpected, "BuildRenderTree").WithArguments("Test.TestComponent.BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder)").WithLocation(18, 33)
]
);
}

[IntegrationTestFact, WorkItem("https://github.com/dotnet/razor/issues/10963")]
public void InheritsDirective_IncompleteGenericType()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse("""
namespace Test;

public abstract class BaseComponent<T> : Microsoft.AspNetCore.Components.ComponentBase
{
}
"""));

// Act
var generated = CompileToCSharp("""
@inherits BaseComponent<object
""",
// x:\dir\subdir\Test\TestComponent.cshtml(1,31): error CS1003: Syntax error, '>' expected
// BaseComponent<object
Diagnostic(ErrorCode.ERR_SyntaxError, "").WithArguments(">").WithLocation(1, 31)
);

// Assert
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);

AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
CompileToAssembly(generated,
DesignTime?[
// x:\dir\subdir\Test\TestComponent.cshtml(1,22): error CS1003: Syntax error, '>' expected
// BaseComponent<object __typeHelper = default!;
Diagnostic(ErrorCode.ERR_SyntaxError, "__typeHelper").WithArguments(">").WithLocation(1, 22)
] : [
// x:\dir\subdir\Test\TestComponent.cshtml(1,31): error CS1003: Syntax error, '>' expected
// BaseComponent<object
Diagnostic(ErrorCode.ERR_SyntaxError, "").WithArguments(">").WithLocation(1, 31)
]);
}

[IntegrationTestFact, WorkItem("https://github.com/dotnet/razor/issues/7169")]
public void InheritsDirective_NullableReferenceType()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace AspNetCoreGeneratedDocument
[global::Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemMetadataAttribute("Identifier", "/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml")]
[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute]
#nullable restore
internal sealed class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
internal sealed class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives
#nullable disable
{
#pragma warning disable 1998
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(25,67): error CS0115: 'TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives.ExecuteAsync()': no suitable method found to override
// public async override global::System.Threading.Tasks.Task ExecuteAsync()
Diagnostic(ErrorCode.ERR_OverrideNotExpected, "ExecuteAsync").WithArguments("AspNetCoreGeneratedDocument.TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives.ExecuteAsync()").WithLocation(25, 67),
// TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(27,13): error CS0103: The name 'WriteLiteral' does not exist in the current context
// WriteLiteral("\r\n");
Diagnostic(ErrorCode.ERR_NameNotInContext, "WriteLiteral").WithArguments("WriteLiteral").WithLocation(27, 13),
// TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(28,13): error CS0103: The name 'WriteLiteral' does not exist in the current context
// WriteLiteral("\r\n");
Diagnostic(ErrorCode.ERR_NameNotInContext, "WriteLiteral").WithArguments("WriteLiteral").WithLocation(28, 13),
// TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(29,13): error CS0103: The name 'WriteLiteral' does not exist in the current context
// WriteLiteral("\r\n");
Diagnostic(ErrorCode.ERR_NameNotInContext, "WriteLiteral").WithArguments("WriteLiteral").WithLocation(29, 13),
// TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(30,13): error CS0103: The name 'WriteLiteral' does not exist in the current context
// WriteLiteral("\r\n");
Diagnostic(ErrorCode.ERR_NameNotInContext, "WriteLiteral").WithArguments("WriteLiteral").WithLocation(30, 13),
// TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(31,13): error CS0103: The name 'WriteLiteral' does not exist in the current context
// WriteLiteral("\r\n");
Diagnostic(ErrorCode.ERR_NameNotInContext, "WriteLiteral").WithArguments("WriteLiteral").WithLocation(31, 13),
// TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(32,13): error CS0103: The name 'WriteLiteral' does not exist in the current context
// WriteLiteral("\r\n\r\n");
Diagnostic(ErrorCode.ERR_NameNotInContext, "WriteLiteral").WithArguments("WriteLiteral").WithLocation(32, 13),
// TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(33,13): error CS0103: The name 'WriteLiteral' does not exist in the current context
// WriteLiteral("\r\n");
Diagnostic(ErrorCode.ERR_NameNotInContext, "WriteLiteral").WithArguments("WriteLiteral").WithLocation(33, 13),
// TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(34,13): error CS0103: The name 'WriteLiteral' does not exist in the current context
// WriteLiteral("\r\n\r\n");
Diagnostic(ErrorCode.ERR_NameNotInContext, "WriteLiteral").WithArguments("WriteLiteral").WithLocation(34, 13),
// TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(35,13): error CS0103: The name 'WriteLiteral' does not exist in the current context
// WriteLiteral("{\r\n");
Diagnostic(ErrorCode.ERR_NameNotInContext, "WriteLiteral").WithArguments("WriteLiteral").WithLocation(35, 13)
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
RazorSourceChecksumAttribute -
RazorCompiledItemMetadataAttribute -
CreateNewOnMetadataUpdateAttribute -
ClassDeclaration - - internal sealed - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic> -
ClassDeclaration - - internal sealed - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_IncompleteDirectives - -
MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync
HtmlContent - (85:1,0 [2] IncompleteDirectives.cshtml)
LazyIntermediateToken - (85:1,0 [2] IncompleteDirectives.cshtml) - Html - \n
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// <auto-generated/>
#pragma warning disable 1591
namespace Test
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class TestComponent :
#nullable restore
#line (1,11)-(1,31) "x:\dir\subdir\Test\TestComponent.cshtml"
BaseComponent<object

#line default
#line hidden
#nullable disable

#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Document -
NamespaceDeclaration - - Test
UsingDirective - (3:1,1 [20] ) - global::System
UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic
UsingDirective - (69:3,1 [25] ) - global::System.Linq
UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks
UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components
ClassDeclaration - - public partial - TestComponent - BaseComponent<object -
MethodDeclaration - - protected override - void - BuildRenderTree
MalformedDirective - (0:0,0 [30] x:\dir\subdir\Test\TestComponent.cshtml) - inherits
DirectiveToken - (10:0,10 [20] x:\dir\subdir\Test\TestComponent.cshtml) - BaseComponent<object
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Source Location: (10:0,10 [20] x:\dir\subdir\Test\TestComponent.cshtml)
|BaseComponent<object|
Generated Location: (476:16,0 [20] )
|BaseComponent<object|

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// <auto-generated/>
#pragma warning disable 1591
namespace Test
{
#line default
using global::System;
using global::System.Collections.Generic;
using global::System.Linq;
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#line default
#line hidden
#nullable restore
public partial class TestComponent
#nullable disable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Document -
NamespaceDeclaration - - Test
UsingDirective - (3:1,1 [20] ) - global::System
UsingDirective - (26:2,1 [40] ) - global::System.Collections.Generic
UsingDirective - (69:3,1 [25] ) - global::System.Linq
UsingDirective - (97:4,1 [36] ) - global::System.Threading.Tasks
UsingDirective - (136:5,1 [45] ) - global::Microsoft.AspNetCore.Components
ClassDeclaration - - public partial - TestComponent - -
MethodDeclaration - - protected override - void - BuildRenderTree
MalformedDirective - (0:0,0 [10] x:\dir\subdir\Test\TestComponent.cshtml) - inherits
DirectiveToken - (10:0,10 [0] x:\dir\subdir\Test\TestComponent.cshtml) -
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte
return;
}

foreach (var inherits in documentNode.FindDirectiveReferences(InheritsDirective.Directive))
foreach (var inherits in documentNode.FindDirectiveReferences(InheritsDirective.Directive, includeMalformed: codeDocument.GetParserOptions()?.DesignTime != true))
{
var token = ((DirectiveIntermediateNode)inherits.Node).Tokens.FirstOrDefault();
var token = inherits.Node.Children.OfType<DirectiveTokenIntermediateNode>().FirstOrDefault();
if (token != null)
{
var source = codeDocument.GetParserOptions()?.DesignTime == true ? null : token.Source;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static NamespaceDeclarationIntermediateNode FindPrimaryNamespace(this Doc
return FindWithAnnotation<NamespaceDeclarationIntermediateNode>(node, CommonAnnotations.PrimaryNamespace);
}

public static IReadOnlyList<IntermediateNodeReference> FindDirectiveReferences(this DocumentIntermediateNode node, DirectiveDescriptor directive)
public static IReadOnlyList<IntermediateNodeReference> FindDirectiveReferences(this DocumentIntermediateNode node, DirectiveDescriptor directive, bool includeMalformed = false)
{
if (node == null)
{
Expand All @@ -52,7 +52,7 @@ public static IReadOnlyList<IntermediateNodeReference> FindDirectiveReferences(t
throw new ArgumentNullException(nameof(directive));
}

var visitor = new DirectiveVisitor(directive);
var visitor = new DirectiveVisitor(directive, includeMalformed);
visitor.Visit(node);
return visitor.Directives;
}
Expand Down Expand Up @@ -92,10 +92,12 @@ private static T FindWithAnnotation<T>(IntermediateNode node, object annotation)
private class DirectiveVisitor : IntermediateNodeWalker
{
private readonly DirectiveDescriptor _directive;
private readonly bool _includeMalformed;

public DirectiveVisitor(DirectiveDescriptor directive)
public DirectiveVisitor(DirectiveDescriptor directive, bool includeMalformed)
{
_directive = directive;
_includeMalformed = includeMalformed;
}

public List<IntermediateNodeReference> Directives = new List<IntermediateNodeReference>();
Expand All @@ -109,6 +111,15 @@ public override void VisitDirective(DirectiveIntermediateNode node)

base.VisitDirective(node);
}

public override void VisitMalformedDirective(MalformedDirectiveIntermediateNode node)
{
if (_includeMalformed && _directive == node.Directive)
{
Directives.Add(new IntermediateNodeReference(Parent, node));
}
base.VisitMalformedDirective(node);
}
}

private class ReferenceVisitor<TNode> : IntermediateNodeWalker
Expand Down
Loading
Loading