Skip to content

Commit

Permalink
Add ReplaceLambdaWithDotLambda code fix
Browse files Browse the repository at this point in the history
  • Loading branch information
nojaf committed Mar 22, 2024
1 parent aa57439 commit 27f76d4
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 2 deletions.
104 changes: 104 additions & 0 deletions src/FsAutoComplete/CodeFixes/ReplaceLambdaWithDotLambda.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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) =
let visitor =
{ new SyntaxVisitorBase<ReplaceInfo>() with
member _.VisitExpr(path, traverseSynExpr, defaultTraverse, synExpr) =
match synExpr with
| SynExpr.Lambda(parsedData = Some([ SynPat.Named(ident = SynIdent(patIdent, _)) ], bodyExpr)) when
rangeContainsPos synExpr.Range cursor
->
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
| e -> defaultTraverse e }

SyntaxTraversal.Traverse(cursor, tree, visitor)

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 @@ -1905,7 +1905,8 @@ type AdaptiveState(lspClient: FSharpLspClient, sourceTextFactory: ISourceTextFac
ToInterpolatedString.fix tryGetParseAndCheckResultsForFile getLanguageVersion
AdjustConstant.fix tryGetParseAndCheckResultsForFile
UpdateValueInSignatureFile.fix tryGetParseAndCheckResultsForFile
RemoveUnnecessaryParentheses.fix forceFindSourceText |])
RemoveUnnecessaryParentheses.fix forceFindSourceText
ReplaceLambdaWithDotLambda.fix getLanguageVersion tryGetParseAndCheckResultsForFile |])

let forgetDocument (uri: DocumentUri) =
async {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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()" ])
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 @@ -3454,4 +3454,5 @@ let tests textFactory state =
removeRedundantAttributeSuffixTests state
removePatternArgumentTests state
UpdateValueInSignatureFileTests.tests state
removeUnnecessaryParenthesesTests state ]
removeUnnecessaryParenthesesTests state
ReplaceLambdaWithDotLambdaTests.tests state ]

0 comments on commit 27f76d4

Please sign in to comment.