dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19.02k stars 4.03k forks source link

Suggest usings for types from available shared frameworks and add required <FrameworkReference /> along with using directive #64505

Open DamianEdwards opened 2 years ago

DamianEdwards commented 2 years ago

Summary

Enable the editor to suggest usings for types from shared frameworks that aren't referenced and then add the required framework reference and using import automatically (like is supported for types in unimported namespaces and unreferenced NuGet packages today).

Background and Motivation

We often get questions from customers asking how to use ASP.NET Core types in class libraries, generally when they're trying to create reusable libraries of web-related code for their ASP.NET Core apps.

Discovering that one needs to add <FrameworkReference Include="Microsoft.AspNetCore.App" /> to the project file is fairly difficult from within the product today, although there are docs. One thing we can do to perhaps make this a easier is enable the editor to resolve types from available shared frameworks and then suggest adding the required the using directive and framework reference.

Proposed Feature

I imagine it would be a new addition to the "Suggest usings for types in..." options here:

image

Alternative Designs

None considered, although related to dotnet/project-system#8558

CyrusNajmabadi commented 2 years ago

What is the difference between this and "Suggest usings for types in .Net Framework assemblies"? Thanks @DamianEdwards .

DamianEdwards commented 2 years ago

".NET Framework" refers to the original version of .NET included in Windows. <FrameworkReference /> is a feature of .NET Core 3+/.NET 5+ only and is how references to "ref packs" for shared frameworks are added, e.g. Microsoft.NETCore.App,Microsoft.AspNetCore.App`, etc.

CyrusNajmabadi commented 2 years ago

So the way this works is there's an index that is provided to us (roslyn does not build it) that lists the assemblies/types of importance. For .net assemblies, we then go and resolve the assembly of interest using the Microsoft.VisualStudio.Shell.Design.VsTargetFrameworkProvider api.

Once we have this, we can just literally add a reference to that assembly to the project. I'm not really sure how the above changes from that? Is htere some new way to add references? From the roslyn perspective, we generally sit a layer that just understands source files, options and pe-references. So not totally clear how we would know about this stuff and convey it to the project system.

But, if you can give details on how these differ and what we would need to coordinate here, we could possibly do this.

We'll also presumably need to figure out who builds the index and get them to add these new 'frameworks' to it.

DamianEdwards commented 2 years ago

Shared frameworks have been a concept since .NET Core 3.x. They're typically included as part of the SDK installer and are represented by the "ref packs" located at DOTNET_ROOT/packs/, e.g. on my machine C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\6.0.10\ref\net6.0

As for an index of assemblies and types I'm not aware of one existing already but seemingly would be trivial to generate. However, adding a reference to a shared framework is not like adding a reference to an assembly. Shared frameworks are referenced by adding a <FrameworkReference /> item to the project file, e.g. <FrameworkReference Include="Microsoft.AspNetCore.App" />. For most projects this is done implicitly by the SDK targeted by the project, e.g. the Microsoft.NET.Sdk.Web SDK implicitly adds a <FrameworkReference Include="Microsoft.AspNetCore.App" />, and the Microsoft.NET.Sdk SDK implicitly adds a <FrameworkReference Include="Microsoft.NETCore.App" /> (and conditionally adds references to the Microsoft.WindowsDesktop.App.Ref if UseWindowsForms or UseWpf are true).

In the case of class libraries though, the project is most often configured to use the base Microsoft.NET.Sdk SDK and so if types from shared frameworks other than Microsoft.NETCore.App are needed the <FrameworkReference /> must be added manually. Versions are not specified for framework references.

This seems like it's more similar to the "Suggest usings for types in NuGet packages" feature based on what's been said here.

Adding @dsplaisted for his thoughts.

CyrusNajmabadi commented 2 years ago

Ok. For roslyn to support this, we'd need two things done:

  1. the info needs to eb added to the index. I think we'd need to add @joelverhagen about that.
  2. Roslyn would need an API provided to talk to the project system to add these <FrameworkReference... elements to teh project file.

What roslyn can do is look at the index and say "ah, that looks like a suitable result for the type-name we see in the file here, let's offer to add it". But that's our role in this system (and what we do for Nuget and Framework assemblies).

joelverhagen commented 2 years ago

the info needs to eb added to the index. I think we'd need to add @joelverhagen about that.

We (NuGet.org team) have an index builder that essentially generates a "type name" to "package ID" index which makes its way onto a CDN, which VS pulls periodically for the "Suggest usings for types in NuGet packages" experience, as @CyrusNajmabadi stated. This could be used as a model for any such "type name" to "framework reference" index.

As it stands right now, I don't think the NuGet.org team is the right team to own such a framework mapping since the shape and content of .NET frameworks is mostly orthogonal to packages stored on NuGet.org (AFAIK). The acquisition method is not via NuGet restore, etc. etc. However, we'd be happy to share knowledge in this area, so feel free to reach out to me directly if a team would like to pursue this.

DamianEdwards commented 2 years ago

Thanks @joelverhagen. Seems producing such an index for frameworks would be the responsibility of each of the framework owners (runtime, asp.net core, winforms/wpf?, perhaps via a common target in arcade) and have it packaged in the VS .NET workload (seems wasteful to put it in the SDK when it's only used by VS).

@mmitche @dougbu @ericstj

dougbu commented 2 years ago

I'm probably missing something but don't understand why we'd need to build the index in the individual repos. Why not index Microsoft.AspNetCore.App.Ref and so on❔

Separately, the different SDKs do more than add <FrameworkReference /> items. I suspect this feature will result in a poor user experience unless changing the SDK is at least offered. I'm thinking of developer who decides they need a class library they've been playing with to "grow up" to be a web site. There are probably other scenarios. (My apologies if this is already handled elsewhere.)

CyrusNajmabadi commented 2 years ago

@dougbu as long as you give us an appropriate API to call, you can do whatever you need to the project behind the scenes in response to it. Think of the flow as follows:

  1. you produce an index that contains the list of fully-qualified-type-names of interest and whatever assocaited data you will need that they correspond to. For example, for nuget, this has hte names of the types, and the nuget-package they come from. For Framework references this is the names of the type and the assembly name they come from. You can just have the index contain whatever you will need here.
  2. Roslyn then sees a naked name like "Blorp", we look in the index and can see the namespace this belongs to and wahtever additional data you've attached that you need. We then offer the fix to the user.
  3. If the user selects the fix, we then call your api, passing along whatever information you had in the index. You then make whatever changes are necessary for your scenarios/users. All roslyn will do itself is add the using directive to teh file and then call into you.

Does that make sense? :)

DamianEdwards commented 2 years ago

@dougbu the primary scenario I'm concerned about for now is folks trying to use types from shared frameworks when they're in a class library. If applying the offered fix calls back into the project system to modify the project accordingly, it can add the framework reference or set the appropriate MSBuild property (e.g. UseWinForms), etc. We could certainly have the fix offer multiple choices, e.g. "Add Framework Reference for Microsoft.AspNetCore", "Use Microsoft.NETCore.Sdk.Web", etc., but I don't think the changing SDK scenario is anywhere near as common and also multiple choices means the user has to decide which to choose, and it's not obvious to me how they'd know what they want.

dougbu commented 2 years ago

@CyrusNajmabadi my question was about step 0 in the process you outlined. Why aren't we reusing existing tooling to produce an index from a package here❔

@DamianEdwards that makes sense.

CyrusNajmabadi commented 2 years ago

Why aren't we reusing existing tooling to produce an index from a package here❔

I def have no idea :) I'm not involved with the creation of the index or the tooling there. We just consume the prebuilt index.

DamianEdwards commented 2 years ago

@dougbu the details about how the index of a shared framework is produced and how it gets packaged/shipped is something I'd assume you and the other build/SDK folks would figure out.