Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ type internal AddInstanceMemberParameterCodeFixProvider() =
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)

interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync _ span =
member _.GetCodeFixIfAppliesAsync context =
let codeFix =
{
Name = CodeFix.AddInstanceMemberParameter
Message = title
Changes = [ TextChange(TextSpan(span.Start, 0), "x.") ]
Changes = [ TextChange(TextSpan(context.Span.Start, 0), "x.") ]
}

CancellableTask.singleton (Some codeFix)
99 changes: 55 additions & 44 deletions vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,52 +9,63 @@ open System.Collections.Immutable
open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes

open CancellableTasks

[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.AddMissingFunKeyword); Shared>]
type internal AddMissingFunKeywordCodeFixProvider [<ImportingConstructor>] () =
inherit CodeFixProvider()

static let title = SR.AddMissingFunKeyword()

override _.FixableDiagnosticIds = ImmutableArray.Create("FS0010")

override _.RegisterCodeFixesAsync context =
asyncMaybe {
let document = context.Document
let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
let textOfError = sourceText.GetSubText(context.Span).ToString()

// Only trigger when failing to parse `->`, which arises when `fun` is missing
do! Option.guard (textOfError = "->")

let! defines, langVersion =
document.GetFSharpCompilationDefinesAndLangVersionAsync(nameof (AddMissingFunKeywordCodeFixProvider))
|> liftAsync

let adjustedPosition =
let rec loop ch pos =
if not (Char.IsWhiteSpace(ch)) then
pos
else
loop sourceText.[pos] (pos - 1)

loop sourceText.[context.Span.Start - 1] context.Span.Start

let! intendedArgLexerSymbol =
Tokenizer.getSymbolAtPosition (
document.Id,
sourceText,
adjustedPosition,
document.FilePath,
defines,
SymbolLookupKind.Greedy,
false,
false,
Some langVersion,
context.CancellationToken
)

let! intendedArgSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, intendedArgLexerSymbol.Range)

do context.RegisterFsharpFix(CodeFix.AddMissingFunKeyword, title, [| TextChange(TextSpan(intendedArgSpan.Start, 0), "fun ") |])
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
let adjustPosition (sourceText: SourceText) (span: TextSpan) =
let rec loop ch pos =
if not (Char.IsWhiteSpace(ch)) then
pos
else
loop sourceText[pos] (pos - 1)

loop (sourceText[span.Start - 1]) span.Start

override _.FixableDiagnosticIds = ImmutableArray.Create "FS0010"

override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this

interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync context =
cancellableTask {
let! textOfError = context.GetSquigglyTextAsync()

if textOfError <> "->" then
return None
else
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()
let document = context.Document

let! defines, langVersion =
document.GetFSharpCompilationDefinesAndLangVersionAsync(nameof AddMissingFunKeywordCodeFixProvider)

let! sourceText = context.GetSourceTextAsync()
let adjustedPosition = adjustPosition sourceText context.Span

return
Tokenizer.getSymbolAtPosition (
document.Id,
sourceText,
adjustedPosition,
document.FilePath,
defines,
SymbolLookupKind.Greedy,
false,
false,
Some langVersion,
cancellationToken
)
|> Option.bind (fun intendedArgLexerSymbol ->
RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, intendedArgLexerSymbol.Range))
|> Option.map (fun intendedArgSpan ->
{
Name = CodeFix.AddMissingFunKeyword
Message = title
Changes = [ TextChange(TextSpan(intendedArgSpan.Start, 0), "fun ") ]
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ type internal AddMissingRecToMutuallyRecFunctionsCodeFixProvider [<ImportingCons
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)

interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync document span =
member _.GetCodeFixIfAppliesAsync context =
cancellableTask {
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()

let! defines, langVersion =
document.GetFSharpCompilationDefinesAndLangVersionAsync(nameof (AddMissingRecToMutuallyRecFunctionsCodeFixProvider))
context.Document.GetFSharpCompilationDefinesAndLangVersionAsync(
nameof (AddMissingRecToMutuallyRecFunctionsCodeFixProvider)
)

let! sourceText = document.GetTextAsync(cancellationToken)
let! sourceText = context.GetSourceTextAsync()

let funcStartPos =
let rec loop ch pos =
Expand All @@ -38,14 +40,14 @@ type internal AddMissingRecToMutuallyRecFunctionsCodeFixProvider [<ImportingCons
else
loop sourceText.[pos + 1] (pos + 1)

loop sourceText.[span.End + 1] (span.End + 1)
loop sourceText.[context.Span.End + 1] (context.Span.End + 1)

return
Tokenizer.getSymbolAtPosition (
document.Id,
context.Document.Id,
sourceText,
funcStartPos,
document.FilePath,
context.Document.FilePath,
defines,
SymbolLookupKind.Greedy,
false,
Expand All @@ -59,6 +61,6 @@ type internal AddMissingRecToMutuallyRecFunctionsCodeFixProvider [<ImportingCons
{
Name = CodeFix.AddMissingRecToMutuallyRecFunctions
Message = String.Format(titleFormat, funcName)
Changes = [ TextChange(TextSpan(span.End, 0), " rec") ]
Changes = [ TextChange(TextSpan(context.Span.End, 0), " rec") ]
})
}
9 changes: 3 additions & 6 deletions vsintegration/src/FSharp.Editor/CodeFixes/ChangeToUpcast.fs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,9 @@ type internal ChangeToUpcastCodeFixProvider() =
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)

interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync document span =
member _.GetCodeFixIfAppliesAsync context =
cancellableTask {
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()

let! sourceText = document.GetTextAsync(cancellationToken)
let text = sourceText.GetSubText(span).ToString()
let! text = context.GetSquigglyTextAsync()

// Only works if it's one or the other
let isDowncastOperator = text.Contains(":?>")
Expand All @@ -40,7 +37,7 @@ type internal ChangeToUpcastCodeFixProvider() =
else
text.Replace("downcast", "upcast")

let changes = [ TextChange(span, replacement) ]
let changes = [ TextChange(context.Span, replacement) ]

let title =
if isDowncastOperator then
Expand Down
20 changes: 19 additions & 1 deletion vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,26 @@ module internal CodeFixExtensions =

member ctx.RegisterFsharpFix(codeFix: IFSharpCodeFixProvider) =
cancellableTask {
match! codeFix.GetCodeFixIfAppliesAsync ctx.Document ctx.Span with
match! codeFix.GetCodeFixIfAppliesAsync ctx with
| Some codeFix -> ctx.RegisterFsharpFix(codeFix.Name, codeFix.Message, codeFix.Changes)
| None -> ()
}
|> CancellableTask.startAsTask ctx.CancellationToken

member ctx.GetSourceTextAsync() =
cancellableTask {
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()
return! ctx.Document.GetTextAsync cancellationToken
}

member ctx.GetSquigglyTextAsync() =
cancellableTask {
let! sourceText = ctx.GetSourceTextAsync()
return sourceText.GetSubText(ctx.Span).ToString()
}

member ctx.GetErrorRangeAsync() =
cancellableTask {
let! sourceText = ctx.GetSourceTextAsync()
return RoslynHelpers.TextSpanToFSharpRange(ctx.Document.FilePath, ctx.Span, sourceText)
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,26 @@ type internal ConvertCSharpLambdaToFSharpLambdaCodeFixProvider [<ImportingConstr
RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaBodyRange))
|> flatten3

override _.FixableDiagnosticIds = ImmutableArray.Create("FS0039")
override _.FixableDiagnosticIds = ImmutableArray.Create "FS0039"

override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this

interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync document span =
member _.GetCodeFixIfAppliesAsync context =
cancellableTask {
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()

let! parseResults = document.GetFSharpParseResultsAsync(nameof (ConvertCSharpLambdaToFSharpLambdaCodeFixProvider))

let! sourceText = document.GetTextAsync(cancellationToken)

let errorRange =
RoslynHelpers.TextSpanToFSharpRange(document.FilePath, span, sourceText)
let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof ConvertCSharpLambdaToFSharpLambdaCodeFixProvider)
let! sourceText = context.Document.GetTextAsync(cancellationToken)
let! errorRange = context.GetErrorRangeAsync()

return
tryGetSpans parseResults errorRange sourceText
|> Option.map (fun (fullParenSpan, lambdaArgSpan, lambdaBodySpan) ->
let replacement =
let argText = sourceText.GetSubText(lambdaArgSpan).ToString()
let bodyText = sourceText.GetSubText(lambdaBodySpan).ToString()
TextChange(fullParenSpan, "fun " + argText + " -> " + bodyText)
TextChange(fullParenSpan, $"fun {argText} -> {bodyText}")

{
Name = CodeFix.ConvertCSharpLambdaToFSharpLambda
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,11 @@ type internal ConvertToAnonymousRecordCodeFixProvider [<ImportingConstructor>] (
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)

interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync document span =
member _.GetCodeFixIfAppliesAsync context =
cancellableTask {
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()

let! parseResults = document.GetFSharpParseResultsAsync(nameof (ConvertToAnonymousRecordCodeFixProvider))

let! sourceText = document.GetTextAsync(cancellationToken)

let errorRange =
RoslynHelpers.TextSpanToFSharpRange(document.FilePath, span, sourceText)
let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof ConvertToAnonymousRecordCodeFixProvider)
let! sourceText = context.GetSourceTextAsync()
let! errorRange = context.GetErrorRangeAsync()

return
parseResults.TryRangeOfRecordExpressionContainingPos errorRange.Start
Expand Down
4 changes: 2 additions & 2 deletions vsintegration/src/FSharp.Editor/CodeFixes/IFSharpCodeFix.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Microsoft.VisualStudio.FSharp.Editor

open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.Text

open CancellableTasks
Expand All @@ -15,4 +15,4 @@ type FSharpCodeFix =
}

type IFSharpCodeFixProvider =
abstract member GetCodeFixIfAppliesAsync: document: Document -> span: TextSpan -> CancellableTask<FSharpCodeFix option>
abstract member GetCodeFixIfAppliesAsync: context: CodeFixContext -> CancellableTask<FSharpCodeFix option>
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,40 @@ open System.Collections.Immutable
open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes

open CancellableTasks

[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.MakeOuterBindingRecursive); Shared>]
type internal MakeOuterBindingRecursiveCodeFixProvider [<ImportingConstructor>] () =
inherit CodeFixProvider()

override _.FixableDiagnosticIds = ImmutableArray.Create("FS0039")

override _.RegisterCodeFixesAsync context =
asyncMaybe {
let! parseResults =
context.Document.GetFSharpParseResultsAsync(nameof (MakeOuterBindingRecursiveCodeFixProvider))
|> liftAsync

let! sourceText = context.Document.GetTextAsync(context.CancellationToken)

let diagnosticRange =
RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText)

do! Option.guard (parseResults.IsPosContainedInApplication diagnosticRange.Start)

let! outerBindingRange = parseResults.TryRangeOfNameOfNearestOuterBindingContainingPos diagnosticRange.Start
let! outerBindingNameSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, outerBindingRange)

// One last check to verify the names are the same
do!
Option.guard (
sourceText
.GetSubText(outerBindingNameSpan)
.ContentEquals(sourceText.GetSubText(context.Span))
)

let title =
String.Format(SR.MakeOuterBindingRecursive(), sourceText.GetSubText(outerBindingNameSpan).ToString())

do
context.RegisterFsharpFix(
CodeFix.MakeOuterBindingRecursive,
title,
[| TextChange(TextSpan(outerBindingNameSpan.Start, 0), "rec ") |]
)
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
override _.FixableDiagnosticIds = ImmutableArray.Create "FS0039"

override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this

interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync context =
cancellableTask {
let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof MakeOuterBindingRecursiveCodeFixProvider)
let! sourceText = context.GetSourceTextAsync()
let! diagnosticRange = context.GetErrorRangeAsync()

if not <| parseResults.IsPosContainedInApplication diagnosticRange.Start then
return None
else
return
parseResults.TryRangeOfNameOfNearestOuterBindingContainingPos diagnosticRange.Start
|> Option.bind (fun bindingRange -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, bindingRange))
|> Option.filter (fun bindingSpan ->
sourceText
.GetSubText(bindingSpan)
.ContentEquals(sourceText.GetSubText context.Span))
|> Option.map (fun bindingSpan ->
let title =
String.Format(SR.MakeOuterBindingRecursive(), sourceText.GetSubText(bindingSpan).ToString())

{
Name = CodeFix.MakeOuterBindingRecursive
Message = title
Changes = [ TextChange(TextSpan(bindingSpan.Start, 0), "rec ") ]
})
}
Loading