diff --git a/src/fsharp/service/ServiceUntypedParse.fs b/src/fsharp/service/ServiceUntypedParse.fs index 8507dc20f4b..274119117cc 100755 --- a/src/fsharp/service/ServiceUntypedParse.fs +++ b/src/fsharp/service/ServiceUntypedParse.fs @@ -99,6 +99,30 @@ type FSharpParseFileResults(errors: FSharpErrorInfo[], input: ParsedInput option member scope.ParseHadErrors = parseHadErrors member scope.ParseTree = input + + member scope.TryRangeOfParenEnclosingOpEqualsGreaterUsage opGreaterEqualPos = + let (|Ident|_|) ofName = + function | SynExpr.Ident ident when ident.idText = ofName -> Some () + | _ -> None + let (|InfixAppOfOpEqualsGreater|_|) = + function | SynExpr.App(ExprAtomicFlag.NonAtomic, false, SynExpr.App(ExprAtomicFlag.NonAtomic, true, Ident "op_EqualsGreater", actualParamListExpr, _), actualLambdaBodyExpr, _) -> + Some (actualParamListExpr, actualLambdaBodyExpr) + | _ -> None + + match scope.ParseTree with + | Some parseTree -> + AstTraversal.Traverse(opGreaterEqualPos, parseTree, { new AstTraversal.AstVisitorBase<_>() with + member _.VisitExpr(_, _, defaultTraverse, expr) = + match expr with + | SynExpr.Paren((InfixAppOfOpEqualsGreater(lambdaArgs, lambdaBody) as app), _, _, _) -> + Some (app.Range, lambdaArgs.Range, lambdaBody.Range) + | _ -> defaultTraverse expr + member _.VisitBinding(defaultTraverse, binding) = + match binding with + | SynBinding.Binding (_, SynBindingKind.NormalBinding, _, _, _, _, _, _, _, (InfixAppOfOpEqualsGreater(lambdaArgs, lambdaBody) as app), _, _) -> + Some(app.Range, lambdaArgs.Range, lambdaBody.Range) + | _ -> defaultTraverse binding }) + | None -> None member scope.TryRangeOfExprInYieldOrReturn pos = match scope.ParseTree with diff --git a/src/fsharp/service/ServiceUntypedParse.fsi b/src/fsharp/service/ServiceUntypedParse.fsi index b450a7b4023..6ca324fa0fe 100755 --- a/src/fsharp/service/ServiceUntypedParse.fsi +++ b/src/fsharp/service/ServiceUntypedParse.fsi @@ -19,6 +19,9 @@ type public FSharpParseFileResults = /// The syntax tree resulting from the parse member ParseTree : ParsedInput option + /// Attempts to find the range of an attempted lambda expression or pattern, the argument range, and the expr range when writing a C#-style "lambda" (which is actually an operator application) + member TryRangeOfParenEnclosingOpEqualsGreaterUsage: opGreaterEqualPos: pos -> Option + /// Attempts to find the range of an expression `expr` contained in a `yield expr` or `return expr` expression (and bang-variants). member TryRangeOfExprInYieldOrReturn: pos: pos -> Option diff --git a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs index 705d5556114..edaca94e520 100644 --- a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs +++ b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs @@ -22755,6 +22755,7 @@ FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: FSharp.Compiler.Sourc FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] TryRangeOfRefCellDereferenceContainingPos(pos) FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] TryRangeOfRecordExpressionContainingPos(pos) FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] TryRangeOfExprInYieldOrReturn(pos) +FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[System.Tuple`3[FSharp.Compiler.Range+range,FSharp.Compiler.Range+range,FSharp.Compiler.Range+range]] TryRangeOfParenEnclosingOpEqualsGreaterUsage(pos) FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] ValidateBreakpointLocation(pos) FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.SourceCodeServices.FSharpNoteworthyParamInfoLocations] FindNoteworthyParamInfoLocations(pos) FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Boolean IsPositionContainedInACurriedParameter(pos) diff --git a/tests/service/ServiceUntypedParseTests.fs b/tests/service/ServiceUntypedParseTests.fs index 8371b74f61c..5dfe01646cc 100644 --- a/tests/service/ServiceUntypedParseTests.fs +++ b/tests/service/ServiceUntypedParseTests.fs @@ -407,4 +407,58 @@ let f x = |> tups |> shouldEqual ((3, 11), (3, 12)) | None -> - Assert.Fail("Expected to get a range back, but got none.") \ No newline at end of file + Assert.Fail("Expected to get a range back, but got none.") + +[] +let ``TryRangeOfParenEnclosingOpEqualsGreaterUsage - not correct operator``() = + let source = """ +let x = y |> y + 1 +""" + let parseFileResults, _ = getParseAndCheckResults source + let res = parseFileResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage (mkPos 2 8) + Assert.True(res.IsNone, "Expected not to find any ranges.") + +[] +let ``TryRangeOfParenEnclosingOpEqualsGreaterUsage - error arg pos``() = + let source = """ +let x = y => y + 1 +""" + let parseFileResults, _ = getParseAndCheckResults source + let res = parseFileResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage (mkPos 2 8) + match res with + | Some (overallRange, argRange, exprRange) -> + [overallRange; argRange; exprRange] + |> List.map tups + |> shouldEqual [((2, 8), (2, 18)); ((2, 8), (2, 9)); ((2, 13), (2, 18))] + | None -> + Assert.Fail("Expected to get a range back, but got none.") + +[] +let ``TryRangeOfParenEnclosingOpEqualsGreaterUsage - error expr pos``() = + let source = """ +let x = y => y + 1 +""" + let parseFileResults, _ = getParseAndCheckResults source + let res = parseFileResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage (mkPos 2 13) + match res with + | Some (overallRange, argRange, exprRange) -> + [overallRange; argRange; exprRange] + |> List.map tups + |> shouldEqual [((2, 8), (2, 18)); ((2, 8), (2, 9)); ((2, 13), (2, 18))] + | None -> + Assert.Fail("Expected to get a range back, but got none.") + +[] +let ``TryRangeOfParenEnclosingOpEqualsGreaterUsage - parenthesized lambda``() = + let source = """ +[1..10] |> List.map (x => x + 1) +""" + let parseFileResults, _ = getParseAndCheckResults source + let res = parseFileResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage (mkPos 2 21) + match res with + | Some (overallRange, argRange, exprRange) -> + [overallRange; argRange; exprRange] + |> List.map tups + |> shouldEqual [((2, 21), (2, 31)); ((2, 21), (2, 22)); ((2, 26), (2, 31))] + | None -> + Assert.Fail("Expected to get a range back, but got none.") diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs b/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs new file mode 100644 index 00000000000..76d3ae60a6c --- /dev/null +++ b/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System.Composition + +open Microsoft.CodeAnalysis.Text +open Microsoft.CodeAnalysis.CodeFixes + +[] +type internal FSharpConvertCSharpLambdaToFSharpLambdaCodeFixProvider + [] + ( + checkerProvider: FSharpCheckerProvider, + projectInfoManager: FSharpProjectOptionsManager + ) = + inherit CodeFixProvider() + + static let userOpName = "ConvertCSharpLambdaToFSharpLambda" + let fixableDiagnosticIds = set ["FS0039"; "FS0043"] + + override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds + + override _.RegisterCodeFixesAsync context = + asyncMaybe { + let! sourceText = context.Document.GetTextAsync(context.CancellationToken) + let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(context.Document, context.CancellationToken, userOpName) + let! parseResults = checkerProvider.Checker.ParseFile(context.Document.FilePath, sourceText.ToFSharpSourceText(), parsingOptions, userOpName) |> liftAsync + + let errorRange = RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText) + + let! fullParenRange, lambdaArgRange, lambdaBodyRange = parseResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage errorRange.Start + + let! fullParenSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, fullParenRange) + let! lambdaArgSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaArgRange) + let! lambdaBodySpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaBodyRange) + + let replacement = + let argText = sourceText.GetSubText(lambdaArgSpan).ToString() + 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( + title, + context, + (fun () -> asyncMaybe.Return [| replacement |])) + + context.RegisterCodeFix(codeFix, diagnostics) + } + |> Async.Ignore + |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 9f472af48b7..a102f56eaa9 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -89,6 +89,7 @@ + diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx index 6f538df9a13..596442035df 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx @@ -261,4 +261,7 @@ Use '<-' to mutate value + + Use F# lambda syntax + \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf index 7eedda1431b..9b07b63e238 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf @@ -217,6 +217,11 @@ Formátování + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf index 8f23f21afa9..84de0b6c3dd 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf @@ -217,6 +217,11 @@ Formatierung + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf index 5bee9f13e82..50ba9e1cb9c 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf @@ -217,6 +217,11 @@ Formato + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf index 2ca886961df..055d51e596b 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf @@ -217,6 +217,11 @@ Mise en forme + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf index b9a7b8d10a2..278eee4fe97 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf @@ -217,6 +217,11 @@ Formattazione + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf index 4f0433a5ae7..5db9004adba 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf @@ -217,6 +217,11 @@ 書式設定 + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf index c3b54b8a893..6ed1b5e7083 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf @@ -217,6 +217,11 @@ 서식 + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf index c58920c0a1f..bb1390db399 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf @@ -217,6 +217,11 @@ Formatowanie + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf index c81995a7ca2..8225a22cb17 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf @@ -217,6 +217,11 @@ Formatação + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf index 0c96f37ca44..8e79668735a 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf @@ -217,6 +217,11 @@ Форматирование + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf index 318f4be0697..c509d8b9116 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf @@ -217,6 +217,11 @@ Biçimlendirme + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf index 592cab626f5..147cb4a00d7 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf @@ -217,6 +217,11 @@ 正在格式化 + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf index eed1eb8e216..fa7975bd652 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf @@ -217,6 +217,11 @@ 格式化 + + Use F# lambda syntax + Use F# lambda syntax + + Use '<-' to mutate value Use '<-' to mutate value