From 3015b159a92dafa6f6f0e9b8785b5da322fb71e8 Mon Sep 17 00:00:00 2001 From: Steffen Forkmann Date: Mon, 26 Dec 2016 11:58:59 +0100 Subject: [PATCH 1/6] 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 bd16dfa8e0f..731f34a7e84 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 1c769bf8462..fc56b72dc9a 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 00000000000..845774f6a7e --- /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() + 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(context.Span, 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 e993bd37e75..50db6d85303 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -62,6 +62,7 @@ + From 4ea67e7f1be678350538ddbde33fab94a382e5b2 Mon Sep 17 00:00:00 2001 From: Steffen Forkmann Date: Tue, 27 Dec 2016 13:01:30 +0100 Subject: [PATCH 2/6] Use sets --- .../src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs index 845774f6a7e..968e97f46cb 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs @@ -16,7 +16,7 @@ open Microsoft.CodeAnalysis.CodeActions [] type internal FSharpReplaceWithSuggestionCodeFixProvider() = inherit CodeFixProvider() - let fixableDiagnosticIds = ["FS0039"; "FS1129"; "FS0495"] + let fixableDiagnosticIds = ["FS0039"; "FS1129"; "FS0495"] |> Set.ofList let maybeString = FSComp.SR.undefinedNameSuggestionsIntro() let createCodeFix (title: string, context: CodeFixContext, textChange: TextChange) = @@ -34,7 +34,7 @@ type internal FSharpReplaceWithSuggestionCodeFixProvider() = override __.RegisterCodeFixesAsync context : Task = async { context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> List.contains x.Id) + |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) |> Seq.iter (fun x -> let message = x.GetMessage() let splitted = message.Split([|maybeString|], StringSplitOptions.None) From f72331dfb8507677622cc236cbb3731ca45d2767 Mon Sep 17 00:00:00 2001 From: Steffen Forkmann Date: Tue, 27 Dec 2016 13:05:18 +0100 Subject: [PATCH 3/6] Better naming --- .../src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs index 968e97f46cb..2027b2f722d 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs @@ -35,15 +35,15 @@ type internal FSharpReplaceWithSuggestionCodeFixProvider() = async { context.Diagnostics |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.iter (fun x -> - let message = x.GetMessage() + |> Seq.iter (fun diagnostic -> + let message = diagnostic.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() + let diagnostics = [| diagnostic |].ToImmutableArray() for suggestion in suggestions do let codefix = From 2c3557f5318d5ebf9165092d8c725710616fb261 Mon Sep 17 00:00:00 2001 From: Steffen Forkmann Date: Tue, 27 Dec 2016 13:05:43 +0100 Subject: [PATCH 4/6] Change prediction text a bit to better work with MSBuild errors --- src/fsharp/ErrorResolutionHints.fs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/fsharp/ErrorResolutionHints.fs b/src/fsharp/ErrorResolutionHints.fs index 731f34a7e84..54df73ea549 100644 --- a/src/fsharp/ErrorResolutionHints.fs +++ b/src/fsharp/ErrorResolutionHints.fs @@ -31,6 +31,7 @@ let FormatPredictions errorStyle normalizeF (predictions: (float * string) list) match predictions with | [] -> System.String.Empty | _ -> + " " + FSComp.SR.undefinedNameSuggestionsIntro() + match errorStyle with | ErrorLogger.ErrorStyle.VSErrors -> let predictionText = @@ -38,12 +39,9 @@ let FormatPredictions errorStyle normalizeF (predictions: (float * string) list) |> List.map (snd >> normalizeF) |> String.concat ", " - " " + FSComp.SR.undefinedNameSuggestionsIntro() + " " + predictionText + " " + predictionText | _ -> - let predictionText = - predictions - |> List.map (snd >> normalizeF) - |> Seq.map (sprintf "%s %s" System.Environment.NewLine) - |> String.concat "" - - System.Environment.NewLine + FSComp.SR.undefinedNameSuggestionsIntro() + predictionText \ No newline at end of file + predictions + |> List.map (snd >> normalizeF) + |> List.map (sprintf "%s %s" System.Environment.NewLine) + |> String.concat "" From b3f2fd1cf0dd81ef5a7a6c98b818585cd5023266 Mon Sep 17 00:00:00 2001 From: Steffen Forkmann Date: Tue, 27 Dec 2016 13:09:50 +0100 Subject: [PATCH 5/6] cleanup --- .../src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs index 2027b2f722d..2b00d7ddf34 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs @@ -32,7 +32,7 @@ type internal FSharpReplaceWithSuggestionCodeFixProvider() = override __.FixableDiagnosticIds = fixableDiagnosticIds.ToImmutableArray() override __.RegisterCodeFixesAsync context : Task = - async { + async { context.Diagnostics |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) |> Seq.iter (fun diagnostic -> From 0de2322cf7d97ec5cde44b618ac3f4bd2bbb364c Mon Sep 17 00:00:00 2001 From: Steffen Forkmann Date: Tue, 27 Dec 2016 13:14:53 +0100 Subject: [PATCH 6/6] Suggest id if it is an infix --- src/fsharp/ErrorResolutionHints.fs | 5 ++--- tests/fsharp/core/load-script/out.stderr.bsl | 4 +--- tests/fsharp/typecheck/sigs/neg06.bsl | 2 +- .../tests/unittests/DocumentDiagnosticAnalyzerTests.fs | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/fsharp/ErrorResolutionHints.fs b/src/fsharp/ErrorResolutionHints.fs index 54df73ea549..2cf35d28d7b 100644 --- a/src/fsharp/ErrorResolutionHints.fs +++ b/src/fsharp/ErrorResolutionHints.fs @@ -16,11 +16,10 @@ let FilterPredictions (unknownIdent:string) (predictionsF:ErrorLogger.Suggestion predictionsF() |> Seq.choose (fun p -> let similarity = Internal.Utilities.EditDistance.JaroWinklerDistance unknownIdent (p.ToUpperInvariant()) - if not useThreshold || similarity >= thresholdForSuggestions then + if not useThreshold || similarity >= thresholdForSuggestions || p.Contains unknownIdent then Some(similarity,p) else - None - ) + None) |> Seq.sortByDescending fst |> Seq.mapi (fun i x -> i,x) |> Seq.takeWhile (fun (i,_) -> i < maxSuggestions) diff --git a/tests/fsharp/core/load-script/out.stderr.bsl b/tests/fsharp/core/load-script/out.stderr.bsl index 2d92744392a..f68123743db 100644 --- a/tests/fsharp/core/load-script/out.stderr.bsl +++ b/tests/fsharp/core/load-script/out.stderr.bsl @@ -1,6 +1,4 @@ -usesfsi.fsx(2,1): error FS0039: The namespace or module 'fsi' is not defined. - -Maybe you want one of the following: +usesfsi.fsx(2,1): error FS0039: The namespace or module 'fsi' is not defined. Maybe you want one of the following: FSharp diff --git a/tests/fsharp/typecheck/sigs/neg06.bsl b/tests/fsharp/typecheck/sigs/neg06.bsl index fc85a220391..ad317eb7b8d 100644 --- a/tests/fsharp/typecheck/sigs/neg06.bsl +++ b/tests/fsharp/typecheck/sigs/neg06.bsl @@ -1,5 +1,5 @@ -neg06.fs(3,40,3,45): typecheck error FS0039: The field, constructor or member 'Ascii' is not defined. Maybe you want one of the following: ASCII +neg06.fs(3,40,3,45): typecheck error FS0039: The field, constructor or member 'Ascii' is not defined. Maybe you want one of the following: ASCII, get_ASCII neg06.fs(12,6,12,31): typecheck error FS0942: Struct types are always sealed diff --git a/vsintegration/tests/unittests/DocumentDiagnosticAnalyzerTests.fs b/vsintegration/tests/unittests/DocumentDiagnosticAnalyzerTests.fs index 9e5ff4c191b..93d2142060e 100644 --- a/vsintegration/tests/unittests/DocumentDiagnosticAnalyzerTests.fs +++ b/vsintegration/tests/unittests/DocumentDiagnosticAnalyzerTests.fs @@ -231,7 +231,7 @@ let x: float = 1.2(*start*).(*end*)3 let gDateTime (arr: (*start*)DateTime(*end*)[]) = arr.[0] """, - expectedMessage = "The type 'DateTime' is not defined.\r\nMaybe you want one of the following:\r\n nativeint\r\n DelegateEvent") + expectedMessage = "The type 'DateTime' is not defined. Maybe you want one of the following:\r\n nativeint\r\n DelegateEvent") [] member public this.Error_CyclicalDeclarationDoesNotCrash() =