Skip to content

Compiler: Cleanup View Components pass and code gen#12174

Merged
DustinCampbell merged 9 commits intodotnet:mainfrom
DustinCampbell:cleanup-viewcomponents
Sep 8, 2025
Merged

Compiler: Cleanup View Components pass and code gen#12174
DustinCampbell merged 9 commits intodotnet:mainfrom
DustinCampbell:cleanup-viewcomponents

Conversation

@DustinCampbell
Copy link
Member

@DustinCampbell DustinCampbell commented Sep 3, 2025

This change reworks a lot of the code generation for view component tag helpers. Code that's duplicated across the MVC versions has been consolidated and extra effort has been taken to reduce allocations and share common strings. The commits tell a story if you choose to review commit-by-commit.


CI Build: https://dev.azure.com/dnceng/internal/_build/results?buildId=2785649&view=results
Test Insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/666958
Toolset Run: https://dev.azure.com/dnceng/internal/_build/results?buildId=2785650&view=results

Extract all of the get/set instance property strings to constants on a new ViewComponentsApi class that is modeled on ComponentsApi. Make strings that are unique to this ViewComponentTagHelperTargetExtension private consts.
Use the consts from ViewComponentApis throughout the Version1_X.ViewComponentTagHelperTargetExtension.
Use the consts from ViewComponentApis throughout the Version2_X.ViewComponentTagHelperTargetExtension.
- Enable nullability
- Clean up CodeWriterExtensions helpers used to take ImmutableArray<string> and add helpers for writing separated lists
- Make methods static
- Use CommonModifiers in more places
- Stop creating dictionaries unnecessarily
- Where appropriate, rename "parameters" to "arguments"
- Remove unnecessary argument-null checks
Introduce ViewComponentTagHelperTargetExtensionBase and move most implementation into it.

In addition, effort is taken to avoid extra allocations:

- Data is cached or created up front and reused.
- Overloads have been added to some CodeWriterExtensions helpers to take CodeWriter.WriteInterpolatedStringHandler.
- Enable nullability
- Make properties read-only
- Add constructor
- Remove unnecessary argument-null checks
- Small bits of clean up
General clean up with no expected functional change.
@DustinCampbell DustinCampbell requested a review from a team as a code owner September 3, 2025 18:55
@DustinCampbell DustinCampbell requested review from a team and ToddGrun September 3, 2025 18:55
public static ImmutableArray<string> PublicOverrideAsync { get; } = [
GetText(CSharpSyntaxKind.PublicKeyword),
GetText(CSharpSyntaxKind.OverrideKeyword),
GetText(CSharpSyntaxKind.AsyncKeyword)];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as previous, just different ordering?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. I don't want to change the generated output by reordering the modifiers.

{
private static readonly ImmutableArray<string> s_publicModifiers = ["public"];

public string TagHelperTypeName { get; set; } = "Microsoft.AspNetCore.Razor.TagHelpers.TagHelper";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were these all settable because they could be customized by users in their csproj somehow?

No wait, don't tell me, I don't want to know.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's not that interesting. I've found several spots in the compiler that do this, and the only reason I've found is to be able to give them different values in tests. However, the value of doing that is pretty flimsy. It allows the test to ensure that, "yes, this really did run". However, it means that the test is validating invalid compiler output. In my opinion, it's better to protect these values from being written to and ensure they're all shared.

var className = $"__Generated__{viewComponentName}ViewComponentTagHelper";
var fullyQualifiedName = !Namespace.Name.IsNullOrEmpty()
? $"{Namespace.Name}.{Class.Name}.{className}"
: $"{Namespace.Name}{Class.Name}.{className}";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{Namespace.Name}

Why even include this if it's null or empty?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because that's what the code already produced. I'm trying not to change behavior.

Copy link
Contributor

@ToddGrun ToddGrun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@DustinCampbell
Copy link
Member Author

@chsienki, PTAL

@DustinCampbell DustinCampbell merged commit dcc9fc1 into dotnet:main Sep 8, 2025
11 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Sep 8, 2025
@DustinCampbell DustinCampbell deleted the cleanup-viewcomponents branch September 8, 2025 20:11
@akhera99 akhera99 modified the milestones: Next, 18.0 P1, 18.0 P2 Sep 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants