From 7f5fb7b576c63d8aae1ff3b24680d596a63281c0 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Thu, 30 Mar 2017 22:48:34 +0100 Subject: [PATCH 01/15] Added "You must add a reference to 'foo'" code fixer --- .../MissingReferenceCodeFixProvider.fs | 60 +++++++++++++++++++ .../src/FSharp.Editor/FSharp.Editor.fsproj | 1 + 2 files changed, 61 insertions(+) create mode 100644 vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs new file mode 100644 index 00000000000..ed1137d59b8 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace rec Microsoft.VisualStudio.FSharp.Editor + +open System +open System.Composition +open System.Collections.Immutable +open System.Threading +open System.Threading.Tasks + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.CodeFixes +open Microsoft.CodeAnalysis.CodeActions + +[] +type internal MissingReferenceCodeFixProvider() = + inherit CodeFixProvider() + let fixableDiagnosticId = "FS0074" + + let createCodeFix (title: string, context: CodeFixContext, addReference: Project) = + CodeAction.Create( + title, + (fun (cancellationToken: CancellationToken) -> + async { + let project = context.Document.Project + let solution = project.Solution + let references = project.AllProjectReferences + let newReferences = references |> Seq.append [ProjectReference(addReference.Id)] + return solution.WithProjectReferences(project.Id, newReferences) + } |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken) + ), + title) + + override __.FixableDiagnosticIds = Seq.toImmutableArray [fixableDiagnosticId] + + override __.RegisterCodeFixesAsync context : Task = + async { + let solution = context.Document.Project.Solution + + context.Diagnostics + |> Seq.filter (fun x -> x.Id = fixableDiagnosticId) + |> Seq.iter (fun diagnostic -> + let message = diagnostic.GetMessage() + let parts = message.Split([| '\'' |], StringSplitOptions.None) + + let assembly = parts.[3] + + match solution.Projects |> Seq.tryFind (fun project -> project.AssemblyName = assembly) with + | Some addReference -> + let codefix = + createCodeFix( + sprintf "Add a project reference to '%s'" addReference.Name, // TODO: localise + context, + addReference) + + context.RegisterCodeFix (codefix, ImmutableArray.Create diagnostic) + | None -> + () + ) + } |> CommonRoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index a4b26bdc1ea..bdecd30ab6f 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -78,6 +78,7 @@ + From 95e1ec3308ed6e69dd9e5372cf0e0edf9037f3c8 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Thu, 30 Mar 2017 23:20:03 +0100 Subject: [PATCH 02/15] Preliminary work to enable metadata reference code fixing --- .../MissingReferenceCodeFixProvider.fs | 75 ++++++++++++++----- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs index ed1137d59b8..f97095347d1 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs @@ -7,27 +7,42 @@ open System.Composition open System.Collections.Immutable open System.Threading open System.Threading.Tasks +open System.IO open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeActions +type private ReferenceType = +| AddProjectRef of ProjectReference +| AddMetadataRef of MetadataReference + [] type internal MissingReferenceCodeFixProvider() = inherit CodeFixProvider() + let fixableDiagnosticId = "FS0074" - let createCodeFix (title: string, context: CodeFixContext, addReference: Project) = + let createCodeFix (title: string, context: CodeFixContext, addReference: ReferenceType) = CodeAction.Create( title, (fun (cancellationToken: CancellationToken) -> async { let project = context.Document.Project let solution = project.Solution - let references = project.AllProjectReferences - let newReferences = references |> Seq.append [ProjectReference(addReference.Id)] - return solution.WithProjectReferences(project.Id, newReferences) - } |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken) + + match addReference with + | AddProjectRef projectRef -> + let references = project.AllProjectReferences + let newReferences = references |> Seq.append [projectRef] + return solution.WithProjectReferences(project.Id, newReferences) + + | AddMetadataRef metadataRef -> + let references = project.MetadataReferences + let newReferences = references |> Seq.append [metadataRef] + return solution.WithProjectMetadataReferences(project.Id, newReferences) + } + |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken) ), title) @@ -43,18 +58,42 @@ type internal MissingReferenceCodeFixProvider() = let message = diagnostic.GetMessage() let parts = message.Split([| '\'' |], StringSplitOptions.None) - let assembly = parts.[3] + match parts with + | [| _; _type; _; assemblyName; _ |] -> + + let exactProjectMatches = solution.Projects |> Seq.tryFind (fun project -> project.AssemblyName = assemblyName) - match solution.Projects |> Seq.tryFind (fun project -> project.AssemblyName = assembly) with - | Some addReference -> - let codefix = - createCodeFix( - sprintf "Add a project reference to '%s'" addReference.Name, // TODO: localise - context, - addReference) - - context.RegisterCodeFix (codefix, ImmutableArray.Create diagnostic) - | None -> - () + match exactProjectMatches with + | Some refProject -> + let codefix = + createCodeFix( + sprintf "Add a project reference to '%s'" refProject.Name, // TODO: localise + context, + AddProjectRef (ProjectReference refProject.Id) + ) + + context.RegisterCodeFix (codefix, ImmutableArray.Create diagnostic) + | None -> + let metadataReferences = + solution.Projects + |> Seq.collect (fun project -> project.MetadataReferences) + |> Seq.tryFind (fun ref -> + Path.GetFileNameWithoutExtension(ref.Display) = assemblyName + ) + + match metadataReferences with + | Some metadataRef -> + let codefix = + createCodeFix( + sprintf "Add an assembly reference to '%s'" metadataRef.Display, // TODO: localise + context, + AddMetadataRef metadataRef + ) + + context.RegisterCodeFix (codefix, ImmutableArray.Create diagnostic) + | None -> + () + | _ -> () ) - } |> CommonRoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + } + |> CommonRoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) From 8228545390618806c31cf48e2caab263f26fa00d Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Mon, 3 Apr 2017 20:31:40 +0100 Subject: [PATCH 03/15] Fix SetupProjectFile passing in the wrong IVsHierarchy to CreateProjectContext --- .../src/FSharp.Editor/Common/LanguageService.fs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/vsintegration/src/FSharp.Editor/Common/LanguageService.fs b/vsintegration/src/FSharp.Editor/Common/LanguageService.fs index 479a7031bb2..426bf2b97d4 100644 --- a/vsintegration/src/FSharp.Editor/Common/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/Common/LanguageService.fs @@ -10,6 +10,7 @@ open System.Collections.Generic open System.ComponentModel.Composition open System.Runtime.InteropServices open System.IO +open System.Diagnostics open Microsoft.FSharp.Compiler.CompileOps open Microsoft.FSharp.Compiler.SourceCodeServices @@ -329,9 +330,19 @@ and let projectContextFactory = package.ComponentModel.GetService(); let errorReporter = ProjectExternalErrorReporter(projectId, "FS", this.SystemServiceProvider) + let hierarchy = + site.ProjectProvider + |> Option.map (fun p -> p :?> IVsHierarchy) + |> Option.toObj + + // Roslyn is expecting site to be an IVsHierarchy. + // It just so happens that the object that implements IProvideProjectSite is also + // an IVsHierarchy. This assertion is to ensure that the assumption holds true. + Debug.Assert(hierarchy <> null, "About to CreateProjectContext with a non-hierarchy site") + let projectContext = projectContextFactory.CreateProjectContext( - FSharpCommonConstants.FSharpLanguageName, projectDisplayName, projectFileName, projectGuid, siteProvider, null, errorReporter) + FSharpCommonConstants.FSharpLanguageName, projectDisplayName, projectFileName, projectGuid, hierarchy, null, errorReporter) let project = projectContext :?> AbstractProject From 957ef1ea8407d9ea72f5d1d7410637fea1b64df4 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Mon, 3 Apr 2017 21:00:25 +0100 Subject: [PATCH 04/15] Localise code fix strings, add support for metadata (assembly) references --- .../CodeFix/MissingReferenceCodeFixProvider.fs | 4 ++-- .../FSharp.Editor/Common/LanguageService.fs | 15 ++++++++++++--- .../src/FSharp.Editor/FSharp.Editor.resx | 6 ++++++ .../src/FSharp.Editor/srFSharp.Editor.fs | 2 ++ .../src/FSharp.LanguageService/IProjectSite.fs | 2 ++ .../ProjectSitesAndFiles.fs | 2 ++ .../src/FSharp.ProjectSystem.FSharp/Project.fs | 18 ++++++++++++++++++ vsintegration/tests/Salsa/salsa.fs | 1 + 8 files changed, 45 insertions(+), 5 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs index f97095347d1..85d5f3cc6ff 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs @@ -67,7 +67,7 @@ type internal MissingReferenceCodeFixProvider() = | Some refProject -> let codefix = createCodeFix( - sprintf "Add a project reference to '%s'" refProject.Name, // TODO: localise + String.Format(SR.AddProjectReference.Value, refProject.Name), context, AddProjectRef (ProjectReference refProject.Id) ) @@ -85,7 +85,7 @@ type internal MissingReferenceCodeFixProvider() = | Some metadataRef -> let codefix = createCodeFix( - sprintf "Add an assembly reference to '%s'" metadataRef.Display, // TODO: localise + String.Format(SR.AddAssemblyReference.Value, assemblyName), context, AddMetadataRef metadataRef ) diff --git a/vsintegration/src/FSharp.Editor/Common/LanguageService.fs b/vsintegration/src/FSharp.Editor/Common/LanguageService.fs index 426bf2b97d4..363adca9969 100644 --- a/vsintegration/src/FSharp.Editor/Common/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/Common/LanguageService.fs @@ -301,10 +301,9 @@ and let hashSetIgnoreCase x = new HashSet(x, StringComparer.OrdinalIgnoreCase) let updatedFiles = site.SourceFilesOnDisk() |> hashSetIgnoreCase let workspaceFiles = project.GetCurrentDocuments() |> Seq.map(fun file -> file.FilePath) |> hashSetIgnoreCase - - // If syncing project upon some reference changes, we don't have a mechanism to recognize which references have been added/removed. - // Hence, the current solution is to force update current project options. + let mutable updated = forceUpdate + for file in updatedFiles do if not(workspaceFiles.Contains(file)) then projectContext.AddSourceFile(file) @@ -313,6 +312,16 @@ and if not(updatedFiles.Contains(file)) then projectContext.RemoveSourceFile(file) updated <- true + + let updatedRefs = site.AssemblyReferences() |> hashSetIgnoreCase + let workspaceRefs = project.GetCurrentMetadataReferences() |> Seq.map(fun ref -> ref.FilePath) |> hashSetIgnoreCase + + for ref in updatedRefs do + if not(workspaceRefs.Contains(ref)) then + projectContext.AddMetadataReference(ref, MetadataReferenceProperties.Assembly) + for ref in workspaceRefs do + if not(workspaceRefs.Contains(ref)) then + projectContext.RemoveMetadataReference(ref) // update the cached options if updated then diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx index 3b90215e1b1..8891c79eb20 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx @@ -171,4 +171,10 @@ Show completion list after a character is typed + + Add an assembly reference to '{0}' + + + Add a project reference to '{0}' + \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/srFSharp.Editor.fs b/vsintegration/src/FSharp.Editor/srFSharp.Editor.fs index 34372f9e576..d3519acd263 100644 --- a/vsintegration/src/FSharp.Editor/srFSharp.Editor.fs +++ b/vsintegration/src/FSharp.Editor/srFSharp.Editor.fs @@ -31,6 +31,8 @@ module SR = let FSharpDisposablesClassificationType = lazy (GetString "FSharpDisposablesClassificationType") let RemoveUnusedOpens = lazy (GetString "RemoveUnusedOpens") let UnusedOpens = lazy (GetString "UnusedOpens") + let AddProjectReference = lazy (GetString "AddProjectReference") + let AddAssemblyReference = lazy (GetString "AddAssemblyReference") [] let IntelliSensePropertyPageMiscCategory = "IntelliSensePropertyPageMiscCategory" diff --git a/vsintegration/src/FSharp.LanguageService/IProjectSite.fs b/vsintegration/src/FSharp.LanguageService/IProjectSite.fs index 2d9342f823f..3bc1ad88bae 100644 --- a/vsintegration/src/FSharp.LanguageService/IProjectSite.fs +++ b/vsintegration/src/FSharp.LanguageService/IProjectSite.fs @@ -57,3 +57,5 @@ and internal IProjectSite = abstract LoadTime : System.DateTime abstract ProjectProvider : IProvideProjectSite option + + abstract AssemblyReferences : unit -> string [] diff --git a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs index 5ae4ff77e30..f01f66d419b 100644 --- a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs +++ b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs @@ -34,6 +34,7 @@ type private ProjectSiteOfScriptFile(filename:string, checkOptions : FSharpProje override this.ProjectGuid = "" override this.LoadTime = checkOptions.LoadTime override this.ProjectProvider = None + override this.AssemblyReferences() = [||] interface IHaveCheckOptions with override this.OriginalCheckOptions() = checkOptions @@ -68,6 +69,7 @@ type private ProjectSiteOfSingleFile(sourceFile) = override this.ProjectGuid = "" override this.LoadTime = new DateTime(2000,1,1) // any constant time is fine, orphan files do not interact with reloading based on update time override this.ProjectProvider = None + override this.AssemblyReferences() = [||] /// Information about projects, open files and other active artifacts in visual studio. /// Keeps track of the relationship between IVsTextLines buffers, IFSharpSource objects, IProjectSite objects and FSharpProjectOptions diff --git a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs index caaff45807b..245d5d6e13b 100644 --- a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs +++ b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs @@ -101,6 +101,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem member ips.IsIncompleteTypeCheckEnvironment = false member ips.LoadTime = inner.LoadTime member ips.ProjectProvider = inner.ProjectProvider + member ips.AssemblyReferences() = inner.AssemblyReferences() type internal ProjectSiteOptionLifetimeState = | Opening=1 // The project has been opened, but has not yet called Compile() to compute sources/flags @@ -1494,6 +1495,14 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem member this.ProjectGuid = x.GetProjectGuid() member this.LoadTime = creationTime member this.ProjectProvider = Some (x :> IProvideProjectSite) + member this.AssemblyReferences() = + x.GetReferenceContainer().EnumReferences() + |> Seq.choose ( + function + | :? AssemblyReferenceNode as arn -> Some arn.Url + | _ -> None + ) + |> Array.ofSeq } // Snapshot-capture relevent values from "this", and returns an IProjectSite @@ -1510,6 +1519,14 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem let taskReporter = Some(x.TaskReporter) let targetFrameworkMoniker = x.GetTargetFrameworkMoniker() let creationTime = System.DateTime.Now + let assemblyReferences = + x.GetReferenceContainer().EnumReferences() + |> Seq.choose ( + function + | :? AssemblyReferenceNode as arn -> Some arn.Url + | _ -> None + ) + |> Array.ofSeq // This object is thread-safe { new Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite with member ips.SourceFilesOnDisk() = compileItems @@ -1526,6 +1543,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem member this.ProjectGuid = x.GetProjectGuid() member this.LoadTime = creationTime member this.ProjectProvider = Some (x :> IProvideProjectSite) + member this.AssemblyReferences() = assemblyReferences } // let the language service ask us questions diff --git a/vsintegration/tests/Salsa/salsa.fs b/vsintegration/tests/Salsa/salsa.fs index 420bb339a39..cf7f9462dd7 100644 --- a/vsintegration/tests/Salsa/salsa.fs +++ b/vsintegration/tests/Salsa/salsa.fs @@ -297,6 +297,7 @@ module internal Salsa = let projectObj, projectObjFlags = MSBuild.CrackProject(projectfile, configurationFunc(), platformFunc()) projectObj.GetProperty(ProjectFileConstants.ProjectGuid).EvaluatedValue member this.ProjectProvider = None + member this.AssemblyReferences() = [||] // Attempt to treat as MSBuild project. let internal NewMSBuildProjectSite(configurationFunc, platformFunc, msBuildProjectName) = From 91c2cebe48d5c6ef29de13db5be99f4153c666b1 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Mon, 3 Apr 2017 21:05:39 +0100 Subject: [PATCH 05/15] Fix removing references not being reflected in the Roslyn workspace --- vsintegration/src/FSharp.Editor/Common/LanguageService.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsintegration/src/FSharp.Editor/Common/LanguageService.fs b/vsintegration/src/FSharp.Editor/Common/LanguageService.fs index 363adca9969..f620183534c 100644 --- a/vsintegration/src/FSharp.Editor/Common/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/Common/LanguageService.fs @@ -320,7 +320,7 @@ and if not(workspaceRefs.Contains(ref)) then projectContext.AddMetadataReference(ref, MetadataReferenceProperties.Assembly) for ref in workspaceRefs do - if not(workspaceRefs.Contains(ref)) then + if not(updatedRefs.Contains(ref)) then projectContext.RemoveMetadataReference(ref) // update the cached options From 5cd8748cacbc561ed72f0e9e0559567721653fa4 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Thu, 30 Mar 2017 22:48:34 +0100 Subject: [PATCH 06/15] Added "You must add a reference to 'foo'" code fixer --- .../MissingReferenceCodeFixProvider.fs | 60 +++++++++++++++++++ .../src/FSharp.Editor/FSharp.Editor.fsproj | 1 + 2 files changed, 61 insertions(+) create mode 100644 vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs new file mode 100644 index 00000000000..ed1137d59b8 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace rec Microsoft.VisualStudio.FSharp.Editor + +open System +open System.Composition +open System.Collections.Immutable +open System.Threading +open System.Threading.Tasks + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.CodeFixes +open Microsoft.CodeAnalysis.CodeActions + +[] +type internal MissingReferenceCodeFixProvider() = + inherit CodeFixProvider() + let fixableDiagnosticId = "FS0074" + + let createCodeFix (title: string, context: CodeFixContext, addReference: Project) = + CodeAction.Create( + title, + (fun (cancellationToken: CancellationToken) -> + async { + let project = context.Document.Project + let solution = project.Solution + let references = project.AllProjectReferences + let newReferences = references |> Seq.append [ProjectReference(addReference.Id)] + return solution.WithProjectReferences(project.Id, newReferences) + } |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken) + ), + title) + + override __.FixableDiagnosticIds = Seq.toImmutableArray [fixableDiagnosticId] + + override __.RegisterCodeFixesAsync context : Task = + async { + let solution = context.Document.Project.Solution + + context.Diagnostics + |> Seq.filter (fun x -> x.Id = fixableDiagnosticId) + |> Seq.iter (fun diagnostic -> + let message = diagnostic.GetMessage() + let parts = message.Split([| '\'' |], StringSplitOptions.None) + + let assembly = parts.[3] + + match solution.Projects |> Seq.tryFind (fun project -> project.AssemblyName = assembly) with + | Some addReference -> + let codefix = + createCodeFix( + sprintf "Add a project reference to '%s'" addReference.Name, // TODO: localise + context, + addReference) + + context.RegisterCodeFix (codefix, ImmutableArray.Create diagnostic) + | None -> + () + ) + } |> CommonRoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index b86cce018ed..387e254a17b 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -83,6 +83,7 @@ + From b2d36fdd3d8e86dd66a1956b091b12cd267e2e1b Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Thu, 30 Mar 2017 23:20:03 +0100 Subject: [PATCH 07/15] Preliminary work to enable metadata reference code fixing --- .../MissingReferenceCodeFixProvider.fs | 75 ++++++++++++++----- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs index ed1137d59b8..f97095347d1 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs @@ -7,27 +7,42 @@ open System.Composition open System.Collections.Immutable open System.Threading open System.Threading.Tasks +open System.IO open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeActions +type private ReferenceType = +| AddProjectRef of ProjectReference +| AddMetadataRef of MetadataReference + [] type internal MissingReferenceCodeFixProvider() = inherit CodeFixProvider() + let fixableDiagnosticId = "FS0074" - let createCodeFix (title: string, context: CodeFixContext, addReference: Project) = + let createCodeFix (title: string, context: CodeFixContext, addReference: ReferenceType) = CodeAction.Create( title, (fun (cancellationToken: CancellationToken) -> async { let project = context.Document.Project let solution = project.Solution - let references = project.AllProjectReferences - let newReferences = references |> Seq.append [ProjectReference(addReference.Id)] - return solution.WithProjectReferences(project.Id, newReferences) - } |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken) + + match addReference with + | AddProjectRef projectRef -> + let references = project.AllProjectReferences + let newReferences = references |> Seq.append [projectRef] + return solution.WithProjectReferences(project.Id, newReferences) + + | AddMetadataRef metadataRef -> + let references = project.MetadataReferences + let newReferences = references |> Seq.append [metadataRef] + return solution.WithProjectMetadataReferences(project.Id, newReferences) + } + |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken) ), title) @@ -43,18 +58,42 @@ type internal MissingReferenceCodeFixProvider() = let message = diagnostic.GetMessage() let parts = message.Split([| '\'' |], StringSplitOptions.None) - let assembly = parts.[3] + match parts with + | [| _; _type; _; assemblyName; _ |] -> + + let exactProjectMatches = solution.Projects |> Seq.tryFind (fun project -> project.AssemblyName = assemblyName) - match solution.Projects |> Seq.tryFind (fun project -> project.AssemblyName = assembly) with - | Some addReference -> - let codefix = - createCodeFix( - sprintf "Add a project reference to '%s'" addReference.Name, // TODO: localise - context, - addReference) - - context.RegisterCodeFix (codefix, ImmutableArray.Create diagnostic) - | None -> - () + match exactProjectMatches with + | Some refProject -> + let codefix = + createCodeFix( + sprintf "Add a project reference to '%s'" refProject.Name, // TODO: localise + context, + AddProjectRef (ProjectReference refProject.Id) + ) + + context.RegisterCodeFix (codefix, ImmutableArray.Create diagnostic) + | None -> + let metadataReferences = + solution.Projects + |> Seq.collect (fun project -> project.MetadataReferences) + |> Seq.tryFind (fun ref -> + Path.GetFileNameWithoutExtension(ref.Display) = assemblyName + ) + + match metadataReferences with + | Some metadataRef -> + let codefix = + createCodeFix( + sprintf "Add an assembly reference to '%s'" metadataRef.Display, // TODO: localise + context, + AddMetadataRef metadataRef + ) + + context.RegisterCodeFix (codefix, ImmutableArray.Create diagnostic) + | None -> + () + | _ -> () ) - } |> CommonRoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + } + |> CommonRoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) From 4f4b96a1b8831a415abd2669eaf0317a3d1356e1 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Mon, 3 Apr 2017 21:13:38 +0100 Subject: [PATCH 08/15] Rebased onto master --- .../LanguageService/LanguageService.fs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index d92b09a1d11..f9cf108fbbc 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -10,6 +10,7 @@ open System.Collections.Generic open System.ComponentModel.Composition open System.Runtime.InteropServices open System.IO +open System.Diagnostics open Microsoft.FSharp.Compiler.CompileOps open Microsoft.FSharp.Compiler.SourceCodeServices @@ -329,9 +330,19 @@ and let projectContextFactory = package.ComponentModel.GetService(); let errorReporter = ProjectExternalErrorReporter(projectId, "FS", this.SystemServiceProvider) + let hierarchy = + site.ProjectProvider + |> Option.map (fun p -> p :?> IVsHierarchy) + |> Option.toObj + + // Roslyn is expecting site to be an IVsHierarchy. + // It just so happens that the object that implements IProvideProjectSite is also + // an IVsHierarchy. This assertion is to ensure that the assumption holds true. + Debug.Assert(hierarchy <> null, "About to CreateProjectContext with a non-hierarchy site") + let projectContext = projectContextFactory.CreateProjectContext( - FSharpConstants.FSharpLanguageName, projectDisplayName, projectFileName, projectGuid, siteProvider, null, errorReporter) + FSharpConstants.FSharpLanguageName, projectDisplayName, projectFileName, projectGuid, hierarchy, null, errorReporter) let project = projectContext :?> AbstractProject From e82a3110677034c11085c4c027905f711391e273 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Mon, 3 Apr 2017 21:00:25 +0100 Subject: [PATCH 09/15] Localise code fix strings, add support for metadata (assembly) references --- .../CodeFix/MissingReferenceCodeFixProvider.fs | 4 ++-- .../src/FSharp.Editor/FSharp.Editor.resx | 6 ++++++ .../LanguageService/LanguageService.fs | 15 ++++++++++++--- .../src/FSharp.Editor/srFSharp.Editor.fs | 2 ++ .../src/FSharp.LanguageService/IProjectSite.fs | 2 ++ .../ProjectSitesAndFiles.fs | 2 ++ .../src/FSharp.ProjectSystem.FSharp/Project.fs | 18 ++++++++++++++++++ vsintegration/tests/Salsa/salsa.fs | 1 + 8 files changed, 45 insertions(+), 5 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs index f97095347d1..85d5f3cc6ff 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs @@ -67,7 +67,7 @@ type internal MissingReferenceCodeFixProvider() = | Some refProject -> let codefix = createCodeFix( - sprintf "Add a project reference to '%s'" refProject.Name, // TODO: localise + String.Format(SR.AddProjectReference.Value, refProject.Name), context, AddProjectRef (ProjectReference refProject.Id) ) @@ -85,7 +85,7 @@ type internal MissingReferenceCodeFixProvider() = | Some metadataRef -> let codefix = createCodeFix( - sprintf "Add an assembly reference to '%s'" metadataRef.Display, // TODO: localise + String.Format(SR.AddAssemblyReference.Value, assemblyName), context, AddMetadataRef metadataRef ) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx index 3b90215e1b1..8891c79eb20 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx @@ -171,4 +171,10 @@ Show completion list after a character is typed + + Add an assembly reference to '{0}' + + + Add a project reference to '{0}' + \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index f9cf108fbbc..c857ce8c9d4 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -301,10 +301,9 @@ and let hashSetIgnoreCase x = new HashSet(x, StringComparer.OrdinalIgnoreCase) let updatedFiles = site.SourceFilesOnDisk() |> hashSetIgnoreCase let workspaceFiles = project.GetCurrentDocuments() |> Seq.map(fun file -> file.FilePath) |> hashSetIgnoreCase - - // If syncing project upon some reference changes, we don't have a mechanism to recognize which references have been added/removed. - // Hence, the current solution is to force update current project options. + let mutable updated = forceUpdate + for file in updatedFiles do if not(workspaceFiles.Contains(file)) then projectContext.AddSourceFile(file) @@ -313,6 +312,16 @@ and if not(updatedFiles.Contains(file)) then projectContext.RemoveSourceFile(file) updated <- true + + let updatedRefs = site.AssemblyReferences() |> hashSetIgnoreCase + let workspaceRefs = project.GetCurrentMetadataReferences() |> Seq.map(fun ref -> ref.FilePath) |> hashSetIgnoreCase + + for ref in updatedRefs do + if not(workspaceRefs.Contains(ref)) then + projectContext.AddMetadataReference(ref, MetadataReferenceProperties.Assembly) + for ref in workspaceRefs do + if not(workspaceRefs.Contains(ref)) then + projectContext.RemoveMetadataReference(ref) // update the cached options if updated then diff --git a/vsintegration/src/FSharp.Editor/srFSharp.Editor.fs b/vsintegration/src/FSharp.Editor/srFSharp.Editor.fs index 34372f9e576..d3519acd263 100644 --- a/vsintegration/src/FSharp.Editor/srFSharp.Editor.fs +++ b/vsintegration/src/FSharp.Editor/srFSharp.Editor.fs @@ -31,6 +31,8 @@ module SR = let FSharpDisposablesClassificationType = lazy (GetString "FSharpDisposablesClassificationType") let RemoveUnusedOpens = lazy (GetString "RemoveUnusedOpens") let UnusedOpens = lazy (GetString "UnusedOpens") + let AddProjectReference = lazy (GetString "AddProjectReference") + let AddAssemblyReference = lazy (GetString "AddAssemblyReference") [] let IntelliSensePropertyPageMiscCategory = "IntelliSensePropertyPageMiscCategory" diff --git a/vsintegration/src/FSharp.LanguageService/IProjectSite.fs b/vsintegration/src/FSharp.LanguageService/IProjectSite.fs index 2d9342f823f..3bc1ad88bae 100644 --- a/vsintegration/src/FSharp.LanguageService/IProjectSite.fs +++ b/vsintegration/src/FSharp.LanguageService/IProjectSite.fs @@ -57,3 +57,5 @@ and internal IProjectSite = abstract LoadTime : System.DateTime abstract ProjectProvider : IProvideProjectSite option + + abstract AssemblyReferences : unit -> string [] diff --git a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs index 5ae4ff77e30..f01f66d419b 100644 --- a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs +++ b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs @@ -34,6 +34,7 @@ type private ProjectSiteOfScriptFile(filename:string, checkOptions : FSharpProje override this.ProjectGuid = "" override this.LoadTime = checkOptions.LoadTime override this.ProjectProvider = None + override this.AssemblyReferences() = [||] interface IHaveCheckOptions with override this.OriginalCheckOptions() = checkOptions @@ -68,6 +69,7 @@ type private ProjectSiteOfSingleFile(sourceFile) = override this.ProjectGuid = "" override this.LoadTime = new DateTime(2000,1,1) // any constant time is fine, orphan files do not interact with reloading based on update time override this.ProjectProvider = None + override this.AssemblyReferences() = [||] /// Information about projects, open files and other active artifacts in visual studio. /// Keeps track of the relationship between IVsTextLines buffers, IFSharpSource objects, IProjectSite objects and FSharpProjectOptions diff --git a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs index 8bf634c189f..a6e6390339f 100644 --- a/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs +++ b/vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs @@ -101,6 +101,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem member ips.IsIncompleteTypeCheckEnvironment = false member ips.LoadTime = inner.LoadTime member ips.ProjectProvider = inner.ProjectProvider + member ips.AssemblyReferences() = inner.AssemblyReferences() type internal ProjectSiteOptionLifetimeState = | Opening=1 // The project has been opened, but has not yet called Compile() to compute sources/flags @@ -1494,6 +1495,14 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem member this.ProjectGuid = x.GetProjectGuid() member this.LoadTime = creationTime member this.ProjectProvider = Some (x :> IProvideProjectSite) + member this.AssemblyReferences() = + x.GetReferenceContainer().EnumReferences() + |> Seq.choose ( + function + | :? AssemblyReferenceNode as arn -> Some arn.Url + | _ -> None + ) + |> Array.ofSeq } // Snapshot-capture relevent values from "this", and returns an IProjectSite @@ -1510,6 +1519,14 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem let taskReporter = Some(x.TaskReporter) let targetFrameworkMoniker = x.GetTargetFrameworkMoniker() let creationTime = System.DateTime.Now + let assemblyReferences = + x.GetReferenceContainer().EnumReferences() + |> Seq.choose ( + function + | :? AssemblyReferenceNode as arn -> Some arn.Url + | _ -> None + ) + |> Array.ofSeq // This object is thread-safe { new Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite with member ips.SourceFilesOnDisk() = compileItems @@ -1526,6 +1543,7 @@ namespace rec Microsoft.VisualStudio.FSharp.ProjectSystem member this.ProjectGuid = x.GetProjectGuid() member this.LoadTime = creationTime member this.ProjectProvider = Some (x :> IProvideProjectSite) + member this.AssemblyReferences() = assemblyReferences } // let the language service ask us questions diff --git a/vsintegration/tests/Salsa/salsa.fs b/vsintegration/tests/Salsa/salsa.fs index 420bb339a39..cf7f9462dd7 100644 --- a/vsintegration/tests/Salsa/salsa.fs +++ b/vsintegration/tests/Salsa/salsa.fs @@ -297,6 +297,7 @@ module internal Salsa = let projectObj, projectObjFlags = MSBuild.CrackProject(projectfile, configurationFunc(), platformFunc()) projectObj.GetProperty(ProjectFileConstants.ProjectGuid).EvaluatedValue member this.ProjectProvider = None + member this.AssemblyReferences() = [||] // Attempt to treat as MSBuild project. let internal NewMSBuildProjectSite(configurationFunc, platformFunc, msBuildProjectName) = From b5e242abee0eb50e16d48e48da9bf3685be64b3f Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Mon, 3 Apr 2017 21:05:39 +0100 Subject: [PATCH 10/15] Fix removing references not being reflected in the Roslyn workspace --- .../src/FSharp.Editor/LanguageService/LanguageService.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index c857ce8c9d4..7df51dc2ca5 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -320,7 +320,7 @@ and if not(workspaceRefs.Contains(ref)) then projectContext.AddMetadataReference(ref, MetadataReferenceProperties.Assembly) for ref in workspaceRefs do - if not(workspaceRefs.Contains(ref)) then + if not(updatedRefs.Contains(ref)) then projectContext.RemoveMetadataReference(ref) // update the cached options From a582f5672f3aa6bf0408fe50917b3af16a435ef6 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Tue, 4 Apr 2017 07:34:11 +0100 Subject: [PATCH 11/15] Fix dodgy merge --- .../FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs index 85d5f3cc6ff..f37f3b5ad06 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs @@ -17,7 +17,7 @@ type private ReferenceType = | AddProjectRef of ProjectReference | AddMetadataRef of MetadataReference -[] +[] type internal MissingReferenceCodeFixProvider() = inherit CodeFixProvider() @@ -42,7 +42,7 @@ type internal MissingReferenceCodeFixProvider() = let newReferences = references |> Seq.append [metadataRef] return solution.WithProjectMetadataReferences(project.Id, newReferences) } - |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken) + |> RoslynHelpers.StartAsyncAsTask(cancellationToken) ), title) From d772c65e4c0dab2270ed67f35992575348d11cfa Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Sat, 15 Apr 2017 19:00:31 +0100 Subject: [PATCH 12/15] Fix reference to CommonRoslynHelpers --- .../FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs index f37f3b5ad06..5541e06e975 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs @@ -96,4 +96,4 @@ type internal MissingReferenceCodeFixProvider() = | _ -> () ) } - |> CommonRoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) From 001397670eb524cecab1842cbe8760d173c3817c Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Sun, 16 Apr 2017 10:03:47 +0100 Subject: [PATCH 13/15] Removed intellisense strings accidentally added in merge --- vsintegration/src/FSharp.Editor/srFSharp.Editor.fs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/srFSharp.Editor.fs b/vsintegration/src/FSharp.Editor/srFSharp.Editor.fs index d3519acd263..e9c61206e89 100644 --- a/vsintegration/src/FSharp.Editor/srFSharp.Editor.fs +++ b/vsintegration/src/FSharp.Editor/srFSharp.Editor.fs @@ -34,13 +34,6 @@ module SR = let AddProjectReference = lazy (GetString "AddProjectReference") let AddAssemblyReference = lazy (GetString "AddAssemblyReference") - [] - let IntelliSensePropertyPageMiscCategory = "IntelliSensePropertyPageMiscCategory" - [] - let IntelliSensePropertyPageShowAfterCharIsTyped = "IntelliSensePropertyPageShowAfterCharIsTyped" - [] - let IntelliSensePropertyPageShowAfterCharIsTypedDescr = "IntelliSensePropertyPageShowAfterCharIsTypedDescr" - //-------------------------------------------------------------------------------------- // Attributes used to mark up editable properties From 75ce1ff41df5db00a4381371541dd1c9b5d55d53 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Sun, 16 Apr 2017 23:06:03 +0100 Subject: [PATCH 14/15] Ignore case when comparing assembly names --- .../CodeFix/MissingReferenceCodeFixProvider.fs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs index 5541e06e975..cd817129040 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MissingReferenceCodeFixProvider.fs @@ -61,7 +61,11 @@ type internal MissingReferenceCodeFixProvider() = match parts with | [| _; _type; _; assemblyName; _ |] -> - let exactProjectMatches = solution.Projects |> Seq.tryFind (fun project -> project.AssemblyName = assemblyName) + let exactProjectMatches = + solution.Projects + |> Seq.tryFind (fun project -> + String.Compare(project.AssemblyName, assemblyName, StringComparison.OrdinalIgnoreCase) = 0 + ) match exactProjectMatches with | Some refProject -> @@ -78,7 +82,8 @@ type internal MissingReferenceCodeFixProvider() = solution.Projects |> Seq.collect (fun project -> project.MetadataReferences) |> Seq.tryFind (fun ref -> - Path.GetFileNameWithoutExtension(ref.Display) = assemblyName + let referenceAssemblyName = Path.GetFileNameWithoutExtension(ref.Display) + String.Compare(referenceAssemblyName, assemblyName, StringComparison.OrdinalIgnoreCase) = 0 ) match metadataReferences with From 1d2bbebe48447a7e3449780dce7d3c9be644eba9 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Wed, 19 Apr 2017 17:47:46 +0100 Subject: [PATCH 15/15] Fix dodgy merge --- vsintegration/src/FSharp.Editor/FSharp.Editor.resx | 1 + 1 file changed, 1 insertion(+) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx index cff48a16284..cf3d87957d0 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx @@ -170,6 +170,7 @@ Add a project reference to '{0}' + Code Fixes