提交 4c31706e 编写于 作者: S Saul Rennison 提交者: Kevin Ransom (msft)

Added "You must add a reference to '<assembly>'" code fixer (#2743)

* Added "You must add a reference to 'foo'" code fixer

* Preliminary work to enable metadata reference code fixing

* Fix SetupProjectFile passing in the wrong IVsHierarchy to CreateProjectContext

* Localise code fix strings, add support for metadata (assembly) references

* Fix removing references not being reflected in the Roslyn workspace

* Added "You must add a reference to 'foo'" code fixer

* Preliminary work to enable metadata reference code fixing

* Rebased onto master

* Localise code fix strings, add support for metadata (assembly) references

* Fix removing references not being reflected in the Roslyn workspace

* Fix dodgy merge

* Fix reference to CommonRoslynHelpers

* Removed intellisense strings accidentally added in merge

* Ignore case when comparing assembly names

* Fix dodgy merge
上级 aa53f290
// 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)
......@@ -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">
......
......@@ -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>
......
......@@ -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
......@@ -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)
......@@ -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
......@@ -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
......
......@@ -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
......
......@@ -57,3 +57,5 @@ and internal IProjectSite =
abstract LoadTime : System.DateTime
abstract ProjectProvider : IProvideProjectSite option
abstract AssemblyReferences : unit -> string []
......@@ -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
......
......@@ -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
......@@ -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
......@@ -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
......@@ -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
......
......@@ -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) =
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册