Skip to content

Conversation

@DustinCampbell
Copy link
Member

@DustinCampbell DustinCampbell commented Apr 8, 2025

Throughout the compiler and tooling Razor files have a "kind" that is represented as a string. Prior to the Razor API being taken internal, using a string made it theoretically possible for the compiler to be extended to file types other than those that the Razor compiler already knows about. With extensibility no longer a priority, file kinds can be represented as an enum (RazorFileKind) that provides the values that the compiler supports, e.g.. Component, ComponentImport, Legacy. This makes checks for the file kind much cheaper, since they were all string comparisons before.

There are a lot of files changed, but much of the work is mechanical.

Important

This is a breaking change for the RazorSDK. When the Razor compiler flows to the .NET SDK, it will introduce build errors that can be fixed by applying this commit: dotnet/sdk@f33a5e7.

VS Test Insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/626687

Add RazorFileKind enum that provides the allowable values from FileKinds. In addition, include a None value for scenarios where a file kind would be null.
Note: These helper methods will be removed once the code base is converted to RazorFIleKind.
Update RazorProjectItem.FileKind property to return a RazorFIleKind rather than a string. This requires changes to all sub types as well.

Note: This will require changes to the RazorSdk, which declares custom RazorProjectItem descendants.
Update RazorIntegrationTestBase.FileKind to return a RazorFileKind and update inherited test classes.
Updates RemoteProjectItem to take a RazorFileKind rather than a string.
Updates NotFoundProjectItem to take a RazorFileKind rather than a string.
Updates SourceGeneratorProjectItem to take a RazorFileKind rather than a string.
Updates DefaultRazorProjectItem to take a RazorFileKind rather than a string.
Updates TestRazorProjectItem to take a RazorFileKind rather than a string.
Updates TestSnapshotProjectItem to take a RazorFileKind rather than a string.
Update RazorToolingIntegrationTestBase.FileKind to return a RazorFileKind and update inherited test classes.
Updates RazorProjectFileSystem.GetItem(...) to take a RazorFileKind rather than a string and update all overrides and callers.
This updates the various RazorProjectEngine Process and CreateCodeDocument methods to take a RazorFileKind rather than a string.
Also includes updates for DocumentSnapshotHandle, which affect serialization.
Note: This change deletes a lot of unused test infrastructure code from
ToolingParserTestBase.
This change represents mechanical clean up:

- Use collection expressions
- Switch from IReadOnlyList<TagHelperDescriptor> to ImmutableArray<TagHelperDescriptor>
- Make CreateProvider() helper private protected
- Use primary constructor
- Mark for nullability
- Remove unused parameters
- Update static test TagHelperDescriptors to be actual singletons and use fluent builder API.
- Add TryGetFileKindFromPath
- Rename FilePathToRazorFileKind to GetFileKindFromPath and implement using TryGetFileKindFromPath.
- Remove ComponentFilePathToRazorFileKind
Copy link
Member

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

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

LGTM

return FileKinds.ComponentImport;
}
else if (string.Equals(".razor", Path.GetExtension(filePath), StringComparison.OrdinalIgnoreCase))
if (string.Equals(LegacyFileExtension, extension, StringComparison.OrdinalIgnoreCase))
Copy link
Member

Choose a reason for hiding this comment

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

I guess this is based on pre-existing code, but it's weird we use OrdinalIgnoreCase in most comparisons here and Ordinal when checking for _Imports.razor.

Copy link
Member Author

Choose a reason for hiding this comment

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

Totally agree, but I took a look at the history and Ordinal has been used since that check was introduced.


public enum RazorFileKind : byte
{
None = 0,
Copy link
Member

Choose a reason for hiding this comment

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

Do we need both None file kind and nullable RazorFileKind? usages?

Copy link
Member Author

Choose a reason for hiding this comment

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

We do, unfortunately. None gets surfaced in certain cases, namely, RazorProjectItem instances representing legacy default imports. So, RazorFileKind? is needed in a few places to know whether a RazorFileKind has been provided or not.

Copy link
Member

@jjonescz jjonescz Apr 11, 2025

Choose a reason for hiding this comment

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

I'm not sure I follow. How did it surface previously when the file kind was a string? I imagine (string?)null corresponds to RazorFileKind.None, so we shouldn't need another value (RazorFileKind?)null.

Copy link
Member Author

@DustinCampbell DustinCampbell Apr 11, 2025

Choose a reason for hiding this comment

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

RazorFileKind.None allows us to represent valid "null" cases without forcing RazorFileKind? into our APIs. The same scenarios with string were marked as #nulalble disable, so calling code didn't really have to deal with it. A FileKind property could return null, but it was exceedingly rare, so most accesses never bothered checking for null. However, since RazorFIleKind is an enum, we would need to make FileKind properties return RazorFileKind? -- a nulalble struct -- and every caller would have to deal with it. IMO, this is a good happy medium for now.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is the one case that's truly problematic:

public virtual RazorFileKind FileKind
=> FilePath == null
? RazorFileKind.None
: FileKinds.GetFileKindFromPath(FilePath);

I will be following up with another PR to rework RazorProjectItem and make this scenario essentially impossible.

Copy link
Member Author

@DustinCampbell DustinCampbell Apr 14, 2025

Choose a reason for hiding this comment

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

That doesn't work unfortunately. RazorFileKind? is used to indicate that a RazorFileKind has not been provided and it should be calculated from the file path. RazorFileKind.None is not the right signal for that -- at least not right now. RazorFileKind.None is used for the specific case where the RazorFileKind couldn't be computed from the file path.

Copy link
Member

@jjonescz jjonescz Apr 14, 2025

Choose a reason for hiding this comment

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

So yeah, I'm confused how it could have worked before this PR when the only "special value" we had was null - and now we have two (None and null) and use them to indicate different things. Why didn't we need two values previously? I thought this PR just maps from string to an enum and back, i.e., it's a bi-directional mapping. Something like:

(enum <-> string)
None <-> null
Component <-> "component"
Legacy <-> "mvc"
Imports <-> "imports"

Copy link
Member Author

Choose a reason for hiding this comment

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

I definitely understand your confusion, and I tried to avoid this situation. However, the design of RaorProjectItem.FileKind and all of the test helpers that took string? fileKind = null made tis tricky. As I mentioned, RazorFileKind.None is a valid result for RazorProjectItem.FileKind in the case of legacy imports files. Replacing every RazorFileKind? with RazorFileKind.None causes RazorFileKind.None have two meanings:

  1. The case where is RazorFileKind.None is a valid value.
  2. The case where RazorFileKind.None would used to indicate that a RazorFileKind has not yet be specified and still needs to be computed.

I opted to keep RazorFileKind? in place for now to mean the latter case. I fully intend to clean this up with a different change to RazorProjectItem. However, I felt that change was outside the scope of this PR, which already touched a lot of code.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, thanks for the explanation, sorry if I was slow to understand

Copy link
Member Author

Choose a reason for hiding this comment

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

Not at all! I'm very glad that you quickly spotted the obvious wart in this change. 😄

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