Skip to content

Fix 'new project' not properly reading current user options when formatting initial files#81523

Merged
CyrusNajmabadi merged 6 commits intodotnet:mainfrom
CyrusNajmabadi:newProjectTemplate
Dec 3, 2025
Merged

Fix 'new project' not properly reading current user options when formatting initial files#81523
CyrusNajmabadi merged 6 commits intodotnet:mainfrom
CyrusNajmabadi:newProjectTemplate

Conversation

@CyrusNajmabadi
Copy link
Contributor

Fixes #79231

// has the right fallback analyzer options. This normally happens in Workspace.SetCurrentSolutionAsync as
// it mutates. But that has never happened so far, so we have to simulate that manually here. This ensures
// we pick up the right host/vs options which is needed in order to run the code cleanup pass below.
solution = solution.WithFallbackAnalyzerOptions(Workspace.ComputeFinalFallbackAnalyzerOptions(solution, projectToAddTo.Solution));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

i don't love this. alternatively, above this, we call solution.AddProject. if we're comfortable with the idea, i could make it so that this logic automatically happens in .Add/.RemoveProject so that our in-memory forks reread this host data.

It feels OOGy to me though. because that means you might have a solution snapshot with one set of options, and you fork it,a nd now the options are different due to the underlying host. it feels like we don't want that, and options in forked snapshots should not change unless explicit. instead, only when the host updates .CurrentSolution should it change.

So this, while oogy, feels the cleanest to me.

Copy link
Member

Choose a reason for hiding this comment

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

Why not fork the solution with the project projectToAddTo.Solution further to add the options?
Then you could just call InitializeAnalyzerFallbackOptions and we wouldn't need ComputeFinalFallbackAnalyzerOptions

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oops. that's just a mistake. though, wehen you say "to add the options", how do you envision computing which options are needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i suppose i could just do:

            var provider = oldSolution.Services.GetRequiredService<IFallbackAnalyzerConfigOptionsProvider>();
            newFallbackOptions = newFallbackOptions.Add(language, provider.GetOptions(language));

though the benefit of having and calling into the helper is that it works regardless of the state the workspace is in when FormatDocumentCreatedFromTemplateAsync is called.

Copy link
Member

Choose a reason for hiding this comment

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

This does the same thing now, no?

private static Solution InitializeAnalyzerFallbackOptions(Solution oldSolution, Solution newSolution)
        => newSolution.WithFallbackAnalyzerOptions(ComputeFinalFallbackAnalyzerOptions(oldSolution, newSolution));

continue;
}

var provider = Services.GetRequiredService<IFallbackAnalyzerConfigOptionsProvider>();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Services is 'HostWorkspaceServices', which is the exact same backing store for Solution.Services. So we dno't need to access this.Services and can instead make this a static method that only depends on OldSolution/NewSolution. To make it clearer what this is doing, i had this just return the final desired 'fallback analyzer options', and it's up to the caller to decide what to do with it.

Note: as a static method on Workspace, this could just move to an instance/static method on Solution instead, which seems to be the more appropriate location. I'm open to thoughts here.

@CyrusNajmabadi
Copy link
Contributor Author

@tmat @jasonmalinowski ptal.

Core issue, as described in internal teams chat is that we're being asked to format a file on disk prior to our workspace having had anything happen to it. As such, ti's just an empty-solution initially (with no projects) and as such doesn't know what languages we have, and hasn't done anything to prepopulate itself with information about the current host options.

This means that when we cleanup the file, we get all the defaults (or editorconfig values) instead of picking up what the user has set in the host.

This fix is incredibly simple and works, but pushes the logic to the AbstractEditorFactory.FormatDocumentCreatedFromTemplateAsync helper to have to know about this. I don't personally hate this, as that function also knows that hte solution is empty,a nd is simulating a project/doc by forking in memory.

But i'm open to thoughts on alternative strategies here if we find this unpleasant/inappropriate. I've given thoughts in the PR to an alternate routee, but why i did not like doing things that way.

@CyrusNajmabadi
Copy link
Contributor Author

@tmat @jasonmalinowski @JoeRobich ptal.

@CyrusNajmabadi CyrusNajmabadi merged commit 560ae42 into dotnet:main Dec 3, 2025
26 checks passed
@CyrusNajmabadi CyrusNajmabadi deleted the newProjectTemplate branch December 3, 2025 14:42
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Dec 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

When creating class library in VS, it reverts to block-scoped namespace

4 participants