Skip to content
This repository was archived by the owner on Dec 23, 2024. It is now read-only.

Commit 1077487

Browse files
cartermpnosami
authored andcommitted
Add ConvertToF#LambdaSyntax code fixer (dotnet#10637)
* Add RemoveReturnOrYield code fixer * area: * Update testing, which was frankly wrong
1 parent b661470 commit 1077487

20 files changed

+211
-1
lines changed

src/fsharp/service/ServiceUntypedParse.fs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,30 @@ type FSharpParseFileResults(errors: FSharpErrorInfo[], input: ParsedInput option
9999
member scope.ParseHadErrors = parseHadErrors
100100

101101
member scope.ParseTree = input
102+
103+
member scope.TryRangeOfParenEnclosingOpEqualsGreaterUsage opGreaterEqualPos =
104+
let (|Ident|_|) ofName =
105+
function | SynExpr.Ident ident when ident.idText = ofName -> Some ()
106+
| _ -> None
107+
let (|InfixAppOfOpEqualsGreater|_|) =
108+
function | SynExpr.App(ExprAtomicFlag.NonAtomic, false, SynExpr.App(ExprAtomicFlag.NonAtomic, true, Ident "op_EqualsGreater", actualParamListExpr, _), actualLambdaBodyExpr, _) ->
109+
Some (actualParamListExpr, actualLambdaBodyExpr)
110+
| _ -> None
111+
112+
match scope.ParseTree with
113+
| Some parseTree ->
114+
AstTraversal.Traverse(opGreaterEqualPos, parseTree, { new AstTraversal.AstVisitorBase<_>() with
115+
member _.VisitExpr(_, _, defaultTraverse, expr) =
116+
match expr with
117+
| SynExpr.Paren((InfixAppOfOpEqualsGreater(lambdaArgs, lambdaBody) as app), _, _, _) ->
118+
Some (app.Range, lambdaArgs.Range, lambdaBody.Range)
119+
| _ -> defaultTraverse expr
120+
member _.VisitBinding(defaultTraverse, binding) =
121+
match binding with
122+
| SynBinding.Binding (_, SynBindingKind.NormalBinding, _, _, _, _, _, _, _, (InfixAppOfOpEqualsGreater(lambdaArgs, lambdaBody) as app), _, _) ->
123+
Some(app.Range, lambdaArgs.Range, lambdaBody.Range)
124+
| _ -> defaultTraverse binding })
125+
| None -> None
102126

103127
member scope.TryRangeOfExprInYieldOrReturn pos =
104128
match scope.ParseTree with

src/fsharp/service/ServiceUntypedParse.fsi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ type public FSharpParseFileResults =
1919
/// The syntax tree resulting from the parse
2020
member ParseTree : ParsedInput option
2121

22+
/// 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)
23+
member TryRangeOfParenEnclosingOpEqualsGreaterUsage: opGreaterEqualPos: pos -> Option<range * range * range>
24+
2225
/// Attempts to find the range of an expression `expr` contained in a `yield expr` or `return expr` expression (and bang-variants).
2326
member TryRangeOfExprInYieldOrReturn: pos: pos -> Option<range>
2427

tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22755,6 +22755,7 @@ FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: FSharp.Compiler.Sourc
2275522755
FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] TryRangeOfRefCellDereferenceContainingPos(pos)
2275622756
FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] TryRangeOfRecordExpressionContainingPos(pos)
2275722757
FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] TryRangeOfExprInYieldOrReturn(pos)
22758+
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)
2275822759
FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] ValidateBreakpointLocation(pos)
2275922760
FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.SourceCodeServices.FSharpNoteworthyParamInfoLocations] FindNoteworthyParamInfoLocations(pos)
2276022761
FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Boolean IsPositionContainedInACurriedParameter(pos)

tests/service/ServiceUntypedParseTests.fs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,4 +407,58 @@ let f x =
407407
|> tups
408408
|> shouldEqual ((3, 11), (3, 12))
409409
| None ->
410-
Assert.Fail("Expected to get a range back, but got none.")
410+
Assert.Fail("Expected to get a range back, but got none.")
411+
412+
[<Test>]
413+
let ``TryRangeOfParenEnclosingOpEqualsGreaterUsage - not correct operator``() =
414+
let source = """
415+
let x = y |> y + 1
416+
"""
417+
let parseFileResults, _ = getParseAndCheckResults source
418+
let res = parseFileResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage (mkPos 2 8)
419+
Assert.True(res.IsNone, "Expected not to find any ranges.")
420+
421+
[<Test>]
422+
let ``TryRangeOfParenEnclosingOpEqualsGreaterUsage - error arg pos``() =
423+
let source = """
424+
let x = y => y + 1
425+
"""
426+
let parseFileResults, _ = getParseAndCheckResults source
427+
let res = parseFileResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage (mkPos 2 8)
428+
match res with
429+
| Some (overallRange, argRange, exprRange) ->
430+
[overallRange; argRange; exprRange]
431+
|> List.map tups
432+
|> shouldEqual [((2, 8), (2, 18)); ((2, 8), (2, 9)); ((2, 13), (2, 18))]
433+
| None ->
434+
Assert.Fail("Expected to get a range back, but got none.")
435+
436+
[<Test>]
437+
let ``TryRangeOfParenEnclosingOpEqualsGreaterUsage - error expr pos``() =
438+
let source = """
439+
let x = y => y + 1
440+
"""
441+
let parseFileResults, _ = getParseAndCheckResults source
442+
let res = parseFileResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage (mkPos 2 13)
443+
match res with
444+
| Some (overallRange, argRange, exprRange) ->
445+
[overallRange; argRange; exprRange]
446+
|> List.map tups
447+
|> shouldEqual [((2, 8), (2, 18)); ((2, 8), (2, 9)); ((2, 13), (2, 18))]
448+
| None ->
449+
Assert.Fail("Expected to get a range back, but got none.")
450+
451+
[<Test>]
452+
let ``TryRangeOfParenEnclosingOpEqualsGreaterUsage - parenthesized lambda``() =
453+
let source = """
454+
[1..10] |> List.map (x => x + 1)
455+
"""
456+
let parseFileResults, _ = getParseAndCheckResults source
457+
let res = parseFileResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage (mkPos 2 21)
458+
match res with
459+
| Some (overallRange, argRange, exprRange) ->
460+
[overallRange; argRange; exprRange]
461+
|> List.map tups
462+
|> shouldEqual [((2, 21), (2, 31)); ((2, 21), (2, 22)); ((2, 26), (2, 31))]
463+
| None ->
464+
Assert.Fail("Expected to get a range back, but got none.")
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2+
3+
namespace Microsoft.VisualStudio.FSharp.Editor
4+
5+
open System.Composition
6+
7+
open Microsoft.CodeAnalysis.Text
8+
open Microsoft.CodeAnalysis.CodeFixes
9+
10+
[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = "ConvertCSharpLambdaToFSharpLambda"); Shared>]
11+
type internal FSharpConvertCSharpLambdaToFSharpLambdaCodeFixProvider
12+
[<ImportingConstructor>]
13+
(
14+
checkerProvider: FSharpCheckerProvider,
15+
projectInfoManager: FSharpProjectOptionsManager
16+
) =
17+
inherit CodeFixProvider()
18+
19+
static let userOpName = "ConvertCSharpLambdaToFSharpLambda"
20+
let fixableDiagnosticIds = set ["FS0039"; "FS0043"]
21+
22+
override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds
23+
24+
override _.RegisterCodeFixesAsync context =
25+
asyncMaybe {
26+
let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
27+
let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(context.Document, context.CancellationToken, userOpName)
28+
let! parseResults = checkerProvider.Checker.ParseFile(context.Document.FilePath, sourceText.ToFSharpSourceText(), parsingOptions, userOpName) |> liftAsync
29+
30+
let errorRange = RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText)
31+
32+
let! fullParenRange, lambdaArgRange, lambdaBodyRange = parseResults.TryRangeOfParenEnclosingOpEqualsGreaterUsage errorRange.Start
33+
34+
let! fullParenSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, fullParenRange)
35+
let! lambdaArgSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaArgRange)
36+
let! lambdaBodySpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lambdaBodyRange)
37+
38+
let replacement =
39+
let argText = sourceText.GetSubText(lambdaArgSpan).ToString()
40+
let bodyText = sourceText.GetSubText(lambdaBodySpan).ToString()
41+
TextChange(fullParenSpan, "fun " + argText + " -> " + bodyText)
42+
43+
let diagnostics =
44+
context.Diagnostics
45+
|> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id)
46+
|> Seq.toImmutableArray
47+
48+
let title = SR.UseFSharpLambda()
49+
50+
let codeFix =
51+
CodeFixHelpers.createTextChangeCodeFix(
52+
title,
53+
context,
54+
(fun () -> asyncMaybe.Return [| replacement |]))
55+
56+
context.RegisterCodeFix(codeFix, diagnostics)
57+
}
58+
|> Async.Ignore
59+
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)

vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@
184184
<Compile Include="Commands\FsiCommandService.fs" />
185185
<Compile Include="Commands\XmlDocCommandService.fs" />
186186
<Compile Include="CodeFix\CodeFixHelpers.fs" />
187+
<Compile Include="CodeFix\ConvertCSharpLambdaToFSharpLambda.fs" />
187188
<Compile Include="CodeFix\RemoveReturnOrYield.fs" />
188189
<Compile Include="CodeFix\ConvertToAnonymousRecord.fs" />
189190
<Compile Include="CodeFix\UseMutationWhenValueIsMutable.fs" />

vsintegration/src/FSharp.Editor/FSharp.Editor.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,4 +261,7 @@
261261
<data name="UseMutationWhenValueIsMutable" xml:space="preserve">
262262
<value>Use '&lt;-' to mutate value</value>
263263
</data>
264+
<data name="UseFSharpLambda" xml:space="preserve">
265+
<value>Use F# lambda syntax</value>
266+
</data>
264267
</root>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">Formátování</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">Formatierung</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">Formato</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">Mise en forme</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">Formattazione</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">書式設定</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">서식</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">Formatowanie</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">Formatação</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">Форматирование</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">Biçimlendirme</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">正在格式化</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@
217217
<target state="translated">格式化</target>
218218
<note />
219219
</trans-unit>
220+
<trans-unit id="UseFSharpLambda">
221+
<source>Use F# lambda syntax</source>
222+
<target state="new">Use F# lambda syntax</target>
223+
<note />
224+
</trans-unit>
220225
<trans-unit id="UseMutationWhenValueIsMutable">
221226
<source>Use '&lt;-' to mutate value</source>
222227
<target state="new">Use '&lt;-' to mutate value</target>

0 commit comments

Comments
 (0)