From a8c967ae63e57f099f538b029ac8cf18cb798ce0 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 5 Apr 2023 10:58:06 +0200 Subject: [PATCH 01/11] temporary stash --- src/Compiler/Checking/CheckExpressions.fs | 2 +- .../FSharp.Editor/CodeFix/CodeFixHelpers.fs | 4 +- .../FSharp.Editor/CodeFix/FixIndexerAccess.fs | 7 +++ .../CodeFix/RemoveUnusedOpens.fs | 55 ++++++++----------- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index a1cb3b8063b..c7151f74acd 100644 --- a/src/Compiler/Checking/CheckExpressions.fs +++ b/src/Compiler/Checking/CheckExpressions.fs @@ -6103,7 +6103,7 @@ and RewriteRangeExpr synExpr = // a..b..c (parsed as (a..b)..c ) | SynExpr.IndexRange(Some (SynExpr.IndexRange(Some synExpr1, _, Some synStepExpr, _, _, _)), _, Some synExpr2, _m1, _m2, mWhole) -> let mWhole = mWhole.MakeSynthetic() - Some (mkSynTrifix mWhole ".. .." synExpr1 synStepExpr synExpr2) + Some (mkSynTrifix mWhole "DisableOptimizationsValidator " synExpr1 synStepExpr synExpr2) // a..b | SynExpr.IndexRange (Some synExpr1, mOperator, Some synExpr2, _m1, _m2, mWhole) -> let otherExpr = diff --git a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs index 37b05589e9c..59bcb5b976f 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs @@ -3,7 +3,9 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Threading +open System.Threading.Tasks +open Microsoft open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeActions @@ -37,4 +39,4 @@ module internal CodeFixExtensions = let replaceCodeFix = CodeFixHelpers.createTextChangeCodeFix (fixName, context, (fun () -> asyncMaybe.Return [| fixChange |])) - context.RegisterCodeFix(replaceCodeFix, this.GetPrunedDiagnostics(context)) + context.RegisterCodeFix(replaceCodeFix, this.GetPrunedDiagnostics(context)) \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs b/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs index bc6d1bfb820..36f0175ad51 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs @@ -75,3 +75,10 @@ type internal FsharpFixRemoveDotFromIndexerAccessOptIn() as this = if not relevantDiagnostics.IsEmpty then this.RegisterFix(context, fixName, TextChange(context.Span, "")) } + + override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> + task{ + let changes = allDiagnostics |> Seq.map (fun x -> TextChange(x.Location.SourceSpan,"")) + let! text = doc.GetTextAsync(fixAllCtx.CancellationToken) + return doc.WithText(text.WithChanges(changes)) + } ) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs index e748c3e168b..5cfc0991c13 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs @@ -3,49 +3,42 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition +open System.Threading open System.Threading.Tasks +open System.Collections.Immutable +open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open Microsoft.CodeAnalysis.CodeActions open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics -open FSharp.Compiler.Text - [] type internal FSharpRemoveUnusedOpensCodeFixProvider [] () = inherit CodeFixProvider() - let fixableDiagnosticIds = - [ FSharpIDEDiagnosticIds.RemoveUnnecessaryImportsDiagnosticId ] - - override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds - - override _.RegisterCodeFixesAsync context : Task = - asyncMaybe { - let document = context.Document - let! sourceText = document.GetTextAsync() - let! unusedOpens = UnusedOpensDiagnosticAnalyzer.GetUnusedOpenRanges(document) + static let title = SR.RemoveUnusedOpens() - let changes = - unusedOpens - |> List.map (fun m -> - let span = sourceText.Lines.[Line.toZ m.StartLine].SpanIncludingLineBreak - TextChange(span, "")) - |> List.toArray + override _.FixableDiagnosticIds = ImmutableArray.Create FSharpIDEDiagnosticIds.RemoveUnnecessaryImportsDiagnosticId - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id) - |> Seq.toImmutableArray - - let title = SR.RemoveUnusedOpens() - - let codefix = - CodeFixHelpers.createTextChangeCodeFix (title, context, (fun () -> asyncMaybe.Return changes)) + member this.GetChangedDocument(document : Document, diagnostics : ImmutableArray, ct : CancellationToken ) = + task{ + let! sourceText = document.GetTextAsync(ct) + let changes = + diagnostics + |> Seq.map (fun d -> sourceText.Lines.GetLineFromPosition(d.Location.SourceSpan.Start).SpanIncludingLineBreak) + |> Seq.map (fun span -> TextChange(span, "")) + + return document.WithText(sourceText.WithChanges(changes)) + } - context.RegisterCodeFix(codefix, diagnostics) + override this.RegisterCodeFixesAsync ctx : Task = + task { + let codeAction = CodeAction.Create(title, (fun ct -> + this.GetChangedDocument(ctx.Document,ctx.Diagnostics, ct)) + , title) + ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) - override _.GetFixAllProvider() = WellKnownFixAllProviders.BatchFixer + override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> + this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) \ No newline at end of file From bfae9d1ea14c02c1ad1c35018c054d312b4e28b8 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 6 Apr 2023 14:26:21 +0200 Subject: [PATCH 02/11] pick which codefixes should be turned into having bulk support --- .../CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs | 3 +++ .../CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs | 3 +++ vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs | 3 +++ .../src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs | 3 +++ vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs | 3 +++ vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs | 3 +++ 6 files changed, 18 insertions(+) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs index d6dcfd30d34..e04d67bbf0c 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs @@ -36,3 +36,6 @@ type internal FSharpAddNewKeywordCodeFixProvider() = context.RegisterCodeFix(codeFix, diagnostics) } |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + + override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> + this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs index bc49d1f13d3..513a705b662 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs @@ -50,3 +50,6 @@ type internal RemoveSuperflousCaptureForUnionCaseWithNoDataProvider [ Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + + override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> + this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs index e290cb7173e..385fe3bfe80 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs @@ -68,3 +68,6 @@ type internal FSharpRemoveUnusedBindingCodeFixProvider [] } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + + override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> + this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs b/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs index 68ca143aef5..cbc583ee1bf 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs @@ -72,3 +72,6 @@ type internal FSharpRenameParamToMatchSignature [] () = } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + + override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> + this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs b/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs index 06a99d9538f..8fea932c46a 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs @@ -91,3 +91,6 @@ type internal FSharpRenameUnusedValueCodeFixProvider [] () } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + + override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> + this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs b/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs index 34bbfab9d93..5acff3552a8 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs @@ -36,3 +36,6 @@ type internal FSharpSimplifyNameCodeFixProvider() = context.RegisterCodeFix(codefix, ImmutableArray.Create(diagnostic)) } |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + + override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> + this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) From c400baeb5fb9a2c57abfa08276c2b51782a9ef63 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Fri, 7 Apr 2023 13:04:55 +0200 Subject: [PATCH 03/11] format --- vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs index ad8da3dbdeb..98b20b4e9b2 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs @@ -36,6 +36,7 @@ type internal FSharpRemoveUnusedOpensCodeFixProvider [] () override this.RegisterCodeFixesAsync ctx : Task = task { + let codeAction = CodeAction.Create(title, (fun ct -> this.GetChangedDocument(ctx.Document,ctx.Diagnostics, ct)) , title) From 7b3a4b22038ab86d973c37d1d4bb75e927c2b675 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 13 Apr 2023 16:50:35 +0200 Subject: [PATCH 04/11] bulk codefix refactor --- ...eywordToDisposableConstructorInvocation.fs | 43 ++-- .../FSharp.Editor/CodeFix/CodeFixHelpers.fs | 22 +- .../FSharp.Editor/CodeFix/FixIndexerAccess.fs | 22 +- ...SuperflousCaptureForUnionCaseWithNoData.fs | 67 +++--- .../CodeFix/RemoveUnusedBinding.fs | 90 ++++---- .../CodeFix/RemoveUnusedOpens.fs | 39 ++-- .../CodeFix/RenameParamToMatchSignature.fs | 122 +++++----- .../CodeFix/RenameUnusedValue.fs | 216 ++++++++++++------ .../src/FSharp.Editor/CodeFix/SimplifyName.fs | 61 +++-- .../src/FSharp.Editor/Common/Constants.fs | 3 + 10 files changed, 421 insertions(+), 264 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs index e04d67bbf0c..fbfe775db7e 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs @@ -3,39 +3,42 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition +open System.Threading open System.Threading.Tasks +open System.Collections.Immutable +open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open Microsoft.CodeAnalysis.CodeActions [] type internal FSharpAddNewKeywordCodeFixProvider() = inherit CodeFixProvider() - static let fixableDiagnosticIds = set [ "FS0760" ] + static let title = SR.AddNewKeyword() + override _.FixableDiagnosticIds = ImmutableArray.Create "FS0760" - override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds + member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + backgroundTask { + let! sourceText = document.GetTextAsync(ct) - override _.RegisterCodeFixesAsync context : Task = - async { - let title = SR.AddNewKeyword() + let changes = + diagnostics + |> Seq.map (fun d -> TextChange(TextSpan(d.Location.SourceSpan.Start, 0), "new ")) - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray + CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.AddNewKeyword + return document.WithText(sourceText.WithChanges(changes)) + } + + override this.RegisterCodeFixesAsync ctx : Task = + backgroundTask { - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.AddNewKeyword, - title, - context, - (fun () -> asyncMaybe.Return [| TextChange(TextSpan(context.Span.Start, 0), "new ") |]) - ) + let codeAction = + CodeAction.Create(title, (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), title) - context.RegisterCodeFix(codeFix, diagnostics) + ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) } - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) - override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> - this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) + override this.GetFixAllProvider() = + FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs index 042038b578d..d46a44db377 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs @@ -4,8 +4,10 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Threading open System.Threading.Tasks +open System.Collections.Immutable open Microsoft +open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeActions @@ -13,6 +15,24 @@ open Microsoft.VisualStudio.FSharp.Editor.Telemetry [] module internal CodeFixHelpers = + let reportCodeFixRecommendation (diagnostics: ImmutableArray) (doc: Document) (staticName: string) = + let ids = + diagnostics |> Seq.map (fun d -> d.Id) |> Seq.distinct |> String.concat "," + + let props: (string * obj) list = + [ + "name", staticName + "ids", ids + // The following can help building a unique but anonymized codefix target: + // #projectid#documentid#span + // Then we can check if the codefix was actually activated after its creation. + "context.document.project.id", doc.Project.Id.Id.ToString() + "context.document.id", doc.Id.Id.ToString() + "context.diagnostics.count", diagnostics.Length + ] + + TelemetryReporter.reportEvent "codefixrecommendation" props + let createTextChangeCodeFix ( name: string, @@ -72,4 +92,4 @@ module internal CodeFixExtensions = let replaceCodeFix = CodeFixHelpers.createTextChangeCodeFix (name, title, context, (fun () -> asyncMaybe.Return [| fixChange |])) - context.RegisterCodeFix(replaceCodeFix, this.GetPrunedDiagnostics(context)) \ No newline at end of file + context.RegisterCodeFix(replaceCodeFix, this.GetPrunedDiagnostics(context)) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs b/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs index 20a1e8419d3..3f9ec29897d 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs @@ -71,15 +71,17 @@ type internal FsharpFixRemoveDotFromIndexerAccessOptIn() as this = override _.RegisterCodeFixesAsync context : Task = backgroundTask { - let relevantDiagnostics = this.GetPrunedDiagnostics(context) - - if not relevantDiagnostics.IsEmpty then - this.RegisterFix(CodeFix.RemoveIndexerDotBeforeBracket, title, context, TextChange(context.Span, "")) + this.RegisterFix(CodeFix.RemoveIndexerDotBeforeBracket, title, context, TextChange(context.Span, "")) } - override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> - task{ - let changes = allDiagnostics |> Seq.map (fun x -> TextChange(x.Location.SourceSpan,"")) - let! text = doc.GetTextAsync(fixAllCtx.CancellationToken) - return doc.WithText(text.WithChanges(changes)) - } ) + override this.GetFixAllProvider() = + FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> + backgroundTask { + let changes = + allDiagnostics |> Seq.map (fun x -> TextChange(x.Location.SourceSpan, "")) + + let! text = doc.GetTextAsync(fixAllCtx.CancellationToken) + + CodeFixHelpers.reportCodeFixRecommendation allDiagnostics doc CodeFix.RemoveIndexerDotBeforeBracket + return doc.WithText(text.WithChanges(changes)) + }) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs index 513a705b662..fb119716953 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs @@ -3,8 +3,12 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition +open System.Threading open System.Threading.Tasks +open System.Collections.Immutable +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.CodeActions open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes @@ -15,41 +19,50 @@ type internal RemoveSuperflousCaptureForUnionCaseWithNoDataProvider [, ct: CancellationToken) = + backgroundTask { - let document = context.Document - let! sourceText = document.GetTextAsync(context.CancellationToken) + let! sourceText = document.GetTextAsync(ct) + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(CodeFix.RemoveSuperfluousCapture) - let! _, checkResults = - document.GetFSharpParseAndCheckResultsAsync(nameof (RemoveSuperflousCaptureForUnionCaseWithNoDataProvider)) - |> liftAsync + let changes = + seq { + for d in diagnostics do + let textSpan = d.Location.SourceSpan + let m = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText) + let classifications = checkResults.GetSemanticClassification(Some m) - let m = - RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) + let unionCaseItem = + classifications + |> Array.tryFind (fun c -> c.Type = SemanticClassificationType.UnionCase) - let classifications = checkResults.GetSemanticClassification(Some m) + match unionCaseItem with + | None -> () + | Some unionCaseItem -> + // 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". + let typeInfoLength = unionCaseItem.Range.EndColumn - m.StartColumn - let unionCaseItem = - classifications - |> Array.tryFind (fun c -> c.Type = SemanticClassificationType.UnionCase) + let reminderSpan = + new TextSpan(textSpan.Start + typeInfoLength, textSpan.Length - typeInfoLength) - match unionCaseItem with - | None -> () - | Some unionCaseItem -> - // 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". - let typeInfoLength = unionCaseItem.Range.EndColumn - m.StartColumn + yield TextChange(reminderSpan, "") + } - let reminderSpan = - new TextSpan(context.Span.Start + typeInfoLength, context.Span.Length - typeInfoLength) + CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.RemoveSuperfluousCapture + return document.WithText(sourceText.WithChanges(changes)) + } + + override this.RegisterCodeFixesAsync ctx : Task = + backgroundTask { + if ctx.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then + let codeAction = + CodeAction.Create(title, (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), title) - this.RegisterFix(CodeFix.RemoveSuperfluousCapture, SR.RemoveUnusedBinding(), context, TextChange(reminderSpan, "")) + ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) - override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> - this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) + override this.GetFixAllProvider() = + FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs index 385fe3bfe80..b1468dd4731 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs @@ -4,70 +4,74 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System open System.Composition +open System.Threading open System.Threading.Tasks +open System.Collections.Immutable +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.CodeActions open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open FSharp.Compiler.EditorServices + [] type internal FSharpRemoveUnusedBindingCodeFixProvider [] () = inherit CodeFixProvider() - let fixableDiagnosticIds = set [ "FS1182" ] - - override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds + static let title = SR.RemoveUnusedBinding() + override _.FixableDiagnosticIds = ImmutableArray.Create("FS1182") - override _.RegisterCodeFixesAsync context : Task = - asyncMaybe { - // Don't show code fixes for unused values, even if they are compiler-generated. - do! Option.guard context.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled + member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + backgroundTask { - let document = context.Document - let! sourceText = document.GetTextAsync(context.CancellationToken) + let! sourceText = document.GetTextAsync(ct) + let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpRemoveUnusedBindingCodeFixProvider)) - let! parseResults = - context.Document.GetFSharpParseResultsAsync(nameof (FSharpRemoveUnusedBindingCodeFixProvider)) - |> liftAsync + let changes = + seq { + for d in diagnostics do + let textSpan = d.Location.SourceSpan - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray + let symbolRange = + RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText) - let symbolRange = - RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) + let spanOfBindingOpt = + parseResults.TryRangeOfBindingWithHeadPatternWithPos(symbolRange.Start) + |> Option.bind (fun rangeOfBinding -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, rangeOfBinding)) - let! rangeOfBinding = parseResults.TryRangeOfBindingWithHeadPatternWithPos(symbolRange.Start) - let! spanOfBinding = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, rangeOfBinding) + match spanOfBindingOpt with + | Some spanOfBinding -> + let keywordEndColumn = + let rec loop ch pos = + if not (Char.IsWhiteSpace(ch)) then + pos + else + loop sourceText.[pos - 1] (pos - 1) - let keywordEndColumn = - let rec loop ch pos = - if not (Char.IsWhiteSpace(ch)) then - pos - else - loop sourceText.[pos - 1] (pos - 1) + loop sourceText.[spanOfBinding.Start - 1] (spanOfBinding.Start - 1) - loop sourceText.[spanOfBinding.Start - 1] (spanOfBinding.Start - 1) + // This is safe, since we could never have gotten here unless there was a `let` or `use` + let keywordStartColumn = keywordEndColumn - 2 + let fullSpan = TextSpan(keywordStartColumn, spanOfBinding.End - keywordStartColumn) - // This is safe, since we could never have gotten here unless there was a `let` or `use` - let keywordStartColumn = keywordEndColumn - 2 - let fullSpan = TextSpan(keywordStartColumn, spanOfBinding.End - keywordStartColumn) + yield TextChange(fullSpan, "") + | None -> () + } - let prefixTitle = SR.RemoveUnusedBinding() + CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.RemoveUnusedBinding + return document.WithText(sourceText.WithChanges(changes)) + } - let removalCodeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.RemoveUnusedBinding, - prefixTitle, - context, - (fun () -> asyncMaybe.Return [| TextChange(fullSpan, "") |]) - ) + override this.RegisterCodeFixesAsync ctx : Task = + backgroundTask { + if ctx.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then + let codeAction = + CodeAction.Create(title, (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), title) - context.RegisterCodeFix(removalCodeFix, diagnostics) + ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) - override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> - this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) + override this.GetFixAllProvider() = + FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs index 98b20b4e9b2..765912a5b9e 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs @@ -21,27 +21,36 @@ type internal FSharpRemoveUnusedOpensCodeFixProvider [] () static let title = SR.RemoveUnusedOpens() - override _.FixableDiagnosticIds = ImmutableArray.Create FSharpIDEDiagnosticIds.RemoveUnnecessaryImportsDiagnosticId + override _.FixableDiagnosticIds = + ImmutableArray.Create FSharpIDEDiagnosticIds.RemoveUnnecessaryImportsDiagnosticId - member this.GetChangedDocument(document : Document, diagnostics : ImmutableArray, ct : CancellationToken ) = - task{ + member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + backgroundTask { let! sourceText = document.GetTextAsync(ct) - let changes = - diagnostics - |> Seq.map (fun d -> sourceText.Lines.GetLineFromPosition(d.Location.SourceSpan.Start).SpanIncludingLineBreak) + + let changes = + diagnostics + |> Seq.map (fun d -> + sourceText + .Lines + .GetLineFromPosition( + d.Location.SourceSpan.Start + ) + .SpanIncludingLineBreak) |> Seq.map (fun span -> TextChange(span, "")) - + + CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.RemoveUnusedOpens return document.WithText(sourceText.WithChanges(changes)) } override this.RegisterCodeFixesAsync ctx : Task = - task { - - let codeAction = CodeAction.Create(title, (fun ct -> - this.GetChangedDocument(ctx.Document,ctx.Diagnostics, ct)) - , title) - ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) + backgroundTask { + if ctx.Document.Project.IsFSharpCodeFixesUnusedOpensEnabled then + let codeAction = + CodeAction.Create(title, (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), title) + + ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) } - override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> - this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) \ No newline at end of file + override this.GetFixAllProvider() = + FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs b/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs index cbc583ee1bf..705279c294b 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs @@ -2,14 +2,18 @@ namespace Microsoft.VisualStudio.FSharp.Editor -open System.Collections.Immutable open System.Composition +open System.Threading open System.Threading.Tasks +open System.Collections.Immutable +open System.Text.RegularExpressions +open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes - +open Microsoft.CodeAnalysis.CodeActions open Microsoft.VisualStudio.FSharp.Editor.SymbolHelpers + open FSharp.Compiler.Diagnostics open FSharp.Compiler.Tokenization.FSharpKeywords @@ -18,60 +22,64 @@ type internal FSharpRenameParamToMatchSignature [] () = inherit CodeFixProvider() - let fixableDiagnosticIds = [ "FS3218" ] - - override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds - - override _.RegisterCodeFixesAsync context : Task = - asyncMaybe { - match - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id) - |> Seq.toList - with - | [ diagnostic ] -> - let message = diagnostic.GetMessage() - - let parts = - System.Text.RegularExpressions.Regex.Match(message, ".+'(.+)'.+'(.+)'.+") - - if parts.Success then - - let diagnostics = ImmutableArray.Create diagnostic - let suggestion = parts.Groups.[1].Value - let replacement = NormalizeIdentifierBackticks suggestion - - let computeChanges () = - asyncMaybe { - let document = context.Document - let! cancellationToken = Async.CancellationToken |> liftAsync - let! sourceText = document.GetTextAsync(cancellationToken) - let! symbolUses = getSymbolUsesOfSymbolAtLocationInDocument (document, context.Span.Start) - - let changes = - [| - for symbolUse in symbolUses do - match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) with - | None -> () - | Some span -> - let textSpan = Tokenizer.fixupSpan (sourceText, span) - yield TextChange(textSpan, replacement) - |] - - return changes - } - - let title = - CompilerDiagnostics.GetErrorMessage(FSharpDiagnosticKind.ReplaceWithSuggestion suggestion) - - let codefix = - CodeFixHelpers.createTextChangeCodeFix (CodeFix.FSharpRenameParamToMatchSignature, title, context, computeChanges) - - context.RegisterCodeFix(codefix, diagnostics) - | _ -> () + let getSuggestion (d: Diagnostic) = + let parts = Regex.Match(d.GetMessage(), ".+'(.+)'.+'(.+)'.+") + + if parts.Success then + ValueSome parts.Groups.[1].Value + else + ValueNone + + override _.FixableDiagnosticIds = ImmutableArray.Create("FS3218") + + member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + backgroundTask { + let! sourceText = document.GetTextAsync(ct) + + let! changes = + seq { + for d in diagnostics do + let suggestionOpt = getSuggestion d + + match suggestionOpt with + | ValueSome suggestion -> + let replacement = NormalizeIdentifierBackticks suggestion + + async { + let! symbolUses = getSymbolUsesOfSymbolAtLocationInDocument (document, d.Location.SourceSpan.Start) + let symbolUses = symbolUses |> Option.defaultValue [||] + + return + [| + for symbolUse in symbolUses do + match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) with + | None -> () + | Some span -> + let textSpan = Tokenizer.fixupSpan (sourceText, span) + yield TextChange(textSpan, replacement) + |] + + } + | ValueNone -> () + } + |> Async.Parallel + + CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.FSharpRenameParamToMatchSignature + return document.WithText(sourceText.WithChanges(changes |> Array.concat)) + } + + override this.RegisterCodeFixesAsync ctx : Task = + backgroundTask { + let title = ctx.Diagnostics |> Seq.head |> getSuggestion + + match title with + | ValueSome title -> + let codeAction = + CodeAction.Create(title, (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), title) + + ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) + | ValueNone -> () } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) - override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> - this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) + override this.GetFixAllProvider() = + FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs b/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs index 8fea932c46a..4fb6276d06e 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs @@ -4,93 +4,177 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System open System.Composition +open System.Threading open System.Threading.Tasks +open System.Collections.Immutable +open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open Microsoft.CodeAnalysis.CodeActions +open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics open FSharp.Compiler open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Symbols open FSharp.Compiler.Syntax -[] -type internal FSharpRenameUnusedValueCodeFixProvider [] () = +module UnusedCodeFixHelper = + let getUnusedSymbol (sourceText: SourceText) (textSpan: TextSpan) (document: Document) = - inherit CodeFixProvider() + let ident = sourceText.ToString(textSpan) - let fixableDiagnosticIds = set [ "FS1182" ] + // Prefixing operators and backticked identifiers does not make sense. + // We have to use the additional check for backtickes + if PrettyNaming.IsIdentifierName ident then + asyncMaybe { + let! lexerSymbol = + document.TryFindFSharpLexerSymbolAsync(textSpan.Start, SymbolLookupKind.Greedy, false, false, CodeFix.RenameUnusedValue) - override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds + let m = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText) - override _.RegisterCodeFixesAsync context : Task = - asyncMaybe { - // Don't show code fixes for unused values, even if they are compiler-generated. - do! Option.guard context.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled + let lineText = (sourceText.Lines.GetLineFromPosition textSpan.Start).ToString() - let document = context.Document - let! sourceText = document.GetTextAsync(context.CancellationToken) - let ident = sourceText.ToString(context.Span) + let! _, checkResults = + document.GetFSharpParseAndCheckResultsAsync(CodeFix.RenameUnusedValue) + |> liftAsync - // Prefixing operators and backticked identifiers does not make sense. - // We have to use the additional check for backtickes - if PrettyNaming.IsIdentifierName ident then - let! lexerSymbol = - document.TryFindFSharpLexerSymbolAsync( - context.Span.Start, - SymbolLookupKind.Greedy, - false, - false, - nameof (FSharpRenameUnusedValueCodeFixProvider) - ) + return! checkResults.GetSymbolUseAtLocation(m.StartLine, m.EndColumn, lineText, lexerSymbol.FullIsland) - let m = - RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) + } + else + async { return None } - let lineText = (sourceText.Lines.GetLineFromPosition context.Span.Start).ToString() +[] +type internal FSharpPrefixUnusedValueWithUnderscoreCodeFixProvider [] () = - let! _, checkResults = - document.GetFSharpParseAndCheckResultsAsync(nameof (FSharpRenameUnusedValueCodeFixProvider)) - |> liftAsync + inherit CodeFixProvider() + + static let title (symbolName: string) = + String.Format(SR.PrefixValueNameWithUnderscore(), symbolName) + + override _.FixableDiagnosticIds = ImmutableArray.Create("FS1182") + + member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + backgroundTask { + let! sourceText = document.GetTextAsync(ct) + + let! changes = + seq { + for d in diagnostics do + let textSpan = d.Location.SourceSpan + + yield + async { + let! symbolUse = UnusedCodeFixHelper.getUnusedSymbol sourceText textSpan document + + return + seq { + match symbolUse with + | None -> () + | Some symbolUse -> + match symbolUse.Symbol with + | :? FSharpMemberOrFunctionOrValue -> yield TextChange(TextSpan(textSpan.Start, 0), "_") + | _ -> () + } + } + } + |> Async.Parallel + + CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.PrefixUnusedValue + return document.WithText(sourceText.WithChanges(changes |> Seq.concat)) + } + + override this.RegisterCodeFixesAsync ctx : Task = + backgroundTask { + if ctx.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then + let! sourceText = ctx.Document.GetTextAsync(ctx.CancellationToken) + let! unusedSymbol = UnusedCodeFixHelper.getUnusedSymbol sourceText ctx.Span ctx.Document + + match unusedSymbol with + | None -> () + | Some symbolUse -> + match symbolUse.Symbol with + | :? FSharpMemberOrFunctionOrValue -> + let prefixTitle = title symbolUse.Symbol.DisplayName + + let codeAction = + CodeAction.Create( + prefixTitle, + (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), + prefixTitle + ) + + ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) + | _ -> () + } + + override this.GetFixAllProvider() = + FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) + +[] +type internal FSharpRenameUnusedValueWithUnderscoreCodeFixProvider [] () = + + inherit CodeFixProvider() + + static let title (symbolName: string) = + String.Format(SR.RenameValueToUnderscore(), symbolName) + + override _.FixableDiagnosticIds = ImmutableArray.Create("FS1182") + + member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + backgroundTask { + let! sourceText = document.GetTextAsync(ct) + + let! changes = + seq { + for d in diagnostics do + let textSpan = d.Location.SourceSpan + + yield + async { + let! symbolUse = UnusedCodeFixHelper.getUnusedSymbol sourceText textSpan document + + return + seq { + match symbolUse with + | None -> () + | Some symbolUse -> + match symbolUse.Symbol with + | :? FSharpMemberOrFunctionOrValue as func when func.IsValue -> yield TextChange(textSpan, "_") + | _ -> () + } + } + } + |> Async.Parallel + + CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.RenameUnusedValue + return document.WithText(sourceText.WithChanges(changes |> Seq.concat)) + } - let! symbolUse = checkResults.GetSymbolUseAtLocation(m.StartLine, m.EndColumn, lineText, lexerSymbol.FullIsland) - let symbolName = symbolUse.Symbol.DisplayName - - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - - match symbolUse.Symbol with - | :? FSharpMemberOrFunctionOrValue as func -> - let prefixTitle = String.Format(SR.PrefixValueNameWithUnderscore(), symbolName) - - let prefixCodeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.RenameUnusedValue, - prefixTitle, - context, - (fun () -> asyncMaybe.Return [| TextChange(TextSpan(context.Span.Start, 0), "_") |]) - ) - - context.RegisterCodeFix(prefixCodeFix, diagnostics) - - if func.IsValue then - let replaceTitle = String.Format(SR.RenameValueToUnderscore(), symbolName) - - let replaceCodeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.RenameUnusedValue, - replaceTitle, - context, - (fun () -> asyncMaybe.Return [| TextChange(context.Span, "_") |]) + override this.RegisterCodeFixesAsync ctx : Task = + backgroundTask { + if ctx.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then + let! sourceText = ctx.Document.GetTextAsync(ctx.CancellationToken) + let! unusedSymbol = UnusedCodeFixHelper.getUnusedSymbol sourceText ctx.Span ctx.Document + + match unusedSymbol with + | None -> () + | Some symbolUse -> + match symbolUse.Symbol with + | :? FSharpMemberOrFunctionOrValue as func when func.IsValue -> + let prefixTitle = title symbolUse.Symbol.DisplayName + + let codeAction = + CodeAction.Create( + prefixTitle, + (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), + prefixTitle ) - context.RegisterCodeFix(replaceCodeFix, diagnostics) - | _ -> () + ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) + | _ -> () } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) - override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> - this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) + override this.GetFixAllProvider() = + FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs b/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs index 5acff3552a8..ee2594a19f7 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs @@ -3,39 +3,50 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition -open System.Collections.Immutable +open System.Threading open System.Threading.Tasks +open System.Collections.Immutable +open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open Microsoft.CodeAnalysis.CodeActions open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics +open FSharp.Compiler.Text + [] type internal FSharpSimplifyNameCodeFixProvider() = inherit CodeFixProvider() - let fixableDiagnosticId = FSharpIDEDiagnosticIds.SimplifyNamesDiagnosticId - - override _.FixableDiagnosticIds = ImmutableArray.Create(fixableDiagnosticId) - - override _.RegisterCodeFixesAsync(context: CodeFixContext) : Task = - async { - for diagnostic in context.Diagnostics |> Seq.filter (fun x -> x.Id = fixableDiagnosticId) do - let title = - match diagnostic.Properties.TryGetValue(SimplifyNameDiagnosticAnalyzer.LongIdentPropertyKey) with - | true, longIdent -> sprintf "%s '%s'" (SR.SimplifyName()) longIdent - | _ -> SR.SimplifyName() - - let codefix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.SimplifyName, - title, - context, - (fun () -> asyncMaybe.Return [| TextChange(context.Span, "") |]) - ) - - context.RegisterCodeFix(codefix, ImmutableArray.Create(diagnostic)) + + override _.FixableDiagnosticIds = + ImmutableArray.Create(FSharpIDEDiagnosticIds.SimplifyNamesDiagnosticId) + + member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + backgroundTask { + let! sourceText = document.GetTextAsync(ct) + + let changes = + diagnostics |> Seq.map (fun d -> TextChange(d.Location.SourceSpan, "")) + + CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.SimplifyName + return document.WithText(sourceText.WithChanges(changes)) + } + + override this.RegisterCodeFixesAsync ctx : Task = + backgroundTask { + let diag = ctx.Diagnostics |> Seq.head + + let title = + match diag.Properties.TryGetValue(SimplifyNameDiagnosticAnalyzer.LongIdentPropertyKey) with + | true, longIdent -> sprintf "%s '%s'" (SR.SimplifyName()) longIdent + | _ -> SR.SimplifyName() + + let codeAction = + CodeAction.Create(title, (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), title) + + ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) } - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) - override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> - this.GetChangedDocument(doc,allDiagnostics, fixAllCtx.CancellationToken) ) + override this.GetFixAllProvider() = + FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) diff --git a/vsintegration/src/FSharp.Editor/Common/Constants.fs b/vsintegration/src/FSharp.Editor/Common/Constants.fs index b2585dc3272..4a31072444a 100644 --- a/vsintegration/src/FSharp.Editor/Common/Constants.fs +++ b/vsintegration/src/FSharp.Editor/Common/Constants.fs @@ -125,6 +125,9 @@ module internal CodeFix = [] let RenameUnusedValue = "RenameUnusedValue" + [] + let PrefixUnusedValue = "PrefixUnusedValue" + [] let FixIndexerAccess = "FixIndexerAccess" From 62a79b1e990b540531fe621a35eccda73079c2f1 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 13 Apr 2023 18:32:33 +0200 Subject: [PATCH 05/11] cleanup telemetry reporting --- vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs | 3 --- 1 file changed, 3 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs index d46a44db377..1eba8bfeca9 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs @@ -23,9 +23,6 @@ module internal CodeFixHelpers = [ "name", staticName "ids", ids - // The following can help building a unique but anonymized codefix target: - // #projectid#documentid#span - // Then we can check if the codefix was actually activated after its creation. "context.document.project.id", doc.Project.Id.Id.ToString() "context.document.id", doc.Id.Id.ToString() "context.diagnostics.count", diagnostics.Length From e91d8dfb1c5b53f8a56e52cff9351370ccdf0ce6 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 13 Apr 2023 18:37:48 +0200 Subject: [PATCH 06/11] remove stupid copy paste bug --- src/Compiler/Checking/CheckExpressions.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index 8a3bf633592..7d7ba39a0e4 100644 --- a/src/Compiler/Checking/CheckExpressions.fs +++ b/src/Compiler/Checking/CheckExpressions.fs @@ -6110,7 +6110,7 @@ and RewriteRangeExpr synExpr = // a..b..c (parsed as (a..b)..c ) | SynExpr.IndexRange(Some (SynExpr.IndexRange(Some synExpr1, _, Some synStepExpr, _, _, _)), _, Some synExpr2, _m1, _m2, mWhole) -> let mWhole = mWhole.MakeSynthetic() - Some (mkSynTrifix mWhole "DisableOptimizationsValidator " synExpr1 synStepExpr synExpr2) + Some (mkSynTrifix mWhole ".. .." synExpr1 synStepExpr synExpr2) // a..b | SynExpr.IndexRange (Some synExpr1, mOperator, Some synExpr2, _m1, _m2, mWhole) -> let otherExpr = From dd2d23ac8520beb6c52704a44e877249eca26813 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 16:58:37 +0000 Subject: [PATCH 07/11] Automated command ran: fantomas Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs b/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs index 3f9ec29897d..3221b3b9d31 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs @@ -70,9 +70,7 @@ type internal FsharpFixRemoveDotFromIndexerAccessOptIn() as this = override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context : Task = - backgroundTask { - this.RegisterFix(CodeFix.RemoveIndexerDotBeforeBracket, title, context, TextChange(context.Span, "")) - } + backgroundTask { this.RegisterFix(CodeFix.RemoveIndexerDotBeforeBracket, title, context, TextChange(context.Span, "")) } override this.GetFixAllProvider() = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> From 37104950aed585628427e11d7d9e86d766a5fdae Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 18 Apr 2023 12:32:34 +0200 Subject: [PATCH 08/11] codefix refactorings --- ...eywordToDisposableConstructorInvocation.fs | 3 +-- .../FSharp.Editor/CodeFix/CodeFixHelpers.fs | 25 ++++++++++++++++--- .../FSharp.Editor/CodeFix/FixIndexerAccess.fs | 22 ++++++++-------- ...SuperflousCaptureForUnionCaseWithNoData.fs | 3 +-- .../CodeFix/RemoveUnusedBinding.fs | 3 +-- .../CodeFix/RemoveUnusedOpens.fs | 3 +-- .../CodeFix/RenameParamToMatchSignature.fs | 3 +-- .../CodeFix/RenameUnusedValue.fs | 10 +++----- .../src/FSharp.Editor/CodeFix/SimplifyName.fs | 3 +-- 9 files changed, 44 insertions(+), 31 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs index fbfe775db7e..67778d34a36 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs @@ -27,7 +27,6 @@ type internal FSharpAddNewKeywordCodeFixProvider() = diagnostics |> Seq.map (fun d -> TextChange(TextSpan(d.Location.SourceSpan.Start, 0), "new ")) - CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.AddNewKeyword return document.WithText(sourceText.WithChanges(changes)) } @@ -41,4 +40,4 @@ type internal FSharpAddNewKeywordCodeFixProvider() = } override this.GetFixAllProvider() = - FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) + CodeFixHelpers.createFixAllProvider CodeFix.AddNewKeyword this.GetChangedDocument diff --git a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs index 2dc6e26b977..041930ec3ee 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs @@ -2,9 +2,11 @@ namespace Microsoft.VisualStudio.FSharp.Editor +open System open System.Threading open System.Threading.Tasks open System.Collections.Immutable +open System.Diagnostics open Microsoft open Microsoft.CodeAnalysis @@ -15,7 +17,13 @@ open Microsoft.VisualStudio.FSharp.Editor.Telemetry [] module internal CodeFixHelpers = - let reportCodeFixRecommendation (diagnostics: ImmutableArray) (doc: Document) (staticName: string) = + let private reportCodeFixTelemetry + (diagnostics: ImmutableArray) + (doc: Document) + (staticName: string) + (scope: FixAllScope) + (ellapsedMs: int64) + = let ids = diagnostics |> Seq.map (fun d -> d.Id) |> Seq.distinct |> String.concat "," @@ -26,9 +34,20 @@ module internal CodeFixHelpers = "context.document.project.id", doc.Project.Id.Id.ToString() "context.document.id", doc.Id.Id.ToString() "context.diagnostics.count", diagnostics.Length + "context.bulkChange.scope", scope.ToString() + "ellapsedMs", ellapsedMs ] - TelemetryReporter.reportEvent "codefixrecommendation" props + TelemetryReporter.reportEvent "codefixactivated" props + + let createFixAllProvider name getChangedDocument = + FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> + backgroundTask { + let sw = Stopwatch.StartNew() + let! doc = getChangedDocument (doc, allDiagnostics, fixAllCtx.CancellationToken) + do reportCodeFixTelemetry allDiagnostics doc name (fixAllCtx.Scope) sw.ElapsedMilliseconds + return doc + }) let createTextChangeCodeFix ( @@ -75,7 +94,7 @@ module internal CodeFixHelpers = return context.Document.WithText(sourceText.WithChanges(textChanges)) } |> RoslynHelpers.StartAsyncAsTask(cancellationToken)), - title + name ) [] diff --git a/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs b/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs index 3221b3b9d31..1be567e129c 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs @@ -4,8 +4,10 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition open System.Collections.Immutable +open System.Threading open System.Threading.Tasks +open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes open FSharp.Compiler.Diagnostics @@ -67,19 +69,19 @@ type internal FsharpFixRemoveDotFromIndexerAccessOptIn() as this = static let title = CompilerDiagnostics.GetErrorMessage FSharpDiagnosticKind.RemoveIndexerDot + member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + backgroundTask { + let changes = + diagnostics |> Seq.map (fun x -> TextChange(x.Location.SourceSpan, "")) + + let! text = document.GetTextAsync(ct) + return document.WithText(text.WithChanges(changes)) + } + override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context : Task = backgroundTask { this.RegisterFix(CodeFix.RemoveIndexerDotBeforeBracket, title, context, TextChange(context.Span, "")) } override this.GetFixAllProvider() = - FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> - backgroundTask { - let changes = - allDiagnostics |> Seq.map (fun x -> TextChange(x.Location.SourceSpan, "")) - - let! text = doc.GetTextAsync(fixAllCtx.CancellationToken) - - CodeFixHelpers.reportCodeFixRecommendation allDiagnostics doc CodeFix.RemoveIndexerDotBeforeBracket - return doc.WithText(text.WithChanges(changes)) - }) + CodeFixHelpers.createFixAllProvider CodeFix.RemoveIndexerDotBeforeBracket this.GetChangedDocument diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs index fb119716953..993491d8e6f 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs @@ -51,7 +51,6 @@ type internal RemoveSuperflousCaptureForUnionCaseWithNoDataProvider [ this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) + CodeFixHelpers.createFixAllProvider CodeFix.RemoveSuperfluousCapture this.GetChangedDocument diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs index b1468dd4731..c89ec472ddc 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs @@ -60,7 +60,6 @@ type internal FSharpRemoveUnusedBindingCodeFixProvider [] | None -> () } - CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.RemoveUnusedBinding return document.WithText(sourceText.WithChanges(changes)) } @@ -74,4 +73,4 @@ type internal FSharpRemoveUnusedBindingCodeFixProvider [] } override this.GetFixAllProvider() = - FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) + CodeFixHelpers.createFixAllProvider CodeFix.RemoveUnusedBinding this.GetChangedDocument diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs index 765912a5b9e..fea761b6b1d 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs @@ -39,7 +39,6 @@ type internal FSharpRemoveUnusedOpensCodeFixProvider [] () .SpanIncludingLineBreak) |> Seq.map (fun span -> TextChange(span, "")) - CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.RemoveUnusedOpens return document.WithText(sourceText.WithChanges(changes)) } @@ -53,4 +52,4 @@ type internal FSharpRemoveUnusedOpensCodeFixProvider [] () } override this.GetFixAllProvider() = - FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) + CodeFixHelpers.createFixAllProvider CodeFix.RemoveUnusedOpens this.GetChangedDocument diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs b/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs index 705279c294b..abb4d76a869 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs @@ -64,7 +64,6 @@ type internal FSharpRenameParamToMatchSignature [] () = } |> Async.Parallel - CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.FSharpRenameParamToMatchSignature return document.WithText(sourceText.WithChanges(changes |> Array.concat)) } @@ -82,4 +81,4 @@ type internal FSharpRenameParamToMatchSignature [] () = } override this.GetFixAllProvider() = - FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) + CodeFixHelpers.createFixAllProvider CodeFix.FSharpRenameParamToMatchSignature this.GetChangedDocument diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs b/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs index 4fb6276d06e..6ea949d0d26 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs @@ -81,7 +81,6 @@ type internal FSharpPrefixUnusedValueWithUnderscoreCodeFixProvider [ Async.Parallel - CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.PrefixUnusedValue return document.WithText(sourceText.WithChanges(changes |> Seq.concat)) } @@ -102,7 +101,7 @@ type internal FSharpPrefixUnusedValueWithUnderscoreCodeFixProvider [ this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), - prefixTitle + CodeFix.PrefixUnusedValue ) ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) @@ -110,7 +109,7 @@ type internal FSharpPrefixUnusedValueWithUnderscoreCodeFixProvider [ this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) + CodeFixHelpers.createFixAllProvider CodeFix.PrefixUnusedValue this.GetChangedDocument [] type internal FSharpRenameUnusedValueWithUnderscoreCodeFixProvider [] () = @@ -148,7 +147,6 @@ type internal FSharpRenameUnusedValueWithUnderscoreCodeFixProvider [ Async.Parallel - CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.RenameUnusedValue return document.WithText(sourceText.WithChanges(changes |> Seq.concat)) } @@ -169,7 +167,7 @@ type internal FSharpRenameUnusedValueWithUnderscoreCodeFixProvider [ this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), - prefixTitle + CodeFix.RenameUnusedValue ) ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) @@ -177,4 +175,4 @@ type internal FSharpRenameUnusedValueWithUnderscoreCodeFixProvider [ this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) + CodeFixHelpers.createFixAllProvider CodeFix.RenameUnusedValue this.GetChangedDocument diff --git a/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs b/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs index ee2594a19f7..ab18ca3fc08 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs @@ -29,7 +29,6 @@ type internal FSharpSimplifyNameCodeFixProvider() = let changes = diagnostics |> Seq.map (fun d -> TextChange(d.Location.SourceSpan, "")) - CodeFixHelpers.reportCodeFixRecommendation diagnostics document CodeFix.SimplifyName return document.WithText(sourceText.WithChanges(changes)) } @@ -49,4 +48,4 @@ type internal FSharpSimplifyNameCodeFixProvider() = } override this.GetFixAllProvider() = - FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> this.GetChangedDocument(doc, allDiagnostics, fixAllCtx.CancellationToken)) + CodeFixHelpers.createFixAllProvider CodeFix.SimplifyName this.GetChangedDocument From f12e5a49a7a4f3390d5a204527ef1a679ec8b517 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 18 Apr 2023 13:48:25 +0200 Subject: [PATCH 09/11] codefix registration - for now the ones using CodeFixHelpers --- .../CodeFix/AddInstanceMemberParameter.fs | 18 +--- .../AddMissingEqualsToTypeDefinition.fs | 16 +--- .../CodeFix/AddMissingFunKeyword.fs | 18 +--- ...eywordToDisposableConstructorInvocation.fs | 14 +-- .../CodeFix/AddOpenCodeFixProvider.fs | 9 +- .../ChangePrefixNegationToInfixSubtraction.fs | 19 +--- .../ChangeRefCellDerefToNotExpression.fs | 18 +--- .../FSharp.Editor/CodeFix/ChangeToUpcast.fs | 15 +-- .../FSharp.Editor/CodeFix/CodeFixHelpers.fs | 91 ++++++------------- .../ConvertCSharpLambdaToFSharpLambda.fs | 19 +--- .../CodeFix/ConvertCSharpUsingToFSharpOpen.fs | 26 +----- .../ConvertToNotEqualsEqualityExpression.fs | 19 +--- ...ConvertToSingleEqualsEqualityExpression.fs | 19 +--- .../FSharp.Editor/CodeFix/FixIndexerAccess.fs | 80 ++++++++-------- .../CodeFix/MakeDeclarationMutable.fs | 18 +--- .../CodeFix/MakeOuterBindingRecursive.fs | 14 +-- .../CodeFix/RemoveReturnOrYield.fs | 14 +-- ...SuperflousCaptureForUnionCaseWithNoData.fs | 12 +-- .../CodeFix/RemoveUnusedBinding.fs | 12 +-- .../CodeFix/RemoveUnusedOpens.fs | 12 +-- .../CodeFix/RenameParamToMatchSignature.fs | 12 +-- .../CodeFix/RenameUnusedValue.fs | 33 ++----- .../CodeFix/ReplaceWithSuggestion.fs | 14 +-- .../src/FSharp.Editor/CodeFix/SimplifyName.fs | 14 +-- .../CodeFix/UseMutationWhenValueIsMutable.fs | 18 +--- .../CodeFix/UseTripleQuotedInterpolation.fs | 19 +--- 26 files changed, 147 insertions(+), 426 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs index e3180651f9b..8d537a0ac21 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs @@ -13,27 +13,17 @@ type internal FSharpAddInstanceMemberParameterCodeFixProvider() = inherit CodeFixProvider() let fixableDiagnosticIds = set [ "FS0673" ] + static let title = SR.AddMissingInstanceMemberParameter() override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context : Task = asyncMaybe { - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - - let title = SR.AddMissingInstanceMemberParameter() - - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( + do context.RegisterFsharpFix ( CodeFix.AddInstanceMemberParameter, - title, - context, - (fun () -> asyncMaybe.Return [| TextChange(TextSpan(context.Span.Start, 0), "x.") |]) + title, + [| TextChange(TextSpan(context.Span.Start, 0), "x.") |] ) - - context.RegisterCodeFix(codeFix, diagnostics) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingEqualsToTypeDefinition.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingEqualsToTypeDefinition.fs index f667c624e3b..a6cdefd76af 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingEqualsToTypeDefinition.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingEqualsToTypeDefinition.fs @@ -14,15 +14,11 @@ type internal FSharpAddMissingEqualsToTypeDefinitionCodeFixProvider() = inherit CodeFixProvider() let fixableDiagnosticIds = set [ "FS3360" ] - + static let title = SR.AddMissingEqualsToTypeDefinition() override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context : Task = asyncMaybe { - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray let! sourceText = context.Document.GetTextAsync(context.CancellationToken) @@ -37,19 +33,13 @@ type internal FSharpAddMissingEqualsToTypeDefinitionCodeFixProvider() = pos <- pos - 1 ch <- sourceText.[pos] - let title = SR.AddMissingEqualsToTypeDefinition() - - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( + do context.RegisterFsharpFix ( CodeFix.AddMissingEqualsToTypeDefinition, title, - context, // 'pos + 1' is here because 'pos' is now the position of the first non-whitespace character. // Using just 'pos' will creat uncompilable code. - (fun () -> asyncMaybe.Return [| TextChange(TextSpan(pos + 1, 0), " =") |]) + [| TextChange(TextSpan(pos + 1, 0), " =") |] ) - - context.RegisterCodeFix(codeFix, diagnostics) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs index 234c4bd0594..597190a7d17 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs @@ -14,6 +14,7 @@ open FSharp.Compiler.CodeAnalysis [] type internal FSharpAddMissingFunKeywordCodeFixProvider [] () = inherit CodeFixProvider() + static let title = SR.AddMissingFunKeyword() let fixableDiagnosticIds = set [ "FS0010" ] @@ -56,22 +57,7 @@ type internal FSharpAddMissingFunKeywordCodeFixProvider [] let! intendedArgSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, intendedArgLexerSymbol.Range) - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - - let title = SR.AddMissingFunKeyword() - - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.AddMissingFunKeyword, - title, - context, - (fun () -> asyncMaybe.Return [| TextChange(TextSpan(intendedArgSpan.Start, 0), "fun ") |]) - ) - - context.RegisterCodeFix(codeFix, diagnostics) + do context.RegisterFsharpFix(CodeFix.AddMissingFunKeyword, title, [| TextChange(TextSpan(intendedArgSpan.Start, 0), "fun ") |]) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs index 67778d34a36..8a72534a804 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddNewKeywordToDisposableConstructorInvocation.fs @@ -19,25 +19,21 @@ type internal FSharpAddNewKeywordCodeFixProvider() = static let title = SR.AddNewKeyword() override _.FixableDiagnosticIds = ImmutableArray.Create "FS0760" - member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + member this.GetChanges(_document: Document, diagnostics: ImmutableArray, _ct: CancellationToken) = backgroundTask { - let! sourceText = document.GetTextAsync(ct) let changes = diagnostics |> Seq.map (fun d -> TextChange(TextSpan(d.Location.SourceSpan.Start, 0), "new ")) - return document.WithText(sourceText.WithChanges(changes)) + return changes } override this.RegisterCodeFixesAsync ctx : Task = backgroundTask { - - let codeAction = - CodeAction.Create(title, (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), title) - - ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) + let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken) + ctx.RegisterFsharpFix(CodeFix.AddNewKeyword, title, changes) } override this.GetFixAllProvider() = - CodeFixHelpers.createFixAllProvider CodeFix.AddNewKeyword this.GetChangedDocument + CodeFixHelpers.createFixAllProvider CodeFix.AddNewKeyword this.GetChanges diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs index dcb40c4cb6c..0b30fe67920 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs @@ -30,7 +30,7 @@ type internal FSharpAddOpenCodeFixProvider [] (assemblyCon CodeFix.AddOpen, fixUnderscoresInMenuText fullName, context, - (fun () -> asyncMaybe.Return [| TextChange(context.Span, qualifier) |]) + [| TextChange(context.Span, qualifier) |] ) let openNamespaceFix (context: CodeFixContext) ctx name ns multipleNames = @@ -77,12 +77,7 @@ type internal FSharpAddOpenCodeFixProvider [] (assemblyCon |> Seq.toList for codeFix in openNamespaceFixes @ qualifiedSymbolFixes do - context.RegisterCodeFix( - codeFix, - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id) - |> Seq.toImmutableArray - ) + context.RegisterCodeFix(codeFix, context.Diagnostics) override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ChangePrefixNegationToInfixSubtraction.fs b/vsintegration/src/FSharp.Editor/CodeFix/ChangePrefixNegationToInfixSubtraction.fs index 12296f0bd67..8d82926e692 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ChangePrefixNegationToInfixSubtraction.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ChangePrefixNegationToInfixSubtraction.fs @@ -14,16 +14,12 @@ type internal FSharpChangePrefixNegationToInfixSubtractionodeFixProvider() = inherit CodeFixProvider() let fixableDiagnosticIds = set [ "FS0003" ] + static let title = SR.ChangePrefixNegationToInfixSubtraction() override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context : Task = asyncMaybe { - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) let mutable pos = context.Span.End + 1 @@ -39,18 +35,7 @@ type internal FSharpChangePrefixNegationToInfixSubtractionodeFixProvider() = // Bail if this isn't a negation do! Option.guard (ch = '-') - - let title = SR.ChangePrefixNegationToInfixSubtraction() - - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.ChangePrefixNegationToInfixSubtraction, - title, - context, - (fun () -> asyncMaybe.Return [| TextChange(TextSpan(pos, 1), "- ") |]) - ) - - context.RegisterCodeFix(codeFix, diagnostics) + do context.RegisterFsharpFix(CodeFix.ChangePrefixNegationToInfixSubtraction, title, [| TextChange(TextSpan(pos, 1), "- ") |]) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs b/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs index 04b40ec52be..8937117cfd0 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs @@ -13,6 +13,7 @@ type internal FSharpChangeRefCellDerefToNotExpressionCodeFixProvider [ Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.ChangeRefCellDerefToNotExpression, - title, - context, - (fun () -> asyncMaybe.Return [| TextChange(derefSpan, "not ") |]) - ) - - context.RegisterCodeFix(codeFix, diagnostics) + do context.RegisterFsharpFix(CodeFix.ChangeRefCellDerefToNotExpression, title, [| TextChange(derefSpan, "not ") |]) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ChangeToUpcast.fs b/vsintegration/src/FSharp.Editor/CodeFix/ChangeToUpcast.fs index d2e7faedc3c..16032747c5b 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ChangeToUpcast.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ChangeToUpcast.fs @@ -43,20 +43,7 @@ type internal FSharpChangeToUpcastCodeFixProvider() = else SR.UseUpcastKeyword() - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.ChangeToUpcast, - title, - context, - (fun () -> asyncMaybe.Return [| TextChange(context.Span, replacement) |]) - ) - - context.RegisterCodeFix(codeFix, diagnostics) + do context.RegisterFsharpFix(CodeFix.ChangeToUpcast, title, [| TextChange(context.Span, replacement) |]) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs index 041930ec3ee..0db4a43ee2c 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs @@ -17,95 +17,60 @@ open Microsoft.VisualStudio.FSharp.Editor.Telemetry [] module internal CodeFixHelpers = - let private reportCodeFixTelemetry - (diagnostics: ImmutableArray) - (doc: Document) - (staticName: string) - (scope: FixAllScope) - (ellapsedMs: int64) - = + let private reportCodeFixTelemetry (diagnostics: ImmutableArray) (doc: Document) (staticName: string) (additionalProps) = let ids = diagnostics |> Seq.map (fun d -> d.Id) |> Seq.distinct |> String.concat "," let props: (string * obj) list = - [ + additionalProps + @ [ "name", staticName "ids", ids "context.document.project.id", doc.Project.Id.Id.ToString() "context.document.id", doc.Id.Id.ToString() "context.diagnostics.count", diagnostics.Length - "context.bulkChange.scope", scope.ToString() - "ellapsedMs", ellapsedMs ] TelemetryReporter.reportEvent "codefixactivated" props - let createFixAllProvider name getChangedDocument = + let createFixAllProvider name getChanges = FixAllProvider.Create(fun fixAllCtx doc allDiagnostics -> backgroundTask { let sw = Stopwatch.StartNew() - let! doc = getChangedDocument (doc, allDiagnostics, fixAllCtx.CancellationToken) - do reportCodeFixTelemetry allDiagnostics doc name (fixAllCtx.Scope) sw.ElapsedMilliseconds - return doc - }) - - let createTextChangeCodeFix - ( - name: string, - title: string, - context: CodeFixContext, - computeTextChanges: unit -> Async - ) = - - // Currently there should be one Id here. - // Keeping it this way to be error- and futureproof - // as the underlying API does allow multiple Ids here. - let ids = context.Diagnostics |> Seq.map (fun d -> d.Id) |> String.concat "," - - let props: (string * obj) list = - [ - "name", name - "ids", ids + let! (changes: seq) = getChanges (doc, allDiagnostics, fixAllCtx.CancellationToken) + let! text = doc.GetTextAsync(fixAllCtx.CancellationToken) + let doc = doc.WithText(text.WithChanges(changes)) - // The following can help building a unique but anonymized codefix target: - // #projectid#documentid#span - // Then we can check if the codefix was actually activated after its creation. - "context.document.project.id", context.Document.Project.Id.Id.ToString() - "context.document.id", context.Document.Id.Id.ToString() - "context.span", context.Span.ToString() - ] + do + reportCodeFixTelemetry + allDiagnostics + doc + name + [ "scope", fixAllCtx.Scope.ToString(); "ellapsedMs", sw.ElapsedMilliseconds ] - TelemetryReporter.reportEvent "codefixregistered" props + return doc + }) + let createTextChangeCodeFix (name: string, title: string, context: CodeFixContext, changes: TextChange seq) = CodeAction.Create( title, (fun (cancellationToken: CancellationToken) -> - async { - let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask - let! changesOpt = computeTextChanges () - - match changesOpt with - | None -> return context.Document - | Some textChanges -> - // Note: "activated" doesn't mean "applied". - // It's one step prior to that: - // e.g. when one clicks (Ctrl + .) and looks at the potential change. - TelemetryReporter.reportEvent "codefixactivated" props - return context.Document.WithText(sourceText.WithChanges(textChanges)) - } - |> RoslynHelpers.StartAsyncAsTask(cancellationToken)), + backgroundTask { + let! sourceText = context.Document.GetTextAsync(cancellationToken) + let doc = context.Document.WithText(sourceText.WithChanges(changes)) + reportCodeFixTelemetry context.Diagnostics context.Document name [] + return doc + }), name ) [] module internal CodeFixExtensions = - type CodeFixProvider with - - member this.GetPrunedDiagnostics(context: CodeFixContext) = - context.Diagnostics.RemoveAll(fun x -> this.FixableDiagnosticIds.Contains(x.Id) |> not) + type CodeFixContext with - member this.RegisterFix(name, title, context: CodeFixContext, fixChange) = - let replaceCodeFix = - CodeFixHelpers.createTextChangeCodeFix (name, title, context, (fun () -> asyncMaybe.Return [| fixChange |])) + member ctx.RegisterFsharpFix(staticName, title, changes, ?diagnostics) = + let codeAction = + CodeFixHelpers.createTextChangeCodeFix (staticName, title, ctx, changes) - context.RegisterCodeFix(replaceCodeFix, this.GetPrunedDiagnostics(context)) + let diag = diagnostics |> Option.defaultValue ctx.Diagnostics + ctx.RegisterCodeFix(codeAction, diag) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs b/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs index 1c38dc2e021..aaff8595165 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs @@ -12,7 +12,7 @@ type internal FSharpConvertCSharpLambdaToFSharpLambdaCodeFixProvider [ " + bodyText) - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - - let title = SR.UseFSharpLambda() - - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.ConvertCSharpLambdaToFSharpLambda, - title, - context, - (fun () -> asyncMaybe.Return [| replacement |]) - ) - - context.RegisterCodeFix(codeFix, diagnostics) + do context.RegisterFsharpFix(CodeFix.ConvertCSharpLambdaToFSharpLambda, title, [| replacement |]) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpUsingToFSharpOpen.fs b/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpUsingToFSharpOpen.fs index d92eea460a2..eba65241e98 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpUsingToFSharpOpen.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpUsingToFSharpOpen.fs @@ -15,6 +15,7 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [] () = inherit CodeFixProvider() let fixableDiagnosticIds = set [ "FS0039"; "FS0201" ] + static let title = SR.ConvertCSharpUsingToFSharpOpen() let usingLength = "using".Length let isCSharpUsingShapeWithPos (context: CodeFixContext) (sourceText: SourceText) = @@ -39,34 +40,17 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [] () = let slice = sourceText.GetSubText(span).ToString() struct (slice = "using", start) - let registerCodeFix (context: CodeFixContext) (diagnostics: ImmutableArray) (str: string) (span: TextSpan) = + let registerCodeFix (context: CodeFixContext) (str: string) (span: TextSpan) = let replacement = let str = str.Replace("using", "open").Replace(";", "") TextChange(span, str) - let title = SR.ConvertCSharpUsingToFSharpOpen() - - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.ConvertCSharpUsingToFSharpOpen, - title, - context, - (fun () -> asyncMaybe.Return [| replacement |]) - ) - - context.RegisterCodeFix(codeFix, diagnostics) + do context.RegisterFsharpFix(CodeFix.ConvertCSharpUsingToFSharpOpen, title, [| replacement |]) override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context = asyncMaybe { - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - - do! Option.guard (diagnostics.Length > 0) - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) // TODO: handle single-line case? @@ -83,7 +67,7 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [] () = (statementWithSemicolon.StartsWith("using") && statementWithSemicolon.EndsWith(";")) then - registerCodeFix context diagnostics statementWithSemicolon statementWithSemicolonSpan + registerCodeFix context statementWithSemicolon statementWithSemicolonSpan else // Only the identifier being opened has a diagnostic, so we try to find the rest of the statement let struct (isCSharpUsingShape, start) = @@ -93,7 +77,7 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [] () = let len = (context.Span.Start - start) + statementWithSemicolonSpan.Length let fullSpan = TextSpan(start, len) let str = sourceText.GetSubText(fullSpan).ToString() - registerCodeFix context diagnostics str fullSpan + registerCodeFix context str fullSpan } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ConvertToNotEqualsEqualityExpression.fs b/vsintegration/src/FSharp.Editor/CodeFix/ConvertToNotEqualsEqualityExpression.fs index f1304f3996f..21cb064378f 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ConvertToNotEqualsEqualityExpression.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ConvertToNotEqualsEqualityExpression.fs @@ -13,6 +13,7 @@ type internal FSharpConvertToNotEqualsEqualityExpressionCodeFixProvider() = inherit CodeFixProvider() let fixableDiagnosticIds = set [ "FS0043" ] + static let title = SR.ConvertToNotEqualsEqualityExpression() override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds @@ -24,23 +25,7 @@ type internal FSharpConvertToNotEqualsEqualityExpressionCodeFixProvider() = // We're converting '!=' into '<>', a common new user mistake. // If this is an FS00043 that is anything other than that, bail out do! Option.guard (text = "!=") - - let title = SR.ConvertToNotEqualsEqualityExpression() - - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.ConvertToNotEqualsEqualityExpression, - title, - context, - (fun () -> asyncMaybe.Return [| TextChange(context.Span, "<>") |]) - ) - - context.RegisterCodeFix(codeFix, diagnostics) + do context.RegisterFsharpFix(CodeFix.ConvertToNotEqualsEqualityExpression, title, [| TextChange(context.Span, "<>") |]) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ConvertToSingleEqualsEqualityExpression.fs b/vsintegration/src/FSharp.Editor/CodeFix/ConvertToSingleEqualsEqualityExpression.fs index 6832851a189..0a8defff17d 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ConvertToSingleEqualsEqualityExpression.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ConvertToSingleEqualsEqualityExpression.fs @@ -13,6 +13,7 @@ type internal FSharpConvertToSingleEqualsEqualityExpressionCodeFixProvider() = inherit CodeFixProvider() let fixableDiagnosticIds = set [ "FS0043" ] + static let title = SR.ConvertToSingleEqualsEqualityExpression() override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds @@ -24,23 +25,7 @@ type internal FSharpConvertToSingleEqualsEqualityExpressionCodeFixProvider() = // We're converting '==' into '=', a common new user mistake. // If this is an FS00043 that is anything other than that, bail out do! Option.guard (text = "==") - - let title = SR.ConvertToSingleEqualsEqualityExpression() - - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.ConvertToSingleEqualsEqualityExpression, - title, - context, - (fun () -> asyncMaybe.Return [| TextChange(context.Span, "=") |]) - ) - - context.RegisterCodeFix(codeFix, diagnostics) + do context.RegisterFsharpFix(CodeFix.ConvertToSingleEqualsEqualityExpression, title, [| TextChange(context.Span, "=") |]) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs b/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs index 1be567e129c..9c10a6e0552 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs @@ -16,48 +16,40 @@ open FSharp.Compiler.Diagnostics type internal LegacyFsharpFixAddDotToIndexerAccess() = inherit CodeFixProvider() let fixableDiagnosticIds = set [ "FS3217" ] + static let title = CompilerDiagnostics.GetErrorMessage FSharpDiagnosticKind.AddIndexerDot override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context : Task = async { - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toList - - if not (List.isEmpty diagnostics) then - let! sourceText = context.Document.GetTextAsync() |> Async.AwaitTask - - diagnostics - |> Seq.iter (fun diagnostic -> - let diagnostics = ImmutableArray.Create diagnostic - - let span, replacement = - try - let mutable span = context.Span - - let notStartOfBracket (span: TextSpan) = - let t = sourceText.GetSubText(TextSpan(span.Start, span.Length + 1)) - t.[t.Length - 1] <> '[' - - // skip all braces and blanks until we find [ - while span.End < sourceText.Length && notStartOfBracket span do - span <- TextSpan(span.Start, span.Length + 1) - - span, sourceText.GetSubText(span).ToString() - with _ -> - context.Span, sourceText.GetSubText(context.Span).ToString() - - let codefix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.FixIndexerAccess, - CompilerDiagnostics.GetErrorMessage FSharpDiagnosticKind.AddIndexerDot, - context, - (fun () -> asyncMaybe.Return [| TextChange(span, replacement.TrimEnd() + ".") |]) - ) - - context.RegisterCodeFix(codefix, diagnostics)) + let! sourceText = context.Document.GetTextAsync() |> Async.AwaitTask + + context.Diagnostics + |> Seq.iter (fun diagnostic -> + + let span, replacement = + try + let mutable span = context.Span + + let notStartOfBracket (span: TextSpan) = + let t = sourceText.GetSubText(TextSpan(span.Start, span.Length + 1)) + t.[t.Length - 1] <> '[' + + // skip all braces and blanks until we find [ + while span.End < sourceText.Length && notStartOfBracket span do + span <- TextSpan(span.Start, span.Length + 1) + + span, sourceText.GetSubText(span).ToString() + with _ -> + context.Span, sourceText.GetSubText(context.Span).ToString() + + do + context.RegisterFsharpFix( + CodeFix.FixIndexerAccess, + title, + [| TextChange(span, replacement.TrimEnd() + ".") |], + ImmutableArray.Create(diagnostic) + )) } |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) @@ -69,19 +61,21 @@ type internal FsharpFixRemoveDotFromIndexerAccessOptIn() as this = static let title = CompilerDiagnostics.GetErrorMessage FSharpDiagnosticKind.RemoveIndexerDot - member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + member this.GetChanges(_document: Document, diagnostics: ImmutableArray, _ct: CancellationToken) = backgroundTask { let changes = diagnostics |> Seq.map (fun x -> TextChange(x.Location.SourceSpan, "")) - let! text = document.GetTextAsync(ct) - return document.WithText(text.WithChanges(changes)) + return changes } override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds - override _.RegisterCodeFixesAsync context : Task = - backgroundTask { this.RegisterFix(CodeFix.RemoveIndexerDotBeforeBracket, title, context, TextChange(context.Span, "")) } + override _.RegisterCodeFixesAsync ctx : Task = + backgroundTask { + let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken) + ctx.RegisterFsharpFix(CodeFix.RemoveIndexerDotBeforeBracket, title, changes) + } override this.GetFixAllProvider() = - CodeFixHelpers.createFixAllProvider CodeFix.RemoveIndexerDotBeforeBracket this.GetChangedDocument + CodeFixHelpers.createFixAllProvider CodeFix.RemoveIndexerDotBeforeBracket this.GetChanges diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MakeDeclarationMutable.fs b/vsintegration/src/FSharp.Editor/CodeFix/MakeDeclarationMutable.fs index aefbc74be70..5285b20007c 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MakeDeclarationMutable.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MakeDeclarationMutable.fs @@ -16,15 +16,12 @@ type internal FSharpMakeDeclarationMutableFixProvider [] ( inherit CodeFixProvider() let fixableDiagnosticIds = set [ "FS0027" ] + static let title = SR.MakeDeclarationMutable() override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.RegisterCodeFixesAsync context : Task = asyncMaybe { - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray let document = context.Document do! Option.guard (not (isSignatureFile document.FilePath)) @@ -65,18 +62,7 @@ type internal FSharpMakeDeclarationMutableFixProvider [] ( // Bail if it's a parameter, because like, that ain't allowed do! Option.guard (not (parseFileResults.IsPositionContainedInACurriedParameter declRange.Start)) - - let title = SR.MakeDeclarationMutable() - - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.MakeDeclarationMutable, - title, - context, - (fun () -> asyncMaybe.Return [| TextChange(TextSpan(span.Start, 0), "mutable ") |]) - ) - - context.RegisterCodeFix(codeFix, diagnostics) + do context.RegisterFsharpFix(CodeFix.MakeDeclarationMutable, title, [| TextChange(TextSpan(span.Start, 0), "mutable ") |]) | _ -> () } |> Async.Ignore diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MakeOuterBindingRecursive.fs b/vsintegration/src/FSharp.Editor/CodeFix/MakeOuterBindingRecursive.fs index bae864afebb..7f0ae479743 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MakeOuterBindingRecursive.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MakeOuterBindingRecursive.fs @@ -40,23 +40,15 @@ type internal FSharpMakeOuterBindingRecursiveCodeFixProvider [ Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - let title = String.Format(SR.MakeOuterBindingRecursive(), sourceText.GetSubText(outerBindingNameSpan).ToString()) - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( + do + context.RegisterFsharpFix( CodeFix.MakeOuterBindingRecursive, title, - context, - (fun () -> asyncMaybe.Return [| TextChange(TextSpan(outerBindingNameSpan.Start, 0), "rec ") |]) + [| TextChange(TextSpan(outerBindingNameSpan.Start, 0), "rec ") |] ) - - context.RegisterCodeFix(codeFix, diagnostics) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs index a002916639d..7e76fc0003a 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs @@ -29,11 +29,6 @@ type internal FSharpRemoveReturnOrYieldCodeFixProvider [] let! exprRange = parseResults.TryRangeOfExprInYieldOrReturn errorRange.Start let! exprSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, exprRange) - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - let title = let text = sourceText.GetSubText(context.Span).ToString() @@ -42,15 +37,12 @@ type internal FSharpRemoveReturnOrYieldCodeFixProvider [] elif text.StartsWith("yield!") then SR.RemoveYieldBang() else SR.RemoveYield() - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( + do + context.RegisterFsharpFix( CodeFix.RemoveReturnOrYield, title, - context, - (fun () -> asyncMaybe.Return [| TextChange(context.Span, sourceText.GetSubText(exprSpan).ToString()) |]) + [| TextChange(context.Span, sourceText.GetSubText(exprSpan).ToString()) |] ) - - context.RegisterCodeFix(codeFix, diagnostics) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs index 993491d8e6f..d2fcfa51b19 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveSuperflousCaptureForUnionCaseWithNoData.fs @@ -22,7 +22,7 @@ type internal RemoveSuperflousCaptureForUnionCaseWithNoDataProvider [, ct: CancellationToken) = + member this.GetChanges(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = backgroundTask { let! sourceText = document.GetTextAsync(ct) @@ -51,17 +51,15 @@ type internal RemoveSuperflousCaptureForUnionCaseWithNoDataProvider [ this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), title) - - ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) + let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken) + ctx.RegisterFsharpFix(CodeFix.RemoveSuperfluousCapture, title, changes) } override this.GetFixAllProvider() = - CodeFixHelpers.createFixAllProvider CodeFix.RemoveSuperfluousCapture this.GetChangedDocument + CodeFixHelpers.createFixAllProvider CodeFix.RemoveSuperfluousCapture this.GetChanges diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs index c89ec472ddc..9335eda0000 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs @@ -23,7 +23,7 @@ type internal FSharpRemoveUnusedBindingCodeFixProvider [] static let title = SR.RemoveUnusedBinding() override _.FixableDiagnosticIds = ImmutableArray.Create("FS1182") - member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + member this.GetChanges(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = backgroundTask { let! sourceText = document.GetTextAsync(ct) @@ -60,17 +60,15 @@ type internal FSharpRemoveUnusedBindingCodeFixProvider [] | None -> () } - return document.WithText(sourceText.WithChanges(changes)) + return changes } override this.RegisterCodeFixesAsync ctx : Task = backgroundTask { if ctx.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then - let codeAction = - CodeAction.Create(title, (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), title) - - ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) + let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken) + ctx.RegisterFsharpFix(CodeFix.RemoveUnusedBinding, title, changes) } override this.GetFixAllProvider() = - CodeFixHelpers.createFixAllProvider CodeFix.RemoveUnusedBinding this.GetChangedDocument + CodeFixHelpers.createFixAllProvider CodeFix.RemoveUnusedBinding this.GetChanges diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs index fea761b6b1d..b57f35bd61b 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs @@ -24,7 +24,7 @@ type internal FSharpRemoveUnusedOpensCodeFixProvider [] () override _.FixableDiagnosticIds = ImmutableArray.Create FSharpIDEDiagnosticIds.RemoveUnnecessaryImportsDiagnosticId - member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + member this.GetChanges(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = backgroundTask { let! sourceText = document.GetTextAsync(ct) @@ -39,17 +39,15 @@ type internal FSharpRemoveUnusedOpensCodeFixProvider [] () .SpanIncludingLineBreak) |> Seq.map (fun span -> TextChange(span, "")) - return document.WithText(sourceText.WithChanges(changes)) + return changes } override this.RegisterCodeFixesAsync ctx : Task = backgroundTask { if ctx.Document.Project.IsFSharpCodeFixesUnusedOpensEnabled then - let codeAction = - CodeAction.Create(title, (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), title) - - ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) + let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken) + ctx.RegisterFsharpFix(CodeFix.RemoveUnusedOpens, title, changes) } override this.GetFixAllProvider() = - CodeFixHelpers.createFixAllProvider CodeFix.RemoveUnusedOpens this.GetChangedDocument + CodeFixHelpers.createFixAllProvider CodeFix.RemoveUnusedOpens this.GetChanges diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs b/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs index abb4d76a869..fb916fbb849 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RenameParamToMatchSignature.fs @@ -32,7 +32,7 @@ type internal FSharpRenameParamToMatchSignature [] () = override _.FixableDiagnosticIds = ImmutableArray.Create("FS3218") - member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + member this.GetChanges(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = backgroundTask { let! sourceText = document.GetTextAsync(ct) @@ -64,7 +64,7 @@ type internal FSharpRenameParamToMatchSignature [] () = } |> Async.Parallel - return document.WithText(sourceText.WithChanges(changes |> Array.concat)) + return (changes |> Seq.concat) } override this.RegisterCodeFixesAsync ctx : Task = @@ -73,12 +73,10 @@ type internal FSharpRenameParamToMatchSignature [] () = match title with | ValueSome title -> - let codeAction = - CodeAction.Create(title, (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), title) - - ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) + let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken) + ctx.RegisterFsharpFix(CodeFix.FSharpRenameParamToMatchSignature, title, changes) | ValueNone -> () } override this.GetFixAllProvider() = - CodeFixHelpers.createFixAllProvider CodeFix.FSharpRenameParamToMatchSignature this.GetChangedDocument + CodeFixHelpers.createFixAllProvider CodeFix.FSharpRenameParamToMatchSignature this.GetChanges diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs b/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs index 6ea949d0d26..63f8c0af545 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs @@ -55,7 +55,7 @@ type internal FSharpPrefixUnusedValueWithUnderscoreCodeFixProvider [, ct: CancellationToken) = + member this.GetChanges(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = backgroundTask { let! sourceText = document.GetTextAsync(ct) @@ -81,7 +81,7 @@ type internal FSharpPrefixUnusedValueWithUnderscoreCodeFixProvider [ Async.Parallel - return document.WithText(sourceText.WithChanges(changes |> Seq.concat)) + return (changes |> Seq.concat) } override this.RegisterCodeFixesAsync ctx : Task = @@ -96,20 +96,13 @@ type internal FSharpPrefixUnusedValueWithUnderscoreCodeFixProvider [ let prefixTitle = title symbolUse.Symbol.DisplayName - - let codeAction = - CodeAction.Create( - prefixTitle, - (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), - CodeFix.PrefixUnusedValue - ) - - ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) + let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken) + ctx.RegisterFsharpFix(CodeFix.PrefixUnusedValue, prefixTitle, changes) | _ -> () } override this.GetFixAllProvider() = - CodeFixHelpers.createFixAllProvider CodeFix.PrefixUnusedValue this.GetChangedDocument + CodeFixHelpers.createFixAllProvider CodeFix.PrefixUnusedValue this.GetChanges [] type internal FSharpRenameUnusedValueWithUnderscoreCodeFixProvider [] () = @@ -121,7 +114,7 @@ type internal FSharpRenameUnusedValueWithUnderscoreCodeFixProvider [, ct: CancellationToken) = + member this.GetChanges(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = backgroundTask { let! sourceText = document.GetTextAsync(ct) @@ -147,7 +140,7 @@ type internal FSharpRenameUnusedValueWithUnderscoreCodeFixProvider [ Async.Parallel - return document.WithText(sourceText.WithChanges(changes |> Seq.concat)) + return (changes |> Seq.concat) } override this.RegisterCodeFixesAsync ctx : Task = @@ -163,16 +156,10 @@ type internal FSharpRenameUnusedValueWithUnderscoreCodeFixProvider [ let prefixTitle = title symbolUse.Symbol.DisplayName - let codeAction = - CodeAction.Create( - prefixTitle, - (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), - CodeFix.RenameUnusedValue - ) - - ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) + let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken) + ctx.RegisterFsharpFix(CodeFix.RenameUnusedValue, prefixTitle, changes) | _ -> () } override this.GetFixAllProvider() = - CodeFixHelpers.createFixAllProvider CodeFix.RenameUnusedValue this.GetChangedDocument + CodeFixHelpers.createFixAllProvider CodeFix.RenameUnusedValue this.GetChanges diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs index b6c53999150..f192a0f573b 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs @@ -51,23 +51,15 @@ type internal FSharpReplaceWithSuggestionCodeFixProvider [ for item in declInfo.Items do addToBuffer item.NameInList - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - for suggestion in CompilerDiagnostics.GetSuggestedNames addNames unresolvedIdentifierText do let replacement = PrettyNaming.NormalizeIdentifierBackticks suggestion - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( + do + context.RegisterFsharpFix( CodeFix.ReplaceWithSuggestion, CompilerDiagnostics.GetErrorMessage(FSharpDiagnosticKind.ReplaceWithSuggestion suggestion), - context, - (fun () -> asyncMaybe.Return [| TextChange(context.Span, replacement) |]) + [| TextChange(context.Span, replacement) |] ) - - context.RegisterCodeFix(codeFix, diagnostics) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs b/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs index ab18ca3fc08..dae2e3095a5 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs @@ -22,14 +22,12 @@ type internal FSharpSimplifyNameCodeFixProvider() = override _.FixableDiagnosticIds = ImmutableArray.Create(FSharpIDEDiagnosticIds.SimplifyNamesDiagnosticId) - member this.GetChangedDocument(document: Document, diagnostics: ImmutableArray, ct: CancellationToken) = + member this.GetChanges(_document: Document, diagnostics: ImmutableArray, _ct: CancellationToken) = backgroundTask { - let! sourceText = document.GetTextAsync(ct) - let changes = diagnostics |> Seq.map (fun d -> TextChange(d.Location.SourceSpan, "")) - return document.WithText(sourceText.WithChanges(changes)) + return changes } override this.RegisterCodeFixesAsync ctx : Task = @@ -41,11 +39,9 @@ type internal FSharpSimplifyNameCodeFixProvider() = | true, longIdent -> sprintf "%s '%s'" (SR.SimplifyName()) longIdent | _ -> SR.SimplifyName() - let codeAction = - CodeAction.Create(title, (fun ct -> this.GetChangedDocument(ctx.Document, ctx.Diagnostics, ct)), title) - - ctx.RegisterCodeFix(codeAction, this.GetPrunedDiagnostics(ctx)) + let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken) + ctx.RegisterFsharpFix(CodeFix.SimplifyName, title, changes) } override this.GetFixAllProvider() = - CodeFixHelpers.createFixAllProvider CodeFix.SimplifyName this.GetChangedDocument + CodeFixHelpers.createFixAllProvider CodeFix.SimplifyName this.GetChanges diff --git a/vsintegration/src/FSharp.Editor/CodeFix/UseMutationWhenValueIsMutable.fs b/vsintegration/src/FSharp.Editor/CodeFix/UseMutationWhenValueIsMutable.fs index f1358f569dc..0bf89b2a2b7 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/UseMutationWhenValueIsMutable.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/UseMutationWhenValueIsMutable.fs @@ -17,16 +17,11 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider [ Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray - let document = context.Document do! Option.guard (not (isSignatureFile document.FilePath)) @@ -68,7 +63,6 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider [ - let title = SR.UseMutationWhenValueIsMutable() let! symbolSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) let mutable pos = symbolSpan.End let mutable ch = sourceText.[pos] @@ -78,15 +72,7 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider [ asyncMaybe.Return [| TextChange(TextSpan(pos + 1, 1), "<-") |]) - ) - - context.RegisterCodeFix(codeFix, diagnostics) + do context.RegisterFsharpFix(CodeFix.UseMutationWhenValueIsMutable, title, [| TextChange(TextSpan(pos + 1, 1), "<-") |]) | _ -> () } |> Async.Ignore diff --git a/vsintegration/src/FSharp.Editor/CodeFix/UseTripleQuotedInterpolation.fs b/vsintegration/src/FSharp.Editor/CodeFix/UseTripleQuotedInterpolation.fs index 9249c01088f..3748ae53e6b 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/UseTripleQuotedInterpolation.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/UseTripleQuotedInterpolation.fs @@ -12,7 +12,7 @@ type internal FSharpUseTripleQuotedInterpolationCodeFixProvider [ Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id) - |> Seq.toImmutableArray - - let title = SR.UseTripleQuotedInterpolation() - - let codeFix = - CodeFixHelpers.createTextChangeCodeFix ( - CodeFix.UseTripleQuotedInterpolation, - title, - context, - (fun () -> asyncMaybe.Return [| replacement |]) - ) - - context.RegisterCodeFix(codeFix, diagnostics) + do context.RegisterFsharpFix(CodeFix.UseTripleQuotedInterpolation, title, [| replacement |]) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) From 81e4a1b97b105198a53affd39f5343cfa52d6162 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 18 Apr 2023 13:59:14 +0200 Subject: [PATCH 10/11] fantomas'd --- .../src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs | 6 +----- .../CodeFix/AddMissingEqualsToTypeDefinition.fs | 3 ++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs index 8d537a0ac21..6121af4da44 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs @@ -19,11 +19,7 @@ type internal FSharpAddInstanceMemberParameterCodeFixProvider() = override _.RegisterCodeFixesAsync context : Task = asyncMaybe { - do context.RegisterFsharpFix ( - CodeFix.AddInstanceMemberParameter, - title, - [| TextChange(TextSpan(context.Span.Start, 0), "x.") |] - ) + do context.RegisterFsharpFix(CodeFix.AddInstanceMemberParameter, title, [| TextChange(TextSpan(context.Span.Start, 0), "x.") |]) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingEqualsToTypeDefinition.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingEqualsToTypeDefinition.fs index a6cdefd76af..673f1bffee5 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingEqualsToTypeDefinition.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingEqualsToTypeDefinition.fs @@ -33,7 +33,8 @@ type internal FSharpAddMissingEqualsToTypeDefinitionCodeFixProvider() = pos <- pos - 1 ch <- sourceText.[pos] - do context.RegisterFsharpFix ( + do + context.RegisterFsharpFix( CodeFix.AddMissingEqualsToTypeDefinition, title, // 'pos + 1' is here because 'pos' is now the position of the first non-whitespace character. From b43ede05680274e2b8ba03243ba3268de85e85b1 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 18 Apr 2023 13:59:46 +0200 Subject: [PATCH 11/11] typo fix --- vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs index 0db4a43ee2c..e8af59ad674 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs @@ -46,7 +46,7 @@ module internal CodeFixHelpers = allDiagnostics doc name - [ "scope", fixAllCtx.Scope.ToString(); "ellapsedMs", sw.ElapsedMilliseconds ] + [ "scope", fixAllCtx.Scope.ToString(); "elapsedMs", sw.ElapsedMilliseconds ] return doc })