Skip to content
Merged
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 @@ -37,6 +37,28 @@ internal static string StripGenericArity(string typeName)
return baseName.Length == typeName.Length ? typeName : baseName.ToString();
}

/// <summary>
/// Returns the name of the component type a descriptor belongs to. A component descriptor's owning
/// type is itself, but descriptors derived from a component -- child content and bind, for example --
/// carry the owning component's namespace and identifier while their own <see cref="TagHelperDescriptor.TypeName"/>
/// is suffixed (e.g. <c>Ns.Card.Header</c> for the <c>Header</c> child content of <c>Ns.Card</c>).
/// Reconstructing the owning name from the namespace and identifier lets ownership filtering keep or
/// exclude a fallback component and all its derived descriptors together, rather than only the
/// component descriptor whose <see cref="TagHelperDescriptor.TypeName"/> matches exactly.
/// </summary>
internal static string GetOwningTypeName(TagHelperDescriptor descriptor)
{
var identifier = descriptor.TypeNameIdentifier;
if (identifier is null)
{
return descriptor.TypeName;
}

return descriptor.TypeNamespace is { Length: > 0 } typeNamespace
? typeNamespace + "." + identifier
: identifier;
}

/// <summary>
/// Returns the hint name for the decl half of a Razor component generated source given
/// the impl half's hint name. The decl file substitutes <c>.decl.g.cs</c> for the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
return TagHelperCollection.Empty;
}

return all.Where(fallbackTypeNames, static (descriptor, names) => names.Contains(StripGenericArity(descriptor.TypeName)));
return all.Where(fallbackTypeNames, static (descriptor, names) => names.Contains(StripGenericArity(GetOwningTypeName(descriptor))));
})
.WithLambdaComparer(static (a, b) => a!.SequenceEqual(b!))
.WithTrackingName("SlowTagHelpers");
Expand All @@ -239,7 +239,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var ((fast, slow), fallbackTypeNames) = pair;
var fastOwned = fallbackTypeNames.IsEmpty
? fast
: fast.Where(fallbackTypeNames, static (descriptor, names) => !names.Contains(StripGenericArity(descriptor.TypeName)));
: fast.Where(fallbackTypeNames, static (descriptor, names) => !names.Contains(StripGenericArity(GetOwningTypeName(descriptor))));
return TagHelperCollection.Merge(fastOwned, slow);
})
.WithTrackingName("TagHelpersFromCompilation");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,52 @@ public class MyGrid<T> : ComponentBase
Assert.Equal(2, result.GeneratedSources.Length);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/84817")]
public async Task Component_FallbackComponent_ChildContentParameters()
{
// A fallback component (unsplittable via @inherits) exposes RenderFragment child-content
// parameters. A consumer referencing them as child elements must resolve them, i.e. no
// RZ10012 "unexpected name" for Header/ChildContent.
var project = CreateTestProject(new()
{
["Shared/Card.razor"] = """
@inherits EmptyBase

<header>@Header</header>
<main>@ChildContent</main>

@code {
[Parameter]
public RenderFragment? Header { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }
}
""",
["Shared/Consumer.razor"] = """
<Card>
<Header>Expected header</Header>
<ChildContent>Expected content</ChildContent>
</Card>
""",
}, new()
{
["EmptyBase.cs"] = """
using Microsoft.AspNetCore.Components;

public abstract class EmptyBase : ComponentBase;
""",
});
var compilation = await project.GetCompilationAsync();
var driver = await GetDriverAsync(project);

// Act
var result = RunGenerator(compilation!, ref driver);

// Assert -- no RZ10012 for the fallback component's child-content parameters
result.Diagnostics.Verify();
}

[Fact]
public async Task Component_WithImplicitContext_NestedInWrapper()
{
Expand Down
Loading