Skip to content

Commit 6838eab

Browse files
authored
Fixing code fixes, part 5 (#15663)
* Fixing code fixes, part 5 * Up * fantomas * Update CodeFixHelpers.fs * fantomas * Update RenameUnusedValue.fs * action * lambda * up * Update CodeFixHelpers.fs * ftms
1 parent 74a8c45 commit 6838eab

34 files changed

+883
-340
lines changed

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

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,32 @@
33
namespace Microsoft.VisualStudio.FSharp.Editor
44

55
open System.Composition
6-
open System.Threading
7-
open System.Threading.Tasks
86
open System.Collections.Immutable
97

10-
open Microsoft.CodeAnalysis
118
open Microsoft.CodeAnalysis.Text
129
open Microsoft.CodeAnalysis.CodeFixes
1310

11+
open CancellableTasks
12+
1413
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.AddNewKeyword); Shared>]
1514
type internal AddNewKeywordCodeFixProvider() =
1615
inherit CodeFixProvider()
1716

1817
static let title = SR.AddNewKeyword()
19-
override _.FixableDiagnosticIds = ImmutableArray.Create "FS0760"
2018

21-
member this.GetChanges(_document: Document, diagnostics: ImmutableArray<Diagnostic>, _ct: CancellationToken) =
22-
backgroundTask {
23-
24-
let changes =
25-
diagnostics
26-
|> Seq.map (fun d -> TextChange(TextSpan(d.Location.SourceSpan.Start, 0), "new "))
19+
override _.FixableDiagnosticIds = ImmutableArray.Create "FS0760"
2720

28-
return changes
29-
}
21+
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this
3022

31-
override this.RegisterCodeFixesAsync ctx : Task =
32-
backgroundTask {
33-
let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
34-
ctx.RegisterFsharpFix(CodeFix.AddNewKeyword, title, changes)
35-
}
23+
override this.GetFixAllProvider() = this.RegisterFsharpFixAll()
3624

37-
override this.GetFixAllProvider() =
38-
CodeFixHelpers.createFixAllProvider CodeFix.AddNewKeyword this.GetChanges
25+
interface IFSharpCodeFixProvider with
26+
member _.GetCodeFixIfAppliesAsync context =
27+
CancellableTask.singleton (
28+
Some
29+
{
30+
Name = CodeFix.AddNewKeyword
31+
Message = title
32+
Changes = [ TextChange(TextSpan(context.Span.Start, 0), "new ") ]
33+
}
34+
)

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

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace Microsoft.VisualStudio.FSharp.Editor
44

5+
open System
56
open System.Threading
7+
open System.Threading.Tasks
68
open System.Collections.Immutable
79
open System.Diagnostics
810

@@ -17,7 +19,7 @@ open CancellableTasks
1719

1820
[<RequireQualifiedAccess>]
1921
module internal CodeFixHelpers =
20-
let private reportCodeFixTelemetry
22+
let reportCodeFixTelemetry
2123
(diagnostics: ImmutableArray<Diagnostic>)
2224
(doc: Document)
2325
(staticName: string)
@@ -106,3 +108,62 @@ module internal CodeFixExtensions =
106108
let! sourceText = ctx.GetSourceTextAsync()
107109
return RoslynHelpers.TextSpanToFSharpRange(ctx.Document.FilePath, ctx.Span, sourceText)
108110
}
111+
112+
// This cannot be an extension on the code fix context
113+
// because the underlying GetFixAllProvider method doesn't take the context in.
114+
#nowarn "3511" // state machine not statically compilable
115+
116+
[<AutoOpen>]
117+
module IFSharpCodeFixProviderExtensions =
118+
type IFSharpCodeFixProvider with
119+
120+
// this is not used anywhere, it's just needed to create the context
121+
static member private Action =
122+
Action<CodeActions.CodeAction, ImmutableArray<Diagnostic>>(fun _ _ -> ())
123+
124+
member private provider.FixAllAsync (fixAllCtx: FixAllContext) (doc: Document) (allDiagnostics: ImmutableArray<Diagnostic>) =
125+
cancellableTask {
126+
let sw = Stopwatch.StartNew()
127+
128+
let! token = CancellableTask.getCurrentCancellationToken ()
129+
let! sourceText = doc.GetTextAsync token
130+
131+
let! codeFixOpts =
132+
allDiagnostics
133+
// The distiction is to avoid collisions of compiler and analyzer diags.
134+
// See: https://github.com/dotnet/fsharp/issues/15620
135+
// TODO: this crops the diags on a very high level,
136+
// a proper fix is needed.
137+
|> Seq.distinctBy (fun d -> d.Id, d.Location)
138+
|> Seq.map (fun diag -> CodeFixContext(doc, diag, IFSharpCodeFixProvider.Action, token))
139+
|> Seq.map (fun context -> provider.GetCodeFixIfAppliesAsync context)
140+
|> Seq.map (fun task -> task token)
141+
|> Task.WhenAll
142+
143+
let codeFixes = codeFixOpts |> Seq.choose id
144+
let changes = codeFixes |> Seq.collect (fun codeFix -> codeFix.Changes)
145+
let updatedDoc = doc.WithText(sourceText.WithChanges changes)
146+
147+
let name =
148+
codeFixes
149+
|> Seq.tryHead
150+
|> Option.map (fun fix -> fix.Name)
151+
// Now, I cannot see this happening.
152+
// How could a bulk code fix get activated for zero changes?
153+
// But since that's for telemetry purposes,
154+
// let's be on the safe side.
155+
|> Option.defaultValue "UnknownCodeFix"
156+
157+
CodeFixHelpers.reportCodeFixTelemetry
158+
allDiagnostics
159+
updatedDoc
160+
name
161+
[| "scope", fixAllCtx.Scope.ToString(); "elapsedMs", sw.ElapsedMilliseconds |]
162+
163+
return updatedDoc
164+
}
165+
166+
member provider.RegisterFsharpFixAll() =
167+
FixAllProvider.Create(fun fixAllCtx doc allDiagnostics ->
168+
provider.FixAllAsync fixAllCtx doc allDiagnostics
169+
|> CancellableTask.start fixAllCtx.CancellationToken)

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

Lines changed: 16 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,77 +4,34 @@ namespace Microsoft.VisualStudio.FSharp.Editor
44

55
open System.Composition
66
open System.Collections.Immutable
7-
open System.Threading
8-
open System.Threading.Tasks
97

10-
open Microsoft.CodeAnalysis
118
open Microsoft.CodeAnalysis.Text
129
open Microsoft.CodeAnalysis.CodeFixes
13-
open FSharp.Compiler.Diagnostics
14-
15-
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.FixIndexerAccess); Shared>]
16-
type internal LegacyFixAddDotToIndexerAccessCodeFixProvider() =
17-
inherit CodeFixProvider()
18-
19-
static let title = CompilerDiagnostics.GetErrorMessage FSharpDiagnosticKind.AddIndexerDot
20-
21-
override _.FixableDiagnosticIds = ImmutableArray.Create("FS3217")
22-
23-
override _.RegisterCodeFixesAsync context : Task =
24-
async {
25-
let! sourceText = context.Document.GetTextAsync() |> Async.AwaitTask
2610

27-
context.Diagnostics
28-
|> Seq.iter (fun diagnostic ->
29-
30-
let span, replacement =
31-
try
32-
let mutable span = context.Span
33-
34-
let notStartOfBracket (span: TextSpan) =
35-
let t = sourceText.GetSubText(TextSpan(span.Start, span.Length + 1))
36-
t.[t.Length - 1] <> '['
37-
38-
// skip all braces and blanks until we find [
39-
while span.End < sourceText.Length && notStartOfBracket span do
40-
span <- TextSpan(span.Start, span.Length + 1)
41-
42-
span, sourceText.GetSubText(span).ToString()
43-
with _ ->
44-
context.Span, sourceText.GetSubText(context.Span).ToString()
11+
open FSharp.Compiler.Diagnostics
4512

46-
do
47-
context.RegisterFsharpFix(
48-
CodeFix.FixIndexerAccess,
49-
title,
50-
[| TextChange(span, replacement.TrimEnd() + ".") |],
51-
ImmutableArray.Create(diagnostic)
52-
))
53-
}
54-
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
13+
open CancellableTasks
5514

5615
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.RemoveIndexerDotBeforeBracket); Shared>]
57-
type internal FsharpFixRemoveDotFromIndexerAccessOptIn() as this =
16+
type internal RemoveDotFromIndexerAccessOptInCodeFixProvider() =
5817
inherit CodeFixProvider()
5918

6019
static let title =
6120
CompilerDiagnostics.GetErrorMessage FSharpDiagnosticKind.RemoveIndexerDot
6221

63-
member this.GetChanges(_document: Document, diagnostics: ImmutableArray<Diagnostic>, _ct: CancellationToken) =
64-
backgroundTask {
65-
let changes =
66-
diagnostics |> Seq.map (fun x -> TextChange(x.Location.SourceSpan, ""))
67-
68-
return changes
69-
}
22+
override _.FixableDiagnosticIds = ImmutableArray.Create "FS3366"
7023

71-
override _.FixableDiagnosticIds = ImmutableArray.Create("FS3366")
24+
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this
7225

73-
override _.RegisterCodeFixesAsync ctx : Task =
74-
backgroundTask {
75-
let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
76-
ctx.RegisterFsharpFix(CodeFix.RemoveIndexerDotBeforeBracket, title, changes)
77-
}
26+
override this.GetFixAllProvider() = this.RegisterFsharpFixAll()
7827

79-
override this.GetFixAllProvider() =
80-
CodeFixHelpers.createFixAllProvider CodeFix.RemoveIndexerDotBeforeBracket this.GetChanges
28+
interface IFSharpCodeFixProvider with
29+
member _.GetCodeFixIfAppliesAsync context =
30+
CancellableTask.singleton (
31+
Some
32+
{
33+
Name = CodeFix.RemoveIndexerDotBeforeBracket
34+
Message = title
35+
Changes = [ TextChange(context.Span, "") ]
36+
}
37+
)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2+
3+
namespace Microsoft.VisualStudio.FSharp.Editor
4+
5+
open System.Composition
6+
open System.Collections.Immutable
7+
open System.Threading.Tasks
8+
9+
open Microsoft.CodeAnalysis.Text
10+
open Microsoft.CodeAnalysis.CodeFixes
11+
open FSharp.Compiler.Diagnostics
12+
13+
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.FixIndexerAccess); Shared>]
14+
type internal LegacyFixAddDotToIndexerAccessCodeFixProvider() =
15+
inherit CodeFixProvider()
16+
17+
static let title = CompilerDiagnostics.GetErrorMessage FSharpDiagnosticKind.AddIndexerDot
18+
19+
override _.FixableDiagnosticIds = ImmutableArray.Create("FS3217")
20+
21+
override _.RegisterCodeFixesAsync context : Task =
22+
async {
23+
let! sourceText = context.Document.GetTextAsync() |> Async.AwaitTask
24+
25+
context.Diagnostics
26+
|> Seq.iter (fun diagnostic ->
27+
28+
let span, replacement =
29+
try
30+
let mutable span = context.Span
31+
32+
let notStartOfBracket (span: TextSpan) =
33+
let t = sourceText.GetSubText(TextSpan(span.Start, span.Length + 1))
34+
t.[t.Length - 1] <> '['
35+
36+
// skip all braces and blanks until we find [
37+
while span.End < sourceText.Length && notStartOfBracket span do
38+
span <- TextSpan(span.Start, span.Length + 1)
39+
40+
span, sourceText.GetSubText(span).ToString()
41+
with _ ->
42+
context.Span, sourceText.GetSubText(context.Span).ToString()
43+
44+
do
45+
context.RegisterFsharpFix(
46+
CodeFix.FixIndexerAccess,
47+
title,
48+
[| TextChange(span, replacement.TrimEnd() + ".") |],
49+
ImmutableArray.Create(diagnostic)
50+
))
51+
}
52+
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)

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

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,62 +3,56 @@
33
namespace Microsoft.VisualStudio.FSharp.Editor
44

55
open System.Composition
6-
open System.Threading
76
open System.Threading.Tasks
87
open System.Collections.Immutable
98

10-
open Microsoft.CodeAnalysis
119
open Microsoft.CodeAnalysis.Text
1210
open Microsoft.CodeAnalysis.CodeFixes
1311

1412
open FSharp.Compiler.EditorServices
1513

16-
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.RemoveSuperfluousCapture); Shared>]
17-
type internal RemoveSuperflousCaptureForUnionCaseWithNoDataCodeFixProvider [<ImportingConstructor>] () =
14+
open CancellableTasks
1815

16+
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.RemoveSuperfluousCapture); Shared>]
17+
type internal RemoveSuperfluousCaptureForUnionCaseWithNoDataCodeFixProvider [<ImportingConstructor>] () =
1918
inherit CodeFixProvider()
2019

2120
static let title = SR.RemoveUnusedBinding()
22-
override _.FixableDiagnosticIds = ImmutableArray.Create("FS0725", "FS3548")
23-
24-
member this.GetChanges(document: Document, diagnostics: ImmutableArray<Diagnostic>, ct: CancellationToken) =
25-
backgroundTask {
2621

27-
let! sourceText = document.GetTextAsync(ct)
28-
let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(CodeFix.RemoveSuperfluousCapture)
22+
override _.FixableDiagnosticIds = ImmutableArray.Create("FS0725", "FS3548") // cannot happen at once
2923

30-
let changes =
31-
seq {
32-
for d in diagnostics do
33-
let textSpan = d.Location.SourceSpan
34-
let m = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText)
35-
let classifications = checkResults.GetSemanticClassification(Some m)
24+
override this.RegisterCodeFixesAsync ctx =
25+
if ctx.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then
26+
ctx.RegisterFsharpFix this
27+
else
28+
Task.CompletedTask
3629

37-
let unionCaseItem =
38-
classifications
39-
|> Array.tryFind (fun c -> c.Type = SemanticClassificationType.UnionCase)
30+
override this.GetFixAllProvider() = this.RegisterFsharpFixAll()
4031

41-
match unionCaseItem with
42-
| None -> ()
43-
| Some unionCaseItem ->
44-
// The error/warning captures entire pattern match, like "Ns.Type.DuName bindingName". We want to keep type info when suggesting a replacement, and only remove "bindingName".
45-
let typeInfoLength = unionCaseItem.Range.EndColumn - m.StartColumn
32+
interface IFSharpCodeFixProvider with
33+
member _.GetCodeFixIfAppliesAsync context =
34+
cancellableTask {
35+
let! sourceText = context.GetSourceTextAsync()
36+
let! _, checkResults = context.Document.GetFSharpParseAndCheckResultsAsync CodeFix.RemoveSuperfluousCapture
4637

47-
let reminderSpan =
48-
new TextSpan(textSpan.Start + typeInfoLength, textSpan.Length - typeInfoLength)
38+
let m =
39+
RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText)
4940

50-
yield TextChange(reminderSpan, "")
51-
}
41+
let classifications = checkResults.GetSemanticClassification(Some m)
5242

53-
return changes
54-
}
43+
return
44+
classifications
45+
|> Array.tryFind (fun c -> c.Type = SemanticClassificationType.UnionCase)
46+
|> Option.map (fun unionCaseItem ->
47+
// The error/warning captures entire pattern match, like "Ns.Type.DuName bindingName". We want to keep type info when suggesting a replacement, and only remove "bindingName".
48+
let typeInfoLength = unionCaseItem.Range.EndColumn - m.StartColumn
5549

56-
override this.RegisterCodeFixesAsync ctx : Task =
57-
backgroundTask {
58-
if ctx.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then
59-
let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
60-
ctx.RegisterFsharpFix(CodeFix.RemoveSuperfluousCapture, title, changes)
61-
}
50+
let reminderSpan =
51+
TextSpan(context.Span.Start + typeInfoLength, context.Span.Length - typeInfoLength)
6252

63-
override this.GetFixAllProvider() =
64-
CodeFixHelpers.createFixAllProvider CodeFix.RemoveSuperfluousCapture this.GetChanges
53+
{
54+
Name = CodeFix.RemoveSuperfluousCapture
55+
Message = title
56+
Changes = [ TextChange(reminderSpan, "") ]
57+
})
58+
}

0 commit comments

Comments
 (0)