Add opt-in ~/ asset-path expansion for Razor components (compiler) - #84796
Conversation
4b70ac9 to
5bf0f6c
Compare
Introduce the descriptor and property metadata the Razor compiler uses to recognize opt-in asset-path expansion, ahead of the discovery and lowering that consume them: - AssetPathMetadata carries a single (element, attribute) pair declared via [AcceptsAssetPath], surfaced as a tag-helper descriptor. - PropertyMetadata.AcceptsAssetPath marks a component parameter opted in via [AssetPath]. - New AssetPath / AcceptsAssetPath members on the TagHelperKind, MetadataKind, and TagHelperProducerKind enums, with the component-kind range extended to include the new tag-helper kind. - ComponentsApi gains the well-known metadata names for the [AssetPath] and [AcceptsAssetPath] attributes and the AssetPathAttributes convention type. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ef56ab10-2d3d-4e55-a318-3d03bfbdd713
5bf0f6c to
87fe605
Compare
|
Azure Pipelines: Successfully started running 2 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Adds compiler-side support in the Razor component pipeline for opt-in expansion of literal attribute values starting with ~/ into Assets[...] expressions, using runtime-declared opt-in metadata ([AcceptsAssetPath] for specific HTML element/attribute pairs and [AssetPath] for component parameters). This lives in the Razor compiler layer (src/Razor/src/Compiler/...) and is gated on Razor language version 11.0.
Changes:
- Introduces
AcceptsAssetPathTagHelperProducer+AssetPathMetadatacarriers andPropertyMetadata.AcceptsAssetPathto surface opt-in metadata into lowering/optimization. - Adds
ComponentTildePathPass(Order 75) to rewrite opted-in~/...literals intoAssets[@"..."]tokens and emit warning RZ10029 for mixed content. - Adds extensive baseline coverage in
ComponentCodeGenerationTestBaseand new documentation describing the feature.
Reviewed changes
Copilot reviewed 99 out of 99 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelpers/Producers/TagHelperProducerKind.cs | Adds producer kind for [AcceptsAssetPath] discovery. |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelpers/Producers/ComponentTagHelperProducer.cs | Records [AssetPath] on component parameters as AcceptsAssetPath metadata. |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelpers/Producers/AcceptsAssetPathTagHelperProducer.Factory.cs | Factory to enable producer only when runtime attribute type is present. |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelpers/Producers/AcceptsAssetPathTagHelperProducer.cs | Produces carrier tag helpers containing (element, attribute) allowlist metadata. |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperKindExtensions.cs | Extends “component kinds” range to include AssetPath. |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperKind.cs | Adds new TagHelperKind.AssetPath value. |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorProjectEngine.cs | Registers ComponentTildePathPass in the component feature set. |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/MetadataObject.cs | Adds MetadataKind.AssetPath for new metadata record type. |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/PropertyMetadata.cs | Adds AcceptsAssetPath flag to component-parameter metadata and checksum. |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentTildePathPass.cs | Implements the rewrite pass and mixed-content diagnostic emission. |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentsApi.cs | Adds metadata-name constants for new runtime attributes and ComponentBase.Assets. |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentDiagnosticFactory.cs | Introduces warning descriptor + factory for RZ10029 (mixed content). |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/AssetPathMetadata.cs | New metadata record for (element, attribute) allowlist entries. |
| src/Razor/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/CompilerFeatures.cs | Registers AcceptsAssetPathTagHelperProducer for discovery. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_WithSlash/TestComponent.ir.txt | New baseline: IR for opted-in ~/ expansion (HTML). |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_WithSlash/TestComponent.decl.ir.txt | New baseline: declaration IR for TildePath_WithSlash. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_WithSlash/TestComponent.decl.codegen.cs | New baseline: declaration codegen for TildePath_WithSlash. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_WithSlash/TestComponent.codegen.cs | New baseline: codegen for TildePath_WithSlash. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_WithSlash/TestComponent.builder.txt | New baseline: render-tree builder output for TildePath_WithSlash. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_NoTilde/TestComponent.ir.txt | New baseline: non-expansion when no tilde. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_NoTilde/TestComponent.decl.ir.txt | New baseline: declaration IR for TildePath_NoTilde. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_NoTilde/TestComponent.decl.codegen.cs | New baseline: declaration codegen for TildePath_NoTilde. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_NoTilde/TestComponent.codegen.cs | New baseline: codegen for TildePath_NoTilde. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_NoTilde/TestComponent.builder.txt | New baseline: builder output for TildePath_NoTilde. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_NotExpandedBeforeLanguageVersion11/TestComponent.ir.txt | New baseline: version-gated non-expansion (pre-11.0). |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_NotExpandedBeforeLanguageVersion11/TestComponent.decl.ir.txt | New baseline: declaration IR for version-gated case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_NotExpandedBeforeLanguageVersion11/TestComponent.decl.codegen.cs | New baseline: declaration codegen for version-gated case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_NotExpandedBeforeLanguageVersion11/TestComponent.codegen.cs | New baseline: codegen for version-gated case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_NotExpandedBeforeLanguageVersion11/TestComponent.builder.txt | New baseline: builder output for version-gated case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_MultipleElements/TestComponent.ir.txt | New baseline: multiple opted-in HTML attributes. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_MultipleElements/TestComponent.decl.ir.txt | New baseline: declaration IR for multiple-elements case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_MultipleElements/TestComponent.decl.codegen.cs | New baseline: declaration codegen for multiple-elements case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_MultipleElements/TestComponent.codegen.cs | New baseline: codegen for multiple-elements case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_MultipleElements/TestComponent.builder.txt | New baseline: builder output for multiple-elements case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_MixedContent/TestComponent.mappings.txt | New baseline: source mappings for mixed-content scenario. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_MixedContent/TestComponent.ir.txt | New baseline: IR showing mixed literal/expression attribute content. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_MixedContent/TestComponent.diagnostics.txt | New baseline: RZ10029 warning for mixed content. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_MixedContent/TestComponent.decl.ir.txt | New baseline: declaration IR for mixed-content case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_MixedContent/TestComponent.decl.codegen.cs | New baseline: declaration codegen for mixed-content case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_MixedContent/TestComponent.codegen.cs | New baseline: codegen for mixed-content case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_MixedContent/TestComponent.builder.txt | New baseline: builder output for mixed-content case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_HtmlElement/TestComponent.ir.txt | New baseline: HTML opted-in expansion (img[src]). |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_HtmlElement/TestComponent.decl.ir.txt | New baseline: declaration IR for HTML opted-in case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_HtmlElement/TestComponent.decl.codegen.cs | New baseline: declaration codegen for HTML opted-in case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_HtmlElement/TestComponent.codegen.cs | New baseline: codegen for HTML opted-in case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_HtmlElement/TestComponent.builder.txt | New baseline: builder output for HTML opted-in case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_HtmlElement_NotOptedIn/TestComponent.ir.txt | New baseline: non-expansion when HTML allowlist isn’t present. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_HtmlElement_NotOptedIn/TestComponent.decl.ir.txt | New baseline: declaration IR for HTML not-opted-in case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_HtmlElement_NotOptedIn/TestComponent.decl.codegen.cs | New baseline: declaration codegen for HTML not-opted-in case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_HtmlElement_NotOptedIn/TestComponent.codegen.cs | New baseline: codegen for HTML not-opted-in case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_HtmlElement_NotOptedIn/TestComponent.builder.txt | New baseline: builder output for HTML not-opted-in case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ExplicitExpressionNotExpanded/TestComponent.mappings.txt | New baseline: mappings for explicit-expression escape case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ExplicitExpressionNotExpanded/TestComponent.ir.txt | New baseline: IR for explicit-expression escape case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ExplicitExpressionNotExpanded/TestComponent.decl.ir.txt | New baseline: declaration IR for explicit-expression escape case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ExplicitExpressionNotExpanded/TestComponent.decl.codegen.cs | New baseline: declaration codegen for explicit-expression escape case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ExplicitExpressionNotExpanded/TestComponent.codegen.cs | New baseline: codegen for explicit-expression escape case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ExplicitExpressionNotExpanded/TestComponent.builder.txt | New baseline: builder output for explicit-expression escape case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_EmptyPathWithSlash/TestComponent.ir.txt | New baseline: no expansion for ~/ with empty path. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_EmptyPathWithSlash/TestComponent.decl.ir.txt | New baseline: declaration IR for empty-path-with-slash case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_EmptyPathWithSlash/TestComponent.decl.codegen.cs | New baseline: declaration codegen for empty-path-with-slash case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_EmptyPathWithSlash/TestComponent.codegen.cs | New baseline: codegen for empty-path-with-slash case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_EmptyPathWithSlash/TestComponent.builder.txt | New baseline: builder output for empty-path-with-slash case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_EmptyPath/TestComponent.ir.txt | New baseline: no expansion for bare ~. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_EmptyPath/TestComponent.decl.ir.txt | New baseline: declaration IR for bare-tilde case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_EmptyPath/TestComponent.decl.codegen.cs | New baseline: declaration codegen for bare-tilde case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_EmptyPath/TestComponent.codegen.cs | New baseline: codegen for bare-tilde case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_EmptyPath/TestComponent.builder.txt | New baseline: builder output for bare-tilde case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam/TestComponent.mappings.txt | New baseline: mappings for component parameter expansion. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam/TestComponent.ir.txt | New baseline: IR for component parameter expansion. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam/TestComponent.decl.ir.txt | New baseline: declaration IR for component parameter expansion. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam/TestComponent.decl.codegen.cs | New baseline: declaration codegen for component parameter expansion. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam/TestComponent.codegen.cs | New baseline: codegen for component parameter expansion. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam/TestComponent.builder.txt | New baseline: builder output for component parameter expansion. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_NotOptedIn/TestComponent.mappings.txt | New baseline: mappings for component param non-expansion case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_NotOptedIn/TestComponent.ir.txt | New baseline: IR for component param non-expansion case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_NotOptedIn/TestComponent.decl.ir.txt | New baseline: declaration IR for component param non-expansion case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_NotOptedIn/TestComponent.decl.codegen.cs | New baseline: declaration codegen for component param non-expansion case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_NotOptedIn/TestComponent.codegen.cs | New baseline: codegen for component param non-expansion case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_ComponentParam_NotOptedIn/TestComponent.builder.txt | New baseline: builder output for component param non-expansion case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_BareTildeNotExpanded/TestComponent.ir.txt | New baseline: bare ~path does not expand. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_BareTildeNotExpanded/TestComponent.decl.ir.txt | New baseline: declaration IR for bare-tilde case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_BareTildeNotExpanded/TestComponent.decl.codegen.cs | New baseline: declaration codegen for bare-tilde case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_BareTildeNotExpanded/TestComponent.codegen.cs | New baseline: codegen for bare-tilde case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_BareTildeNotExpanded/TestComponent.builder.txt | New baseline: builder output for bare-tilde case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_BackslashInPath/TestComponent.ir.txt | New baseline: expansion preserves backslashes via verbatim literal. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_BackslashInPath/TestComponent.decl.ir.txt | New baseline: declaration IR for backslash case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_BackslashInPath/TestComponent.decl.codegen.cs | New baseline: declaration codegen for backslash case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_BackslashInPath/TestComponent.codegen.cs | New baseline: codegen for backslash case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_BackslashInPath/TestComponent.builder.txt | New baseline: builder output for backslash case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_AttributeNotOptedIn/TestComponent.ir.txt | New baseline: non-expansion for attributes outside allowlist. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_AttributeNotOptedIn/TestComponent.decl.ir.txt | New baseline: declaration IR for attribute-not-opted-in case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_AttributeNotOptedIn/TestComponent.decl.codegen.cs | New baseline: declaration codegen for attribute-not-opted-in case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_AttributeNotOptedIn/TestComponent.codegen.cs | New baseline: codegen for attribute-not-opted-in case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/TestFiles/IntegrationTests/ComponentCodeGenerationTest/TildePath_AttributeNotOptedIn/TestComponent.builder.txt | New baseline: builder output for attribute-not-opted-in case. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorProjectEngineTest.cs | Updates default feature list assertions to include ComponentTildePathPass. |
| src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs | Adds test region, stubs, and integration tests for tilde expansion scenarios. |
| docs/razor/tilde-path-expansion.md | New end-to-end documentation describing syntax, opt-in model, diagnostics, and implementation. |
| .github/memory/testing/razor.md | Updates Razor test guidance (baselines and two-phase compilation notes). |
| .github/instructions/Razor.instructions.md | Documents the “runtime-declared attribute list” pattern for Razor compiler work. |
Suppressed comments (2)
docs/razor/tilde-path-expansion.md:22
- The docs’ generated C# snippet uses
Assets["images/logo.png"], but the compiler emits a verbatim literal (Assets[@"images/logo.png"]). The snippet should match actual output (including the@).
Both produce the same generated C#:
```csharp
__builder.AddAttribute(1, "src", Assets["images/logo.png"]);
**docs/razor/tilde-path-expansion.md:62**
* In the Syntax table, the “Generated C#” column shows `Assets["..."]`, but the compiler emits verbatim literals (`Assets[@"..."]`). Updating the table keeps it consistent with actual codegen and with the backslash-handling rationale later in the doc.
| Input | Generated C# | Notes |
|---|---|---|
src="~/images/logo.png" |
Assets["images/logo.png"] |
Standard form |
src="~images/logo.png" |
"~images/logo.png" (literal) |
No slash, no transformation |
src="~/~images.png" |
Assets["~images.png"] |
Only leading ~/ is special |
</details>
Read the runtime's asset-path opt-in metadata during tag-helper discovery: - AcceptsAssetPathTagHelperProducer finds the public AssetPathAttributes convention type and emits one carrier tag-helper descriptor per declared (element, attribute) pair, following the BindAttributes / EventHandlers model. The descriptors declare no tag-matching rules; they exist only to carry the allowlist to the lowering pass. - ComponentTagHelperProducer records [AssetPath] on a component parameter as PropertyMetadata.AcceptsAssetPath. - CompilerFeatures registers the new producer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ef56ab10-2d3d-4e55-a318-3d03bfbdd713
Rewrite literal ~/-prefixed attribute values into Assets["..."] expressions, but only where the target has opted in: - HTML element attributes whose (element, attribute) pair appears in the discovered [AcceptsAssetPath] allowlist. - Component parameters whose bound attribute has PropertyMetadata.AcceptsAssetPath. The pass reads the full discovered tag-helper set from ITagHelperFeature so the allowlist is compilation-global rather than scoped to the document's in-scope tag helpers. Mixed literal/expression content on an opted-in attribute is reported as RZ10025. Expansion is gated on Razor language version 11.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ef56ab10-2d3d-4e55-a318-3d03bfbdd713
Cover opted-in expansion for HTML elements and component parameters, plus the backward-compatible cases where ~/ is left untouched: no [AssetPath], an attribute outside the allowlist, bare ~, explicit @() expressions, pre-11.0 language version, and mixed content (RZ10025). Baselines are generated with: dotnet test src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/Microsoft.AspNetCore.Razor.Language.UnitTests.csproj /p:GenerateBaselines=true Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ef56ab10-2d3d-4e55-a318-3d03bfbdd713
Add the feature design doc and record the discovery/allowlist pattern and the baseline-regeneration workflow in the agent knowledge base. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ef56ab10-2d3d-4e55-a318-3d03bfbdd713
87fe605 to
d0f1c32
Compare
|
/azp run roslyn-CI |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
| } | ||
|
|
||
| // Verbatim string literal: backslashes (common in paths) stay literal and only embedded | ||
| // quotes need doubling. Source is null -- synthetic code with no .razor mapping. |
There was a problem hiding this comment.
This is interesting. Tooling supports Go To Def on file paths in string literals, we will have to follow up here because the existing system won't work without mapping. We probably can't even use an enhanced line pragma to map part of the string, right, because of the trimmed ~/?
- Convert the AssetPathMetadata and ComponentTildePathPass header comments to <summary> doc comments, and move the BuildAllowedElementAttributes note inside the method body. - Use a list pattern in GetSingleLiteralToken. - Add TildePath_MultipleAttributesSameElement covering two opted-in attributes on a single element (img srcset + src), and opt img[srcset] into the test stub. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ef56ab10-2d3d-4e55-a318-3d03bfbdd713
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 101 out of 101 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/Razor/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs:13489
- Comment says the backslash should be "escaped" in generated C#, but the generated code uses a verbatim string literal (e.g., @"images\logo.png"), so backslashes are preserved as-is. Update the comment to match the actual behavior being asserted by the baselines.
// Assert - backslash should be escaped in the generated C# string literal
docs/razor/tilde-path-expansion.md:49
- This public doc includes internal/non-public references (Azure DevOps board entry and rows marked "Private"), which aren’t actionable for external contributors and will quickly go stale. Consider removing these rows (or replacing them with public links) and keeping only publicly accessible references.
| aspnetcore#68229 | https://github.com/dotnet/aspnetcore/issues/68229 | **Canonical proposal** -- authoritative API names, HTML allowlist, and cross-repo plan |
| aspnetcore#56076 | https://github.com/dotnet/aspnetcore/issues/56076 | Runtime APIs that introduced `Assets` indexer |
| AzDO#2623010 | DevDiv Razor Experiences board | Internal tracking work item |
| Razor-Language-Design#10 | Private | Language design discussion |
| aspnet/specs#769 | Private | Javier's design note |
|
|
||
| // [AssetPath] enables ~/ expansion only for string parameters. On any other type the | ||
| // rewritten Assets[...] value wouldn't be assignable, so ignore the opt-in and warn. | ||
| if (property.GetAttributes().Any(static a => a.HasFullName(ComponentsApi.AssetPathAttribute.MetadataName))) |
There was a problem hiding this comment.
@chsienki This doesn't consider inheritance right?
The attribute per the approved API in dotnet/aspnetcore#68237 uses Inherited = true
I do believe that either we should change it to Inherited = false or otherwise make sure that Razor respects inheritance correctly to avoid confusion.
`AssetPathAttribute` has `Inherited = true`, but component parameter discovery only inspects attributes declared directly on the selected property. An override that redeclares `[Parameter]` therefore loses the base property's asset-path opt-in and leaves `~/` literals unexpanded. Follow the `OverriddenProperty` chain when resolving `[AssetPath]`. This preserves attribute inheritance for overrides without applying the opt-in to hidden properties. Add an integration baseline covering a base virtual parameter with `[AssetPath]` and a derived override that redeclares only `[Parameter]`. Addresses #84796 (comment) ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/dotnet/roslyn/pull/85171) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dd15ad81-c929-4d0c-91de-354c0c777533
This is the Roslyn (Razor compiler) half of the
~/asset-path expansion feature proposed in dotnet/aspnetcore#68229.Note
The feature is inert until the runtime ships
[AssetPath]/[AcceptsAssetPath]/ theAssetPathAttributesconvention type inMicrosoft.AspNetCore.Components. The tests stub those attributes, so the compiler builds and its tests pass today, but end-to-end behavior depends on the coordinated runtime + SDK changes landing.What this does
Adds opt-in, compile-time expansion of literal
~/-prefixed attribute values in.razorfiles intoAssets[@"..."]expressions, so<img src="~/images/logo.png">behaves like<img src="@Assets[@"images/logo.png"]">-- only where the target has explicitly opted in:(element, attribute)pair the runtime declares via[AcceptsAssetPath(elementName, attributeName)]on a publicAssetPathAttributesconvention type (built-in allowlist:img[src],link[href],script[src]).[AssetPath](honored only onstringparameters).When nothing is opted in,
~/is left untouched -- existing markup is unaffected. There is no ambient behavior, so no MSBuild opt-out is needed.Design
AcceptsAssetPathTagHelperProducerdiscovers theAssetPathAttributesconvention type during tag-helper discovery and emits carrierTagHelperDescriptors (AssetPathMetadata) with no tag-matching rules -- they exist purely to carry the allowlist to the lowering pass.[AssetPath]on astringcomponent parameter is recorded asPropertyMetadata.AcceptsAssetPath.ComponentTildePathPass(anIRazorOptimizationPass, Order 75) reads the full discovered tag-helper set viaITagHelperFeature(compilation-global, not scoped to the document's in-scope tag helpers), builds an element -> attributes allowlist once per engine, and rewrites only opted-in single-literal~/values. Gated on Razor language version 11.0.Diagnostics
~/literal is mixed with dynamic content on an opted-in attribute, so it can't be expanded.[AssetPath]is applied to a non-stringparameter; the opt-in is ignored (expanding~/would yield anAssets[...]string that isn't assignable).Commits
AssetPathMetadata+PropertyMetadata.AcceptsAssetPath).[AssetPath]/[AcceptsAssetPath]declarations (producer + registration; RZ10030 for non-string[AssetPath]).~/literals viaComponentTildePathPass(+ RZ10029).docs/razor/tilde-path-expansion.md).Each commit builds and is test-green.
Testing
ComponentCodeGenerationTestBaseTildePath_*cases cover opted-in expansion (HTML + component params) and the backward-compatible non-expansion cases: not opted in, attribute outside the allowlist, bare~, explicit@(), pre-11.0 language version, mixed content (RZ10029), and non-string[AssetPath](RZ10030).Related
~/asset paths in Razor components #84793