未验证 提交 93c135e0 编写于 作者: T Tomas Grosup 提交者: GitHub

Fixing Bulk codefix (Removed unused opens) + adding new ones in Bulk mode (#15082)

Fixing Bulk codefix (Removed unused opens) + adding new ones in Bulk mode
上级 fe29acea
...@@ -13,27 +13,13 @@ type internal FSharpAddInstanceMemberParameterCodeFixProvider() = ...@@ -13,27 +13,13 @@ type internal FSharpAddInstanceMemberParameterCodeFixProvider() =
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS0673" ] let fixableDiagnosticIds = set [ "FS0673" ]
static let title = SR.AddMissingInstanceMemberParameter()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.RegisterCodeFixesAsync context : Task = override _.RegisterCodeFixesAsync context : Task =
asyncMaybe { asyncMaybe {
let diagnostics = do context.RegisterFsharpFix(CodeFix.AddInstanceMemberParameter, title, [| TextChange(TextSpan(context.Span.Start, 0), "x.") |])
context.Diagnostics
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
|> Seq.toImmutableArray
let title = SR.AddMissingInstanceMemberParameter()
let codeFix =
CodeFixHelpers.createTextChangeCodeFix (
CodeFix.AddInstanceMemberParameter,
title,
context,
(fun () -> asyncMaybe.Return [| TextChange(TextSpan(context.Span.Start, 0), "x.") |])
)
context.RegisterCodeFix(codeFix, diagnostics)
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -14,15 +14,11 @@ type internal FSharpAddMissingEqualsToTypeDefinitionCodeFixProvider() = ...@@ -14,15 +14,11 @@ type internal FSharpAddMissingEqualsToTypeDefinitionCodeFixProvider() =
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS3360" ] let fixableDiagnosticIds = set [ "FS3360" ]
static let title = SR.AddMissingEqualsToTypeDefinition()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.RegisterCodeFixesAsync context : Task = override _.RegisterCodeFixesAsync context : Task =
asyncMaybe { asyncMaybe {
let diagnostics =
context.Diagnostics
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
|> Seq.toImmutableArray
let! sourceText = context.Document.GetTextAsync(context.CancellationToken) let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
...@@ -37,19 +33,14 @@ type internal FSharpAddMissingEqualsToTypeDefinitionCodeFixProvider() = ...@@ -37,19 +33,14 @@ type internal FSharpAddMissingEqualsToTypeDefinitionCodeFixProvider() =
pos <- pos - 1 pos <- pos - 1
ch <- sourceText.[pos] ch <- sourceText.[pos]
let title = SR.AddMissingEqualsToTypeDefinition() do
context.RegisterFsharpFix(
let codeFix =
CodeFixHelpers.createTextChangeCodeFix (
CodeFix.AddMissingEqualsToTypeDefinition, CodeFix.AddMissingEqualsToTypeDefinition,
title, title,
context,
// 'pos + 1' is here because 'pos' is now the position of the first non-whitespace character. // 'pos + 1' is here because 'pos' is now the position of the first non-whitespace character.
// Using just 'pos' will creat uncompilable code. // 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 |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -14,6 +14,7 @@ open FSharp.Compiler.CodeAnalysis ...@@ -14,6 +14,7 @@ open FSharp.Compiler.CodeAnalysis
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.AddMissingFunKeyword); Shared>] [<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.AddMissingFunKeyword); Shared>]
type internal FSharpAddMissingFunKeywordCodeFixProvider [<ImportingConstructor>] () = type internal FSharpAddMissingFunKeywordCodeFixProvider [<ImportingConstructor>] () =
inherit CodeFixProvider() inherit CodeFixProvider()
static let title = SR.AddMissingFunKeyword()
let fixableDiagnosticIds = set [ "FS0010" ] let fixableDiagnosticIds = set [ "FS0010" ]
...@@ -56,22 +57,7 @@ type internal FSharpAddMissingFunKeywordCodeFixProvider [<ImportingConstructor>] ...@@ -56,22 +57,7 @@ type internal FSharpAddMissingFunKeywordCodeFixProvider [<ImportingConstructor>]
let! intendedArgSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, intendedArgLexerSymbol.Range) let! intendedArgSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, intendedArgLexerSymbol.Range)
let diagnostics = do context.RegisterFsharpFix(CodeFix.AddMissingFunKeyword, title, [| TextChange(TextSpan(intendedArgSpan.Start, 0), "fun ") |])
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)
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -3,36 +3,37 @@ ...@@ -3,36 +3,37 @@
namespace Microsoft.VisualStudio.FSharp.Editor namespace Microsoft.VisualStudio.FSharp.Editor
open System.Composition open System.Composition
open System.Threading
open System.Threading.Tasks open System.Threading.Tasks
open System.Collections.Immutable
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.AddNewKeyword); Shared>] [<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.AddNewKeyword); Shared>]
type internal FSharpAddNewKeywordCodeFixProvider() = type internal FSharpAddNewKeywordCodeFixProvider() =
inherit CodeFixProvider() inherit CodeFixProvider()
static let fixableDiagnosticIds = set [ "FS0760" ] static let title = SR.AddNewKeyword()
override _.FixableDiagnosticIds = ImmutableArray.Create "FS0760"
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds member this.GetChanges(_document: Document, diagnostics: ImmutableArray<Diagnostic>, _ct: CancellationToken) =
backgroundTask {
override _.RegisterCodeFixesAsync context : Task = let changes =
async { diagnostics
let title = SR.AddNewKeyword() |> Seq.map (fun d -> TextChange(TextSpan(d.Location.SourceSpan.Start, 0), "new "))
let diagnostics = return changes
context.Diagnostics }
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
|> Seq.toImmutableArray
let codeFix =
CodeFixHelpers.createTextChangeCodeFix (
CodeFix.AddNewKeyword,
title,
context,
(fun () -> asyncMaybe.Return [| TextChange(TextSpan(context.Span.Start, 0), "new ") |])
)
context.RegisterCodeFix(codeFix, diagnostics) override this.RegisterCodeFixesAsync ctx : Task =
backgroundTask {
let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
ctx.RegisterFsharpFix(CodeFix.AddNewKeyword, title, changes)
} }
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
override this.GetFixAllProvider() =
CodeFixHelpers.createFixAllProvider CodeFix.AddNewKeyword this.GetChanges
...@@ -30,7 +30,7 @@ type internal FSharpAddOpenCodeFixProvider [<ImportingConstructor>] (assemblyCon ...@@ -30,7 +30,7 @@ type internal FSharpAddOpenCodeFixProvider [<ImportingConstructor>] (assemblyCon
CodeFix.AddOpen, CodeFix.AddOpen,
fixUnderscoresInMenuText fullName, fixUnderscoresInMenuText fullName,
context, context,
(fun () -> asyncMaybe.Return [| TextChange(context.Span, qualifier) |]) [| TextChange(context.Span, qualifier) |]
) )
let openNamespaceFix (context: CodeFixContext) ctx name ns multipleNames = let openNamespaceFix (context: CodeFixContext) ctx name ns multipleNames =
...@@ -77,12 +77,7 @@ type internal FSharpAddOpenCodeFixProvider [<ImportingConstructor>] (assemblyCon ...@@ -77,12 +77,7 @@ type internal FSharpAddOpenCodeFixProvider [<ImportingConstructor>] (assemblyCon
|> Seq.toList |> Seq.toList
for codeFix in openNamespaceFixes @ qualifiedSymbolFixes do for codeFix in openNamespaceFixes @ qualifiedSymbolFixes do
context.RegisterCodeFix( context.RegisterCodeFix(codeFix, context.Diagnostics)
codeFix,
context.Diagnostics
|> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id)
|> Seq.toImmutableArray
)
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
......
...@@ -14,16 +14,12 @@ type internal FSharpChangePrefixNegationToInfixSubtractionodeFixProvider() = ...@@ -14,16 +14,12 @@ type internal FSharpChangePrefixNegationToInfixSubtractionodeFixProvider() =
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS0003" ] let fixableDiagnosticIds = set [ "FS0003" ]
static let title = SR.ChangePrefixNegationToInfixSubtraction()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.RegisterCodeFixesAsync context : Task = override _.RegisterCodeFixesAsync context : Task =
asyncMaybe { asyncMaybe {
let diagnostics =
context.Diagnostics
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
|> Seq.toImmutableArray
let! sourceText = context.Document.GetTextAsync(context.CancellationToken) let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
let mutable pos = context.Span.End + 1 let mutable pos = context.Span.End + 1
...@@ -39,18 +35,7 @@ type internal FSharpChangePrefixNegationToInfixSubtractionodeFixProvider() = ...@@ -39,18 +35,7 @@ type internal FSharpChangePrefixNegationToInfixSubtractionodeFixProvider() =
// Bail if this isn't a negation // Bail if this isn't a negation
do! Option.guard (ch = '-') do! Option.guard (ch = '-')
do context.RegisterFsharpFix(CodeFix.ChangePrefixNegationToInfixSubtraction, title, [| TextChange(TextSpan(pos, 1), "- ") |])
let title = SR.ChangePrefixNegationToInfixSubtraction()
let codeFix =
CodeFixHelpers.createTextChangeCodeFix (
CodeFix.ChangePrefixNegationToInfixSubtraction,
title,
context,
(fun () -> asyncMaybe.Return [| TextChange(TextSpan(pos, 1), "- ") |])
)
context.RegisterCodeFix(codeFix, diagnostics)
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -13,6 +13,7 @@ type internal FSharpChangeRefCellDerefToNotExpressionCodeFixProvider [<Importing ...@@ -13,6 +13,7 @@ type internal FSharpChangeRefCellDerefToNotExpressionCodeFixProvider [<Importing
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS0001" ] let fixableDiagnosticIds = set [ "FS0001" ]
static let title = SR.UseNotForNegation()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
...@@ -32,22 +33,7 @@ type internal FSharpChangeRefCellDerefToNotExpressionCodeFixProvider [<Importing ...@@ -32,22 +33,7 @@ type internal FSharpChangeRefCellDerefToNotExpressionCodeFixProvider [<Importing
let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos errorRange.Start let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos errorRange.Start
let! derefSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, derefRange) let! derefSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, derefRange)
let title = SR.UseNotForNegation() do context.RegisterFsharpFix(CodeFix.ChangeRefCellDerefToNotExpression, title, [| TextChange(derefSpan, "not ") |])
let diagnostics =
context.Diagnostics
|> 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)
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -43,20 +43,7 @@ type internal FSharpChangeToUpcastCodeFixProvider() = ...@@ -43,20 +43,7 @@ type internal FSharpChangeToUpcastCodeFixProvider() =
else else
SR.UseUpcastKeyword() SR.UseUpcastKeyword()
let diagnostics = do context.RegisterFsharpFix(CodeFix.ChangeToUpcast, title, [| TextChange(context.Span, replacement) |])
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)
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -2,8 +2,14 @@ ...@@ -2,8 +2,14 @@
namespace Microsoft.VisualStudio.FSharp.Editor namespace Microsoft.VisualStudio.FSharp.Editor
open System
open System.Threading open System.Threading
open System.Threading.Tasks
open System.Collections.Immutable
open System.Diagnostics
open Microsoft
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions open Microsoft.CodeAnalysis.CodeActions
...@@ -11,63 +17,60 @@ open Microsoft.VisualStudio.FSharp.Editor.Telemetry ...@@ -11,63 +17,60 @@ open Microsoft.VisualStudio.FSharp.Editor.Telemetry
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module internal CodeFixHelpers = module internal CodeFixHelpers =
let createTextChangeCodeFix let private reportCodeFixTelemetry (diagnostics: ImmutableArray<Diagnostic>) (doc: Document) (staticName: string) (additionalProps) =
( let ids =
name: string, diagnostics |> Seq.map (fun d -> d.Id) |> Seq.distinct |> String.concat ","
title: string,
context: CodeFixContext,
computeTextChanges: unit -> Async<TextChange[] option>
) =
// 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 = let props: (string * obj) list =
[ additionalProps
"name", name @ [
"name", staticName
"ids", ids "ids", ids
"context.document.project.id", doc.Project.Id.Id.ToString()
// The following can help building a unique but anonymized codefix target: "context.document.id", doc.Id.Id.ToString()
// #projectid#documentid#span "context.diagnostics.count", diagnostics.Length
// 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()
] ]
TelemetryReporter.reportEvent "codefixregistered" props TelemetryReporter.reportEvent "codefixactivated" props
let createFixAllProvider name getChanges =
FixAllProvider.Create(fun fixAllCtx doc allDiagnostics ->
backgroundTask {
let sw = Stopwatch.StartNew()
let! (changes: seq<TextChange>) = getChanges (doc, allDiagnostics, fixAllCtx.CancellationToken)
let! text = doc.GetTextAsync(fixAllCtx.CancellationToken)
let doc = doc.WithText(text.WithChanges(changes))
do
reportCodeFixTelemetry
allDiagnostics
doc
name
[ "scope", fixAllCtx.Scope.ToString(); "elapsedMs", sw.ElapsedMilliseconds ]
return doc
})
let createTextChangeCodeFix (name: string, title: string, context: CodeFixContext, changes: TextChange seq) =
CodeAction.Create( CodeAction.Create(
title, title,
(fun (cancellationToken: CancellationToken) -> (fun (cancellationToken: CancellationToken) ->
async { backgroundTask {
let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask let! sourceText = context.Document.GetTextAsync(cancellationToken)
let! changesOpt = computeTextChanges () let doc = context.Document.WithText(sourceText.WithChanges(changes))
reportCodeFixTelemetry context.Diagnostics context.Document name []
match changesOpt with return doc
| None -> return context.Document }),
| Some textChanges -> name
// 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)),
title
) )
[<AutoOpen>] [<AutoOpen>]
module internal CodeFixExtensions = module internal CodeFixExtensions =
type CodeFixProvider with type CodeFixContext with
member this.GetPrunedDiagnostics(context: CodeFixContext) =
context.Diagnostics.RemoveAll(fun x -> this.FixableDiagnosticIds.Contains(x.Id) |> not)
member this.RegisterFix(name, title, context: CodeFixContext, fixChange) = member ctx.RegisterFsharpFix(staticName, title, changes, ?diagnostics) =
let replaceCodeFix = let codeAction =
CodeFixHelpers.createTextChangeCodeFix (name, title, context, (fun () -> asyncMaybe.Return [| fixChange |])) CodeFixHelpers.createTextChangeCodeFix (staticName, title, ctx, changes)
context.RegisterCodeFix(replaceCodeFix, this.GetPrunedDiagnostics(context)) let diag = diagnostics |> Option.defaultValue ctx.Diagnostics
ctx.RegisterCodeFix(codeAction, diag)
...@@ -12,7 +12,7 @@ type internal FSharpConvertCSharpLambdaToFSharpLambdaCodeFixProvider [<Importing ...@@ -12,7 +12,7 @@ type internal FSharpConvertCSharpLambdaToFSharpLambdaCodeFixProvider [<Importing
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS0039"; "FS0043" ] let fixableDiagnosticIds = set [ "FS0039"; "FS0043" ]
static let title = SR.UseFSharpLambda()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.RegisterCodeFixesAsync context = override _.RegisterCodeFixesAsync context =
...@@ -38,22 +38,7 @@ type internal FSharpConvertCSharpLambdaToFSharpLambdaCodeFixProvider [<Importing ...@@ -38,22 +38,7 @@ type internal FSharpConvertCSharpLambdaToFSharpLambdaCodeFixProvider [<Importing
let bodyText = sourceText.GetSubText(lambdaBodySpan).ToString() let bodyText = sourceText.GetSubText(lambdaBodySpan).ToString()
TextChange(fullParenSpan, "fun " + argText + " -> " + bodyText) TextChange(fullParenSpan, "fun " + argText + " -> " + bodyText)
let diagnostics = do context.RegisterFsharpFix(CodeFix.ConvertCSharpLambdaToFSharpLambda, title, [| replacement |])
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)
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -15,6 +15,7 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [<ImportingConstructor>] () = ...@@ -15,6 +15,7 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [<ImportingConstructor>] () =
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS0039"; "FS0201" ] let fixableDiagnosticIds = set [ "FS0039"; "FS0201" ]
static let title = SR.ConvertCSharpUsingToFSharpOpen()
let usingLength = "using".Length let usingLength = "using".Length
let isCSharpUsingShapeWithPos (context: CodeFixContext) (sourceText: SourceText) = let isCSharpUsingShapeWithPos (context: CodeFixContext) (sourceText: SourceText) =
...@@ -39,34 +40,17 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [<ImportingConstructor>] () = ...@@ -39,34 +40,17 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [<ImportingConstructor>] () =
let slice = sourceText.GetSubText(span).ToString() let slice = sourceText.GetSubText(span).ToString()
struct (slice = "using", start) struct (slice = "using", start)
let registerCodeFix (context: CodeFixContext) (diagnostics: ImmutableArray<Diagnostic>) (str: string) (span: TextSpan) = let registerCodeFix (context: CodeFixContext) (str: string) (span: TextSpan) =
let replacement = let replacement =
let str = str.Replace("using", "open").Replace(";", "") let str = str.Replace("using", "open").Replace(";", "")
TextChange(span, str) TextChange(span, str)
let title = SR.ConvertCSharpUsingToFSharpOpen() do context.RegisterFsharpFix(CodeFix.ConvertCSharpUsingToFSharpOpen, title, [| replacement |])
let codeFix =
CodeFixHelpers.createTextChangeCodeFix (
CodeFix.ConvertCSharpUsingToFSharpOpen,
title,
context,
(fun () -> asyncMaybe.Return [| replacement |])
)
context.RegisterCodeFix(codeFix, diagnostics)
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.RegisterCodeFixesAsync context = override _.RegisterCodeFixesAsync context =
asyncMaybe { 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) let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
// TODO: handle single-line case? // TODO: handle single-line case?
...@@ -83,7 +67,7 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [<ImportingConstructor>] () = ...@@ -83,7 +67,7 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [<ImportingConstructor>] () =
(statementWithSemicolon.StartsWith("using") (statementWithSemicolon.StartsWith("using")
&& statementWithSemicolon.EndsWith(";")) && statementWithSemicolon.EndsWith(";"))
then then
registerCodeFix context diagnostics statementWithSemicolon statementWithSemicolonSpan registerCodeFix context statementWithSemicolon statementWithSemicolonSpan
else else
// Only the identifier being opened has a diagnostic, so we try to find the rest of the statement // Only the identifier being opened has a diagnostic, so we try to find the rest of the statement
let struct (isCSharpUsingShape, start) = let struct (isCSharpUsingShape, start) =
...@@ -93,7 +77,7 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [<ImportingConstructor>] () = ...@@ -93,7 +77,7 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [<ImportingConstructor>] () =
let len = (context.Span.Start - start) + statementWithSemicolonSpan.Length let len = (context.Span.Start - start) + statementWithSemicolonSpan.Length
let fullSpan = TextSpan(start, len) let fullSpan = TextSpan(start, len)
let str = sourceText.GetSubText(fullSpan).ToString() let str = sourceText.GetSubText(fullSpan).ToString()
registerCodeFix context diagnostics str fullSpan registerCodeFix context str fullSpan
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -13,6 +13,7 @@ type internal FSharpConvertToNotEqualsEqualityExpressionCodeFixProvider() = ...@@ -13,6 +13,7 @@ type internal FSharpConvertToNotEqualsEqualityExpressionCodeFixProvider() =
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS0043" ] let fixableDiagnosticIds = set [ "FS0043" ]
static let title = SR.ConvertToNotEqualsEqualityExpression()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
...@@ -24,23 +25,7 @@ type internal FSharpConvertToNotEqualsEqualityExpressionCodeFixProvider() = ...@@ -24,23 +25,7 @@ type internal FSharpConvertToNotEqualsEqualityExpressionCodeFixProvider() =
// We're converting '!=' into '<>', a common new user mistake. // We're converting '!=' into '<>', a common new user mistake.
// If this is an FS00043 that is anything other than that, bail out // If this is an FS00043 that is anything other than that, bail out
do! Option.guard (text = "!=") do! Option.guard (text = "!=")
do context.RegisterFsharpFix(CodeFix.ConvertToNotEqualsEqualityExpression, title, [| TextChange(context.Span, "<>") |])
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)
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -13,6 +13,7 @@ type internal FSharpConvertToSingleEqualsEqualityExpressionCodeFixProvider() = ...@@ -13,6 +13,7 @@ type internal FSharpConvertToSingleEqualsEqualityExpressionCodeFixProvider() =
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS0043" ] let fixableDiagnosticIds = set [ "FS0043" ]
static let title = SR.ConvertToSingleEqualsEqualityExpression()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
...@@ -24,23 +25,7 @@ type internal FSharpConvertToSingleEqualsEqualityExpressionCodeFixProvider() = ...@@ -24,23 +25,7 @@ type internal FSharpConvertToSingleEqualsEqualityExpressionCodeFixProvider() =
// We're converting '==' into '=', a common new user mistake. // We're converting '==' into '=', a common new user mistake.
// If this is an FS00043 that is anything other than that, bail out // If this is an FS00043 that is anything other than that, bail out
do! Option.guard (text = "==") do! Option.guard (text = "==")
do context.RegisterFsharpFix(CodeFix.ConvertToSingleEqualsEqualityExpression, title, [| TextChange(context.Span, "=") |])
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)
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -4,8 +4,10 @@ namespace Microsoft.VisualStudio.FSharp.Editor ...@@ -4,8 +4,10 @@ namespace Microsoft.VisualStudio.FSharp.Editor
open System.Composition open System.Composition
open System.Collections.Immutable open System.Collections.Immutable
open System.Threading
open System.Threading.Tasks open System.Threading.Tasks
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeFixes
open FSharp.Compiler.Diagnostics open FSharp.Compiler.Diagnostics
...@@ -14,48 +16,40 @@ open FSharp.Compiler.Diagnostics ...@@ -14,48 +16,40 @@ open FSharp.Compiler.Diagnostics
type internal LegacyFsharpFixAddDotToIndexerAccess() = type internal LegacyFsharpFixAddDotToIndexerAccess() =
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS3217" ] let fixableDiagnosticIds = set [ "FS3217" ]
static let title = CompilerDiagnostics.GetErrorMessage FSharpDiagnosticKind.AddIndexerDot
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.RegisterCodeFixesAsync context : Task = override _.RegisterCodeFixesAsync context : Task =
async { async {
let diagnostics = let! sourceText = context.Document.GetTextAsync() |> Async.AwaitTask
context.Diagnostics
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) context.Diagnostics
|> Seq.toList |> Seq.iter (fun diagnostic ->
if not (List.isEmpty diagnostics) then let span, replacement =
let! sourceText = context.Document.GetTextAsync() |> Async.AwaitTask try
let mutable span = context.Span
diagnostics
|> Seq.iter (fun diagnostic -> let notStartOfBracket (span: TextSpan) =
let diagnostics = ImmutableArray.Create diagnostic let t = sourceText.GetSubText(TextSpan(span.Start, span.Length + 1))
t.[t.Length - 1] <> '['
let span, replacement =
try // skip all braces and blanks until we find [
let mutable span = context.Span while span.End < sourceText.Length && notStartOfBracket span do
span <- TextSpan(span.Start, span.Length + 1)
let notStartOfBracket (span: TextSpan) =
let t = sourceText.GetSubText(TextSpan(span.Start, span.Length + 1)) span, sourceText.GetSubText(span).ToString()
t.[t.Length - 1] <> '[' with _ ->
context.Span, sourceText.GetSubText(context.Span).ToString()
// skip all braces and blanks until we find [
while span.End < sourceText.Length && notStartOfBracket span do do
span <- TextSpan(span.Start, span.Length + 1) context.RegisterFsharpFix(
CodeFix.FixIndexerAccess,
span, sourceText.GetSubText(span).ToString() title,
with _ -> [| TextChange(span, replacement.TrimEnd() + ".") |],
context.Span, sourceText.GetSubText(context.Span).ToString() ImmutableArray.Create(diagnostic)
))
let codefix =
CodeFixHelpers.createTextChangeCodeFix (
CodeFix.FixIndexerAccess,
CompilerDiagnostics.GetErrorMessage FSharpDiagnosticKind.AddIndexerDot,
context,
(fun () -> asyncMaybe.Return [| TextChange(span, replacement.TrimEnd() + ".") |])
)
context.RegisterCodeFix(codefix, diagnostics))
} }
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -67,12 +61,21 @@ type internal FsharpFixRemoveDotFromIndexerAccessOptIn() as this = ...@@ -67,12 +61,21 @@ type internal FsharpFixRemoveDotFromIndexerAccessOptIn() as this =
static let title = static let title =
CompilerDiagnostics.GetErrorMessage FSharpDiagnosticKind.RemoveIndexerDot CompilerDiagnostics.GetErrorMessage FSharpDiagnosticKind.RemoveIndexerDot
member this.GetChanges(_document: Document, diagnostics: ImmutableArray<Diagnostic>, _ct: CancellationToken) =
backgroundTask {
let changes =
diagnostics |> Seq.map (fun x -> TextChange(x.Location.SourceSpan, ""))
return changes
}
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.RegisterCodeFixesAsync context : Task = override _.RegisterCodeFixesAsync ctx : Task =
backgroundTask { backgroundTask {
let relevantDiagnostics = this.GetPrunedDiagnostics(context) let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
ctx.RegisterFsharpFix(CodeFix.RemoveIndexerDotBeforeBracket, title, changes)
if not relevantDiagnostics.IsEmpty then
this.RegisterFix(CodeFix.RemoveIndexerDotBeforeBracket, title, context, TextChange(context.Span, ""))
} }
override this.GetFixAllProvider() =
CodeFixHelpers.createFixAllProvider CodeFix.RemoveIndexerDotBeforeBracket this.GetChanges
...@@ -16,15 +16,12 @@ type internal FSharpMakeDeclarationMutableFixProvider [<ImportingConstructor>] ( ...@@ -16,15 +16,12 @@ type internal FSharpMakeDeclarationMutableFixProvider [<ImportingConstructor>] (
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS0027" ] let fixableDiagnosticIds = set [ "FS0027" ]
static let title = SR.MakeDeclarationMutable()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.RegisterCodeFixesAsync context : Task = override _.RegisterCodeFixesAsync context : Task =
asyncMaybe { asyncMaybe {
let diagnostics =
context.Diagnostics
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
|> Seq.toImmutableArray
let document = context.Document let document = context.Document
do! Option.guard (not (isSignatureFile document.FilePath)) do! Option.guard (not (isSignatureFile document.FilePath))
...@@ -65,18 +62,7 @@ type internal FSharpMakeDeclarationMutableFixProvider [<ImportingConstructor>] ( ...@@ -65,18 +62,7 @@ type internal FSharpMakeDeclarationMutableFixProvider [<ImportingConstructor>] (
// Bail if it's a parameter, because like, that ain't allowed // Bail if it's a parameter, because like, that ain't allowed
do! Option.guard (not (parseFileResults.IsPositionContainedInACurriedParameter declRange.Start)) do! Option.guard (not (parseFileResults.IsPositionContainedInACurriedParameter declRange.Start))
do context.RegisterFsharpFix(CodeFix.MakeDeclarationMutable, title, [| TextChange(TextSpan(span.Start, 0), "mutable ") |])
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)
| _ -> () | _ -> ()
} }
|> Async.Ignore |> Async.Ignore
......
...@@ -40,23 +40,15 @@ type internal FSharpMakeOuterBindingRecursiveCodeFixProvider [<ImportingConstruc ...@@ -40,23 +40,15 @@ type internal FSharpMakeOuterBindingRecursiveCodeFixProvider [<ImportingConstruc
.ContentEquals(sourceText.GetSubText(context.Span)) .ContentEquals(sourceText.GetSubText(context.Span))
) )
let diagnostics =
context.Diagnostics
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
|> Seq.toImmutableArray
let title = let title =
String.Format(SR.MakeOuterBindingRecursive(), sourceText.GetSubText(outerBindingNameSpan).ToString()) String.Format(SR.MakeOuterBindingRecursive(), sourceText.GetSubText(outerBindingNameSpan).ToString())
let codeFix = do
CodeFixHelpers.createTextChangeCodeFix ( context.RegisterFsharpFix(
CodeFix.MakeOuterBindingRecursive, CodeFix.MakeOuterBindingRecursive,
title, title,
context, [| TextChange(TextSpan(outerBindingNameSpan.Start, 0), "rec ") |]
(fun () -> asyncMaybe.Return [| TextChange(TextSpan(outerBindingNameSpan.Start, 0), "rec ") |])
) )
context.RegisterCodeFix(codeFix, diagnostics)
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -29,11 +29,6 @@ type internal FSharpRemoveReturnOrYieldCodeFixProvider [<ImportingConstructor>] ...@@ -29,11 +29,6 @@ type internal FSharpRemoveReturnOrYieldCodeFixProvider [<ImportingConstructor>]
let! exprRange = parseResults.TryRangeOfExprInYieldOrReturn errorRange.Start let! exprRange = parseResults.TryRangeOfExprInYieldOrReturn errorRange.Start
let! exprSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, exprRange) let! exprSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, exprRange)
let diagnostics =
context.Diagnostics
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
|> Seq.toImmutableArray
let title = let title =
let text = sourceText.GetSubText(context.Span).ToString() let text = sourceText.GetSubText(context.Span).ToString()
...@@ -42,15 +37,12 @@ type internal FSharpRemoveReturnOrYieldCodeFixProvider [<ImportingConstructor>] ...@@ -42,15 +37,12 @@ type internal FSharpRemoveReturnOrYieldCodeFixProvider [<ImportingConstructor>]
elif text.StartsWith("yield!") then SR.RemoveYieldBang() elif text.StartsWith("yield!") then SR.RemoveYieldBang()
else SR.RemoveYield() else SR.RemoveYield()
let codeFix = do
CodeFixHelpers.createTextChangeCodeFix ( context.RegisterFsharpFix(
CodeFix.RemoveReturnOrYield, CodeFix.RemoveReturnOrYield,
title, title,
context, [| TextChange(context.Span, sourceText.GetSubText(exprSpan).ToString()) |]
(fun () -> asyncMaybe.Return [| TextChange(context.Span, sourceText.GetSubText(exprSpan).ToString()) |])
) )
context.RegisterCodeFix(codeFix, diagnostics)
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -3,8 +3,12 @@ ...@@ -3,8 +3,12 @@
namespace Microsoft.VisualStudio.FSharp.Editor namespace Microsoft.VisualStudio.FSharp.Editor
open System.Composition open System.Composition
open System.Threading
open System.Threading.Tasks open System.Threading.Tasks
open System.Collections.Immutable
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.CodeActions
open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeFixes
...@@ -15,38 +19,47 @@ type internal RemoveSuperflousCaptureForUnionCaseWithNoDataProvider [<ImportingC ...@@ -15,38 +19,47 @@ type internal RemoveSuperflousCaptureForUnionCaseWithNoDataProvider [<ImportingC
inherit CodeFixProvider() inherit CodeFixProvider()
override _.FixableDiagnosticIds = Seq.toImmutableArray [ "FS0725"; "FS3548" ] static let title = SR.RemoveUnusedBinding()
override _.FixableDiagnosticIds = ImmutableArray.Create("FS0725", "FS3548")
override this.RegisterCodeFixesAsync context : Task = member this.GetChanges(document: Document, diagnostics: ImmutableArray<Diagnostic>, ct: CancellationToken) =
asyncMaybe { backgroundTask {
do! Option.guard context.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled
let document = context.Document let! sourceText = document.GetTextAsync(ct)
let! sourceText = document.GetTextAsync(context.CancellationToken) let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(CodeFix.RemoveSuperfluousCapture)
let! _, checkResults = let changes =
document.GetFSharpParseAndCheckResultsAsync(nameof (RemoveSuperflousCaptureForUnionCaseWithNoDataProvider)) seq {
|> liftAsync 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 = let unionCaseItem =
RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) 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 = let reminderSpan =
classifications new TextSpan(textSpan.Start + typeInfoLength, textSpan.Length - typeInfoLength)
|> Array.tryFind (fun c -> c.Type = SemanticClassificationType.UnionCase)
match unionCaseItem with yield TextChange(reminderSpan, "")
| 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 reminderSpan = return changes
new TextSpan(context.Span.Start + typeInfoLength, context.Span.Length - typeInfoLength) }
this.RegisterFix(CodeFix.RemoveSuperfluousCapture, SR.RemoveUnusedBinding(), context, TextChange(reminderSpan, "")) override this.RegisterCodeFixesAsync ctx : Task =
backgroundTask {
if ctx.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then
let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
ctx.RegisterFsharpFix(CodeFix.RemoveSuperfluousCapture, title, changes)
} }
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) override this.GetFixAllProvider() =
CodeFixHelpers.createFixAllProvider CodeFix.RemoveSuperfluousCapture this.GetChanges
...@@ -4,67 +4,71 @@ namespace Microsoft.VisualStudio.FSharp.Editor ...@@ -4,67 +4,71 @@ namespace Microsoft.VisualStudio.FSharp.Editor
open System open System
open System.Composition open System.Composition
open System.Threading
open System.Threading.Tasks open System.Threading.Tasks
open System.Collections.Immutable
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.CodeActions
open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeFixes
open FSharp.Compiler.EditorServices
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.RemoveUnusedBinding); Shared>] [<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.RemoveUnusedBinding); Shared>]
type internal FSharpRemoveUnusedBindingCodeFixProvider [<ImportingConstructor>] () = type internal FSharpRemoveUnusedBindingCodeFixProvider [<ImportingConstructor>] () =
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS1182" ] static let title = SR.RemoveUnusedBinding()
override _.FixableDiagnosticIds = ImmutableArray.Create("FS1182")
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
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 document = context.Document member this.GetChanges(document: Document, diagnostics: ImmutableArray<Diagnostic>, ct: CancellationToken) =
let! sourceText = document.GetTextAsync(context.CancellationToken) backgroundTask {
let! parseResults = let! sourceText = document.GetTextAsync(ct)
context.Document.GetFSharpParseResultsAsync(nameof (FSharpRemoveUnusedBindingCodeFixProvider)) let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpRemoveUnusedBindingCodeFixProvider))
|> liftAsync
let diagnostics = let changes =
context.Diagnostics seq {
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) for d in diagnostics do
|> Seq.toImmutableArray let textSpan = d.Location.SourceSpan
let symbolRange = let symbolRange =
RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText)
let! rangeOfBinding = parseResults.TryRangeOfBindingWithHeadPatternWithPos(symbolRange.Start) let spanOfBindingOpt =
let! spanOfBinding = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, rangeOfBinding) parseResults.TryRangeOfBindingWithHeadPatternWithPos(symbolRange.Start)
|> Option.bind (fun rangeOfBinding -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, rangeOfBinding))
let keywordEndColumn = match spanOfBindingOpt with
let rec loop ch pos = | Some spanOfBinding ->
if not (Char.IsWhiteSpace(ch)) then let keywordEndColumn =
pos let rec loop ch pos =
else if not (Char.IsWhiteSpace(ch)) then
loop sourceText.[pos - 1] (pos - 1) 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` // This is safe, since we could never have gotten here unless there was a `let` or `use`
let keywordStartColumn = keywordEndColumn - 2 let keywordStartColumn = keywordEndColumn - 2
let fullSpan = TextSpan(keywordStartColumn, spanOfBinding.End - keywordStartColumn) let fullSpan = TextSpan(keywordStartColumn, spanOfBinding.End - keywordStartColumn)
let prefixTitle = SR.RemoveUnusedBinding() yield TextChange(fullSpan, "")
| None -> ()
}
let removalCodeFix = return changes
CodeFixHelpers.createTextChangeCodeFix ( }
CodeFix.RemoveUnusedBinding,
prefixTitle,
context,
(fun () -> asyncMaybe.Return [| TextChange(fullSpan, "") |])
)
context.RegisterCodeFix(removalCodeFix, diagnostics) override this.RegisterCodeFixesAsync ctx : Task =
backgroundTask {
if ctx.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then
let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
ctx.RegisterFsharpFix(CodeFix.RemoveUnusedBinding, title, changes)
} }
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) override this.GetFixAllProvider() =
CodeFixHelpers.createFixAllProvider CodeFix.RemoveUnusedBinding this.GetChanges
...@@ -3,10 +3,14 @@ ...@@ -3,10 +3,14 @@
namespace Microsoft.VisualStudio.FSharp.Editor namespace Microsoft.VisualStudio.FSharp.Editor
open System.Composition open System.Composition
open System.Threading
open System.Threading.Tasks open System.Threading.Tasks
open System.Collections.Immutable
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics
open FSharp.Compiler.Text open FSharp.Compiler.Text
...@@ -15,37 +19,35 @@ open FSharp.Compiler.Text ...@@ -15,37 +19,35 @@ open FSharp.Compiler.Text
type internal FSharpRemoveUnusedOpensCodeFixProvider [<ImportingConstructor>] () = type internal FSharpRemoveUnusedOpensCodeFixProvider [<ImportingConstructor>] () =
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = static let title = SR.RemoveUnusedOpens()
[ FSharpIDEDiagnosticIds.RemoveUnnecessaryImportsDiagnosticId ]
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds =
ImmutableArray.Create FSharpIDEDiagnosticIds.RemoveUnnecessaryImportsDiagnosticId
override _.RegisterCodeFixesAsync context : Task = member this.GetChanges(document: Document, diagnostics: ImmutableArray<Diagnostic>, ct: CancellationToken) =
asyncMaybe { backgroundTask {
let document = context.Document let! sourceText = document.GetTextAsync(ct)
let! sourceText = document.GetTextAsync()
let! unusedOpens = UnusedOpensDiagnosticAnalyzer.GetUnusedOpenRanges(document)
let changes = let changes =
unusedOpens diagnostics
|> List.map (fun m -> |> Seq.map (fun d ->
let span = sourceText.Lines.[Line.toZ m.StartLine].SpanIncludingLineBreak sourceText
TextChange(span, "")) .Lines
|> List.toArray .GetLineFromPosition(
d.Location.SourceSpan.Start
let diagnostics = )
context.Diagnostics .SpanIncludingLineBreak)
|> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id) |> Seq.map (fun span -> TextChange(span, ""))
|> Seq.toImmutableArray
return changes
let title = SR.RemoveUnusedOpens() }
let codefix =
CodeFixHelpers.createTextChangeCodeFix (CodeFix.RemoveUnusedOpens, title, context, (fun () -> asyncMaybe.Return changes))
context.RegisterCodeFix(codefix, diagnostics) override this.RegisterCodeFixesAsync ctx : Task =
backgroundTask {
if ctx.Document.Project.IsFSharpCodeFixesUnusedOpensEnabled then
let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
ctx.RegisterFsharpFix(CodeFix.RemoveUnusedOpens, title, changes)
} }
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
override _.GetFixAllProvider() = WellKnownFixAllProviders.BatchFixer override this.GetFixAllProvider() =
CodeFixHelpers.createFixAllProvider CodeFix.RemoveUnusedOpens this.GetChanges
...@@ -2,14 +2,18 @@ ...@@ -2,14 +2,18 @@
namespace Microsoft.VisualStudio.FSharp.Editor namespace Microsoft.VisualStudio.FSharp.Editor
open System.Collections.Immutable
open System.Composition open System.Composition
open System.Threading
open System.Threading.Tasks open System.Threading.Tasks
open System.Collections.Immutable
open System.Text.RegularExpressions
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions
open Microsoft.VisualStudio.FSharp.Editor.SymbolHelpers open Microsoft.VisualStudio.FSharp.Editor.SymbolHelpers
open FSharp.Compiler.Diagnostics open FSharp.Compiler.Diagnostics
open FSharp.Compiler.Tokenization.FSharpKeywords open FSharp.Compiler.Tokenization.FSharpKeywords
...@@ -18,57 +22,61 @@ type internal FSharpRenameParamToMatchSignature [<ImportingConstructor>] () = ...@@ -18,57 +22,61 @@ type internal FSharpRenameParamToMatchSignature [<ImportingConstructor>] () =
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = [ "FS3218" ] let getSuggestion (d: Diagnostic) =
let parts = Regex.Match(d.GetMessage(), ".+'(.+)'.+'(.+)'.+")
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
if parts.Success then
override _.RegisterCodeFixesAsync context : Task = ValueSome parts.Groups.[1].Value
asyncMaybe { else
match ValueNone
context.Diagnostics
|> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id) override _.FixableDiagnosticIds = ImmutableArray.Create("FS3218")
|> Seq.toList
with member this.GetChanges(document: Document, diagnostics: ImmutableArray<Diagnostic>, ct: CancellationToken) =
| [ diagnostic ] -> backgroundTask {
let message = diagnostic.GetMessage() let! sourceText = document.GetTextAsync(ct)
let parts = let! changes =
System.Text.RegularExpressions.Regex.Match(message, ".+'(.+)'.+'(.+)'.+") seq {
for d in diagnostics do
if parts.Success then let suggestionOpt = getSuggestion d
let diagnostics = ImmutableArray.Create diagnostic match suggestionOpt with
let suggestion = parts.Groups.[1].Value | ValueSome suggestion ->
let replacement = NormalizeIdentifierBackticks suggestion let replacement = NormalizeIdentifierBackticks suggestion
let computeChanges () = async {
asyncMaybe { let! symbolUses = getSymbolUsesOfSymbolAtLocationInDocument (document, d.Location.SourceSpan.Start)
let document = context.Document let symbolUses = symbolUses |> Option.defaultValue [||]
let! cancellationToken = Async.CancellationToken |> liftAsync
let! sourceText = document.GetTextAsync(cancellationToken) return
let! symbolUses = getSymbolUsesOfSymbolAtLocationInDocument (document, context.Span.Start) [|
for symbolUse in symbolUses do
let changes = match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) with
[| | None -> ()
for symbolUse in symbolUses do | Some span ->
match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) with let textSpan = Tokenizer.fixupSpan (sourceText, span)
| None -> () yield TextChange(textSpan, replacement)
| Some span -> |]
let textSpan = Tokenizer.fixupSpan (sourceText, span)
yield TextChange(textSpan, replacement) }
|] | ValueNone -> ()
}
return changes |> Async.Parallel
}
return (changes |> Seq.concat)
let title =
CompilerDiagnostics.GetErrorMessage(FSharpDiagnosticKind.ReplaceWithSuggestion suggestion)
let codefix =
CodeFixHelpers.createTextChangeCodeFix (CodeFix.FSharpRenameParamToMatchSignature, title, context, computeChanges)
context.RegisterCodeFix(codefix, diagnostics)
| _ -> ()
} }
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) override this.RegisterCodeFixesAsync ctx : Task =
backgroundTask {
let title = ctx.Diagnostics |> Seq.head |> getSuggestion
match title with
| ValueSome title ->
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.GetChanges
...@@ -4,90 +4,162 @@ namespace Microsoft.VisualStudio.FSharp.Editor ...@@ -4,90 +4,162 @@ namespace Microsoft.VisualStudio.FSharp.Editor
open System open System
open System.Composition open System.Composition
open System.Threading
open System.Threading.Tasks open System.Threading.Tasks
open System.Collections.Immutable
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics
open FSharp.Compiler open FSharp.Compiler
open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Symbols open FSharp.Compiler.Symbols
open FSharp.Compiler.Syntax open FSharp.Compiler.Syntax
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.RenameUnusedValue); Shared>] module UnusedCodeFixHelper =
type internal FSharpRenameUnusedValueCodeFixProvider [<ImportingConstructor>] () = 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 = let lineText = (sourceText.Lines.GetLineFromPosition textSpan.Start).ToString()
asyncMaybe {
// Don't show code fixes for unused values, even if they are compiler-generated.
do! Option.guard context.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled
let document = context.Document let! _, checkResults =
let! sourceText = document.GetTextAsync(context.CancellationToken) document.GetFSharpParseAndCheckResultsAsync(CodeFix.RenameUnusedValue)
let ident = sourceText.ToString(context.Span) |> liftAsync
// Prefixing operators and backticked identifiers does not make sense. return! checkResults.GetSymbolUseAtLocation(m.StartLine, m.EndColumn, lineText, lexerSymbol.FullIsland)
// 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)
)
let m = }
RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) else
async { return None }
let lineText = (sourceText.Lines.GetLineFromPosition context.Span.Start).ToString() [<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.PrefixUnusedValue); Shared>]
type internal FSharpPrefixUnusedValueWithUnderscoreCodeFixProvider [<ImportingConstructor>] () =
let! _, checkResults = inherit CodeFixProvider()
document.GetFSharpParseAndCheckResultsAsync(nameof (FSharpRenameUnusedValueCodeFixProvider))
|> liftAsync static let title (symbolName: string) =
String.Format(SR.PrefixValueNameWithUnderscore(), symbolName)
override _.FixableDiagnosticIds = ImmutableArray.Create("FS1182")
member this.GetChanges(document: Document, diagnostics: ImmutableArray<Diagnostic>, 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
return (changes |> Seq.concat)
}
let! symbolUse = checkResults.GetSymbolUseAtLocation(m.StartLine, m.EndColumn, lineText, lexerSymbol.FullIsland) override this.RegisterCodeFixesAsync ctx : Task =
let symbolName = symbolUse.Symbol.DisplayName backgroundTask {
if ctx.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled then
let diagnostics = let! sourceText = ctx.Document.GetTextAsync(ctx.CancellationToken)
context.Diagnostics let! unusedSymbol = UnusedCodeFixHelper.getUnusedSymbol sourceText ctx.Span ctx.Document
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
|> Seq.toImmutableArray match unusedSymbol with
| None -> ()
match symbolUse.Symbol with | Some symbolUse ->
| :? FSharpMemberOrFunctionOrValue as func -> match symbolUse.Symbol with
let prefixTitle = String.Format(SR.PrefixValueNameWithUnderscore(), symbolName) | :? FSharpMemberOrFunctionOrValue ->
let prefixTitle = title symbolUse.Symbol.DisplayName
let prefixCodeFix = let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
CodeFixHelpers.createTextChangeCodeFix ( ctx.RegisterFsharpFix(CodeFix.PrefixUnusedValue, prefixTitle, changes)
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, "_") |])
)
context.RegisterCodeFix(replaceCodeFix, diagnostics)
| _ -> ()
} }
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) override this.GetFixAllProvider() =
CodeFixHelpers.createFixAllProvider CodeFix.PrefixUnusedValue this.GetChanges
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.RenameUnusedValue); Shared>]
type internal FSharpRenameUnusedValueWithUnderscoreCodeFixProvider [<ImportingConstructor>] () =
inherit CodeFixProvider()
static let title (symbolName: string) =
String.Format(SR.RenameValueToUnderscore(), symbolName)
override _.FixableDiagnosticIds = ImmutableArray.Create("FS1182")
member this.GetChanges(document: Document, diagnostics: ImmutableArray<Diagnostic>, 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
return (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 as func when func.IsValue ->
let prefixTitle = title symbolUse.Symbol.DisplayName
let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
ctx.RegisterFsharpFix(CodeFix.RenameUnusedValue, prefixTitle, changes)
| _ -> ()
}
override this.GetFixAllProvider() =
CodeFixHelpers.createFixAllProvider CodeFix.RenameUnusedValue this.GetChanges
...@@ -51,23 +51,15 @@ type internal FSharpReplaceWithSuggestionCodeFixProvider [<ImportingConstructor> ...@@ -51,23 +51,15 @@ type internal FSharpReplaceWithSuggestionCodeFixProvider [<ImportingConstructor>
for item in declInfo.Items do for item in declInfo.Items do
addToBuffer item.NameInList 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 for suggestion in CompilerDiagnostics.GetSuggestedNames addNames unresolvedIdentifierText do
let replacement = PrettyNaming.NormalizeIdentifierBackticks suggestion let replacement = PrettyNaming.NormalizeIdentifierBackticks suggestion
let codeFix = do
CodeFixHelpers.createTextChangeCodeFix ( context.RegisterFsharpFix(
CodeFix.ReplaceWithSuggestion, CodeFix.ReplaceWithSuggestion,
CompilerDiagnostics.GetErrorMessage(FSharpDiagnosticKind.ReplaceWithSuggestion suggestion), CompilerDiagnostics.GetErrorMessage(FSharpDiagnosticKind.ReplaceWithSuggestion suggestion),
context, [| TextChange(context.Span, replacement) |]
(fun () -> asyncMaybe.Return [| TextChange(context.Span, replacement) |])
) )
context.RegisterCodeFix(codeFix, diagnostics)
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -3,36 +3,45 @@ ...@@ -3,36 +3,45 @@
namespace Microsoft.VisualStudio.FSharp.Editor namespace Microsoft.VisualStudio.FSharp.Editor
open System.Composition open System.Composition
open System.Collections.Immutable open System.Threading
open System.Threading.Tasks open System.Threading.Tasks
open System.Collections.Immutable
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics
open FSharp.Compiler.Text
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.SimplifyName); Shared>] [<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.SimplifyName); Shared>]
type internal FSharpSimplifyNameCodeFixProvider() = type internal FSharpSimplifyNameCodeFixProvider() =
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticId = FSharpIDEDiagnosticIds.SimplifyNamesDiagnosticId
override _.FixableDiagnosticIds =
override _.FixableDiagnosticIds = ImmutableArray.Create(fixableDiagnosticId) ImmutableArray.Create(FSharpIDEDiagnosticIds.SimplifyNamesDiagnosticId)
override _.RegisterCodeFixesAsync(context: CodeFixContext) : Task = member this.GetChanges(_document: Document, diagnostics: ImmutableArray<Diagnostic>, _ct: CancellationToken) =
async { backgroundTask {
for diagnostic in context.Diagnostics |> Seq.filter (fun x -> x.Id = fixableDiagnosticId) do let changes =
let title = diagnostics |> Seq.map (fun d -> TextChange(d.Location.SourceSpan, ""))
match diagnostic.Properties.TryGetValue(SimplifyNameDiagnosticAnalyzer.LongIdentPropertyKey) with
| true, longIdent -> sprintf "%s '%s'" (SR.SimplifyName()) longIdent return changes
| _ -> SR.SimplifyName()
let codefix =
CodeFixHelpers.createTextChangeCodeFix (
CodeFix.SimplifyName,
title,
context,
(fun () -> asyncMaybe.Return [| TextChange(context.Span, "") |])
)
context.RegisterCodeFix(codefix, ImmutableArray.Create(diagnostic))
} }
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
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! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
ctx.RegisterFsharpFix(CodeFix.SimplifyName, title, changes)
}
override this.GetFixAllProvider() =
CodeFixHelpers.createFixAllProvider CodeFix.SimplifyName this.GetChanges
...@@ -17,16 +17,11 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider [<ImportingConstruc ...@@ -17,16 +17,11 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider [<ImportingConstruc
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS0020" ] let fixableDiagnosticIds = set [ "FS0020" ]
static let title = SR.UseMutationWhenValueIsMutable()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.RegisterCodeFixesAsync context : Task = override _.RegisterCodeFixesAsync context : Task =
asyncMaybe { asyncMaybe {
let diagnostics =
context.Diagnostics
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
|> Seq.toImmutableArray
let document = context.Document let document = context.Document
do! Option.guard (not (isSignatureFile document.FilePath)) do! Option.guard (not (isSignatureFile document.FilePath))
...@@ -68,7 +63,6 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider [<ImportingConstruc ...@@ -68,7 +63,6 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider [<ImportingConstruc
match symbolUse.Symbol with match symbolUse.Symbol with
| :? FSharpMemberOrFunctionOrValue as mfv when mfv.IsMutable || mfv.HasSetterMethod -> | :? FSharpMemberOrFunctionOrValue as mfv when mfv.IsMutable || mfv.HasSetterMethod ->
let title = SR.UseMutationWhenValueIsMutable()
let! symbolSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) let! symbolSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range)
let mutable pos = symbolSpan.End let mutable pos = symbolSpan.End
let mutable ch = sourceText.[pos] let mutable ch = sourceText.[pos]
...@@ -78,15 +72,7 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider [<ImportingConstruc ...@@ -78,15 +72,7 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider [<ImportingConstruc
pos <- pos + 1 pos <- pos + 1
ch <- sourceText.[pos] ch <- sourceText.[pos]
let codeFix = do context.RegisterFsharpFix(CodeFix.UseMutationWhenValueIsMutable, title, [| TextChange(TextSpan(pos + 1, 1), "<-") |])
CodeFixHelpers.createTextChangeCodeFix (
CodeFix.UseMutationWhenValueIsMutable,
title,
context,
(fun () -> asyncMaybe.Return [| TextChange(TextSpan(pos + 1, 1), "<-") |])
)
context.RegisterCodeFix(codeFix, diagnostics)
| _ -> () | _ -> ()
} }
|> Async.Ignore |> Async.Ignore
......
...@@ -12,7 +12,7 @@ type internal FSharpUseTripleQuotedInterpolationCodeFixProvider [<ImportingConst ...@@ -12,7 +12,7 @@ type internal FSharpUseTripleQuotedInterpolationCodeFixProvider [<ImportingConst
inherit CodeFixProvider() inherit CodeFixProvider()
let fixableDiagnosticIds = [ "FS3373" ] let fixableDiagnosticIds = [ "FS3373" ]
static let title = SR.UseTripleQuotedInterpolation()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.RegisterCodeFixesAsync context = override _.RegisterCodeFixesAsync context =
...@@ -33,22 +33,7 @@ type internal FSharpUseTripleQuotedInterpolationCodeFixProvider [<ImportingConst ...@@ -33,22 +33,7 @@ type internal FSharpUseTripleQuotedInterpolationCodeFixProvider [<ImportingConst
let interpolation = sourceText.GetSubText(interpolationSpan).ToString() let interpolation = sourceText.GetSubText(interpolationSpan).ToString()
TextChange(interpolationSpan, "$\"\"" + interpolation.[1..] + "\"\"") TextChange(interpolationSpan, "$\"\"" + interpolation.[1..] + "\"\"")
let diagnostics = do context.RegisterFsharpFix(CodeFix.UseTripleQuotedInterpolation, title, [| replacement |])
context.Diagnostics
|> 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)
} }
|> Async.Ignore |> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
...@@ -125,6 +125,9 @@ module internal CodeFix = ...@@ -125,6 +125,9 @@ module internal CodeFix =
[<Literal>] [<Literal>]
let RenameUnusedValue = "RenameUnusedValue" let RenameUnusedValue = "RenameUnusedValue"
[<Literal>]
let PrefixUnusedValue = "PrefixUnusedValue"
[<Literal>] [<Literal>]
let FixIndexerAccess = "FixIndexerAccess" let FixIndexerAccess = "FixIndexerAccess"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册