Skip to content

Commit 855a5d4

Browse files
authored
Democratizing code fixes - part three (#15508)
* up * up * get lazy * Option.guard * fantomas * oops * Update ProposeUppercaseLabel.fs * Update FSharpCodeFixContext.fs * Update FSharpCodeFixContext.fs * Update FSharpCodeFixContext.fs * Update FSharpCodeFixContext.fs * Reshuffle a few things * Update FSharpCodeFixContext.fs * UP
1 parent b9685e8 commit 855a5d4

19 files changed

+350
-225
lines changed

vsintegration/src/FSharp.Editor/CodeFixes/AddInstanceMemberParameter.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ type internal AddInstanceMemberParameterCodeFixProvider() =
2121
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)
2222

2323
interface IFSharpCodeFixProvider with
24-
member _.GetCodeFixIfAppliesAsync _ span =
24+
member _.GetCodeFixIfAppliesAsync context =
2525
let codeFix =
2626
{
2727
Name = CodeFix.AddInstanceMemberParameter
2828
Message = title
29-
Changes = [ TextChange(TextSpan(span.Start, 0), "x.") ]
29+
Changes = [ TextChange(TextSpan(context.Span.Start, 0), "x.") ]
3030
}
3131

3232
CancellableTask.singleton (Some codeFix)

vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,63 @@ open System.Collections.Immutable
99
open Microsoft.CodeAnalysis.Text
1010
open Microsoft.CodeAnalysis.CodeFixes
1111

12+
open CancellableTasks
13+
1214
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.AddMissingFunKeyword); Shared>]
1315
type internal AddMissingFunKeywordCodeFixProvider [<ImportingConstructor>] () =
1416
inherit CodeFixProvider()
17+
1518
static let title = SR.AddMissingFunKeyword()
1619

17-
override _.FixableDiagnosticIds = ImmutableArray.Create("FS0010")
18-
19-
override _.RegisterCodeFixesAsync context =
20-
asyncMaybe {
21-
let document = context.Document
22-
let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
23-
let textOfError = sourceText.GetSubText(context.Span).ToString()
24-
25-
// Only trigger when failing to parse `->`, which arises when `fun` is missing
26-
do! Option.guard (textOfError = "->")
27-
28-
let! defines, langVersion =
29-
document.GetFSharpCompilationDefinesAndLangVersionAsync(nameof (AddMissingFunKeywordCodeFixProvider))
30-
|> liftAsync
31-
32-
let adjustedPosition =
33-
let rec loop ch pos =
34-
if not (Char.IsWhiteSpace(ch)) then
35-
pos
36-
else
37-
loop sourceText.[pos] (pos - 1)
38-
39-
loop sourceText.[context.Span.Start - 1] context.Span.Start
40-
41-
let! intendedArgLexerSymbol =
42-
Tokenizer.getSymbolAtPosition (
43-
document.Id,
44-
sourceText,
45-
adjustedPosition,
46-
document.FilePath,
47-
defines,
48-
SymbolLookupKind.Greedy,
49-
false,
50-
false,
51-
Some langVersion,
52-
context.CancellationToken
53-
)
54-
55-
let! intendedArgSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, intendedArgLexerSymbol.Range)
56-
57-
do context.RegisterFsharpFix(CodeFix.AddMissingFunKeyword, title, [| TextChange(TextSpan(intendedArgSpan.Start, 0), "fun ") |])
58-
}
59-
|> Async.Ignore
60-
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
20+
let adjustPosition (sourceText: SourceText) (span: TextSpan) =
21+
let rec loop ch pos =
22+
if not (Char.IsWhiteSpace(ch)) then
23+
pos
24+
else
25+
loop sourceText[pos] (pos - 1)
26+
27+
loop (sourceText[span.Start - 1]) span.Start
28+
29+
override _.FixableDiagnosticIds = ImmutableArray.Create "FS0010"
30+
31+
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this
32+
33+
interface IFSharpCodeFixProvider with
34+
member _.GetCodeFixIfAppliesAsync context =
35+
cancellableTask {
36+
let! textOfError = context.GetSquigglyTextAsync()
37+
38+
if textOfError <> "->" then
39+
return None
40+
else
41+
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()
42+
let document = context.Document
43+
44+
let! defines, langVersion =
45+
document.GetFSharpCompilationDefinesAndLangVersionAsync(nameof AddMissingFunKeywordCodeFixProvider)
46+
47+
let! sourceText = context.GetSourceTextAsync()
48+
let adjustedPosition = adjustPosition sourceText context.Span
49+
50+
return
51+
Tokenizer.getSymbolAtPosition (
52+
document.Id,
53+
sourceText,
54+
adjustedPosition,
55+
document.FilePath,
56+
defines,
57+
SymbolLookupKind.Greedy,
58+
false,
59+
false,
60+
Some langVersion,
61+
cancellationToken
62+
)
63+
|> Option.bind (fun intendedArgLexerSymbol ->
64+
RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, intendedArgLexerSymbol.Range))
65+
|> Option.map (fun intendedArgSpan ->
66+
{
67+
Name = CodeFix.AddMissingFunKeyword
68+
Message = title
69+
Changes = [ TextChange(TextSpan(intendedArgSpan.Start, 0), "fun ") ]
70+
})
71+
}

vsintegration/src/FSharp.Editor/CodeFixes/AddMissingRecToMutuallyRecFunctions.fs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ type internal AddMissingRecToMutuallyRecFunctionsCodeFixProvider [<ImportingCons
2222
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)
2323

2424
interface IFSharpCodeFixProvider with
25-
member _.GetCodeFixIfAppliesAsync document span =
25+
member _.GetCodeFixIfAppliesAsync context =
2626
cancellableTask {
2727
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()
2828

2929
let! defines, langVersion =
30-
document.GetFSharpCompilationDefinesAndLangVersionAsync(nameof (AddMissingRecToMutuallyRecFunctionsCodeFixProvider))
30+
context.Document.GetFSharpCompilationDefinesAndLangVersionAsync(
31+
nameof (AddMissingRecToMutuallyRecFunctionsCodeFixProvider)
32+
)
3133

32-
let! sourceText = document.GetTextAsync(cancellationToken)
34+
let! sourceText = context.GetSourceTextAsync()
3335

3436
let funcStartPos =
3537
let rec loop ch pos =
@@ -38,14 +40,14 @@ type internal AddMissingRecToMutuallyRecFunctionsCodeFixProvider [<ImportingCons
3840
else
3941
loop sourceText.[pos + 1] (pos + 1)
4042

41-
loop sourceText.[span.End + 1] (span.End + 1)
43+
loop sourceText.[context.Span.End + 1] (context.Span.End + 1)
4244

4345
return
4446
Tokenizer.getSymbolAtPosition (
45-
document.Id,
47+
context.Document.Id,
4648
sourceText,
4749
funcStartPos,
48-
document.FilePath,
50+
context.Document.FilePath,
4951
defines,
5052
SymbolLookupKind.Greedy,
5153
false,
@@ -59,6 +61,6 @@ type internal AddMissingRecToMutuallyRecFunctionsCodeFixProvider [<ImportingCons
5961
{
6062
Name = CodeFix.AddMissingRecToMutuallyRecFunctions
6163
Message = String.Format(titleFormat, funcName)
62-
Changes = [ TextChange(TextSpan(span.End, 0), " rec") ]
64+
Changes = [ TextChange(TextSpan(context.Span.End, 0), " rec") ]
6365
})
6466
}

vsintegration/src/FSharp.Editor/CodeFixes/ChangeToUpcast.fs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@ type internal ChangeToUpcastCodeFixProvider() =
1919
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)
2020

2121
interface IFSharpCodeFixProvider with
22-
member _.GetCodeFixIfAppliesAsync document span =
22+
member _.GetCodeFixIfAppliesAsync context =
2323
cancellableTask {
24-
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()
25-
26-
let! sourceText = document.GetTextAsync(cancellationToken)
27-
let text = sourceText.GetSubText(span).ToString()
24+
let! text = context.GetSquigglyTextAsync()
2825

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

43-
let changes = [ TextChange(span, replacement) ]
40+
let changes = [ TextChange(context.Span, replacement) ]
4441

4542
let title =
4643
if isDowncastOperator then

vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,26 @@ module internal CodeFixExtensions =
8383

8484
member ctx.RegisterFsharpFix(codeFix: IFSharpCodeFixProvider) =
8585
cancellableTask {
86-
match! codeFix.GetCodeFixIfAppliesAsync ctx.Document ctx.Span with
86+
match! codeFix.GetCodeFixIfAppliesAsync ctx with
8787
| Some codeFix -> ctx.RegisterFsharpFix(codeFix.Name, codeFix.Message, codeFix.Changes)
8888
| None -> ()
8989
}
9090
|> CancellableTask.startAsTask ctx.CancellationToken
91+
92+
member ctx.GetSourceTextAsync() =
93+
cancellableTask {
94+
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()
95+
return! ctx.Document.GetTextAsync cancellationToken
96+
}
97+
98+
member ctx.GetSquigglyTextAsync() =
99+
cancellableTask {
100+
let! sourceText = ctx.GetSourceTextAsync()
101+
return sourceText.GetSubText(ctx.Span).ToString()
102+
}
103+
104+
member ctx.GetErrorRangeAsync() =
105+
cancellableTask {
106+
let! sourceText = ctx.GetSourceTextAsync()
107+
return RoslynHelpers.TextSpanToFSharpRange(ctx.Document.FilePath, ctx.Span, sourceText)
108+
}

vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,29 +32,26 @@ type internal ConvertCSharpLambdaToFSharpLambdaCodeFixProvider [<ImportingConstr
3232
RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaBodyRange))
3333
|> flatten3
3434

35-
override _.FixableDiagnosticIds = ImmutableArray.Create("FS0039")
35+
override _.FixableDiagnosticIds = ImmutableArray.Create "FS0039"
3636

37-
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)
37+
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this
3838

3939
interface IFSharpCodeFixProvider with
40-
member _.GetCodeFixIfAppliesAsync document span =
40+
member _.GetCodeFixIfAppliesAsync context =
4141
cancellableTask {
4242
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()
4343

44-
let! parseResults = document.GetFSharpParseResultsAsync(nameof (ConvertCSharpLambdaToFSharpLambdaCodeFixProvider))
45-
46-
let! sourceText = document.GetTextAsync(cancellationToken)
47-
48-
let errorRange =
49-
RoslynHelpers.TextSpanToFSharpRange(document.FilePath, span, sourceText)
44+
let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof ConvertCSharpLambdaToFSharpLambdaCodeFixProvider)
45+
let! sourceText = context.Document.GetTextAsync(cancellationToken)
46+
let! errorRange = context.GetErrorRangeAsync()
5047

5148
return
5249
tryGetSpans parseResults errorRange sourceText
5350
|> Option.map (fun (fullParenSpan, lambdaArgSpan, lambdaBodySpan) ->
5451
let replacement =
5552
let argText = sourceText.GetSubText(lambdaArgSpan).ToString()
5653
let bodyText = sourceText.GetSubText(lambdaBodySpan).ToString()
57-
TextChange(fullParenSpan, "fun " + argText + " -> " + bodyText)
54+
TextChange(fullParenSpan, $"fun {argText} -> {bodyText}")
5855

5956
{
6057
Name = CodeFix.ConvertCSharpLambdaToFSharpLambda

vsintegration/src/FSharp.Editor/CodeFixes/ConvertToAnonymousRecord.fs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,11 @@ type internal ConvertToAnonymousRecordCodeFixProvider [<ImportingConstructor>] (
2121
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this)
2222

2323
interface IFSharpCodeFixProvider with
24-
member _.GetCodeFixIfAppliesAsync document span =
24+
member _.GetCodeFixIfAppliesAsync context =
2525
cancellableTask {
26-
let! cancellationToken = CancellableTask.getCurrentCancellationToken ()
27-
28-
let! parseResults = document.GetFSharpParseResultsAsync(nameof (ConvertToAnonymousRecordCodeFixProvider))
29-
30-
let! sourceText = document.GetTextAsync(cancellationToken)
31-
32-
let errorRange =
33-
RoslynHelpers.TextSpanToFSharpRange(document.FilePath, span, sourceText)
26+
let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof ConvertToAnonymousRecordCodeFixProvider)
27+
let! sourceText = context.GetSourceTextAsync()
28+
let! errorRange = context.GetErrorRangeAsync()
3429

3530
return
3631
parseResults.TryRangeOfRecordExpressionContainingPos errorRange.Start

vsintegration/src/FSharp.Editor/CodeFixes/IFSharpCodeFix.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Microsoft.VisualStudio.FSharp.Editor
44

5-
open Microsoft.CodeAnalysis
5+
open Microsoft.CodeAnalysis.CodeFixes
66
open Microsoft.CodeAnalysis.Text
77

88
open CancellableTasks
@@ -15,4 +15,4 @@ type FSharpCodeFix =
1515
}
1616

1717
type IFSharpCodeFixProvider =
18-
abstract member GetCodeFixIfAppliesAsync: document: Document -> span: TextSpan -> CancellableTask<FSharpCodeFix option>
18+
abstract member GetCodeFixIfAppliesAsync: context: CodeFixContext -> CancellableTask<FSharpCodeFix option>

vsintegration/src/FSharp.Editor/CodeFixes/MakeOuterBindingRecursive.fs

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,45 +9,40 @@ open System.Collections.Immutable
99
open Microsoft.CodeAnalysis.Text
1010
open Microsoft.CodeAnalysis.CodeFixes
1111

12+
open CancellableTasks
13+
1214
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.MakeOuterBindingRecursive); Shared>]
1315
type internal MakeOuterBindingRecursiveCodeFixProvider [<ImportingConstructor>] () =
1416
inherit CodeFixProvider()
1517

16-
override _.FixableDiagnosticIds = ImmutableArray.Create("FS0039")
17-
18-
override _.RegisterCodeFixesAsync context =
19-
asyncMaybe {
20-
let! parseResults =
21-
context.Document.GetFSharpParseResultsAsync(nameof (MakeOuterBindingRecursiveCodeFixProvider))
22-
|> liftAsync
23-
24-
let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
25-
26-
let diagnosticRange =
27-
RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText)
28-
29-
do! Option.guard (parseResults.IsPosContainedInApplication diagnosticRange.Start)
30-
31-
let! outerBindingRange = parseResults.TryRangeOfNameOfNearestOuterBindingContainingPos diagnosticRange.Start
32-
let! outerBindingNameSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, outerBindingRange)
33-
34-
// One last check to verify the names are the same
35-
do!
36-
Option.guard (
37-
sourceText
38-
.GetSubText(outerBindingNameSpan)
39-
.ContentEquals(sourceText.GetSubText(context.Span))
40-
)
41-
42-
let title =
43-
String.Format(SR.MakeOuterBindingRecursive(), sourceText.GetSubText(outerBindingNameSpan).ToString())
44-
45-
do
46-
context.RegisterFsharpFix(
47-
CodeFix.MakeOuterBindingRecursive,
48-
title,
49-
[| TextChange(TextSpan(outerBindingNameSpan.Start, 0), "rec ") |]
50-
)
51-
}
52-
|> Async.Ignore
53-
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
18+
override _.FixableDiagnosticIds = ImmutableArray.Create "FS0039"
19+
20+
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this
21+
22+
interface IFSharpCodeFixProvider with
23+
member _.GetCodeFixIfAppliesAsync context =
24+
cancellableTask {
25+
let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof MakeOuterBindingRecursiveCodeFixProvider)
26+
let! sourceText = context.GetSourceTextAsync()
27+
let! diagnosticRange = context.GetErrorRangeAsync()
28+
29+
if not <| parseResults.IsPosContainedInApplication diagnosticRange.Start then
30+
return None
31+
else
32+
return
33+
parseResults.TryRangeOfNameOfNearestOuterBindingContainingPos diagnosticRange.Start
34+
|> Option.bind (fun bindingRange -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, bindingRange))
35+
|> Option.filter (fun bindingSpan ->
36+
sourceText
37+
.GetSubText(bindingSpan)
38+
.ContentEquals(sourceText.GetSubText context.Span))
39+
|> Option.map (fun bindingSpan ->
40+
let title =
41+
String.Format(SR.MakeOuterBindingRecursive(), sourceText.GetSubText(bindingSpan).ToString())
42+
43+
{
44+
Name = CodeFix.MakeOuterBindingRecursive
45+
Message = title
46+
Changes = [ TextChange(TextSpan(bindingSpan.Start, 0), "rec ") ]
47+
})
48+
}

0 commit comments

Comments
 (0)