Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor most code fixes to construct the CodeFix consistently #10691

Merged
merged 1 commit into from
Dec 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,33 @@
namespace Microsoft.VisualStudio.FSharp.Editor

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

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

[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = "AddNewKeyword"); Shared>]
type internal FSharpAddNewKeywordCodeFixProvider() =
inherit CodeFixProvider()

override __.FixableDiagnosticIds = ImmutableArray.Create "FS0760"
static let fixableDiagnosticIds = set ["FS0760"]

override this.RegisterCodeFixesAsync context : Task =
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds

override _.RegisterCodeFixesAsync context : Task =
async {
let title = SR.AddNewKeyword()
context.RegisterCodeFix(
CodeAction.Create(
let diagnostics =
context.Diagnostics
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
|> Seq.toImmutableArray

let codeFix =
CodeFixHelpers.createTextChangeCodeFix(
title,
(fun (cancellationToken: CancellationToken) ->
async {
let! cancellationToken = Async.CancellationToken
let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask
return context.Document.WithText(sourceText.WithChanges(TextChange(TextSpan(context.Span.Start, 0), "new ")))
} |> RoslynHelpers.StartAsyncAsTask(cancellationToken)),
title), context.Diagnostics |> Seq.filter (fun x -> this.FixableDiagnosticIds.Contains x.Id) |> Seq.toImmutableArray)
context,
(fun () -> asyncMaybe.Return [| TextChange(TextSpan(context.Span.Start, 0), "new ") |]))

context.RegisterCodeFix(codeFix, diagnostics)
} |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)

Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,25 @@ type internal FSharpAddOpenCodeFixProvider
let checker = checkerProvider.Checker
let fixUnderscoresInMenuText (text: string) = text.Replace("_", "__")

let qualifySymbolFix (context: CodeFixContext) (fullName, qualifier) =
CodeAction.Create(
let qualifySymbolFix (context: CodeFixContext) (fullName, qualifier) =
CodeFixHelpers.createTextChangeCodeFix(
fixUnderscoresInMenuText fullName,
fun (cancellationToken: CancellationToken) ->
async {
let! cancellationToken = Async.CancellationToken
let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask
return context.Document.WithText(sourceText.Replace(context.Span, qualifier))
} |> RoslynHelpers.StartAsyncAsTask(cancellationToken))
context,
(fun () -> asyncMaybe.Return [| TextChange(context.Span, qualifier) |]))

let openNamespaceFix (context: CodeFixContext) ctx name ns multipleNames =
let displayText = "open " + ns + if multipleNames then " (" + name + ")" else ""
// TODO when fresh Roslyn NuGet packages are published, assign "Namespace" Tag to this CodeAction to show proper glyph.
CodeAction.Create(
fixUnderscoresInMenuText displayText,
(fun (cancellationToken: CancellationToken) ->
async {
let! cancellationToken = Async.CancellationToken
let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask
let changedText, _ = OpenDeclarationHelper.insertOpenDeclaration sourceText ctx ns
return context.Document.WithText(changedText)
} |> RoslynHelpers.StartAsyncAsTask(cancellationToken)),
displayText)

let getSuggestions (context: CodeFixContext) (candidates: (Entity * InsertContext) list) : unit =
//Logging.Logging.logInfof "Candidates: %+A" candidates

let addSuggestionsAsCodeFixes (context: CodeFixContext) (candidates: (Entity * InsertContext) list) =
let openNamespaceFixes =
candidates
|> Seq.choose (fun (entity, ctx) -> entity.Namespace |> Option.map (fun ns -> ns, entity.Name, ctx))
Expand Down Expand Up @@ -91,9 +83,9 @@ type internal FSharpAddOpenCodeFixProvider
for codeFix in openNamespaceFixes @ qualifiedSymbolFixes do
context.RegisterCodeFix(codeFix, context.Diagnostics |> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id) |> Seq.toImmutableArray)

override __.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds

override __.RegisterCodeFixesAsync context : Task =
override _.RegisterCodeFixesAsync context : Task =
asyncMaybe {
let document = context.Document
let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName)
Expand Down Expand Up @@ -150,7 +142,7 @@ type internal FSharpAddOpenCodeFixProvider
else OpenStatementInsertionPoint.Nearest

let createEntity = ParsedInput.tryFindInsertionContext unresolvedIdentRange.StartLine parsedInput maybeUnresolvedIdents insertionPoint
return entities |> Seq.map createEntity |> Seq.concat |> Seq.toList |> getSuggestions context
return entities |> Seq.map createEntity |> Seq.concat |> Seq.toList |> addSuggestionsAsCodeFixes context
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ module internal CodeFixHelpers =
title,
(fun (cancellationToken: CancellationToken) ->
async {
let! cancellationToken = Async.CancellationToken
let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask
let! changesOpt = computeTextChanges()
match changesOpt with
Expand Down
4 changes: 2 additions & 2 deletions vsintegration/src/FSharp.Editor/CodeFix/FixIndexerAccess.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ type internal FSharpFixIndexerAccessCodeFixProvider() =
inherit CodeFixProvider()
let fixableDiagnosticIds = set ["FS3217"]

override __.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds

override __.RegisterCodeFixesAsync context : Task =
override _.RegisterCodeFixesAsync context : Task =
async {
let diagnostics =
context.Diagnostics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions

open FSharp.Compiler
open FSharp.Compiler.Range
open FSharp.Compiler.SourceCodeServices
open FSharp.Compiler.SyntaxTree
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions

type private ReferenceType =
| AddProjectRef of ProjectReference
| AddMetadataRef of MetadataReference
| AddProjectRef of ProjectReference
| AddMetadataRef of MetadataReference

[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = "MissingReference"); Shared>]
type internal MissingReferenceCodeFixProvider() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ type internal FSharpProposeUpperCaseLabelCodeFixProvider
let fixableDiagnosticIds = ["FS0053"]
static let userOpName = "ProposeUpperCaseLabel"

override __.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds

override __.RegisterCodeFixesAsync context : Task =
override _.RegisterCodeFixesAsync context : Task =
asyncMaybe {
let textChanger (originalText: string) = originalText.[0].ToString().ToUpper() + originalText.Substring(1)
let! solutionChanger, originalText = SymbolHelpers.changeAllSymbolReferences(context.Document, context.Span, textChanger, projectInfoManager, checkerProvider.Checker, userOpName)
Expand Down
68 changes: 34 additions & 34 deletions vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedOpens.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
namespace Microsoft.VisualStudio.FSharp.Editor

open System.Composition
open System.Threading
open System.Threading.Tasks

open Microsoft.CodeAnalysis.Diagnostics
open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics

open FSharp.Compiler.Range
Expand All @@ -24,37 +21,40 @@ type internal FSharpRemoveUnusedOpensCodeFixProvider
inherit CodeFixProvider()
let userOpName = "FSharpRemoveUnusedOpensCodeFixProvider"
let fixableDiagnosticIds = [FSharpIDEDiagnosticIds.RemoveUnnecessaryImportsDiagnosticId]

let createCodeFix (title: string, context: CodeFixContext) =
CodeAction.Create(
title,
(fun (cancellationToken: CancellationToken) ->
asyncMaybe {
let document = context.Document
let! sourceText = document.GetTextAsync()
let checker = checkerProvider.Checker
let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName)
let! unusedOpens = UnusedOpensDiagnosticAnalyzer.GetUnusedOpenRanges(document, projectOptions, checker)
let changes =
unusedOpens
|> List.map (fun m ->
let span = sourceText.Lines.[Line.toZ m.StartLine].SpanIncludingLineBreak
TextChange(span, ""))
|> List.toArray

return document.WithText(sourceText.WithChanges(changes))
}
|> Async.map (Option.defaultValue context.Document)
|> RoslynHelpers.StartAsyncAsTask(cancellationToken)),
title)

override __.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds

override __.RegisterCodeFixesAsync context : Task =
async {
let diagnostics = context.Diagnostics |> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id) |> Seq.toImmutableArray
context.RegisterCodeFix(createCodeFix(SR.RemoveUnusedOpens(), context), diagnostics)
} |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)

override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds

override _.RegisterCodeFixesAsync context : Task =
asyncMaybe {
let document = context.Document
let! sourceText = document.GetTextAsync()
let checker = checkerProvider.Checker
let! _, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName)
let! unusedOpens = UnusedOpensDiagnosticAnalyzer.GetUnusedOpenRanges(document, projectOptions, checker)
let changes =
unusedOpens
|> List.map (fun m ->
let span = sourceText.Lines.[Line.toZ m.StartLine].SpanIncludingLineBreak
TextChange(span, ""))
|> List.toArray

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))

context.RegisterCodeFix(codefix, diagnostics)
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)

override __.GetFixAllProvider() = WellKnownFixAllProviders.BatchFixer

Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ type internal FSharpRenameParamToMatchSignature
let fixableDiagnosticIds = ["FS3218"]


override __.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds

override __.RegisterCodeFixesAsync context : Task =
override _.RegisterCodeFixesAsync context : Task =
asyncMaybe {
match context.Diagnostics |> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id) |> Seq.toList with
| [diagnostic] ->
Expand Down
40 changes: 22 additions & 18 deletions vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,8 @@ type internal FSharpRenameUnusedValueCodeFixProvider

inherit CodeFixProvider()
static let userOpName = "RenameUnusedValueCodeFix"
let fixableDiagnosticIds = ["FS1182"]
let fixableDiagnosticIds = set ["FS1182"]
let checker = checkerProvider.Checker

let createCodeFix (context: CodeFixContext, symbolName: string, titleFormat: string, textChange: TextChange) =
let title = String.Format(titleFormat, symbolName)
let codeAction =
CodeAction.Create(
title,
(fun (cancellationToken: CancellationToken) ->
async {
let! cancellationToken = Async.CancellationToken
let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask
return context.Document.WithText(sourceText.WithChanges(textChange))
} |> RoslynHelpers.StartAsyncAsTask(cancellationToken)),
title)
let diagnostics = context.Diagnostics |> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id) |> Seq.toImmutableArray
context.RegisterCodeFix(codeAction, diagnostics)

override __.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds

Expand All @@ -66,10 +51,29 @@ type internal FSharpRenameUnusedValueCodeFixProvider
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 ->
createCodeFix(context, symbolName, SR.PrefixValueNameWithUnderscore(), TextChange(TextSpan(context.Span.Start, 0), "_"))
if func.IsValue then createCodeFix(context, symbolName, SR.RenameValueToUnderscore(), TextChange(context.Span, "_"))
let prefixTitle = String.Format(SR.PrefixValueNameWithUnderscore(), symbolName)
let prefixCodeFix =
CodeFixHelpers.createTextChangeCodeFix(
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(
replaceTitle,
context,
(fun () -> asyncMaybe.Return [| TextChange(context.Span, "_") |]))
context.RegisterCodeFix(replaceCodeFix, diagnostics)
| _ -> ()
}
|> Async.Ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ type internal FSharpReplaceWithSuggestionCodeFixProvider
let fixableDiagnosticIds = set ["FS0039"; "FS1129"; "FS0495"]
let checker = checkerProvider.Checker

override __.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds

override __.RegisterCodeFixesAsync context : Task =
override _.RegisterCodeFixesAsync context : Task =
asyncMaybe {
do! Option.guard settings.CodeFixes.SuggestNamesForErrors

Expand Down
4 changes: 2 additions & 2 deletions vsintegration/src/FSharp.Editor/CodeFix/SimplifyName.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ type internal FSharpSimplifyNameCodeFixProvider() =
inherit CodeFixProvider()
let fixableDiagnosticId = FSharpIDEDiagnosticIds.SimplifyNamesDiagnosticId

override __.FixableDiagnosticIds = ImmutableArray.Create(fixableDiagnosticId)
override _.FixableDiagnosticIds = ImmutableArray.Create(fixableDiagnosticId)

override __.RegisterCodeFixesAsync(context: CodeFixContext) : Task =
override _.RegisterCodeFixesAsync(context: CodeFixContext) : Task =
async {
for diagnostic in context.Diagnostics |> Seq.filter (fun x -> x.Id = fixableDiagnosticId) do
let title =
Expand Down