Skip to content

Commit

Permalink
Add ReplaceLambdaWithDotLambda code fix (#1251)
Browse files Browse the repository at this point in the history
* Add ReplaceLambdaWithDotLambda code fix

* Add additional unit test
  • Loading branch information
nojaf authored Mar 28, 2024
1 parent e505c1f commit e5201d2
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 2 deletions.
95 changes: 95 additions & 0 deletions src/FsAutoComplete/CodeFixes/ReplaceLambdaWithDotLambda.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
module FsAutoComplete.CodeFix.ReplaceLambdaWithDotLambda

open FSharp.Compiler.Syntax
open FsAutoComplete.FCSPatches
open FsToolkit.ErrorHandling
open Ionide.LanguageServerProtocol.Types
open FsAutoComplete.CodeFix.Types
open FsAutoComplete
open FsAutoComplete.LspHelpers
open FSharp.Compiler.Text.Range
open FSharp.Compiler.Text

[<return: Struct>]
let (|LongIdentExprIdentifier|_|) =
function
| SynExpr.LongIdent(longDotId = SynLongIdent(id = identifierIdent :: _)) -> ValueSome identifierIdent
| _ -> ValueNone

[<RequireQualifiedAccess>]
type ReplaceInfo =
| RawLambda of range
| ParenLambda of lpr: range * lambdaRange: range * rpr: range

let tryFindLambda (cursor: pos) (tree: ParsedInput) =
(cursor, tree)
||> ParsedInput.tryPick (fun path node ->
match node with
| SyntaxNode.SynExpr(SynExpr.Lambda(parsedData = Some([ SynPat.Named(ident = SynIdent(patIdent, _)) ], bodyExpr)) as synExpr) ->
match bodyExpr with
// x.Foo
| LongIdentExprIdentifier identifierIdent
// x.Foo().Bar
| SynExpr.DotGet(
expr = SynExpr.App(ExprAtomicFlag.Atomic, false, LongIdentExprIdentifier identifierIdent, SynExpr.Paren _, _))
// x.Foo()
| SynExpr.App(ExprAtomicFlag.Atomic,
false,
LongIdentExprIdentifier identifierIdent,
(SynExpr.Const(constant = SynConst.Unit) | SynExpr.Paren _),
_) ->
if identifierIdent.idText <> patIdent.idText then
None
else
let mLambda =
mkRange synExpr.Range.FileName synExpr.Range.Start identifierIdent.idRange.End

match List.tryHead path with
| Some(SyntaxNode.SynExpr(SynExpr.Paren(leftParenRange = lpr; rightParenRange = Some rpr))) ->
Some(ReplaceInfo.ParenLambda(lpr, mLambda, rpr))
| _ -> Some(ReplaceInfo.RawLambda mLambda)
| _ -> None
| _ -> None)

let title = "Replace lambda with _."

let languageFeature = lazy LanguageFeatureShim("AccessorFunctionShorthand")

let fix (getLanguageVersion: GetLanguageVersion) (getParseResultsForFile: GetParseResultsForFile) : CodeFix =
fun (codeActionParams: CodeActionParams) ->
asyncResult {
let fileName = codeActionParams.TextDocument.GetFilePath() |> Utils.normalizePath
let! languageVersion = getLanguageVersion fileName

if not (languageVersion.SupportsFeature languageFeature.Value) then
return []
else

let fcsPos = protocolPosToPos codeActionParams.Range.Start

let! (parseAndCheckResults: ParseAndCheckResults, _line: string, _sourceText: IFSACSourceText) =
getParseResultsForFile fileName fcsPos

match tryFindLambda fcsPos parseAndCheckResults.GetParseResults.ParseTree with
| None -> return []
| Some replaceInfo ->
let edits =
match replaceInfo with
| ReplaceInfo.RawLambda m ->
[| { Range = fcsRangeToLsp m
NewText = "_" } |]
| ReplaceInfo.ParenLambda(lpr, mLambda, rpr) ->
[| { Range = fcsRangeToLsp lpr
NewText = "" }
{ Range = fcsRangeToLsp mLambda
NewText = " _" }
{ Range = fcsRangeToLsp rpr
NewText = "" } |]

return
[ { SourceDiagnostic = None
Title = title
File = codeActionParams.TextDocument
Edits = edits
Kind = FixKind.Fix } ]
}
6 changes: 6 additions & 0 deletions src/FsAutoComplete/CodeFixes/ReplaceLambdaWithDotLambda.fsi
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module FsAutoComplete.CodeFix.ReplaceLambdaWithDotLambda

open FsAutoComplete.CodeFix.Types

val title: string
val fix: getLanguageVersion: GetLanguageVersion -> getParseResultsForFile: GetParseResultsForFile -> CodeFix
3 changes: 2 additions & 1 deletion src/FsAutoComplete/LspServers/AdaptiveServerState.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1924,7 +1924,8 @@ type AdaptiveState(lspClient: FSharpLspClient, sourceTextFactory: ISourceTextFac
RemoveUnnecessaryParentheses.fix forceFindSourceText
AddTypeAliasToSignatureFile.fix forceGetFSharpProjectOptions tryGetParseAndCheckResultsForFile
UpdateTypeAbbreviationInSignatureFile.fix tryGetParseAndCheckResultsForFile
AddBindingToSignatureFile.fix forceGetFSharpProjectOptions tryGetParseAndCheckResultsForFile |])
AddBindingToSignatureFile.fix forceGetFSharpProjectOptions tryGetParseAndCheckResultsForFile
ReplaceLambdaWithDotLambda.fix getLanguageVersion tryGetParseAndCheckResultsForFile |])

let forgetDocument (uri: DocumentUri) =
async {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
module private FsAutoComplete.Tests.CodeFixTests.ReplaceLambdaWithDotLambdaTests

open Expecto
open Helpers
open Utils.ServerTests
open Utils.CursorbasedTests
open FsAutoComplete.CodeFix

let tests state =
serverTestList (nameof ReplaceLambdaWithDotLambda) state defaultConfigDto None (fun server ->
[ let selectCodeFix = CodeFix.withTitle ReplaceLambdaWithDotLambda.title

testCaseAsync "Simple property"
<| CodeFix.check
server
"let x = \"\" |> fun y -> $0y.Length"
Diagnostics.acceptAll
selectCodeFix
"let x = \"\" |> _.Length"

testCaseAsync "Property of application"
<| CodeFix.check
server
"let a5 : {| Foo : int -> {| X : string |} |} -> string = fun x -> x.$0Foo(5).X"
Diagnostics.acceptAll
selectCodeFix
"let a5 : {| Foo : int -> {| X : string |} |} -> string = _.Foo(5).X"

testCaseAsync "Application"
<| CodeFix.check
server
"let a6 = [1] |> List.map(fun x -> x$0.ToString())"
Diagnostics.acceptAll
selectCodeFix
"let a6 = [1] |> List.map _.ToString()"

testCaseAsync "fun x -> x.ToString()"
<| CodeFix.check
server
"let a6 = fun$0 x -> x.ToString()"
Diagnostics.acceptAll
selectCodeFix
"let a6 = _.ToString()"

])
3 changes: 2 additions & 1 deletion test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3435,4 +3435,5 @@ let tests textFactory state =
removeUnnecessaryParenthesesTests state
AddTypeAliasToSignatureFileTests.tests state
UpdateTypeAbbreviationInSignatureFileTests.tests state
AddBindingToSignatureFileTests.tests state ]
AddBindingToSignatureFileTests.tests state
ReplaceLambdaWithDotLambdaTests.tests state ]

0 comments on commit e5201d2

Please sign in to comment.