Skip to content

Add code refactoring to offer usings for fully qualified names#81484

Merged
CyrusNajmabadi merged 21 commits intomainfrom
copilot/add-usings-for-qualified-types
Nov 29, 2025
Merged

Add code refactoring to offer usings for fully qualified names#81484
CyrusNajmabadi merged 21 commits intomainfrom
copilot/add-usings-for-qualified-types

Conversation

Copy link
Contributor

Copilot AI commented Nov 29, 2025

Summary

This PR implements a new code refactoring that offers to add using/Imports directives for fully qualified type names and simplify them.

Changes

  • Create AbstractAddImportCodeRefactoringProvider in src/Features/Core/Portable/AddImport/
  • Implement two code actions:
    • "Add 'using {0};'" (C#) / "Add 'Imports {0}'" (VB) - adds using/imports and simplifies just the current occurrence
    • "Add 'using {0};' and simplify all occurrences" (C#) / "Add 'Imports {0}' and simplify all occurrences" (VB) - adds using/imports and finds all types from that namespace in the file and simplifies them
  • Create CSharpAddImportCodeRefactoringProvider in src/Features/CSharp/Portable/AddImport/
  • Create VisualBasicAddImportCodeRefactoringProvider in src/Features/VisualBasic/Portable/AddImport/
  • Add language-specific resource strings (C# uses "using", VB uses "Imports")
  • Add abstract properties for resource strings in base class for language-specific overrides
  • Add comprehensive C# tests
  • Add comprehensive VB tests (equivalent to all C# tests)

Behavior

  • Offers refactoring when cursor is on any part of a qualified type reference

  • Does not offer if using/Imports already exists for that namespace

  • Does not offer on nested types (T2 in T1.T2)

  • Does not offer inside using/Imports directives

  • First action simplifies only the invoked qualified name (plus any nested qualified names within it)

  • Second action finds and simplifies all types from the target namespace throughout the file

  • Fixes Offer to add usings for fully qualified names #51355

Original prompt

This section details on the original issue you should resolve

<issue_title>Offer to add usings for fully qualified names</issue_title>
<issue_description>It would be nice to have a codefix that offered to add usings for a fully qualified type and then simplified the name.

so if I had this:

public static async System.Threading.Tasks.Task<string> GetGoodDealsAsync()
{
	using HttpClient client = new HttpClient();
    using HttpResponseMessage response = await client.GetAsync("http://good-deals.com/");
    using HttpContent content = response.Content;
	string result = await content.ReadAsStringAsync();
	return $"Here are the good deals: {result}";
}

it would offer to do this:

using System.Threading.Tasks;

public static async Task<string> GetGoodDealsAsync()
{
	using HttpClient client = new HttpClient();
    using HttpResponseMessage response = await client.GetAsync("http://good-deals.com/");
    using HttpContent content = response.Content;
	string result = await content.ReadAsStringAsync();
	return $"Here are the good deals: {result}";
}
``` </issue_description>

<agent_instructions>Copilot, you're going to be creating a whole new feature here.  

1. this will be a refactoring.   So you will be creating a CodeRefactoringProvider. 
2. This will work for C# and VB.  But we're just going to start by making C# work. 
3. You'll need to create an `AbstractAddImportCodeRefactoringProvider` in `Microsoft.CodeAnalysis.Features`.  This can sit next to AbstractAddImportCodeFixProvider.cs
4. The refactoring will work in the following fashion.  The refactoring will start with the location the user is at, and will see if it is on some name that binds to a namespace, and has a type name accessed off of it.  Note that this may be as simple as `System.Console` or as complex as `global::System.Console` or `System.Collections.Generic.List<int>` etc.  Because this is in the shared 'Features' layer, you should use `ISyntaxFactsService` to do the analysis here so that it will work for VB/C#.
5. If you find such a namespace name, then you will offer the user a code action to `Add import and simplify type name`.
6. When this code action runs, it will do 3 things:

1. It will add the `using` to the proper scope (namespace or compilation unit).  You can use `IAddImportsService` to help with this.
2. It will then both add the `Simplifier.Annotation` annotation to the namespace (or possibly namespace+type) name that it was invoked off of.  This way the code action infrastructure will attempt to remove that superfluous name after the `using` is added.
3. It will go through the file looking for types or expressions that start with some name, and will check if that name is a type-name in the new namespace it's about to add a `using` for.  These type names may now bind differently, or become ambiguous.  So the refactoring should attempt to bind the current name, and replace it with `SyntaxGenerator.TypeExpression(typeSymbol, true/false)` so that the name is now qualified appropriately.  If the name is ambiguous, this will stay.  But if it is not ambiguous, the code action infrastructure will remove that qualification that you added.  

1. You will then need to make a C# specific subclass of your abstract code refactoring provider.  There should be almost nothing in this.  It will exist just to properly export the `[ExportCodeRefactoringProvider(...)]` with a proper name and language binding.

Finally, add a lot of tests for this.  Add a new `AddUsingCodeRefactoringTests.cs` that sits next to `AddUsingTests.cs`.  This should use the VerifyCS pattern like `using VerifyCS = CSharpCodeRefactoringVerifier<...>;` pattern.

Test all your logic extensively.  Importantly, test simplification in all the interesting places a qualified type name could show up in a type-only location (like an inheritance list, parameter type, return type, typeof, `new T`, etc.).   And all the places where it could start an expression, and validate those.

Then, we want to test extensively that the logic to qualify existing names, and have them either become unqualified (if there is no ambiguity) or stay qualified (if there is) works.</agent_instructions>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…efactoringProvider

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Copilot AI and others added 2 commits November 29, 2025 10:36
Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Copilot AI changed the title [WIP] Add code fix to offer usings for fully qualified names Add code refactoring to offer usings for fully qualified names Nov 29, 2025
Copilot AI requested a review from CyrusNajmabadi November 29, 2025 10:45
…space

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
…edback

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
CyrusNajmabadi and others added 2 commits November 29, 2025 15:27
Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
@CyrusNajmabadi
Copy link
Contributor

@copilot code and tests look good. Now implement the Visual Basic side. so you'll need the VB subclass of AbstractAddImportCodeRefactoringProvider, and you'll need equivalent VB tests for all scenarios.

@CyrusNajmabadi CyrusNajmabadi marked this pull request as ready for review November 29, 2025 14:30
@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner November 29, 2025 14:30
Copilot AI requested a review from CyrusNajmabadi November 29, 2025 14:30
@CyrusNajmabadi
Copy link
Contributor

@davidwengier if this is also a feature/fix you want.

…ring

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 29, 2025

@copilot code and tests look good. Now implement the Visual Basic side. so you'll need the VB subclass of AbstractAddImportCodeRefactoringProvider, and you'll need equivalent VB tests for all scenar...

Implemented VB support in commit 0a86c47:

  • Created VisualBasicAddImportCodeRefactoringProvider extending the abstract base class
  • Added VB-specific resources with "Imports" wording (Add 'Imports {0}')
  • Added comprehensive VB tests covering all scenarios from the C# tests (40+ test cases)

<value>Adds a project reference.</value>
</data>
<data name="Add_using_for_0" xml:space="preserve">
<value>Add 'using {0};'</value>
Copy link
Member

Choose a reason for hiding this comment

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

This is entirely subjective so feel free to ignore, but I am surprised not to see "Simplify" name used here. To me, this code action is the same as the existing simplify action, it just doesn't need an existing using directive, and I would argue the fact that they are separate actions is an implementation detail.

Copy link
Contributor

Choose a reason for hiding this comment

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

I contemplated a lot about how to fit this into the existing system and how to best present things. It's like a pseudo cross between add-import and simplify. Ultimately, it felt more like adding the import and the commensurate simplification fallout of that, vs simplifying a name by adding an import for it.

We could def play around with this.

Copy link
Contributor

Choose a reason for hiding this comment

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

it just doesn't need an existing using directive, and I would argue the fact that they are separate actions is an implementation detail.

I hear you. I was thinking about that except that there is some pretty significant difference in the outcome between the two, which makes it less of an impl detail for me. Specifically 'simplify' is always 'safe' and 'local'. When you get it offered, it just makes the obvious change, right there, for what it is trying to do. Even fix-all, only simplifies in the exact way that would be expected.

This, on the other hand, is 'add using'. And, as such, even if you're adding the 'using' for one case, it may have broad impact on the entire file, needing to now qualify other references in teh file to keep them unambiguous. This takes it out of the realm of just 'simplify' for me.

if (namespaceSymbol is null || namespaceSymbol.IsGlobalNamespace)
return;

// Check if there's already a using directive for this namespace
Copy link
Member

@davidwengier davidwengier Nov 29, 2025

Choose a reason for hiding this comment

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

Is it possible/cheaper to check for diagnostics instead? If the other simplify action isn't being offered, then by definition there must not be a using in scope, and if it is, then we don't need to offer this one?

Copy link
Contributor

Choose a reason for hiding this comment

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

I personally think that's more complex, and would require more intimate knowledge on the part of this refactoring on how teh code-analyzer works (and then calling into the analysis framework to either get all diags, or to fork an analyzer to just get those specific diags.

Easier to just actually check if hte using is already in scope :)

@CyrusNajmabadi CyrusNajmabadi merged commit c27ea19 into main Nov 29, 2025
26 checks passed
@CyrusNajmabadi CyrusNajmabadi deleted the copilot/add-usings-for-qualified-types branch November 29, 2025 23:21
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Nov 29, 2025
CyrusNajmabadi added a commit that referenced this pull request Nov 30, 2025
…81487)

Followup to #81484

Also, don't offer if it would add a using for something we have a global
using for.

Addresses something i noticed when dogfooding this.
@davidwengier davidwengier modified the milestones: Next, 18.3 Jan 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Offer to add usings for fully qualified names

3 participants