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

Democratizing code fixes - part two #15398

Merged
merged 11 commits into from
Jun 15, 2023
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
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
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>
psfinaki marked this conversation as resolved.
Show resolved Hide resolved
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