diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs index 978f8e328f1..7028b9b5afe 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs @@ -8,38 +8,57 @@ open System.Collections.Immutable open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.Text + +open CancellableTasks + [] type internal ConvertCSharpLambdaToFSharpLambdaCodeFixProvider [] () = inherit CodeFixProvider() static let title = SR.UseFSharpLambda() - override _.FixableDiagnosticIds = ImmutableArray.Create("FS0039", "FS0043") + let tryGetSpans (parseResults: FSharpParseFileResults) (range: range) sourceText = + let flatten3 options = + match options with + | Some (Some a, Some b, Some c) -> Some(a, b, c) + | _ -> None + + parseResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage range.Start + |> Option.map (fun (fullParenRange, lambdaArgRange, lambdaBodyRange) -> + RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, fullParenRange), + RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaArgRange), + RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaBodyRange)) + |> flatten3 + + override _.FixableDiagnosticIds = ImmutableArray.Create("FS0039") - override _.RegisterCodeFixesAsync context = - asyncMaybe { - let! parseResults = - context.Document.GetFSharpParseResultsAsync(nameof (ConvertCSharpLambdaToFSharpLambdaCodeFixProvider)) - |> liftAsync + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this) - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync document span = + cancellableTask { + let! cancellationToken = CancellableTask.getCurrentCancellationToken () - let errorRange = - RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText) + let! parseResults = document.GetFSharpParseResultsAsync(nameof (ConvertCSharpLambdaToFSharpLambdaCodeFixProvider)) - let! fullParenRange, lambdaArgRange, lambdaBodyRange = - parseResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage errorRange.Start + let! sourceText = document.GetTextAsync(cancellationToken) - let! fullParenSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, fullParenRange) - let! lambdaArgSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaArgRange) - let! lambdaBodySpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaBodyRange) + let errorRange = + RoslynHelpers.TextSpanToFSharpRange(document.FilePath, span, sourceText) - let replacement = - let argText = sourceText.GetSubText(lambdaArgSpan).ToString() - let bodyText = sourceText.GetSubText(lambdaBodySpan).ToString() - TextChange(fullParenSpan, "fun " + argText + " -> " + bodyText) + return + tryGetSpans parseResults errorRange sourceText + |> Option.map (fun (fullParenSpan, lambdaArgSpan, lambdaBodySpan) -> + let replacement = + let argText = sourceText.GetSubText(lambdaArgSpan).ToString() + let bodyText = sourceText.GetSubText(lambdaBodySpan).ToString() + TextChange(fullParenSpan, "fun " + argText + " -> " + bodyText) - do context.RegisterFsharpFix(CodeFix.ConvertCSharpLambdaToFSharpLambda, title, [| replacement |]) - } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + { + Name = CodeFix.ConvertCSharpLambdaToFSharpLambda + Message = title + Changes = [ replacement ] + }) + } diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs index 616bc907fe5..542039ccc58 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs @@ -20,7 +20,7 @@ let getRelevantDiagnostic (document: Document) errorNumber = return checkFileResults.Diagnostics |> Seq.where (fun d -> d.ErrorNumber = errorNumber) - |> Seq.exactlyOne + |> Seq.head } let tryFix (code: string) diagnostic (fixProvider: IFSharpCodeFixProvider) = diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertCSharpLambdaToFSharpLambdaTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertCSharpLambdaToFSharpLambdaTests.fs new file mode 100644 index 00000000000..3c66ceb5370 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertCSharpLambdaToFSharpLambdaTests.fs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.ConvertCSharpLambdaToFSharpLambdaTests + +open Microsoft.VisualStudio.FSharp.Editor +open Xunit + +open CodeFixTestFramework + +let private codeFix = ConvertCSharpLambdaToFSharpLambdaCodeFixProvider() + +let private diagnostic = 0039 // Something is not defined + +[] +let ``Fixes FS0039 for lambdas`` () = + let code = + """ +let incAll = List.map (n => n + 1) +""" + + let expected = + Some + { + Message = "Use F# lambda syntax" + FixedCode = + """ +let incAll = List.map (fun n -> n + 1) +""" + } + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) + +[] +let ``Doesn't fix FS0039 for random undefined stuff`` () = + let code = + """ +let f = g +""" + + let expected = None + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj index a943a2932cb..47be31b4652 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj +++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj @@ -38,6 +38,7 @@ +