diff --git a/src/EditorFeatures/Core/EditorFeatures.csproj b/src/EditorFeatures/Core/EditorFeatures.csproj index 4cbf8dc59eeb190a77683ca46938c0632334ca79..cf34d675ba756ad0d46cfa720ea6c62b40e42b63 100644 --- a/src/EditorFeatures/Core/EditorFeatures.csproj +++ b/src/EditorFeatures/Core/EditorFeatures.csproj @@ -283,6 +283,9 @@ + + + diff --git a/src/Features/Core/Portable/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs b/src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs similarity index 100% rename from src/Features/Core/Portable/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs rename to src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs diff --git a/src/Features/Core/Portable/CodeFixes/CodeFixService.cs b/src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.cs similarity index 96% rename from src/Features/Core/Portable/CodeFixes/CodeFixService.cs rename to src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.cs index 4e1f69487da7a8a6fd5e0aaadc4b8e21aebbda30..20112578561ccbf4c7cf6f56e301567d0f32c2f3 100644 --- a/src/Features/Core/Portable/CodeFixes/CodeFixService.cs +++ b/src/EditorFeatures/Core/Implementation/CodeFixes/CodeFixService.cs @@ -22,11 +22,12 @@ namespace Microsoft.CodeAnalysis.CodeFixes { + using Editor.Shared.Utilities; using DiagnosticId = String; using LanguageKind = String; [Export(typeof(ICodeFixService)), Shared] - internal partial class CodeFixService : ICodeFixService + internal partial class CodeFixService : ForegroundThreadAffinitizedObject, ICodeFixService { private readonly IDiagnosticAnalyzerService _diagnosticService; @@ -52,6 +53,7 @@ internal partial class CodeFixService : ICodeFixService [ImportMany]IEnumerable> loggers, [ImportMany]IEnumerable> fixers, [ImportMany]IEnumerable> suppressionProviders) + : base(assertIsForeground: false) { _errorLoggers = loggers; _diagnosticService = service; @@ -407,12 +409,28 @@ private async Task ContainsAnyFix(Document document, DiagnosticData diagno foreach (var fixer in allFixers) { await extensionManager.PerformActionAsync(fixer, () => fixer.RegisterCodeFixesAsync(context) ?? SpecializedTasks.EmptyTask).ConfigureAwait(false); - if (!fixes.Any()) + foreach (var fix in fixes) { - continue; - } + if (!fix.Action.PerformFinalApplicabilityCheck) + { + return true; + } - return true; + // Have to see if this fix is still applicable. Jump to the foreground thread + // to make that check. + var applicable = await Task.Factory.StartNew(() => + { + this.AssertIsForeground(); + return fix.Action.IsApplicable(document.Project.Solution.Workspace); + }, + cancellationToken, TaskCreationOptions.None, this.ForegroundTaskScheduler).ConfigureAwait(false); + this.AssertIsBackground(); + + if (applicable) + { + return true; + } + } } return false; diff --git a/src/Features/Core/Portable/CodeFixes/ICodeFixService.cs b/src/EditorFeatures/Core/Implementation/CodeFixes/ICodeFixService.cs similarity index 100% rename from src/Features/Core/Portable/CodeFixes/ICodeFixService.cs rename to src/EditorFeatures/Core/Implementation/CodeFixes/ICodeFixService.cs diff --git a/src/Features/Core/Portable/CodeFixes/AddImport/AbstractAddImportCodeFixProvider.SymbolReference.cs b/src/Features/Core/Portable/CodeFixes/AddImport/AbstractAddImportCodeFixProvider.SymbolReference.cs index 293bfde93cc9b3325b4d8f310b6a1f40ba5fc9eb..44314dff3d479253e5ce2d9df40f099885d6954f 100644 --- a/src/Features/Core/Portable/CodeFixes/AddImport/AbstractAddImportCodeFixProvider.SymbolReference.cs +++ b/src/Features/Core/Portable/CodeFixes/AddImport/AbstractAddImportCodeFixProvider.SymbolReference.cs @@ -51,6 +51,11 @@ public override int GetHashCode() public abstract Glyph? GetGlyph(Document document); public abstract Solution UpdateSolution(Document newDocument); + + internal virtual Func GetIsApplicableCheck(Project contextProject) + { + return null; + } } private class ProjectSymbolReference : SymbolReference @@ -86,6 +91,17 @@ public override Solution UpdateSolution(Document newDocument) return newProject.Solution; } + + internal override Func GetIsApplicableCheck(Project contextProject) + { + if (contextProject.Id == _project.Id) + { + // no need to do applicability check for a reference in our own project. + return null; + } + + return workspace => workspace.CanAddProjectReference(contextProject.Id, _project.Id); + } } private class MetadataSymbolReference : SymbolReference diff --git a/src/Features/Core/Portable/CodeFixes/AddImport/AbstractAddImportCodeFixProvider.cs b/src/Features/Core/Portable/CodeFixes/AddImport/AbstractAddImportCodeFixProvider.cs index 88645bf932f65275a7b5cc64fe8ca9c6a50ec295..9bf4ca92168aeb8e36b8de0685e0979c92ed2e23 100644 --- a/src/Features/Core/Portable/CodeFixes/AddImport/AbstractAddImportCodeFixProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/AddImport/AbstractAddImportCodeFixProvider.cs @@ -108,7 +108,8 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) if (description != null) { var action = new MyCodeAction(description, reference.GetGlyph(document), - c => this.AddImportAndReferenceAsync(node, reference, document, placeSystemNamespaceFirst, c)); + c => this.AddImportAndReferenceAsync(node, reference, document, placeSystemNamespaceFirst, c), + reference.GetIsApplicableCheck(document.Project)); context.RegisterCodeFix(action, diagnostic); } } @@ -391,14 +392,27 @@ private static bool NotNull(SymbolReference reference) private class MyCodeAction : CodeAction.SolutionChangeAction { private readonly Glyph? _glyph; + private readonly Func _isApplicable; - public MyCodeAction(string title, Glyph? glyph, Func> createChangedSolution) : + public MyCodeAction( + string title, + Glyph? glyph, + Func> createChangedSolution, + Func isApplicable = null) : base(title, createChangedSolution, equivalenceKey: title) { _glyph = glyph; + _isApplicable = isApplicable; } internal override int? Glyph => _glyph.HasValue ? (int)_glyph.Value : (int?)null; + + internal override bool PerformFinalApplicabilityCheck => _isApplicable != null; + + internal override bool IsApplicable(Workspace workspace) + { + return _isApplicable == null ? true : _isApplicable(workspace); + } } private struct SearchResult where T : ISymbol diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index dd7d734a17a2739e31c549c8fddfd35182704160..92265236f82bd8edd6ed7c96a4f0f69c5dad1b68 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -105,13 +105,10 @@ - - - diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs index e2ba9c716a4f8ee4cb484d6a1115b57d99d8cb44..6468954f0c8a687ab3eb1903313d6b72c3dad4e4 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -1047,6 +1047,66 @@ internal void OnAdditionalDocumentTextUpdatedOnDisk(DocumentId documentId) return this.ServiceProvider.GetService(typeof(TService)) as TInterface; } + internal override bool CanAddProjectReference(ProjectId referencingProject, ProjectId referencedProject) + { + _foregroundObject.AssertIsForeground(); + + IVsHierarchy referencingHierarchy; + IVsHierarchy referencedHierarchy; + if (!TryGetHierarchy(referencingProject, out referencingHierarchy) || + !TryGetHierarchy(referencedProject, out referencedHierarchy)) + { + return false; + } + + var referencingProjectFlavor3 = referencingHierarchy as IVsProjectFlavorReferences3; + var referencedProjectFlavor3 = referencedHierarchy as IVsProjectFlavorReferences3; + + if (referencingProjectFlavor3 != null && referencedProjectFlavor3 != null) + { + const int ContextFlags = (int)__VSQUERYFLAVORREFERENCESCONTEXT.VSQUERYFLAVORREFERENCESCONTEXT_RefreshReference; + + uint canAddProjectReference; + uint canBeReferenced; + string unused; + if (ErrorHandler.Failed(referencingProjectFlavor3.QueryAddProjectReferenceEx(referencedHierarchy, ContextFlags, out canAddProjectReference, out unused)) || + ErrorHandler.Failed(referencedProjectFlavor3.QueryCanBeReferencedEx(referencingHierarchy, ContextFlags, out canBeReferenced, out unused))) + { + // Something went wrong even trying to see if the reference would be allowed. + // Assume it won't be allowed. + return false; + } + + if (canAddProjectReference == (uint)__VSREFERENCEQUERYRESULT.REFERENCE_DENY || + canBeReferenced == (uint)__VSREFERENCEQUERYRESULT.REFERENCE_DENY) + { + // If either of them deny then add a project reference is not allowed. + return false; + } + + if (canAddProjectReference == (int)__VSREFERENCEQUERYRESULT.REFERENCE_ALLOW || + canBeReferenced == (int)__VSREFERENCEQUERYRESULT.REFERENCE_ALLOW) + { + // At least one allows this, and neither deny. So this is allowed. + return true; + } + + // Both are unknown if they should allow this. Fall through and use the regular + // matrix check. + } + + // Normal matrix check for projects that don't implement IVsProjectFlavorReferences3. + var referenceManager = GetVsService(); + if (referenceManager == null) + { + return false; + } + + var result = referenceManager.QueryCanReferenceProject(referencingHierarchy, referencedHierarchy); + return result == (uint)__VSREFERENCEQUERYRESULT.REFERENCE_ALLOW || + result == (uint)__VSREFERENCEQUERYRESULT.REFERENCE_UNKNOWN; + } + /// /// A trivial implementation of that just /// forwards the calls down to the underlying Workspace. diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs index 25daebcd42633ba53221167fc6d8564d39d735e6..1b1962158dea9a14af077a53f418bc788934cb48 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs @@ -255,6 +255,8 @@ protected virtual async Task PostProcessChangesAsync(Document document return document; } + internal virtual bool PerformFinalApplicabilityCheck => false; + /// /// Called by the CodeActions on the UI thread to determine if the CodeAction is still /// applicable and should be presented to the user. CodeActions can override this if they diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 5895e2c8f2d50ccaa06bc61fa07209420d1bf1ee..1e16e31ecf396f5d6d42b908b06c792763f66981 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -878,6 +878,15 @@ public virtual bool CanApplyChange(ApplyChangesKind feature) return false; } + /// + /// Returns true if a reference to referencedProject can be added to + /// referencingProject. false otherwise. + /// + internal virtual bool CanAddProjectReference(ProjectId referencingProject, ProjectId referencedProject) + { + return false; + } + /// /// Apply changes made to a solution back to the workspace. ///