未验证 提交 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() =
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 (
CodeFix.AddInstanceMemberParameter,
title,
context,
(fun () -> asyncMaybe.Return [| TextChange(TextSpan(context.Span.Start, 0), "x.") |])
)
context.RegisterCodeFix(codeFix, diagnostics)
do context.RegisterFsharpFix(CodeFix.AddInstanceMemberParameter, title, [| TextChange(TextSpan(context.Span.Start, 0), "x.") |])
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
......@@ -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,14 @@ 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)
......@@ -14,6 +14,7 @@ open FSharp.Compiler.CodeAnalysis
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.AddMissingFunKeyword); Shared>]
type internal FSharpAddMissingFunKeywordCodeFixProvider [<ImportingConstructor>] () =
inherit CodeFixProvider()
static let title = SR.AddMissingFunKeyword()
let fixableDiagnosticIds = set [ "FS0010" ]
......@@ -56,22 +57,7 @@ type internal FSharpAddMissingFunKeywordCodeFixProvider [<ImportingConstructor>]
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)
......@@ -3,36 +3,37 @@
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
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.AddNewKeyword); Shared>]
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.GetChanges(_document: Document, diagnostics: ImmutableArray<Diagnostic>, _ct: CancellationToken) =
backgroundTask {
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
let codeFix =
CodeFixHelpers.createTextChangeCodeFix (
CodeFix.AddNewKeyword,
title,
context,
(fun () -> asyncMaybe.Return [| TextChange(TextSpan(context.Span.Start, 0), "new ") |])
)
return changes
}
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
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 [<ImportingConstructor>] (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
......
......@@ -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)
......@@ -13,6 +13,7 @@ type internal FSharpChangeRefCellDerefToNotExpressionCodeFixProvider [<Importing
inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS0001" ]
static let title = SR.UseNotForNegation()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
......@@ -32,22 +33,7 @@ type internal FSharpChangeRefCellDerefToNotExpressionCodeFixProvider [<Importing
let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos errorRange.Start
let! derefSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, derefRange)
let title = SR.UseNotForNegation()
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)
do context.RegisterFsharpFix(CodeFix.ChangeRefCellDerefToNotExpression, title, [| TextChange(derefSpan, "not ") |])
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
......@@ -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)
......@@ -2,8 +2,14 @@
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
open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes
open Microsoft.CodeAnalysis.CodeActions
......@@ -11,63 +17,60 @@ open Microsoft.VisualStudio.FSharp.Editor.Telemetry
[<RequireQualifiedAccess>]
module internal CodeFixHelpers =
let createTextChangeCodeFix
(
name: string,
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 private reportCodeFixTelemetry (diagnostics: ImmutableArray<Diagnostic>) (doc: Document) (staticName: string) (additionalProps) =
let ids =
diagnostics |> Seq.map (fun d -> d.Id) |> Seq.distinct |> String.concat ","
let props: (string * obj) list =
[
"name", name
additionalProps
@ [
"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", context.Document.Project.Id.Id.ToString()
"context.document.id", context.Document.Id.Id.ToString()
"context.span", context.Span.ToString()
"context.document.project.id", doc.Project.Id.Id.ToString()
"context.document.id", doc.Id.Id.ToString()
"context.diagnostics.count", diagnostics.Length
]
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(
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)),
title
backgroundTask {
let! sourceText = context.Document.GetTextAsync(cancellationToken)
let doc = context.Document.WithText(sourceText.WithChanges(changes))
reportCodeFixTelemetry context.Diagnostics context.Document name []
return doc
}),
name
)
[<AutoOpen>]
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)
......@@ -12,7 +12,7 @@ type internal FSharpConvertCSharpLambdaToFSharpLambdaCodeFixProvider [<Importing
inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS0039"; "FS0043" ]
static let title = SR.UseFSharpLambda()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.RegisterCodeFixesAsync context =
......@@ -38,22 +38,7 @@ type internal FSharpConvertCSharpLambdaToFSharpLambdaCodeFixProvider [<Importing
let bodyText = sourceText.GetSubText(lambdaBodySpan).ToString()
TextChange(fullParenSpan, "fun " + argText + " -> " + 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)
......@@ -15,6 +15,7 @@ type internal FSharpConvertCSharpUsingToFSharpOpen [<ImportingConstructor>] () =
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 [<ImportingConstructor>] () =
let slice = sourceText.GetSubText(span).ToString()
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 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 [<ImportingConstructor>] () =
(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 [<ImportingConstructor>] () =
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)
......@@ -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)
......@@ -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)
......@@ -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
......@@ -14,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)
......@@ -67,12 +61,21 @@ type internal FsharpFixRemoveDotFromIndexerAccessOptIn() as this =
static let title =
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 _.RegisterCodeFixesAsync context : Task =
override _.RegisterCodeFixesAsync ctx : Task =
backgroundTask {
let relevantDiagnostics = this.GetPrunedDiagnostics(context)
if not relevantDiagnostics.IsEmpty then
this.RegisterFix(CodeFix.RemoveIndexerDotBeforeBracket, title, context, TextChange(context.Span, ""))
let! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
ctx.RegisterFsharpFix(CodeFix.RemoveIndexerDotBeforeBracket, title, changes)
}
override this.GetFixAllProvider() =
CodeFixHelpers.createFixAllProvider CodeFix.RemoveIndexerDotBeforeBracket this.GetChanges
......@@ -16,15 +16,12 @@ type internal FSharpMakeDeclarationMutableFixProvider [<ImportingConstructor>] (
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 [<ImportingConstructor>] (
// 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
......
......@@ -40,23 +40,15 @@ type internal FSharpMakeOuterBindingRecursiveCodeFixProvider [<ImportingConstruc
.ContentEquals(sourceText.GetSubText(context.Span))
)
let diagnostics =
context.Diagnostics
|> 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)
......@@ -29,11 +29,6 @@ type internal FSharpRemoveReturnOrYieldCodeFixProvider [<ImportingConstructor>]
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 [<ImportingConstructor>]
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)
......@@ -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,38 +19,47 @@ type internal RemoveSuperflousCaptureForUnionCaseWithNoDataProvider [<ImportingC
inherit CodeFixProvider()
override _.FixableDiagnosticIds = Seq.toImmutableArray [ "FS0725"; "FS3548" ]
static let title = SR.RemoveUnusedBinding()
override _.FixableDiagnosticIds = ImmutableArray.Create("FS0725", "FS3548")
override this.RegisterCodeFixesAsync context : Task =
asyncMaybe {
do! Option.guard context.Document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled
member this.GetChanges(document: Document, diagnostics: ImmutableArray<Diagnostic>, 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)
return changes
}
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
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
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.RemoveUnusedBinding); Shared>]
type internal FSharpRemoveUnusedBindingCodeFixProvider [<ImportingConstructor>] () =
inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "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
static let title = SR.RemoveUnusedBinding()
override _.FixableDiagnosticIds = ImmutableArray.Create("FS1182")
let document = context.Document
let! sourceText = document.GetTextAsync(context.CancellationToken)
member this.GetChanges(document: Document, diagnostics: ImmutableArray<Diagnostic>, ct: CancellationToken) =
backgroundTask {
let! parseResults =
context.Document.GetFSharpParseResultsAsync(nameof (FSharpRemoveUnusedBindingCodeFixProvider))
|> liftAsync
let! sourceText = document.GetTextAsync(ct)
let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpRemoveUnusedBindingCodeFixProvider))
let diagnostics =
context.Diagnostics
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
|> Seq.toImmutableArray
let changes =
seq {
for d in diagnostics do
let textSpan = d.Location.SourceSpan
let symbolRange =
RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText)
let symbolRange =
RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText)
let! rangeOfBinding = parseResults.TryRangeOfBindingWithHeadPatternWithPos(symbolRange.Start)
let! spanOfBinding = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, rangeOfBinding)
let spanOfBindingOpt =
parseResults.TryRangeOfBindingWithHeadPatternWithPos(symbolRange.Start)
|> Option.bind (fun rangeOfBinding -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, rangeOfBinding))
let keywordEndColumn =
let rec loop ch pos =
if not (Char.IsWhiteSpace(ch)) then
pos
else
loop sourceText.[pos - 1] (pos - 1)
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)
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)
let prefixTitle = SR.RemoveUnusedBinding()
yield TextChange(fullSpan, "")
| None -> ()
}
let removalCodeFix =
CodeFixHelpers.createTextChangeCodeFix (
CodeFix.RemoveUnusedBinding,
prefixTitle,
context,
(fun () -> asyncMaybe.Return [| TextChange(fullSpan, "") |])
)
return changes
}
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 @@
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
......@@ -15,37 +19,35 @@ open FSharp.Compiler.Text
type internal FSharpRemoveUnusedOpensCodeFixProvider [<ImportingConstructor>] () =
inherit CodeFixProvider()
let fixableDiagnosticIds =
[ FSharpIDEDiagnosticIds.RemoveUnnecessaryImportsDiagnosticId ]
static let title = SR.RemoveUnusedOpens()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.FixableDiagnosticIds =
ImmutableArray.Create FSharpIDEDiagnosticIds.RemoveUnnecessaryImportsDiagnosticId
override _.RegisterCodeFixesAsync context : Task =
asyncMaybe {
let document = context.Document
let! sourceText = document.GetTextAsync()
let! unusedOpens = UnusedOpensDiagnosticAnalyzer.GetUnusedOpenRanges(document)
member this.GetChanges(document: Document, diagnostics: ImmutableArray<Diagnostic>, ct: CancellationToken) =
backgroundTask {
let! sourceText = document.GetTextAsync(ct)
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 (CodeFix.RemoveUnusedOpens, title, context, (fun () -> asyncMaybe.Return changes))
diagnostics
|> Seq.map (fun d ->
sourceText
.Lines
.GetLineFromPosition(
d.Location.SourceSpan.Start
)
.SpanIncludingLineBreak)
|> Seq.map (fun span -> TextChange(span, ""))
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 @@
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,57 +22,61 @@ type internal FSharpRenameParamToMatchSignature [<ImportingConstructor>] () =
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.GetChanges(document: Document, diagnostics: ImmutableArray<Diagnostic>, 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
return (changes |> Seq.concat)
}
|> 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
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
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.RenameUnusedValue); Shared>]
type internal FSharpRenameUnusedValueCodeFixProvider [<ImportingConstructor>] () =
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()
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.PrefixUnusedValue); Shared>]
type internal FSharpPrefixUnusedValueWithUnderscoreCodeFixProvider [<ImportingConstructor>] () =
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.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)
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, "_") |])
)
context.RegisterCodeFix(replaceCodeFix, diagnostics)
| _ -> ()
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! changes = this.GetChanges(ctx.Document, ctx.Diagnostics, ctx.CancellationToken)
ctx.RegisterFsharpFix(CodeFix.PrefixUnusedValue, prefixTitle, changes)
| _ -> ()
}
|> 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>
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)
......@@ -3,36 +3,45 @@
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
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.SimplifyName); Shared>]
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.GetChanges(_document: Document, diagnostics: ImmutableArray<Diagnostic>, _ct: CancellationToken) =
backgroundTask {
let changes =
diagnostics |> Seq.map (fun d -> TextChange(d.Location.SourceSpan, ""))
return changes
}
|> 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
inherit CodeFixProvider()
let fixableDiagnosticIds = set [ "FS0020" ]
static let title = SR.UseMutationWhenValueIsMutable()
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))
......@@ -68,7 +63,6 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider [<ImportingConstruc
match symbolUse.Symbol with
| :? FSharpMemberOrFunctionOrValue as mfv when mfv.IsMutable || mfv.HasSetterMethod ->
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 [<ImportingConstruc
pos <- pos + 1
ch <- sourceText.[pos]
let codeFix =
CodeFixHelpers.createTextChangeCodeFix (
CodeFix.UseMutationWhenValueIsMutable,
title,
context,
(fun () -> asyncMaybe.Return [| TextChange(TextSpan(pos + 1, 1), "<-") |])
)
context.RegisterCodeFix(codeFix, diagnostics)
do context.RegisterFsharpFix(CodeFix.UseMutationWhenValueIsMutable, title, [| TextChange(TextSpan(pos + 1, 1), "<-") |])
| _ -> ()
}
|> Async.Ignore
......
......@@ -12,7 +12,7 @@ type internal FSharpUseTripleQuotedInterpolationCodeFixProvider [<ImportingConst
inherit CodeFixProvider()
let fixableDiagnosticIds = [ "FS3373" ]
static let title = SR.UseTripleQuotedInterpolation()
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
override _.RegisterCodeFixesAsync context =
......@@ -33,22 +33,7 @@ type internal FSharpUseTripleQuotedInterpolationCodeFixProvider [<ImportingConst
let interpolation = sourceText.GetSubText(interpolationSpan).ToString()
TextChange(interpolationSpan, "$\"\"" + interpolation.[1..] + "\"\"")
let diagnostics =
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)
do context.RegisterFsharpFix(CodeFix.UseTripleQuotedInterpolation, title, [| replacement |])
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
......@@ -125,6 +125,9 @@ module internal CodeFix =
[<Literal>]
let RenameUnusedValue = "RenameUnusedValue"
[<Literal>]
let PrefixUnusedValue = "PrefixUnusedValue"
[<Literal>]
let FixIndexerAccess = "FixIndexerAccess"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册