Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6646,4 +6646,46 @@ public Task TestAssemblyLevelAttribute(TestHost testHost)

[assembly: NeutralResourcesLanguage("en")]
""", testHost);

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/79462")]
public async Task TestAddUsingsWithSourceGeneratedFile(TestHost testHost)
{
const string InitialWorkspace = """
<Workspace>
<Project Language="C#" AssemblyName="Console" CommonReferences="true">
<Document FilePath="Program.cs">using Goo;

Something a;
[|PInvoke|].GetMessage();

namespace Goo
{
class Something { }
}</Document>
<DocumentFromSourceGenerator>
Copy link
Member

Choose a reason for hiding this comment

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

Is this indentation intentional?

Copy link
Member Author

Choose a reason for hiding this comment

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

no :)

namespace Win32
{
public class PInvoke
{
}
}
</DocumentFromSourceGenerator>
</Project>
</Workspace>
""";

const string ExpectedDocumentText = """
using Goo;
using Win32;

Something a;
PInvoke.GetMessage();

namespace Goo
{
class Something { }
}
""";
await TestAsync(InitialWorkspace, ExpectedDocumentText, testHost);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.FullyQualify;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.FullyQualify), Shared]
[ExtensionOrder(After = PredefinedCodeFixProviderNames.AddImport)]
internal sealed class CSharpFullyQualifyCodeFixProvider : AbstractFullyQualifyCodeFixProvider
[method: ImportingConstructor]
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
internal sealed class CSharpFullyQualifyCodeFixProvider() : AbstractFullyQualifyCodeFixProvider
{
/// <summary>
/// name does not exist in context
Expand All @@ -40,12 +42,6 @@ internal sealed class CSharpFullyQualifyCodeFixProvider : AbstractFullyQualifyCo
/// </summary>
private const string CS0308 = nameof(CS0308);

[ImportingConstructor]
[SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
public CSharpFullyQualifyCodeFixProvider()
{
}

public override ImmutableArray<string> FixableDiagnosticIds { get; } =
[CS0103, CS0104, CS0246, CS0305, CS0308, IDEDiagnosticIds.UnboundIdentifierId];
}
50 changes: 44 additions & 6 deletions src/Features/CSharpTest/FullyQualify/FullyQualifyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,9 @@
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.FullyQualify;

[Trait(Traits.Feature, Traits.Features.CodeActionsFullyQualify)]
public sealed class FullyQualifyTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor
public sealed class FullyQualifyTests(ITestOutputHelper logger)
: AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor(logger)
{
public FullyQualifyTests(ITestOutputHelper logger)
: base(logger)
{
}

internal override (DiagnosticAnalyzer?, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
=> (null, new CSharpFullyQualifyCodeFixProvider());

Expand Down Expand Up @@ -1729,4 +1725,46 @@ static void Main(string[] args)
""", new TestParameters(
options: Option(MemberDisplayOptionsStorage.HideAdvancedMembers, true),
testHost: testHost));

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/79462")]
public async Task TestFullyQualifyWithSourceGeneratedFile(TestHost testHost)
{
const string InitialWorkspace = """
<Workspace>
<Project Language="C#" AssemblyName="Console" CommonReferences="true">
<Document FilePath="Program.cs">using Goo;

Something a;
[|PInvoke|].GetMessage();

namespace Goo
{
class Something { }
}</Document>
<DocumentFromSourceGenerator>
namespace Win32
{
public class PInvoke
{
}
}
</DocumentFromSourceGenerator>
</Project>
</Workspace>
""";

const string ExpectedDocumentText = """
using Goo;

Something a;
Win32.PInvoke.GetMessage();

namespace Goo
{
class Something { }
}
""";

await TestInRegularAndScript1Async(InitialWorkspace, ExpectedDocumentText, new TestParameters(testHost: testHost));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ private sealed class MetadataSymbolsSearchScope(
public override SymbolReference CreateReference<T>(SymbolResult<T> searchResult)
{
return new MetadataSymbolReference(
provider,
Provider,
searchResult.WithSymbol<INamespaceOrTypeSymbol>(searchResult.Symbol),
_assemblyProject.Id,
_metadataReference);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public ProjectSearchScope(
public override SymbolReference CreateReference<T>(SymbolResult<T> symbol)
{
return new ProjectSymbolReference(
provider, symbol.WithSymbol<INamespaceOrTypeSymbol>(symbol.Symbol), _project);
Provider, symbol.WithSymbol<INamespaceOrTypeSymbol>(symbol.Symbol), _project);
}
}
}
75 changes: 46 additions & 29 deletions src/Features/Core/Portable/AddImport/SearchScopes/SearchScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@

using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.AddImport;
Expand All @@ -22,16 +25,11 @@ internal abstract partial class AbstractAddImportFeatureService<TSimpleNameSynta
/// for the current project we're editing we defer to the compiler to do the
/// search.
/// </summary>
private abstract class SearchScope
private abstract class SearchScope(
AbstractAddImportFeatureService<TSimpleNameSyntax> provider, bool exact)
{
public readonly bool Exact;
protected readonly AbstractAddImportFeatureService<TSimpleNameSyntax> provider;

protected SearchScope(AbstractAddImportFeatureService<TSimpleNameSyntax> provider, bool exact)
{
this.provider = provider;
Exact = exact;
}
public readonly bool Exact = exact;
protected readonly AbstractAddImportFeatureService<TSimpleNameSyntax> Provider = provider;

protected abstract Task<ImmutableArray<ISymbol>> FindDeclarationsAsync(SymbolFilter filter, SearchQuery query, CancellationToken cancellationToken);

Expand All @@ -42,35 +40,54 @@ public async Task<ImmutableArray<SymbolResult<ISymbol>>> FindDeclarationsAsync(
{
cancellationToken.ThrowIfCancellationRequested();
if (name != null && string.IsNullOrWhiteSpace(name))
{
return [];
}

using var query = Exact ? SearchQuery.Create(name, ignoreCase: true) : SearchQuery.CreateFuzzy(name);
var symbols = await FindDeclarationsAsync(filter, query, cancellationToken).ConfigureAwait(false);

if (Exact)
{
// We did an exact, case insensitive, search. Case sensitive matches should
// be preferred though over insensitive ones.
return symbols.SelectAsArray(s =>
SymbolResult.Create(s.Name, nameNode, s, weight: s.Name == name ? 0 : 1));
}
// Try finding exact matches first. This provides better results for the common case of
// people typing the right name, and it also allows for searching specialized indices that
// contain those names quickly.
{
using var query = SearchQuery.Create(name, ignoreCase: false);
var symbols = await FindDeclarationsAsync(filter, query, cancellationToken).ConfigureAwait(false);

if (symbols.Length > 0)
return symbols.SelectAsArray(static (s, nameNode) => SymbolResult.Create(s.Name, nameNode, s, weight: 0), nameNode);
}

// TODO(cyrusn): It's a shame we have to compute this twice. However, there's no
// great way to store the original value we compute because it happens deep in the
// compiler bowels when we call FindDeclarations.
using var similarityChecker = new WordSimilarityChecker(name, substringsAreSimilar: false);
// If no exact matches were found, fallback to a the weaker case insensitive search. This
// uses heuristics that can find some additional results, but with less accuracy (so not
// everything will necessarily be found).
{
using var query = SearchQuery.Create(name, ignoreCase: true);
var symbols = await FindDeclarationsAsync(filter, query, cancellationToken).ConfigureAwait(false);

var result = symbols.SelectAsArray(s =>
// Use a weight of '1' to indicate that these were case insensitive matches, and any other
// results from other search scoped should beat it.
return symbols.SelectAsArray(static (s, nameNode) => SymbolResult.Create(s.Name, nameNode, s, weight: 1), nameNode);
}
}
else
{
var areSimilar = similarityChecker.AreSimilar(s.Name, out var matchCost);
using var query = SearchQuery.CreateFuzzy(name);
var symbols = await FindDeclarationsAsync(filter, query, cancellationToken).ConfigureAwait(false);

Debug.Assert(areSimilar);
return SymbolResult.Create(s.Name, nameNode, s, matchCost);
});
// TODO(cyrusn): It's a shame we have to compute this twice. However, there's no
// great way to store the original value we compute because it happens deep in the
// compiler bowels when we call FindDeclarations.
using var similarityChecker = new WordSimilarityChecker(name, substringsAreSimilar: false);

return result;
var results = new FixedSizeArrayBuilder<SymbolResult<ISymbol>>(symbols.Length);
foreach (var symbol in symbols)
{
var areSimilar = similarityChecker.AreSimilar(symbol.Name, out var matchCost);

Debug.Assert(areSimilar);
results.Add(SymbolResult.Create(symbol.Name, nameNode, symbol, matchCost));
}

return results.MoveToImmutable();
}
}
}
}
Loading