From 6b4d8c90aa73bb5c06194d80005455ceb445a1a8 Mon Sep 17 00:00:00 2001 From: scarf Date: Sat, 23 Nov 2024 15:57:49 +0900 Subject: [PATCH] feat: implement `Convert to collect` code action --- docs/features/code-actions.md | 6 + docs/features/gifs/FilterMapToCollect.gif | Bin 0 -> 41887 bytes .../codeactions/CodeActionProvider.scala | 1 + .../FilterMapToCollectCodeAction.scala | 148 ++++++++++ .../FilterMapToCollectCodeActionSuite.scala | 277 ++++++++++++++++++ 5 files changed, 432 insertions(+) create mode 100644 docs/features/gifs/FilterMapToCollect.gif create mode 100644 metals/src/main/scala/scala/meta/internal/metals/codeactions/FilterMapToCollectCodeAction.scala create mode 100644 tests/unit/src/test/scala/tests/codeactions/FilterMapToCollectCodeActionSuite.scala diff --git a/docs/features/code-actions.md b/docs/features/code-actions.md index a6aca9559c8..5095141d1a4 100644 --- a/docs/features/code-actions.md +++ b/docs/features/code-actions.md @@ -55,6 +55,12 @@ It converts a chain of `map`, `flatMap`, `filter` and `filterNot` methods into a ![To For Comprehension](./gifs/FlatMapToForComprehension.gif) +## filter then map to collect + +It converts a chain of `filter` and `map` methods into a `collect` method. + +![To Collect](./gifs/FilterMapToCollect.gif) + ## Implement Abstract Members of the Parent Type Upon inheriting from a type, you also have to implement its abstract members. But manually looking them all up and copying their signature is time consuming, isn't it? You can just use this code action instead. diff --git a/docs/features/gifs/FilterMapToCollect.gif b/docs/features/gifs/FilterMapToCollect.gif new file mode 100644 index 0000000000000000000000000000000000000000..346e36eaf5f4439eb55dbc9894c9b63b896754d8 GIT binary patch literal 41887 zcmeEtS5uQ;^zQqn5dwtJLpAgwB_JKa&_nMXG$2w1>BX7=(z|pNLzP~o3K|fUA_|Hi zC`glzBA^25kK_M*fOB_d&c)e#U96cs`)19oHG4nB(8xel%{>gv27aB9M=QDToKfQA z1%<@%BBCPVXRzX8B2vnFvXTOtY6_ZG!A8brCXpEy z<~kk)ExtZ3w|x?CUk{`_dKRA(nHPV*C?%$}Dy{Ry$n?ma{~!Oq0{<^pK=72oT;JA4 zSKCrwK^6}M0RRv@hQYXjGl2BJ9QdC$iTl6ZB*;^M0m(;fruN4o+0cA=G601@P<%K` za@kNaR!&NUNWDw}K_oV@U~ici$IgItY{pT@AQFMaTaqbY7Uo>wr;)ZtZypk2QT)b_ zt0&8e%nCOPLdfJSh!AR6%}#0vrlF}omQ?T?fmjGvNf%^`ImR1>=Y6Jc2EJBw)`WSN)iz)jkNz)9dK=RWn&t4gzDj_B76~UDHv{D;>&MFgxY+9aoDibS5wfW~Fx8bQmn2Lg!O*m~Vr8 z>oK@x)T=Wj6d;SOpoR<+rqjbJPn*G!?` zP6G(zJeC0m-3Lt6hX{n@9_1xAZdK@QrlA%W%>$0#q38<_VgT0*c;)w8L_mAi;S!@V z)FRou2jcgL=j6HbosU+AgWRNp0?t=EyFIE5Rkhx+7lJ~o@qQn(5NBYw8XMetLBexDlRp(s#3;1#9 zY{-vOH_ALBk+67Pmlpy zq`*@r=P+9^9qY*lI0kM{L`4N$L5 z06|OOsYLhF$+SQ>rT_rSP6gxy3ETn6HY zjf0Qg+Q@x3dbh3w2Lr${3%>oLN<87}s~98EhwPNY(!FZUD~V65zwlxjjPRBX7KRS& zd4(A9WbMbyw{m#i6t3tSh4WVYA-4B!pLI4IulEuk%SkZ( z(MBCI@>ZYA>YxURwQg0X^9fDA=uwtkkRN9G7UD2{8nGHs+8f~I{qp0yM_z09m)!tS z4d=Yp%5wu|jh;?XeDJ{(=rg}v|3@x4A2>ZbkiJYYH}_{&qklbFWNM0fX|k|i^s9Hn zti&tGo-+QYRtLKD#ymYd|I@}A?n`M?5Mxn(zSi{|hi*xXTyJ2#eDqvQwk1i|^xvdRAfMZGx@q&A z#Qm}Bd}7wMn<0PLepkJCK_6w7fPh?53V!+QLnUKSoKD5Cgj4^NQEQfxHHUA%o^� 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 l} + +class FilterMapToCollectCodeAction(trees: Trees) extends CodeAction { + override def kind: String = l.CodeActionKind.RefactorRewrite + + override def contribute(params: CodeActionParams, token: CancelToken)(implicit + ec: ExecutionContext + ): Future[Seq[l.CodeAction]] = Future { + val uri = params.getTextDocument().getUri() + + val path = uri.toAbsolutePath + val range = params.getRange() + + trees + .findLastEnclosingAt[Term.Apply](path, range.getStart()) + .flatMap(findFilterMapChain) + .map(toTextEdit(_)) + .map(toCodeAction(uri, _)) + .toSeq + } + + private def toTextEdit(chain: FilterMapChain) = { + val param = chain.filterFn.params.head + val paramName = Term.Name(param.name.value) + val paramPatWithType = param.decltpe match { + case Some(tpe) => Pat.Typed(Pat.Var(paramName), tpe) + case None => Pat.Var(paramName) + } + + val collectCall = Term.Apply( + fun = Term.Select(chain.qual, Term.Name("collect")), + argClause = Term.ArgClause( + values = List( + Term.PartialFunction( + cases = List( + Case( + pat = paramPatWithType, + cond = Some(chain.filterFn.renameParam(paramName)), + body = chain.mapFn.renameParam(paramName), + ) + ) + ) + ) + ), + ) + new l.TextEdit(chain.tree.pos.toLsp, collectCall.syntax) + } + + private def toCodeAction(uri: String, textEdit: l.TextEdit): l.CodeAction = + CodeActionBuilder.build( + title = FilterMapToCollectCodeAction.title, + kind = this.kind, + changes = List(uri.toAbsolutePath -> List(textEdit)), + ) + + private implicit class FunctionOps(fn: Term.Function) { + def renameParam(to: Term.Name): Term = { + val fnParamName = fn.params.head.name.value + fn.body + .transform { case Term.Name(name) if name == fnParamName => to } + .asInstanceOf[Term] + } + } + + private def findFilterMapChain(tree: Term.Apply): Option[FilterMapChain] = { + val x = Term.Name("x") + def extractFunction(arg: Tree): Option[Term.Function] = arg match { + case fn: Term.Function => Some(fn) + case Term.Block(List(fn: Term.Function)) => extractFunction(fn) + case ref: Term.Name => { + Some( + Term.Function( + UnaryParameterList(x), + Term.Apply(ref, Term.ArgClause(List(x))), + ) + ) + } + case _ => None + } + + def findChain(tree: Term.Apply): Option[FilterMapChain] = + tree match { + case MapFunctionApply(FilterFunctionApply(base, filterArg), mapArg) => + for { + filterFn <- extractFunction(filterArg) + mapFn <- extractFunction(mapArg) + } yield FilterMapChain(tree, base, filterFn, mapFn) + case _ => None + } + + findChain(tree).orElse { + // If we're inside the chain, look at our parent + tree.parent.flatMap { + // We're in a method call or function, look at parent apply + case Term.Select(_, Term.Name("map" | "filter")) | Term.Function(_) => + tree.parent + .flatMap(_.parent) + .collectFirst { case parent: Term.Apply => parent } + .flatMap(findChain) + case _ => None + } + } + } + + private object UnaryParameterList { + def unapply(tree: Tree): Option[Name] = tree match { + case Term.Param(_, name, _, _) => Some(name) + case _ => None + } + def apply(name: Name): List[Term.Param] = List( + Term.Param(Nil, name, None, None) + ) + } + + private case class FunctionApply(val name: String) { + def unapply(tree: Tree): Option[(Term, Term)] = tree match { + case Term.Apply(Term.Select(base, Term.Name(`name`)), List(args)) => + Some((base, args)) + case _ => None + } + } + private val FilterFunctionApply = new FunctionApply("filter") + private val MapFunctionApply = new FunctionApply("map") + + private case class FilterMapChain( + tree: Term.Apply, + qual: Term, + filterFn: Term.Function, + mapFn: Term.Function, + ) +} + +object FilterMapToCollectCodeAction { + val title = "Convert to collect" +} diff --git a/tests/unit/src/test/scala/tests/codeactions/FilterMapToCollectCodeActionSuite.scala b/tests/unit/src/test/scala/tests/codeactions/FilterMapToCollectCodeActionSuite.scala new file mode 100644 index 00000000000..460dbb7fd82 --- /dev/null +++ b/tests/unit/src/test/scala/tests/codeactions/FilterMapToCollectCodeActionSuite.scala @@ -0,0 +1,277 @@ +package tests.codeactions + +import scala.meta.internal.metals.codeactions.FilterMapToCollectCodeAction +import scala.meta.internal.metals.codeactions.FlatMapToForComprehensionCodeAction +import scala.meta.internal.metals.codeactions.RewriteBracesParensCodeAction + +class FilterMapToCollectCodeActionSuite + extends BaseCodeActionLspSuite("filterMapToCollect") { + val simpleOutput: String = + """|object Main { + | val numbers = List(1, 2, 3, 4) + | numbers.collect { + | case x if x > 2 => + | x * 2 + |} + |} + |""".stripMargin + + check( + "cursor-on-filter", + """|object Main { + | val numbers = List(1, 2, 3, 4) + | numbers.fil<<>>ter(x => x > 2).map(x => x * 2) + |} + |""".stripMargin, + s"""|${RewriteBracesParensCodeAction.toBraces("filter")} + |${FlatMapToForComprehensionCodeAction.flatMapToForComprehension} + |${FilterMapToCollectCodeAction.title} + |""".stripMargin, + simpleOutput, + selectedActionIndex = 2, + ) + + check( + "cursor-on-filter-newline", + """|object Main { + | val numbers = List(1, 2, 3, 4) + | numbers.fil<<>>ter{ + | x => x > 2 + | }.map{ + | x => x * 2 + | } + |} + |""".stripMargin, + s"""|${RewriteBracesParensCodeAction.toParens("filter")} + |${FlatMapToForComprehensionCodeAction.flatMapToForComprehension} + |${FilterMapToCollectCodeAction.title} + |""".stripMargin, + simpleOutput, + selectedActionIndex = 2, + ) + + check( + "cursor-on-map", + """|object Main { + | val numbers = List(1, 2, 3, 4) + | numbers.filter(x => x > 2).m<<>>ap(x => x * 2) + |} + |""".stripMargin, + s"""|${RewriteBracesParensCodeAction.toBraces("map")} + |${FlatMapToForComprehensionCodeAction.flatMapToForComprehension} + |${FilterMapToCollectCodeAction.title} + |""".stripMargin, + simpleOutput, + selectedActionIndex = 2, + ) + + check( + "higher-order-function", + """|object Main { + | val numbers = List(1, 2, 3, 4) + | def check = (x: Int) => x > 2 + | def double = (x: Int) => x * 2 + | numbers.fil<<>>ter(check).map(double) + |} + |""".stripMargin, + s"""|${RewriteBracesParensCodeAction.toBraces("filter")} + |${FlatMapToForComprehensionCodeAction.flatMapToForComprehension} + |${FilterMapToCollectCodeAction.title} + |""".stripMargin, + """|object Main { + | val numbers = List(1, 2, 3, 4) + | def check = (x: Int) => x > 2 + | def double = (x: Int) => x * 2 + | numbers.collect { + | case x if check(x) => + | double(x) + |} + |} + |""".stripMargin, + selectedActionIndex = 2, + ) + + check( + "higher-order-function-rename-filter-argument", + """|object Main { + | val numbers = List(1, 2, 3, 4) + | def double = (x: Int) => x * 2 + | numbers.fil<<>>ter(x => x > 0).map(double) + |} + |""".stripMargin, + s"""|${RewriteBracesParensCodeAction.toBraces("filter")} + |${FlatMapToForComprehensionCodeAction.flatMapToForComprehension} + |${FilterMapToCollectCodeAction.title} + |""".stripMargin, + """|object Main { + | val numbers = List(1, 2, 3, 4) + | def double = (x: Int) => x * 2 + | numbers.collect { + | case x if x > 0 => + | double(x) + |} + |} + |""".stripMargin, + selectedActionIndex = 2, + ) + + check( + "higher-order-function-rename-map-argument", + """|object Main { + | val numbers = List(1, 2, 3, 4) + | def check = (x: Int) => x > 2 + | numbers.fil<<>>ter(check).map(y => y * 2) + |} + |""".stripMargin, + s"""|${RewriteBracesParensCodeAction.toBraces("filter")} + |${FlatMapToForComprehensionCodeAction.flatMapToForComprehension} + |${FilterMapToCollectCodeAction.title} + |""".stripMargin, + """|object Main { + | val numbers = List(1, 2, 3, 4) + | def check = (x: Int) => x > 2 + | numbers.collect { + | case x if check(x) => + | x * 2 + |} + |} + |""".stripMargin, + selectedActionIndex = 2, + ) + + check( + "multiple-map-calls", + """|object Main { + | val numbers = List(1, 2, 3, 4) + | numbers.fil<<>>ter(x => x > 2).map(x => x * 2).map(x => x * 2) + |} + |""".stripMargin, + s"""|${RewriteBracesParensCodeAction.toBraces("filter")} + |${FlatMapToForComprehensionCodeAction.flatMapToForComprehension} + |${FilterMapToCollectCodeAction.title} + |""".stripMargin, + """|object Main { + | val numbers = List(1, 2, 3, 4) + | numbers.collect { + | case x if x > 2 => + | x * 2 + |}.map(x => x * 2) + |} + |""".stripMargin, + selectedActionIndex = 2, + ) + + check( + "complex-predicate", + """|object Main { + | val users = List(("Alice", 25), ("Bob", 30)) + | users.fil<<>>ter(u => u._2 >= 18 && u._1.startsWith("A")).map(u => u._1) + |} + |""".stripMargin, + s"""|${RewriteBracesParensCodeAction.toBraces("filter")} + |${FlatMapToForComprehensionCodeAction.flatMapToForComprehension} + |${FilterMapToCollectCodeAction.title} + |""".stripMargin, + """|object Main { + | val users = List(("Alice", 25), ("Bob", 30)) + | users.collect { + | case u if u._2 >= 18 && u._1.startsWith("A") => + | u._1 + |} + |} + |""".stripMargin, + selectedActionIndex = 2, + ) + + check( + "multiline-predicate", + """|object Main { + | val users = List(("Alice", 25), ("Bob", 30)) + | users + | .fil<<>>ter(u => + | u._2 >= 18 && + | u._1.startsWith("A") + | ) + | .map { u => + | // format user name into multline card + | val card = Seq( + | s"Name: ${u._1}", + | s"Age: ${u._2}", + | ) + | card.mkString("\n") + | } + |} + |""".stripMargin, + s"""|${RewriteBracesParensCodeAction.toBraces("filter")} + |${FlatMapToForComprehensionCodeAction.flatMapToForComprehension} + |${FilterMapToCollectCodeAction.title} + |""".stripMargin, + """|object Main { + | val users = List(("Alice", 25), ("Bob", 30)) + | users.collect { + | case u if u._2 >= 18 && u._1.startsWith("A") => + | val card = Seq(s"Name: ${ + | u._1 + | }", s"Age: ${ + | u._2 + | }") + | card.mkString("\n") + |} + |} + |""".stripMargin, + selectedActionIndex = 2, + ) + + check( + "multiline-predicate-block", + """|object Main { + | val users = List(("Alice", 25), ("Bob", 30)) + | users + | .fil<<>>ter { u => + | val nameLength = u._1.length + | + | nameLength > 3 + | } + | .map { u => u._2 } + |} + |""".stripMargin, + s"""|${RewriteBracesParensCodeAction.toParens("map")} + |${FlatMapToForComprehensionCodeAction.flatMapToForComprehension} + |${FilterMapToCollectCodeAction.title} + |""".stripMargin, + """|object Main { + | val users = List(("Alice", 25), ("Bob", 30)) + | users.collect { + | case u if { + | val nameLength = u._1.length + | nameLength > 3 + | } => + | u._2 + |} + |} + |""".stripMargin, + selectedActionIndex = 2, + ) + + check( + "with-type-annotations", + """|object Main { + | val numbers = List(1, 2, 3, 4) + | numbers.fil<<>>ter((x: Int) => x > 2).map((x: Int) => x * 2) + |} + |""".stripMargin, + s"""|${RewriteBracesParensCodeAction.toBraces("filter")} + |${FlatMapToForComprehensionCodeAction.flatMapToForComprehension} + |${FilterMapToCollectCodeAction.title} + |""".stripMargin, + """|object Main { + | val numbers = List(1, 2, 3, 4) + | numbers.collect { + | case x: Int if x > 2 => + | x * 2 + |} + |} + |""".stripMargin, + selectedActionIndex = 2, + ) +}