Skip to content

Commit

Permalink
Democratizing code fixes - part two (#15398)
Browse files Browse the repository at this point in the history
  • Loading branch information
psfinaki authored Jun 15, 2023
1 parent 11f0f70 commit d984929
Show file tree
Hide file tree
Showing 16 changed files with 433 additions and 185 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Microsoft.VisualStudio.FSharp.Editor

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

open Microsoft.CodeAnalysis.Text
Expand All @@ -17,16 +16,17 @@ type internal FSharpAddInstanceMemberParameterCodeFixProvider() =

static let title = SR.AddMissingInstanceMemberParameter()

interface IFSharpCodeFix with
member _.GetChangesAsync _ span =
let changes = [ TextChange(TextSpan(span.Start, 0), "x.") ]
CancellableTask.singleton (title, changes)

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

override this.RegisterCodeFixesAsync context : Task =
cancellableTask {
let! title, changes = (this :> IFSharpCodeFix).GetChangesAsync context.Document context.Span
context.RegisterFsharpFix(CodeFix.AddInstanceMemberParameter, title, changes)
}
|> CancellableTask.startAsTask context.CancellationToken
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)

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

CancellableTask.singleton (Some codeFix)
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,11 @@ namespace Microsoft.VisualStudio.FSharp.Editor
open System
open System.Collections.Immutable
open System.Composition
open System.Threading

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

open FSharp.Compiler
open FSharp.Compiler.CodeAnalysis

open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.CodeActions
open CancellableTasks

[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.AddMissingRecToMutuallyRecFunctions); Shared>]
type internal FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider [<ImportingConstructor>] () =
Expand All @@ -24,48 +19,48 @@ type internal FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider [<Importi

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

override _.RegisterCodeFixesAsync context =
asyncMaybe {
let! defines, langVersion =
context.Document.GetFSharpCompilationDefinesAndLangVersionAsync(
nameof (FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider)
)
|> liftAsync
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)

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

let funcStartPos =
let rec loop ch pos =
if not (Char.IsWhiteSpace(ch)) then
pos
else
loop sourceText.[pos + 1] (pos + 1)
let! defines, langVersion =
document.GetFSharpCompilationDefinesAndLangVersionAsync(
nameof (FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider)
)

loop sourceText.[context.Span.End + 1] (context.Span.End + 1)
let! sourceText = document.GetTextAsync(cancellationToken)

let! funcLexerSymbol =
Tokenizer.getSymbolAtPosition (
context.Document.Id,
sourceText,
funcStartPos,
context.Document.FilePath,
defines,
SymbolLookupKind.Greedy,
false,
false,
Some langVersion,
context.CancellationToken
)
let funcStartPos =
let rec loop ch pos =
if not (Char.IsWhiteSpace(ch)) then
pos
else
loop sourceText.[pos + 1] (pos + 1)

let! funcNameSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, funcLexerSymbol.Range)
let funcName = sourceText.GetSubText(funcNameSpan).ToString()
loop sourceText.[span.End + 1] (span.End + 1)

do
context.RegisterFsharpFix(
CodeFix.AddMissingRecToMutuallyRecFunctions,
String.Format(titleFormat, funcName),
[| TextChange(TextSpan(context.Span.End, 0), " rec") |]
)
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
return
Tokenizer.getSymbolAtPosition (
document.Id,
sourceText,
funcStartPos,
document.FilePath,
defines,
SymbolLookupKind.Greedy,
false,
false,
Some langVersion,
cancellationToken
)
|> Option.bind (fun funcLexerSymbol -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, funcLexerSymbol.Range))
|> Option.map (fun funcNameSpan -> sourceText.GetSubText(funcNameSpan).ToString())
|> Option.map (fun funcName ->
{
Name = CodeFix.AddMissingRecToMutuallyRecFunctions
Message = String.Format(titleFormat, funcName)
Changes = [ TextChange(TextSpan(span.End, 0), " rec") ]
})
}
64 changes: 32 additions & 32 deletions vsintegration/src/FSharp.Editor/CodeFix/ChangeToUpcast.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Microsoft.VisualStudio.FSharp.Editor

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

open Microsoft.CodeAnalysis.Text
Expand All @@ -17,8 +16,10 @@ type internal FSharpChangeToUpcastCodeFixProvider() =

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

interface IFSharpCodeFix with
member _.GetChangesAsync document span =
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)

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

Expand All @@ -29,33 +30,32 @@ type internal FSharpChangeToUpcastCodeFixProvider() =
let isDowncastOperator = text.Contains(":?>")
let isDowncastKeyword = text.Contains("downcast")

let changes =
[
if
(isDowncastOperator || isDowncastKeyword)
&& not (isDowncastOperator && isDowncastKeyword)
then
let replacement =
if isDowncastOperator then
text.Replace(":?>", ":>")
else
text.Replace("downcast", "upcast")

TextChange(span, replacement)
]

let title =
if isDowncastOperator then
SR.UseUpcastOperator()
else
SR.UseUpcastKeyword()

return title, changes
if
(isDowncastOperator || isDowncastKeyword)
&& not (isDowncastOperator && isDowncastKeyword)
then
let replacement =
if isDowncastOperator then
text.Replace(":?>", ":>")
else
text.Replace("downcast", "upcast")

let changes = [ TextChange(span, replacement) ]

let title =
if isDowncastOperator then
SR.UseUpcastOperator()
else
SR.UseUpcastKeyword()

let codeFix =
{
Name = CodeFix.ChangeToUpcast
Message = title
Changes = changes
}

return Some codeFix
else
return None
}

override this.RegisterCodeFixesAsync context : Task =
cancellableTask {
let! title, changes = (this :> IFSharpCodeFix).GetChangesAsync context.Document context.Span
context.RegisterFsharpFix(CodeFix.ChangeToUpcast, title, changes)
}
|> CancellableTask.startAsTask context.CancellationToken
12 changes: 10 additions & 2 deletions vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

namespace Microsoft.VisualStudio.FSharp.Editor

open System
open System.Threading
open System.Threading.Tasks
open System.Collections.Immutable
open System.Diagnostics

Expand All @@ -15,6 +13,8 @@ open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions
open Microsoft.VisualStudio.FSharp.Editor.Telemetry

open CancellableTasks

[<RequireQualifiedAccess>]
module internal CodeFixHelpers =
let private reportCodeFixTelemetry
Expand Down Expand Up @@ -80,3 +80,11 @@ module internal CodeFixExtensions =

let diag = diagnostics |> Option.defaultValue ctx.Diagnostics
ctx.RegisterCodeFix(codeAction, diag)

member ctx.RegisterFsharpFix(codeFix: IFSharpCodeFixProvider) =
cancellableTask {
match! codeFix.GetCodeFixIfAppliesAsync ctx.Document ctx.Span with
| Some codeFix -> ctx.RegisterFsharpFix(codeFix.Name, codeFix.Message, codeFix.Changes)
| None -> ()
}
|> CancellableTask.startAsTask ctx.CancellationToken
32 changes: 14 additions & 18 deletions vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Microsoft.VisualStudio.FSharp.Editor

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

open Microsoft.CodeAnalysis.Text
Expand All @@ -19,8 +18,10 @@ type internal FSharpConvertToAnonymousRecordCodeFixProvider [<ImportingConstruct

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

interface IFSharpCodeFix with
member _.GetChangesAsync document span =
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)

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

Expand All @@ -31,22 +32,17 @@ type internal FSharpConvertToAnonymousRecordCodeFixProvider [<ImportingConstruct
let errorRange =
RoslynHelpers.TextSpanToFSharpRange(document.FilePath, span, sourceText)

let changes =
return
parseResults.TryRangeOfRecordExpressionContainingPos errorRange.Start
|> Option.bind (fun range -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range))
|> Option.map (fun span ->
[
TextChange(TextSpan(span.Start + 1, 0), "|")
TextChange(TextSpan(span.End - 1, 0), "|")
])
|> Option.defaultValue []

return title, changes
{
Name = CodeFix.ConvertToAnonymousRecord
Message = title
Changes =
[
TextChange(TextSpan(span.Start + 1, 0), "|")
TextChange(TextSpan(span.End - 1, 0), "|")
]
})
}

override this.RegisterCodeFixesAsync context : Task =
cancellableTask {
let! title, changes = (this :> IFSharpCodeFix).GetChangesAsync context.Document context.Span
return context.RegisterFsharpFix(CodeFix.ConvertToAnonymousRecord, title, changes)
}
|> CancellableTask.startAsTask context.CancellationToken
11 changes: 9 additions & 2 deletions vsintegration/src/FSharp.Editor/CodeFix/IFSharpCodeFix.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,12 @@ open Microsoft.CodeAnalysis.Text

open CancellableTasks

type IFSharpCodeFix =
abstract member GetChangesAsync: document: Document -> span: TextSpan -> CancellableTask<string * TextChange list>
type FSharpCodeFix =
{
Name: string
Message: string
Changes: TextChange list
}

type IFSharpCodeFixProvider =
abstract member GetCodeFixIfAppliesAsync: document: Document -> span: TextSpan -> CancellableTask<FSharpCodeFix option>
56 changes: 30 additions & 26 deletions vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,44 @@ open System.Collections.Immutable
open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes

open CancellableTasks

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

override _.FixableDiagnosticIds = ImmutableArray.Create("FS0748", "FS0747")
override _.FixableDiagnosticIds = ImmutableArray.Create("FS0747", "FS0748")

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

override _.RegisterCodeFixesAsync context =
asyncMaybe {
let! parseResults =
context.Document.GetFSharpParseResultsAsync(nameof (FSharpRemoveReturnOrYieldCodeFixProvider))
|> liftAsync
interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync document span =
cancellableTask {
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()

let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpRemoveReturnOrYieldCodeFixProvider))

let errorRange =
RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText)
let! sourceText = document.GetTextAsync(cancellationToken)

let! exprRange = parseResults.TryRangeOfExprInYieldOrReturn errorRange.Start
let! exprSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, exprRange)
let errorRange =
RoslynHelpers.TextSpanToFSharpRange(document.FilePath, span, sourceText)

let title =
let text = sourceText.GetSubText(context.Span).ToString()
return
parseResults.TryRangeOfExprInYieldOrReturn errorRange.Start
|> Option.bind (fun exprRange -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, exprRange))
|> Option.map (fun exprSpan -> [ TextChange(span, sourceText.GetSubText(exprSpan).ToString()) ])
|> Option.map (fun changes ->
let title =
let text = sourceText.GetSubText(span).ToString()

if text.StartsWith("return!") then SR.RemoveReturnBang()
elif text.StartsWith("return") then SR.RemoveReturn()
elif text.StartsWith("yield!") then SR.RemoveYieldBang()
else SR.RemoveYield()
if text.StartsWith("return!") then SR.RemoveReturnBang()
elif text.StartsWith("return") then SR.RemoveReturn()
elif text.StartsWith("yield!") then SR.RemoveYieldBang()
else SR.RemoveYield()

do
context.RegisterFsharpFix(
CodeFix.RemoveReturnOrYield,
title,
[| TextChange(context.Span, sourceText.GetSubText(exprSpan).ToString()) |]
)
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
{
Name = CodeFix.RemoveReturnOrYield
Message = title
Changes = changes
})
}
Loading

0 comments on commit d984929

Please sign in to comment.