Skip to content

Commit

Permalink
Add a codefix that suggests replacements for unknown identifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
forki committed Dec 27, 2016
1 parent 17f85d2 commit 9027078
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 4 deletions.
6 changes: 3 additions & 3 deletions src/fsharp/ErrorResolutionHints.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -38,12 +38,12 @@ 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
|> List.map (snd >> normalizeF)
|> Seq.map (sprintf "%s %s" System.Environment.NewLine)
|> String.concat ""

System.Environment.NewLine + FSComp.SR.undefinedNameRecordLabelDetails() + predictionText
System.Environment.NewLine + FSComp.SR.undefinedNameSuggestionsIntro() + predictionText
3 changes: 2 additions & 1 deletion src/fsharp/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 ';'."
Expand Down
55 changes: 55 additions & 0 deletions vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs
Original file line number Diff line number Diff line change
@@ -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

[<ExportCodeFixProvider(FSharpCommonConstants.FSharpLanguageName, Name = "ReplaceWithSuggestion"); Shared>]
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)
1 change: 1 addition & 0 deletions vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
<Compile Include="Commands\FsiCommandService.fs" />
<Compile Include="CodeFix\AddNewKeywordToDisposableConstructorInvocation.fs" />
<Compile Include="CodeFix\AddOpenCodeFixProvider.fs" />
<Compile Include="CodeFix\ReplaceWithSuggestion.fs" />
<Compile Include="CodeFix\PrefixUnusedValueWithUnderscore.fs" />
</ItemGroup>
<ItemGroup>
Expand Down

0 comments on commit 9027078

Please sign in to comment.