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

Change RazorProjectInfo to use HostProject and HostDocument #11030

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

DustinCampbell
Copy link
Member

This pushes the HostProject and HostDocument types down to MS.ANC.Razor.ProjectEngineHost and uses them in RazorProjectInfo. RazorProjectInfo tracks all of the same information as a HostProject, so it makes sense to bring them together. In addition, RazorProjectInfo exposed an ImmutableArray<DocumentSnapshotHandle> but DocumentSnapshotHandle is identical to HostDocument. So, that's been unified as well. In addition, I've gone ahead and simplified some of the code in RazorProjectService to take advantage of HostProject and HostDocument.

These changes require an increment to the serialization format versions. So, this will need to make its way to VS Code as well. (Although, since we don't save bin files to disk anymore, is that strictly necessary for VS Code?)

@DustinCampbell DustinCampbell requested review from a team as code owners October 16, 2024 00:35
@ryzngard
Copy link
Contributor

(Although, since we don't save bin files to disk anymore, is that strictly necessary for VS Code?)

Correct. This is not a breaking change for VS but will block VS Code until a dual insertion. I'll ping you offline for timelines

Copy link
Contributor

@ryzngard ryzngard left a comment

Choose a reason for hiding this comment

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

I'd like to get #11015 in before this goes in to facilitate making the snap for prerelease this week in VS Code.


var version = reader.ReadInt32();
reader.ReadArrayHeaderAndVerify(4);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not have this above the verify, and just have the reader move in VerifyVersionNumber since it's already passed by ref?

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 guess because that felt a bit more awkward to me when I wrote this. The problem is that we need to verify the array header length after the version check. Otherwise, we may never get to the version check before we blow up because the array size changed if a new version happens to add or remove RazorProjectInfo field. I felt that it was better to have a separate method to verify the version by peeking ahead and then leaving this method to do the real data read.

since it's already passed by ref?

MessagePackReader really should be passed by ref in some way. It's a ref struct and a ref has already been taken for it. I'll update VerifyVersionNumber to ref readonly.

{
return null;
}

var projectPath = Path.GetDirectoryName(project.FilePath);
if (projectPath is null)
Copy link
Contributor

Choose a reason for hiding this comment

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

is it possible for this to be null if the project.FilePath is checked above?

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 don't believe so, but it's annotated to be string? on .NET without a [NotNullIfNotNull] attribute.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, it turns out that it is possible for this to return null if project.FilePath is not null. Project.GetDirectoryName(...) returns null if the file path pass to it is null OR if it is a root without a directory name, such as / or C:.

defaultNamespace = rootNamespace ?? "ASP"; // TODO: Source generator does this. Do we want it?

return razorConfiguration;
return (configuration, rootNamespace ?? "ASP"); // TODO: Source generator does this. Do we want it?
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can just remove the TODO from this. It's what we've been doing for a while

Copy link
Contributor

Choose a reason for hiding this comment

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

Or honestly just leave it null

Copy link
Contributor

Choose a reason for hiding this comment

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

Or honestly just leave it null

This is how subtle bugs creep in! 😛

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I'm not comfortable making an intentional behavior change in this method when I was just making a small refactoring.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair enough. Can we remove the null check on line 83 at least?

Copy link
Contributor

Choose a reason for hiding this comment

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

and change the return tuple to (RazorConfiguration configuration, string rootNamespace)

Copy link
Member Author

@DustinCampbell DustinCampbell Oct 16, 2024

Choose a reason for hiding this comment

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

For sure! I had marked string rootNamespace as nullable before I even looked at the implementation, since I know that HostProject.RootNamespace is nullable. Clearly it can't be null though.

{
var normalizedPath = FilePathNormalizer.Normalize(filePath);
Copy link
Contributor

Choose a reason for hiding this comment

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

This worries me. If previous RazorProjectService would ensure normalized paths, but HostProject does not, I think we'll end up [re-]introducing subtle bugs. We've definitely seen scenarios where CPS and Roslyn don't agree on what a path looks like, with both systems even changing for a specific project during load.

The simplest fix here would be to have HostProject normalize the path in its constructor. That seems to fit well with the rest of the PR, centralizing our APIs on a single type that has all the necessary logic.

The other option would be to have HostProject use the normalizing comparer, and doing a pass through everything that uses HostProject.FilePath or IProjectSnapshot.FilePath for comparisons and making them use the normalizing comparer too.

As an example, I think this could now break:
https://github.com/dotnet/razor/blob/main/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManager.cs#L160

That gets called from our CPS data flow receiver, and if it has a project in it that comes from the fallback project manager via Roslyn, it might not see it as the same project.

I am aware that "always call the file path normalizer" is a little bit of cargo cult programming, but I'm not sure I trust our tests to provide coverage and confidence that we can move away from it. Thankfully the Good Ship Cohosting should save us :D

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 love that you spotted this! It was the only place that I was aware of removing a call to FilePathNormalizer.Normalize(...). I fully agree that the whole file-normalization business is super error prone, and I think HostProject is great place to unify some of that. I'll take a closer look at that for this pull request.

Thankfully the Good Ship Cohosting should save us :D

goodshipcohosting.com appears to be available for purchase.

Copy link
Member Author

Choose a reason for hiding this comment

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

FWIW, as I've started looking at this, I'm finding that it's unclear whether HostProject.FIlePath should be normalized or not. Currently, it looks like the VS ProjectSnapshotManager does not contain normalized project paths but the language server ProjectSnapshotManager does. Since CPS only accesses the VS ProjectSnapshotManager, I can't see how it would be affected by the language server's ProjectSnapshotManager suddenly not having normalized file paths. This looks to me like a mess and I'm not sure that I know how it's supposed to work.

Copy link
Contributor

Choose a reason for hiding this comment

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

The VS side not having normalized paths is not too surprising I guess, since it doesn't have a RazorProjectService, so perhaps my worry about that specific location wasn't valid (or is already broken 😁). I still worry about changing the language server side to not be normalized, but perhaps its worth rolling the die for a preview 1?

On the other hand, I don't think normalizing everywhere is likely to introduce issues, the only downside really is reinforcing the cargo cult.

Copy link
Member Author

Choose a reason for hiding this comment

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

perhaps its worth rolling the die for a preview 1

That doesn't feel good to me. I want to get to the bottom of this. 😄

defaultNamespace = rootNamespace ?? "ASP"; // TODO: Source generator does this. Do we want it?

return razorConfiguration;
return (configuration, rootNamespace ?? "ASP"); // TODO: Source generator does this. Do we want it?
Copy link
Contributor

Choose a reason for hiding this comment

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

Or honestly just leave it null

This is how subtle bugs creep in! 😛

Copy link
Member

Choose a reason for hiding this comment

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

btw, why these files live in Shared? they are not used by the razor compiler, are they?

Copy link
Member Author

Choose a reason for hiding this comment

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

Before we switched to message pack, these were compiled into both VS and the Razor language server. Today, I think there's a desire for the compiler to use them rather than a different TagHelperDescriptor JSON converter in tests. Seems reasonable to share that code someday.

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.

4 participants