diff --git a/src/EditorFeatures/Core/ExternalAccess/UnitTestGenerator/Api/WrappedAddImportFixData.cs b/src/EditorFeatures/Core/ExternalAccess/UnitTestGenerator/Api/WrappedAddImportFixData.cs index 209c4bc86d2f2..5ef99d6341f3b 100644 --- a/src/EditorFeatures/Core/ExternalAccess/UnitTestGenerator/Api/WrappedAddImportFixData.cs +++ b/src/EditorFeatures/Core/ExternalAccess/UnitTestGenerator/Api/WrappedAddImportFixData.cs @@ -23,39 +23,39 @@ internal sealed class WrappedAddImportFixData public ImmutableArray TextChanges => Underlying.TextChanges; - public string Title => Underlying.Title; + public string? Title => Underlying.Title; public ImmutableArray Tags => Underlying.Tags; #region When adding P2P references. - public ProjectId ProjectReferenceToAdd => Underlying.ProjectReferenceToAdd; + public ProjectId? ProjectReferenceToAdd => Underlying.ProjectReferenceToAdd; #endregion #region When adding a metadata reference - public ProjectId PortableExecutableReferenceProjectId => Underlying.PortableExecutableReferenceProjectId; + public ProjectId? PortableExecutableReferenceProjectId => Underlying.PortableExecutableReferenceProjectId; - public string PortableExecutableReferenceFilePathToAdd => Underlying.PortableExecutableReferenceFilePathToAdd; + public string? PortableExecutableReferenceFilePathToAdd => Underlying.PortableExecutableReferenceFilePathToAdd; #endregion #region When adding an assembly reference - public string AssemblyReferenceAssemblyName => Underlying.AssemblyReferenceAssemblyName; + public string? AssemblyReferenceAssemblyName => Underlying.AssemblyReferenceAssemblyName; - public string AssemblyReferenceFullyQualifiedTypeName => Underlying.AssemblyReferenceFullyQualifiedTypeName; + public string? AssemblyReferenceFullyQualifiedTypeName => Underlying.AssemblyReferenceFullyQualifiedTypeName; #endregion #region When adding a package reference - public string PackageSource => Underlying.PackageSource; + public string? PackageSource => Underlying.PackageSource; - public string PackageName => Underlying.PackageName; + public string? PackageName => Underlying.PackageName; - public string PackageVersionOpt => Underlying.PackageVersionOpt; + public string? PackageVersionOpt => Underlying.PackageVersionOpt; #endregion diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index cfc2183aab3b2..23bfb8aeb6fa4 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -177,27 +177,27 @@ private async Task> FindResultsAsync( { var allReferences = new ConcurrentQueue(); - // First search the current project to see if any symbols (source or metadata) match the - // search string. - await FindResultsInAllSymbolsInStartingProjectAsync( - allReferences, finder, exact, cancellationToken).ConfigureAwait(false); - - // Only bother doing this for host workspaces. We don't want this for - // things like the Interactive workspace as we can't even add project - // references to the interactive window. We could consider adding metadata - // references with #r in the future. + // First search the current project to see if any symbols (source or metadata) match the search string. + var searchOptions = finder.Options.SearchOptions; + if (searchOptions.SearchReferencedProjectSymbols) + await FindResultsInAllSymbolsInStartingProjectAsync(allReferences, finder, exact, cancellationToken).ConfigureAwait(false); + + // Only bother doing this for host workspaces. We don't want this for things like the Interactive workspace as + // we can't even add project references to the interactive window. We could consider adding metadata references + // with #r in the future. if (IsHostOrRemoteWorkspace(project)) { - // Now search unreferenced projects, and see if they have any source symbols that match - // the search string. - await FindResultsInUnreferencedProjectSourceSymbolsAsync(projectToAssembly, project, allReferences, maxResults, finder, exact, cancellationToken).ConfigureAwait(false); + // Now search unreferenced projects, and see if they have any source symbols that match the search string. + if (searchOptions.SearchUnreferencedProjectSourceSymbols) + await FindResultsInUnreferencedProjectSourceSymbolsAsync(projectToAssembly, project, allReferences, maxResults, finder, exact, cancellationToken).ConfigureAwait(false); - // Finally, check and see if we have any metadata symbols that match the search string. - await FindResultsInUnreferencedMetadataSymbolsAsync(referenceToCompilation, project, allReferences, maxResults, finder, exact, cancellationToken).ConfigureAwait(false); + // Next, check and see if we have any metadata symbols that match the search string. + if (searchOptions.SearchUnreferencedMetadataSymbols) + await FindResultsInUnreferencedMetadataSymbolsAsync(referenceToCompilation, project, allReferences, maxResults, finder, exact, cancellationToken).ConfigureAwait(false); - // We only support searching NuGet in an exact manner currently. - if (exact) - await finder.FindNugetOrReferenceAssemblyReferencesAsync(allReferences, cancellationToken).ConfigureAwait(false); + // Finally, search for nuget or reference assembly symbols that match the search string. + if (searchOptions.SearchNuGetPackages || searchOptions.SearchReferenceAssemblies) + await finder.FindNugetOrReferenceAssemblyReferencesAsync(allReferences, exact, cancellationToken).ConfigureAwait(false); } return [.. allReferences]; diff --git a/src/Features/Core/Portable/AddImport/CodeActions/AddImportCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/AddImportCodeAction.cs index 3646b545bce14..2cc32aae63e1f 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/AddImportCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/AddImportCodeAction.cs @@ -12,10 +12,9 @@ namespace Microsoft.CodeAnalysis.AddImport; /// -/// Code action we use when just adding a using, possibly with a project or -/// metadata reference. We don't use the standard code action types because -/// we want to do things like show a glyph if this will do more than just add -/// an import. +/// Code action we use when just adding a using, possibly with a project or metadata reference. We don't use the +/// standard code action types because we want to do things like show a glyph if this will do more than just add an +/// import. /// internal abstract class AddImportCodeAction : CodeAction { diff --git a/src/Features/Core/Portable/AddImport/IAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/IAddImportFeatureService.cs index fde00770f53cf..57684be593f08 100644 --- a/src/Features/Core/Portable/AddImport/IAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/IAddImportFeatureService.cs @@ -16,8 +16,8 @@ namespace Microsoft.CodeAnalysis.AddImport; internal interface IAddImportFeatureService : ILanguageService { /// - /// Gets data for how to fix a particular id within the specified Document. - /// Useful when you do not have an instance of the diagnostic, such as when invoked as a remote service. + /// Gets data for how to fix a particular id within the specified Document. Useful when + /// you do not have an instance of the diagnostic, such as when invoked as a remote service. /// Task> GetFixesAsync( Document document, TextSpan span, string diagnosticId, int maxResults, @@ -25,8 +25,8 @@ Task> GetFixesAsync( ImmutableArray packageSources, CancellationToken cancellationToken); /// - /// Gets data for how to fix a set of s within the specified Document. - /// The fix data can be used to create code actions that apply the fixes. + /// Gets data for how to fix a set of s within the specified Document. The fix data can be + /// used to create code actions that apply the fixes. /// Task Fixes)>> GetFixesForDiagnosticsAsync( Document document, TextSpan span, ImmutableArray diagnostics, int maxResultsPerDiagnostic, @@ -34,17 +34,18 @@ Task> GetFixesAsync( ImmutableArray packageSources, CancellationToken cancellationToken); /// - /// Gets code actions that, when applied, will fix the missing imports for the document using - /// the information from the provided fixes. + /// Gets code actions that, when applied, will fix the missing imports for the document using the information from + /// the provided fixes. /// ImmutableArray GetCodeActionsForFixes( Document document, ImmutableArray fixes, IPackageInstallerService? installerService, int maxResults); /// - /// Gets data for how to fix a particular id within the specified Document. - /// Similar to - /// except it only returns fix data when there is a single using fix for a given span + /// Gets data for how to fix a particular id within the specified Document. Similar to + /// except it only returns fix data when there is a single + /// using fix for a given span /// Task> GetUniqueFixesAsync( Document document, TextSpan span, ImmutableArray diagnosticIds, diff --git a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs index 7732b06817714..a49e720541646 100644 --- a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs +++ b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs @@ -38,7 +38,6 @@ private sealed partial class SymbolReferenceFinder private readonly SyntaxNode _node; private readonly ISymbolSearchService _symbolSearchService; - private readonly AddImportOptions _options; private readonly ImmutableArray _packageSources; /// @@ -46,6 +45,8 @@ private sealed partial class SymbolReferenceFinder /// private readonly bool _isWithinImport; + public readonly AddImportOptions Options; + public SymbolReferenceFinder( AbstractAddImportFeatureService owner, Document document, @@ -64,7 +65,7 @@ public SymbolReferenceFinder( _node = node; _symbolSearchService = symbolSearchService; - _options = options; + Options = options; _packageSources = packageSources; _syntaxFacts = document.GetLanguageService(); @@ -211,7 +212,7 @@ private async Task> GetReferencesForMatchingType // editor browsable rules. var accessibleTypeSymbols = typeSymbols.WhereAsArray( s => ArityAccessibilityAndAttributeContextAreCorrect(s.Symbol, arity, inAttributeContext, hasIncompleteParentMember, looksGeneric) && - s.Symbol.IsEditorBrowsable(_options.MemberDisplayOptions.HideAdvancedMembers, _semanticModel.Compilation, editorBrowserInfo)); + s.Symbol.IsEditorBrowsable(Options.MemberDisplayOptions.HideAdvancedMembers, _semanticModel.Compilation, editorBrowserInfo)); // These types may be contained within namespaces, or they may be nested // inside generic types. Record these namespaces/types if it would be diff --git a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs index 6693163cf0e2e..54208449e1404 100644 --- a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs +++ b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder_PackageAssemblySearch.cs @@ -19,8 +19,15 @@ internal abstract partial class AbstractAddImportFeatureService allReferences, CancellationToken cancellationToken) + ConcurrentQueue allReferences, bool exact, CancellationToken cancellationToken) { + var options = this.Options.SearchOptions; + Contract.ThrowIfFalse(options.SearchNuGetPackages || options.SearchReferenceAssemblies); + + // We only support searching NuGet in an exact manner currently. + if (!exact) + return; + // Only do this if none of the project or metadata searches produced any results. We always consider source // and local metadata to be better than any NuGet/assembly-reference results. if (!allReferences.IsEmpty) @@ -48,12 +55,9 @@ async Task FindWorkerAsync( NamespaceQuery namespaceQuery, bool isAttributeSearch) { - if (_options.SearchOptions.SearchReferenceAssemblies) - { - cancellationToken.ThrowIfCancellationRequested(); - await FindReferenceAssemblyReferencesAsync( - allReferences, nameNode, typeQuery, namespaceQuery, isAttributeSearch, cancellationToken).ConfigureAwait(false); - } + cancellationToken.ThrowIfCancellationRequested(); + await FindReferenceAssemblyReferencesAsync( + allReferences, nameNode, typeQuery, namespaceQuery, isAttributeSearch, cancellationToken).ConfigureAwait(false); var packageSources = PackageSourceHelper.GetPackageSources(_packageSources); foreach (var (sourceName, sourceUrl) in packageSources) @@ -125,6 +129,9 @@ private async Task FindReferenceAssemblyReferencesAsync( bool isAttributeSearch, CancellationToken cancellationToken) { + if (!this.Options.SearchOptions.SearchReferenceAssemblies) + return; + cancellationToken.ThrowIfCancellationRequested(); var results = await _symbolSearchService.FindReferenceAssembliesAsync( typeQuery, namespaceQuery, cancellationToken).ConfigureAwait(false); @@ -151,6 +158,9 @@ private async Task FindNugetReferencesAsync( bool isAttributeSearch, CancellationToken cancellationToken) { + if (!this.Options.SearchOptions.SearchNuGetPackages) + return; + cancellationToken.ThrowIfCancellationRequested(); var results = await _symbolSearchService.FindPackagesAsync( sourceName, typeQuery, namespaceQuery, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/CodeRefactorings/AddMissingImports/AbstractAddMissingImportsFeatureService.cs b/src/Features/Core/Portable/CodeRefactorings/AddMissingImports/AbstractAddMissingImportsFeatureService.cs index f6627902e84cf..da037a0908436 100644 --- a/src/Features/Core/Portable/CodeRefactorings/AddMissingImports/AbstractAddMissingImportsFeatureService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/AddMissingImports/AbstractAddMissingImportsFeatureService.cs @@ -15,7 +15,9 @@ using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.OrganizeImports; using Microsoft.CodeAnalysis.Packaging; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.SymbolSearch; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Utilities; @@ -29,16 +31,8 @@ internal abstract class AbstractAddMissingImportsFeatureService : IAddMissingImp protected abstract ImmutableArray GetFormatRules(SourceText text); - /// - public async Task AddMissingImportsAsync(Document document, TextSpan textSpan, IProgress progressTracker, CancellationToken cancellationToken) - { - var analysisResult = await AnalyzeAsync(document, textSpan, cancellationToken).ConfigureAwait(false); - return await AddMissingImportsAsync( - document, analysisResult, progressTracker, cancellationToken).ConfigureAwait(false); - } - - /// - public async Task> AnalyzeAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) + public async Task> AnalyzeAsync( + Document document, TextSpan textSpan, CancellationToken cancellationToken) { // Get the diagnostics that indicate a missing import. var addImportFeatureService = document.GetRequiredLanguageService(); @@ -49,27 +43,30 @@ public async Task> AnalyzeAsync(Document docume // Since we are not currently considering NuGet packages, pass an empty array var packageSources = ImmutableArray.Empty; + // Only search for symbols within the current project. We don't want to add any sort of reference/package to + // something outside of the starting project. var addImportOptions = await document.GetAddImportOptionsAsync( - searchOptions: new() { SearchReferenceAssemblies = true, SearchNuGetPackages = false }, + searchOptions: new() + { + SearchUnreferencedProjectSourceSymbols = false, + SearchUnreferencedMetadataSymbols = false, + SearchReferenceAssemblies = false, + SearchNuGetPackages = false, + }, cancellationToken).ConfigureAwait(false); var unambiguousFixes = await addImportFeatureService.GetUniqueFixesAsync( document, textSpan, FixableDiagnosticIds, symbolSearchService, addImportOptions, packageSources, cancellationToken).ConfigureAwait(false); + Debug.Assert(unambiguousFixes.All(d => d.Kind == AddImportFixKind.ProjectSymbol)); + // We do not want to add project or framework references without the user's input, so filter those out. - var usableFixes = unambiguousFixes.WhereAsArray(fixData => DoesNotAddReference(fixData, document.Project.Id)); + var usableFixes = unambiguousFixes.WhereAsArray(fixData => fixData.Kind == AddImportFixKind.ProjectSymbol); return usableFixes; } - private static bool DoesNotAddReference(AddImportFixData fixData, ProjectId currentProjectId) - { - return (fixData.ProjectReferenceToAdd is null || fixData.ProjectReferenceToAdd == currentProjectId) - && (fixData.PortableExecutableReferenceProjectId is null || fixData.PortableExecutableReferenceProjectId == currentProjectId) - && string.IsNullOrEmpty(fixData.AssemblyReferenceAssemblyName); - } - public async Task AddMissingImportsAsync( Document document, ImmutableArray fixes, @@ -80,7 +77,6 @@ public async Task AddMissingImportsAsync( return document; var solution = document.Project.Solution; - var textDiffingService = solution.Services.GetRequiredService(); var packageInstallerService = solution.Services.GetService(); var addImportService = document.GetRequiredLanguageService(); @@ -90,36 +86,26 @@ public async Task AddMissingImportsAsync( var organizeImportsOptions = await document.GetOrganizeImportsOptionsAsync(cancellationToken).ConfigureAwait(false); // Do not limit the results since we plan to fix all the reported issues. - var codeActions = addImportService.GetCodeActionsForFixes(document, fixes, packageInstallerService, maxResults: int.MaxValue); - var getChangesTasks = codeActions.Select( - action => GetChangesForCodeActionAsync(document, action, textDiffingService, progressTracker, cancellationToken)); + var codeActions = addImportService.GetCodeActionsForFixes( + document, fixes, packageInstallerService, maxResults: int.MaxValue); // Using Sets allows us to accumulate only the distinct changes. Only consider insertion changes to reduce the // chance of producing a badly merged final document. - var insertionOnlyChanges = new HashSet(); - - // Some fixes require adding missing references. - var allAddedProjectReferences = new HashSet(); - var allAddedMetaDataReferences = new HashSet(); - - foreach (var getChangesTask in getChangesTasks) - { - var (projectChanges, textChanges) = await getChangesTask.ConfigureAwait(false); + using var _ = PooledHashSet.GetInstance(out var insertionOnlyChanges); - foreach (var textChange in textChanges) + var changes = ProducerConsumer.RunParallelStreamAsync( + codeActions, + produceItems: static async (codeAction, callback, args, cancellationToken) => { - if (textChange.Span.IsEmpty) - insertionOnlyChanges.Add(textChange); - } - - allAddedProjectReferences.UnionWith(projectChanges.GetAddedProjectReferences()); - allAddedMetaDataReferences.UnionWith(projectChanges.GetAddedMetadataReferences()); - } + var (document, progressTracker) = args; + await GetInsertionOnlyChangesForCodeActionAsync( + document, codeAction, progressTracker, callback, cancellationToken).ConfigureAwait(false); + }, + args: (document, progressTracker), + cancellationToken); - // Apply changes to both the project and document. - var newProject = document.Project; - newProject = newProject.AddMetadataReferences(allAddedMetaDataReferences); - newProject = newProject.AddProjectReferences(allAddedProjectReferences); + await foreach (var change in changes) + insertionOnlyChanges.Add(change); // Capture each location where we are inserting imports as well as the total // length of the text we are inserting so that we can format the span afterwards. @@ -129,7 +115,7 @@ public async Task AddMissingImportsAsync( var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var newText = text.WithChanges(insertionOnlyChanges); - var newDocument = newProject.GetRequiredDocument(document.Id).WithText(newText); + var newDocument = document.WithText(newText); // When imports are added to a code file that has no previous imports, extra newlines are generated between each // import because the fix is expecting to separate the imports from the rest of the code file. We need to format @@ -156,9 +142,7 @@ private async Task CleanUpNewLinesAsync(Document document, IEnumerable // format each span individually so that we can retain each newline that was intended // to separate the import section from the other content. foreach (var insertSpan in insertSpans) - { newDocument = await CleanUpNewLinesAsync(newDocument, insertSpan, formattingOptions, cancellationToken).ConfigureAwait(false); - } return newDocument; } @@ -179,9 +163,7 @@ private async Task CleanUpNewLinesAsync(Document document, TextSpan in // If there are no changes then, do less work. if (textChanges.Count == 0) - { return document; - } // The last text change should include where the insert span ends Debug.Assert(textChanges.Last().Span.IntersectsWith(insertSpan.End)); @@ -196,18 +178,18 @@ private async Task CleanUpNewLinesAsync(Document document, TextSpan in return document.WithText(newText); } - private static async Task<(ProjectChanges, IEnumerable)> GetChangesForCodeActionAsync( + private static async ValueTask GetInsertionOnlyChangesForCodeActionAsync( Document document, CodeAction codeAction, - IDocumentTextDifferencingService textDiffingService, IProgress progressTracker, + Action callback, CancellationToken cancellationToken) { // CodeAction.GetChangedSolutionAsync is only implemented for code actions that can fully compute the new // solution without deferred computation or taking a dependency on the main thread. In other cases, the // implementation of GetChangedSolutionAsync will throw an exception and the code action application is // expected to apply the changes by executing the operations in GetOperationsAsync (which may have other - // side effects). This code cannot assume the input CodeAction supports GetChangedSolutionAsync, so it first + // side effects). This code cannot assume the input CodeAction supports GetChangedSolutionAsync, so it first // attempts to apply text changes obtained from GetOperationsAsync. Two forms are supported: // // 1. GetOperationsAsync returns an empty list of operations (i.e. no changes are required) @@ -235,11 +217,15 @@ private async Task CleanUpNewLinesAsync(Document document, TextSpan in var newDocument = newSolution.GetRequiredDocument(document.Id); // Use Line differencing to reduce the possibility of changes that overwrite existing code. + var textDiffingService = document.Project.Solution.Services.GetRequiredService(); var textChanges = await textDiffingService.GetTextChangesAsync( document, newDocument, TextDifferenceTypes.Line, cancellationToken).ConfigureAwait(false); - var projectChanges = newDocument.Project.GetChanges(document.Project); - return (projectChanges, textChanges); + foreach (var change in textChanges) + { + if (change.Span.IsEmpty) + callback(change); + } } protected sealed class CleanUpNewLinesFormatter(SourceText text) : AbstractFormattingRule diff --git a/src/Features/Core/Portable/CodeRefactorings/AddMissingImports/AbstractAddMissingImportsRefactoringProvider.cs b/src/Features/Core/Portable/CodeRefactorings/AddMissingImports/AbstractAddMissingImportsRefactoringProvider.cs index 26fed426758bd..2d8944eaa8bb1 100644 --- a/src/Features/Core/Portable/CodeRefactorings/AddMissingImports/AbstractAddMissingImportsRefactoringProvider.cs +++ b/src/Features/Core/Portable/CodeRefactorings/AddMissingImports/AbstractAddMissingImportsRefactoringProvider.cs @@ -44,8 +44,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var addImportsCodeAction = CodeAction.Create( CodeActionTitle, - async (progressTracker, cancellationToken) => - (await addMissingImportsService.AddMissingImportsAsync(document, fixData, progressTracker, cancellationToken).ConfigureAwait(false)).Project.Solution, + (progressTracker, cancellationToken) => + addMissingImportsService.AddMissingImportsAsync(document, fixData, progressTracker, cancellationToken), CodeActionTitle); context.RegisterRefactoring(addImportsCodeAction, textSpan); diff --git a/src/Features/Core/Portable/CodeRefactorings/AddMissingImports/IAddMissingImportsFeatureService.cs b/src/Features/Core/Portable/CodeRefactorings/AddMissingImports/IAddMissingImportsFeatureService.cs index f5e45b0cb9d6f..d27a91fa8707e 100644 --- a/src/Features/Core/Portable/CodeRefactorings/AddMissingImports/IAddMissingImportsFeatureService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/AddMissingImports/IAddMissingImportsFeatureService.cs @@ -15,22 +15,28 @@ namespace Microsoft.CodeAnalysis.AddMissingImports; internal interface IAddMissingImportsFeatureService : ILanguageService { /// - /// Attempts to add missing imports to the document within the textspan provided. The imports added will not add - /// assembly references to the project. In case of failure, null is returned. Failure can happen if there are - /// ambiguous imports, no known resolutions to import, or if no imports that would be provided would be added - /// without adding a reference for the project. + /// Analyzes the document inside the to determine if imports can be added. /// - Task AddMissingImportsAsync(Document document, TextSpan textSpan, IProgress progressTracker, CancellationToken cancellationToken); + Task> AnalyzeAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken); /// - /// Analyzes the document inside the texstpan to determine if imports can be added. + /// Performs the same action as but + /// with a predetermined analysis of the input instead of recalculating it. /// - Task> AnalyzeAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken); + Task AddMissingImportsAsync(Document document, ImmutableArray analysisResult, IProgress progressTracker, CancellationToken cancellationToken); +} +internal static class IAddMissingImportsFeatureServiceExtensions +{ /// - /// Performs the same action as but with a predetermined analysis of the input instead of - /// recalculating it. + /// Attempts to add missing imports to the document within the provided . The imports + /// added will not add references to the project. /// - Task AddMissingImportsAsync(Document document, ImmutableArray analysisResult, IProgress progressTracker, CancellationToken cancellationToken); + public static async Task AddMissingImportsAsync( + this IAddMissingImportsFeatureService service, Document document, TextSpan textSpan, IProgress progressTracker, CancellationToken cancellationToken) + { + var analysisResult = await service.AnalyzeAsync(document, textSpan, cancellationToken).ConfigureAwait(false); + return await service.AddMissingImportsAsync( + document, analysisResult, progressTracker, cancellationToken).ConfigureAwait(false); + } } diff --git a/src/Features/Core/Portable/SymbolSearch/SymbolSearchOptions.cs b/src/Features/Core/Portable/SymbolSearch/SymbolSearchOptions.cs index c716c388d9208..21c730c746b39 100644 --- a/src/Features/Core/Portable/SymbolSearch/SymbolSearchOptions.cs +++ b/src/Features/Core/Portable/SymbolSearch/SymbolSearchOptions.cs @@ -13,9 +13,37 @@ namespace Microsoft.CodeAnalysis.SymbolSearch; [DataContract] internal readonly record struct SymbolSearchOptions() { + /// + /// Search for symbols contained in the starting project/compilation. These are source or metadata symbols that can + /// be referenced just by adding a using/import. + /// + [DataMember] + public bool SearchReferencedProjectSymbols { get; init; } = true; + + /// + /// Search for source symbols in non-referenced projects. These are source symbols that can be referenced by adding + /// a project reference as well as a using/import. + /// + [DataMember] + public bool SearchUnreferencedProjectSourceSymbols { get; init; } = true; + + /// + /// Search for source symbols in non-referenced metadata assemblies (that are referenced by other projects). These + /// are source symbols that can be referenced by adding a metadata reference as well as a using/import. + /// + [DataMember] + public bool SearchUnreferencedMetadataSymbols { get; init; } = true; + + /// + /// Search for well known symbols in the common set of .Net reference assemblies. We have an index for these and + /// they are common enough to want to always search. + /// [DataMember] public bool SearchReferenceAssemblies { get; init; } = true; + /// + /// Search for symbols in the NuGet package index. + /// [DataMember] public bool SearchNuGetPackages { get; init; } = true; @@ -38,8 +66,30 @@ internal static class SymbolSearchOptionsStorage isEditorConfigOption: true, group: s_optionGroup); + public static PerLanguageOption2 SearchReferencedProjectSymbols = new( + "dotnet_unsupported_search_referenced_project_symbols", + SymbolSearchOptions.Default.SearchReferencedProjectSymbols, + isEditorConfigOption: true, + group: s_optionGroup); + + public static PerLanguageOption2 SearchUnreferencedProjectSourceSymbols = new( + "dotnet_unsupported_search_unreferenced_project_symbols", + SymbolSearchOptions.Default.SearchUnreferencedProjectSourceSymbols, + isEditorConfigOption: true, + group: s_optionGroup); + + public static PerLanguageOption2 SearchUnreferencedMetadataSymbols = new( + "dotnet_unsupported_search_unreferenced_metadata_symbols", + SymbolSearchOptions.Default.SearchUnreferencedMetadataSymbols, + isEditorConfigOption: true, + group: s_optionGroup); + public static readonly ImmutableArray EditorConfigOptions = [SearchReferenceAssemblies]; - public static readonly ImmutableArray UnsupportedOptions = [SearchNuGetPackages]; + public static readonly ImmutableArray UnsupportedOptions = [ + SearchNuGetPackages, + SearchReferencedProjectSymbols, + SearchUnreferencedProjectSourceSymbols, + SearchUnreferencedMetadataSymbols]; } internal static class SymbolSearchOptionsProviders @@ -48,7 +98,10 @@ internal static SymbolSearchOptions GetSymbolSearchOptions(this IOptionsReader o => new() { SearchReferenceAssemblies = options.GetOption(SymbolSearchOptionsStorage.SearchReferenceAssemblies, language), - SearchNuGetPackages = options.GetOption(SymbolSearchOptionsStorage.SearchNuGetPackages, language) + SearchNuGetPackages = options.GetOption(SymbolSearchOptionsStorage.SearchNuGetPackages, language), + SearchReferencedProjectSymbols = options.GetOption(SymbolSearchOptionsStorage.SearchReferencedProjectSymbols, language), + SearchUnreferencedProjectSourceSymbols = options.GetOption(SymbolSearchOptionsStorage.SearchUnreferencedProjectSourceSymbols, language), + SearchUnreferencedMetadataSymbols = options.GetOption(SymbolSearchOptionsStorage.SearchUnreferencedMetadataSymbols, language), }; public static async ValueTask GetSymbolSearchOptionsAsync(this Document document, CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index aa3fe604e34b9..80950f0d3dd13 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -400,6 +400,9 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"visual_studio_enable_symbol_search", new LocalUserProfileStorage(@"Roslyn\Features\SymbolSearch", "Enabled")}, {"dotnet_unsupported_search_nuget_packages", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.SuggestForTypesInNuGetPackages")}, {"dotnet_search_reference_assemblies", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.SuggestForTypesInReferenceAssemblies")}, + {"dotnet_unsupported_search_referenced_project_symbols", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.SearchReferencedProjectSymbols")}, + {"dotnet_unsupported_search_unreferenced_project_symbols", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.SearchUnreferencedProjectSymbols")}, + {"dotnet_unsupported_search_unreferenced_metadata_symbols", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.SearchUnreferencedMetadataSymbols")}, #pragma warning disable CS0612 // Type or member is obsolete {"tab_width", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Tab Size", "TextEditor.Basic.Tab Size")}, #pragma warning restore