From 90270785674236101d347964ec208705a1ea8ff1 Mon Sep 17 00:00:00 2001 From: Steffen Forkmann Date: Mon, 26 Dec 2016 11:58:59 +0100 Subject: [PATCH] Add a codefix that suggests replacements for unknown identifiers --- src/fsharp/ErrorResolutionHints.fs | 6 +- src/fsharp/FSComp.txt | 3 +- .../CodeFix/ReplaceWithSuggestion.fs | 55 +++++++++++++++++++ .../src/FSharp.Editor/FSharp.Editor.fsproj | 1 + 4 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs diff --git a/src/fsharp/ErrorResolutionHints.fs b/src/fsharp/ErrorResolutionHints.fs index bd16dfa8e0f1..731f34a7e84e 100644 --- a/src/fsharp/ErrorResolutionHints.fs +++ b/src/fsharp/ErrorResolutionHints.fs @@ -9,7 +9,7 @@ let minStringLengthForThreshold = 3 let thresholdForSuggestions = 0.7 -/// Filters predictions based on edit distance to an unknown identifier. +/// Filters predictions based on edit distance to the given unknown identifier. let FilterPredictions (unknownIdent:string) (predictionsF:ErrorLogger.Suggestions) = let unknownIdent = unknownIdent.ToUpperInvariant() let useThreshold = unknownIdent.Length >= minStringLengthForThreshold @@ -38,7 +38,7 @@ let FormatPredictions errorStyle normalizeF (predictions: (float * string) list) |> List.map (snd >> normalizeF) |> String.concat ", " - " " + FSComp.SR.undefinedNameRecordLabelDetails() + " " + predictionText + " " + FSComp.SR.undefinedNameSuggestionsIntro() + " " + predictionText | _ -> let predictionText = predictions @@ -46,4 +46,4 @@ let FormatPredictions errorStyle normalizeF (predictions: (float * string) list) |> Seq.map (sprintf "%s %s" System.Environment.NewLine) |> String.concat "" - System.Environment.NewLine + FSComp.SR.undefinedNameRecordLabelDetails() + predictionText \ No newline at end of file + System.Environment.NewLine + FSComp.SR.undefinedNameSuggestionsIntro() + predictionText \ No newline at end of file diff --git a/src/fsharp/FSComp.txt b/src/fsharp/FSComp.txt index 1c769bf84621..fc56b72dc9af 100644 --- a/src/fsharp/FSComp.txt +++ b/src/fsharp/FSComp.txt @@ -12,9 +12,10 @@ undefinedNameType,"The type '%s' is not defined." undefinedNameTypeIn,"The type '%s' is not defined in '%s'." undefinedNameRecordLabelOrNamespace,"The record label or namespace '%s' is not defined." undefinedNameRecordLabel,"The record label '%s' is not defined." -undefinedNameRecordLabelDetails,"Maybe you want one of the following:" +undefinedNameSuggestionsIntro,"Maybe you want one of the following:" undefinedNameTypeParameter,"The type parameter '%s is not defined." undefinedNamePatternDiscriminator,"The pattern discriminator '%s' is not defined." +replaceWithSuggestion,"Replace with '%s'" missingElseBranch,"The 'if' expression is missing an 'else' branch. The 'then' branch has type '%s'. Because 'if' is an expression, and not a statement, add an 'else' branch which returns a value of the same type." elseBranchHasWrongType,"All branches of an 'if' expression must return the same type. This expression was expected to have type '%s' but here has type '%s'." commaInsteadOfSemicolonInRecord,"A ';' is used to separate field values in records. Consider replacing ',' with ';'." diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs new file mode 100644 index 000000000000..98879e9269dc --- /dev/null +++ b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace rec Microsoft.VisualStudio.FSharp.Editor + +open System +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 Microsoft.CodeAnalysis.CodeActions + +[] +type internal FSharpReplaceWithSuggestionCodeFixProvider() = + inherit CodeFixProvider() + let fixableDiagnosticIds = ["FS0039"; "FS1129"; "FS0495"] + let maybeString = FSComp.SR.undefinedNameSuggestionsIntro() + + let createCodeFix (title: string, context: CodeFixContext, textChange: TextChange) = + CodeAction.Create( + title, + (fun (cancellationToken: CancellationToken) -> + async { + let! sourceText = context.Document.GetTextAsync() |> Async.AwaitTask + return context.Document.WithText(sourceText.WithChanges(textChange)) + } |> CommonRoslynHelpers.StartAsyncAsTask(cancellationToken)), + title) + + override __.FixableDiagnosticIds = fixableDiagnosticIds.ToImmutableArray() + + override __.RegisterCodeFixesAsync context : Task = + async { + context.Diagnostics + |> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id) + |> Seq.iter (fun x -> + let message = x.GetMessage() + let splitted = message.Split([|maybeString|], StringSplitOptions.None) + if splitted.Length > 1 then + let suggestions = + splitted.[1].Split([|' '; '\r'; '\n'|], StringSplitOptions.RemoveEmptyEntries) + |> Array.map (fun s -> s.Trim()) + + let diagnostics = [| x |].ToImmutableArray() + + for suggestion in suggestions do + let codefix = + createCodeFix( + FSComp.SR.replaceWithSuggestion suggestion, + context, + TextChange(TextSpan(context.Span.Start, context.Span.End), suggestion)) + context.RegisterCodeFix(codefix, diagnostics)) + } |> CommonRoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index e993bd37e75c..50db6d853032 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -62,6 +62,7 @@ +