Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract changeAllSymbolReferences function #10

Merged
merged 3 commits into from
Dec 28, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
80 changes: 8 additions & 72 deletions vsintegration/src/FSharp.Editor/CodeFix/ProposeUppercaseLabel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,9 @@ namespace rec Microsoft.VisualStudio.FSharp.Editor

open System.Composition
open System.Collections.Immutable
open System.Threading
open System.Threading.Tasks

open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions
open Microsoft.VisualStudio.FSharp.LanguageService
open Microsoft.VisualStudio.Text.Tagging
open Microsoft.VisualStudio.Text.Formatting
open Microsoft.VisualStudio.Shell.Interop

open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.Parser
open Microsoft.FSharp.Compiler.Range
open Microsoft.FSharp.Compiler.SourceCodeServices

open System.Windows.Documents

[<ExportCodeFixProvider(FSharpCommonConstants.FSharpLanguageName, Name = "ProposeUpperCaseLabel"); Shared>]
type internal FSharpProposeUpperCaseLabelCodeFixProvider
Expand All @@ -36,60 +21,11 @@ type internal FSharpProposeUpperCaseLabelCodeFixProvider
override __.FixableDiagnosticIds = fixableDiagnosticIds.ToImmutableArray()

override __.RegisterCodeFixesAsync context : Task =
async {
if context.Span.Length > 0 then
let document = context.Document
let! sourceText = document.GetTextAsync(context.CancellationToken)
let originalText = sourceText.ToString(context.Span)
if originalText.Length > 0 then
match projectInfoManager.TryGetOptionsForEditingDocumentOrProject context.Document with
| Some options ->
let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList)
match CommonHelpers.getSymbolAtPosition(document.Id, sourceText, context.Span.Start, document.FilePath, defines, SymbolLookupKind.Fuzzy) with
| Some symbol ->
let! textVersion = document.GetTextVersionAsync(context.CancellationToken)
let checker = checkerProvider.Checker
let! _, checkFileAnswer = checker.ParseAndCheckFileInProject(context.Document.FilePath, textVersion.GetHashCode(), sourceText.ToString(), options)
match checkFileAnswer with
| FSharpCheckFileAnswer.Aborted -> ()
| FSharpCheckFileAnswer.Succeeded checkFileResults ->
let textLine = sourceText.Lines.GetLineFromPosition(context.Span.Start)
let textLinePos = sourceText.Lines.GetLinePosition(context.Span.Start)
let fcsTextLineNumber = textLinePos.Line + 1
let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, symbol.RightColumn, textLine.Text.ToString(), [symbol.Text])
match symbolUse with
| Some symbolUse ->
match symbolUse.GetDeclarationLocation(document) with
| None -> ()
| Some declLoc ->
let newText = originalText.[0].ToString().ToUpper() + originalText.Substring(1)
let title = FSComp.SR.replaceWithSuggestion newText
// defer finding all symbol uses throughout the solution until the code fix action is executed
let codeFix =
CodeAction.Create(
title,
(fun (cancellationToken: CancellationToken) ->
async {
let! symbolUsesByDocumentId =
SymbolHelpers.getSymbolUsesInSolution(symbolUse.Symbol, declLoc, checkFileResults, projectInfoManager, checker, document.Project.Solution)

let mutable solution = document.Project.Solution

for KeyValue(documentId, symbolUses) in symbolUsesByDocumentId do
let document = document.Project.Solution.GetDocument(documentId)
let! sourceText = document.GetTextAsync(cancellationToken)
let mutable sourceText = sourceText
for symbolUse in symbolUses do
let textSpan = CommonHelpers.fixupSpan(sourceText, CommonRoslynHelpers.FSharpRangeToTextSpan(sourceText, symbolUse.RangeAlternate))
sourceText <- sourceText.Replace(textSpan, newText)
solution <- solution.WithDocumentText(documentId, sourceText)
return solution
} |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken)),
title)
let diagnostics = (context.Diagnostics |> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id)).ToImmutableArray()
context.RegisterCodeFix(codeFix, diagnostics)
| None -> ()
| None -> ()
| _ -> ()
} |> CommonRoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
asyncMaybe {
let textChanger (originalText: string) = originalText.[0].ToString().ToUpper() + originalText.Substring(1)
let! solutionChanger, originalText = SymbolHelpers.changeAllSymbolReferences(context.Document, context.Span, textChanger, projectInfoManager, checkerProvider.Checker)
let title = FSComp.SR.replaceWithSuggestion (textChanger originalText)
context.RegisterCodeFix(
CodeAction.Create(title, solutionChanger, title),
(context.Diagnostics |> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id)).ToImmutableArray())
} |> Async.ignore |> CommonRoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
44 changes: 15 additions & 29 deletions vsintegration/src/FSharp.Editor/Common/CommonHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,20 @@ module internal Extensions =
try Path.GetFullPath path
with _ -> path

type FSharpChecker with
member this.ParseAndCheckDocument(document: Document, options: FSharpProjectOptions) : Async<(Ast.ParsedInput * FSharpCheckFileResults) option> =
async {
let! cancellationToken = Async.CancellationToken
let! sourceText = document.GetTextAsync()
let! textVersion = document.GetTextVersionAsync(cancellationToken)
let! parseResults, checkFileAnswer = this.ParseAndCheckFileInProject(document.FilePath, textVersion.GetHashCode(), sourceText.ToString(), options)
return
match parseResults.ParseTree, checkFileAnswer with
| _, FSharpCheckFileAnswer.Aborted
| None, _ -> None
| Some parsedInput, FSharpCheckFileAnswer.Succeeded checkResults -> Some (parsedInput, checkResults)
}

type FSharpSymbol with
member this.IsInternalToProject =
match this with
Expand Down Expand Up @@ -511,32 +525,4 @@ module internal Extensions =
| GlyphMajor.Error -> Glyph.Error
| _ -> Glyph.None

type Async<'a> with
/// Creates an asynchronous workflow that runs the asynchronous workflow given as an argument at most once.
/// When the returned workflow is started for the second time, it reuses the result of the previous execution.
static member Cache (input : Async<'T>) =
let agent = MailboxProcessor<AsyncReplyChannel<_>>.Start <| fun agent ->
async {
let! replyCh = agent.Receive ()
let! res = input
replyCh.Reply res
while true do
let! replyCh = agent.Receive ()
replyCh.Reply res
}
async { return! agent.PostAndAsyncReply id }

static member inline Map (f: 'a -> 'b) (input: Async<'a>) : Async<'b> =
async {
let! result = input
return f result
}

type AsyncBuilder with
member __.Bind(computation: System.Threading.Tasks.Task<'a>, binder: 'a -> Async<'b>): Async<'b> =
async {
let! a = Async.AwaitTask computation
return! binder a
}

member __.ReturnFrom(computation: System.Threading.Tasks.Task<'a>): Async<'a> = Async.AwaitTask computation

Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,4 @@ module internal RoslynExtensions =
member this.GetDependentProjects() =
[ for project in this.Solution.Projects do
if project.ProjectReferences |> Seq.exists (fun ref -> ref.ProjectId = this.Id) then
yield project ]
yield project ]
196 changes: 195 additions & 1 deletion vsintegration/src/FSharp.Editor/Common/Pervasive.fs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[<AutoOpen>]
module Microsoft.VisualStudio.FSharp.Pervasive
module Microsoft.VisualStudio.FSharp.Editor.Pervasive

open System
open System.Diagnostics

[<RequireQualifiedAccess>]
module String =
Expand All @@ -24,3 +25,196 @@ type System.IServiceProvider with
member x.GetService<'T>() = x.GetService(typeof<'T>) :?> 'T
member x.GetService<'S, 'T>() = x.GetService(typeof<'S>) :?> 'T

[<Sealed>]
type MaybeBuilder () =
// 'T -> M<'T>
[<DebuggerStepThrough>]
member inline __.Return value: 'T option =
Some value

// M<'T> -> M<'T>
[<DebuggerStepThrough>]
member inline __.ReturnFrom value: 'T option =
value

// unit -> M<'T>
[<DebuggerStepThrough>]
member inline __.Zero (): unit option =
Some () // TODO: Should this be None?

// (unit -> M<'T>) -> M<'T>
[<DebuggerStepThrough>]
member __.Delay (f: unit -> 'T option): 'T option =
f ()

// M<'T> -> M<'T> -> M<'T>
// or
// M<unit> -> M<'T> -> M<'T>
[<DebuggerStepThrough>]
member inline __.Combine (r1, r2: 'T option): 'T option =
match r1 with
| None ->
None
| Some () ->
r2

// M<'T> * ('T -> M<'U>) -> M<'U>
[<DebuggerStepThrough>]
member inline __.Bind (value, f: 'T -> 'U option): 'U option =
Option.bind f value

// 'T * ('T -> M<'U>) -> M<'U> when 'U :> IDisposable
[<DebuggerStepThrough>]
member __.Using (resource: ('T :> System.IDisposable), body: _ -> _ option): _ option =
try body resource
finally
if not <| obj.ReferenceEquals (null, box resource) then
resource.Dispose ()

// (unit -> bool) * M<'T> -> M<'T>
[<DebuggerStepThrough>]
member x.While (guard, body: _ option): _ option =
if guard () then
// OPTIMIZE: This could be simplified so we don't need to make calls to Bind and While.
x.Bind (body, (fun () -> x.While (guard, body)))
else
x.Zero ()

// seq<'T> * ('T -> M<'U>) -> M<'U>
// or
// seq<'T> * ('T -> M<'U>) -> seq<M<'U>>
[<DebuggerStepThrough>]
member x.For (sequence: seq<_>, body: 'T -> unit option): _ option =
// OPTIMIZE: This could be simplified so we don't need to make calls to Using, While, Delay.
x.Using (sequence.GetEnumerator (), fun enum ->
x.While (
enum.MoveNext,
x.Delay (fun () ->
body enum.Current)))

let maybe = MaybeBuilder()

[<Sealed>]
type AsyncMaybeBuilder () =
[<DebuggerStepThrough>]
member __.Return value : Async<'T option> = Some value |> async.Return

[<DebuggerStepThrough>]
member __.ReturnFrom value : Async<'T option> = value

[<DebuggerStepThrough>]
member __.ReturnFrom (value: 'T option) : Async<'T option> = async.Return value

[<DebuggerStepThrough>]
member __.Zero () : Async<unit option> =
Some () |> async.Return

[<DebuggerStepThrough>]
member __.Delay (f : unit -> Async<'T option>) : Async<'T option> = f ()

[<DebuggerStepThrough>]
member __.Combine (r1, r2 : Async<'T option>) : Async<'T option> =
async {
let! r1' = r1
match r1' with
| None -> return None
| Some () -> return! r2
}

[<DebuggerStepThrough>]
member __.Bind (value: Async<'T option>, f : 'T -> Async<'U option>) : Async<'U option> =
async {
let! value' = value
match value' with
| None -> return None
| Some result -> return! f result
}

[<DebuggerStepThrough>]
member __.Bind (value: System.Threading.Tasks.Task<'T>, f : 'T -> Async<'U option>) : Async<'U option> =
async {
let! value' = Async.AwaitTask value
return! f value'
}

[<DebuggerStepThrough>]
member __.Bind (value: 'T option, f : 'T -> Async<'U option>) : Async<'U option> =
async {
match value with
| None -> return None
| Some result -> return! f result
}

[<DebuggerStepThrough>]
member __.Using (resource : ('T :> IDisposable), body : _ -> Async<_ option>) : Async<_ option> =
try body resource
finally if not (isNull resource) then resource.Dispose ()

[<DebuggerStepThrough>]
member x.While (guard, body : Async<_ option>) : Async<_ option> =
if guard () then
x.Bind (body, (fun () -> x.While (guard, body)))
else
x.Zero ()

[<DebuggerStepThrough>]
member x.For (sequence : seq<_>, body : 'T -> Async<unit option>) : Async<_ option> =
x.Using (sequence.GetEnumerator (), fun enum ->
x.While (enum.MoveNext, x.Delay (fun () -> body enum.Current)))

[<DebuggerStepThrough>]
member inline __.TryWith (computation : Async<'T option>, catchHandler : exn -> Async<'T option>) : Async<'T option> =
async.TryWith (computation, catchHandler)

[<DebuggerStepThrough>]
member inline __.TryFinally (computation : Async<'T option>, compensation : unit -> unit) : Async<'T option> =
async.TryFinally (computation, compensation)

let asyncMaybe = AsyncMaybeBuilder()

let inline liftAsync (computation : Async<'T>) : Async<'T option> =
async {
let! a = computation
return Some a
}

module Async =
let map (f: 'T -> 'U) (a: Async<'T>) : Async<'U> =
async {
let! a = a
return f a
}

let ignore (a: Async<'T>) : Async<unit> =
async {
let! _ = a
return ()
}

/// Creates an asynchronous workflow that runs the asynchronous workflow given as an argument at most once.
/// When the returned workflow is started for the second time, it reuses the result of the previous execution.
let cache (input : Async<'T>) =
let agent = MailboxProcessor<AsyncReplyChannel<_>>.Start <| fun agent ->
async {
let! replyCh = agent.Receive ()
let! res = input
replyCh.Reply res
while true do
let! replyCh = agent.Receive ()
replyCh.Reply res
}
async { return! agent.PostAndAsyncReply id }

type AsyncBuilder with
member __.Bind(computation: System.Threading.Tasks.Task<'a>, binder: 'a -> Async<'b>): Async<'b> =
async {
let! a = Async.AwaitTask computation
return! binder a
}

member __.ReturnFrom(computation: System.Threading.Tasks.Task<'a>): Async<'a> = Async.AwaitTask computation


module Option =
let guard (x: bool) : Option<unit> =
if x then Some() else None
Loading