Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7f5fb7b
Added "You must add a reference to 'foo'" code fixer
saul Mar 30, 2017
95e1ec3
Preliminary work to enable metadata reference code fixing
saul Mar 30, 2017
8228545
Fix SetupProjectFile passing in the wrong IVsHierarchy to CreateProje…
saul Apr 3, 2017
957ef1e
Localise code fix strings, add support for metadata (assembly) refere…
saul Apr 3, 2017
91c2ceb
Fix removing references not being reflected in the Roslyn workspace
saul Apr 3, 2017
5cd8748
Added "You must add a reference to 'foo'" code fixer
saul Mar 30, 2017
b2d36fd
Preliminary work to enable metadata reference code fixing
saul Mar 30, 2017
4f4b96a
Rebased onto master
saul Apr 3, 2017
e82a311
Localise code fix strings, add support for metadata (assembly) refere…
saul Apr 3, 2017
b5e242a
Fix removing references not being reflected in the Roslyn workspace
saul Apr 3, 2017
b2e35b9
Merge branch 'missing-reference-codefix' of https://github.com/saul/v…
saul Apr 3, 2017
733ce07
Merge remote-tracking branch 'upstream/master' into missing-reference…
saul Apr 4, 2017
a582f56
Fix dodgy merge
saul Apr 4, 2017
d772c65
Fix reference to CommonRoslynHelpers
saul Apr 15, 2017
b261c4b
Merge branch 'master' into missing-reference-codefix
saul Apr 16, 2017
0013976
Removed intellisense strings accidentally added in merge
saul Apr 16, 2017
75ce1ff
Ignore case when comparing assembly names
saul Apr 16, 2017
f174052
Merge branch 'master' into missing-reference-codefix
KevinRansom Apr 17, 2017
1d2bbeb
Fix dodgy merge
saul Apr 19, 2017
20e2bc5
Merge remote-tracking branch 'upstream/master' into missing-reference…
saul Apr 19, 2017
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
@@ -0,0 +1,104 @@
// 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 System.IO

open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions

type private ReferenceType =
| AddProjectRef of ProjectReference
| AddMetadataRef of MetadataReference

[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = "MissingReference"); Shared>]
type internal MissingReferenceCodeFixProvider() =
inherit CodeFixProvider()

let fixableDiagnosticId = "FS0074"

let createCodeFix (title: string, context: CodeFixContext, addReference: ReferenceType) =
CodeAction.Create(
title,
(fun (cancellationToken: CancellationToken) ->
async {
let project = context.Document.Project
let solution = project.Solution

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)
}
|> RoslynHelpers.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)

match parts with
| [| _; _type; _; assemblyName; _ |] ->

let exactProjectMatches =
solution.Projects
|> Seq.tryFind (fun project ->
String.Compare(project.AssemblyName, assemblyName, StringComparison.OrdinalIgnoreCase) = 0
)

match exactProjectMatches with
| Some refProject ->
let codefix =
createCodeFix(
String.Format(SR.AddProjectReference.Value, refProject.Name),
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 ->
let referenceAssemblyName = Path.GetFileNameWithoutExtension(ref.Display)
String.Compare(referenceAssemblyName, assemblyName, StringComparison.OrdinalIgnoreCase) = 0
)

match metadataReferences with
| Some metadataRef ->
let codefix =
createCodeFix(
String.Format(SR.AddAssemblyReference.Value, assemblyName),
context,
AddMetadataRef metadataRef
)

context.RegisterCodeFix (codefix, ImmutableArray.Create diagnostic)
| None ->
()
| _ -> ()
)
}
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
1 change: 1 addition & 0 deletions vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
<Compile Include="CodeFix\ImplementInterfaceCodeFixProvider.fs" />
<Compile Include="CodeFix\SimplifyName.fs" />
<Compile Include="CodeFix\RemoveUnusedOpens.fs" />
<Compile Include="CodeFix\MissingReferenceCodeFixProvider.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(FSharpSourcesRoot)\fsharp\FSharp.Core\FSharp.Core.fsproj">
Expand Down
6 changes: 6 additions & 0 deletions vsintegration/src/FSharp.Editor/FSharp.Editor.resx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@
<data name="6009" xml:space="preserve">
<value>QuickInfo</value>
</data>
<data name="AddAssemblyReference" xml:space="preserve">
<value>Add an assembly reference to '{0}'</value>
</data>
<data name="AddProjectReference" xml:space="preserve">
<value>Add a project reference to '{0}'</value>
</data>
<data name="6010" xml:space="preserve">
<value>Code Fixes</value>
</data>
Expand Down
28 changes: 24 additions & 4 deletions vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -303,10 +304,9 @@ and
let hashSetIgnoreCase x = new HashSet<string>(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)
Expand All @@ -315,6 +315,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(updatedRefs.Contains(ref)) then
projectContext.RemoveMetadataReference(ref)

// update the cached options
if updated then
Expand All @@ -332,9 +342,19 @@ and
let projectContextFactory = package.ComponentModel.GetService<IWorkspaceProjectContextFactory>();
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

Expand Down
4 changes: 3 additions & 1 deletion vsintegration/src/FSharp.Editor/srFSharp.Editor.fs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ 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")

//--------------------------------------------------------------------------------------
// Attributes used to mark up editable properties

Expand Down
2 changes: 2 additions & 0 deletions vsintegration/src/FSharp.LanguageService/IProjectSite.fs
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ and internal IProjectSite =
abstract LoadTime : System.DateTime

abstract ProjectProvider : IProvideProjectSite option

abstract AssemblyReferences : unit -> string []
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions vsintegration/src/FSharp.ProjectSystem.FSharp/Project.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1488,6 +1489,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
Expand All @@ -1504,6 +1513,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
Expand All @@ -1520,6 +1537,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
Expand Down
1 change: 1 addition & 0 deletions vsintegration/tests/Salsa/salsa.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down