diff --git a/scalafix/README.md b/scalafix/README.md index 39a3aa34e8..744d657a71 100644 --- a/scalafix/README.md +++ b/scalafix/README.md @@ -1,11 +1,11 @@ -# Scalafix rewrites for cats +# Scalafix rules for cats ## Try this! Install the scalafix sbt plugin (globally or in a specific project): ```scala -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.5.0-M3") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.5.0-RC2") ``` run @@ -14,7 +14,7 @@ run sbt scalafix github:typelevel/cats/v1.0.0 ``` -## Available rewrites +## Available rules - [x] All Unapply enabled methods, e.g. sequenceU, traverseU, etc. are removed. Unapply enabled syntax ops are also removed. Please use the partial unification SI-2712 fix instead. The easiest way might be this sbt-plugin. diff --git a/scalafix/build.sbt b/scalafix/build.sbt index 96740db285..c588890dd4 100644 --- a/scalafix/build.sbt +++ b/scalafix/build.sbt @@ -2,7 +2,7 @@ val V = _root_.scalafix.Versions scalaVersion in ThisBuild := V.scala212 -lazy val rewrites = project.settings( +lazy val rules = project.settings( libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % V.version ) @@ -35,5 +35,5 @@ lazy val tests = project classDirectory.in(input, Compile).value ) ) - .dependsOn(input, rewrites) + .dependsOn(input, rules) .enablePlugins(BuildInfoPlugin) diff --git a/scalafix/input/src/main/scala/fix/v1_0_0/RemoveCartesianBuilder.scala b/scalafix/input/src/main/scala/fix/v1_0_0/RemoveCartesianBuilder.scala index 8f206fa10a..5bdd45f37c 100644 --- a/scalafix/input/src/main/scala/fix/v1_0_0/RemoveCartesianBuilder.scala +++ b/scalafix/input/src/main/scala/fix/v1_0_0/RemoveCartesianBuilder.scala @@ -1,5 +1,5 @@ /* -rewrite = "scala:fix.v1_0_0.RemoveCartesianBuilder" +rule = "scala:fix.v1_0_0.RemoveCartesianBuilder" */ package fix package to1_0_0 diff --git a/scalafix/input/src/main/scala/fix/v1_0_0/RemoveCartesianBuilder2.scala b/scalafix/input/src/main/scala/fix/v1_0_0/RemoveCartesianBuilder2.scala index 9e78bdf847..0d32840989 100644 --- a/scalafix/input/src/main/scala/fix/v1_0_0/RemoveCartesianBuilder2.scala +++ b/scalafix/input/src/main/scala/fix/v1_0_0/RemoveCartesianBuilder2.scala @@ -1,5 +1,5 @@ /* -rewrite = "scala:fix.v1_0_0.RemoveCartesianBuilder" +rule = "scala:fix.v1_0_0.RemoveCartesianBuilder" */ package fix package to1_0_0 diff --git a/scalafix/input/src/main/scala/fix/v1_0_0/RemoveSplit.scala b/scalafix/input/src/main/scala/fix/v1_0_0/RemoveSplit.scala index a796a7b009..7b593ffd5c 100644 --- a/scalafix/input/src/main/scala/fix/v1_0_0/RemoveSplit.scala +++ b/scalafix/input/src/main/scala/fix/v1_0_0/RemoveSplit.scala @@ -1,5 +1,5 @@ /* -rewrite = "scala:fix.v1_0_0.RemoveSplit" +rule = "scala:fix.v1_0_0.RemoveSplit" */ package fix package to1_0_0 diff --git a/scalafix/input/src/main/scala/fix/v1_0_0/RemoveUnapply.scala b/scalafix/input/src/main/scala/fix/v1_0_0/RemoveUnapply.scala index 15370b5ec7..30e712a8af 100644 --- a/scalafix/input/src/main/scala/fix/v1_0_0/RemoveUnapply.scala +++ b/scalafix/input/src/main/scala/fix/v1_0_0/RemoveUnapply.scala @@ -1,5 +1,5 @@ -/* -rewrite = "scala:fix.v1_0_0.RemoveUnapply" +/* +rule = "scala:fix.v1_0_0.RemoveUnapply" */ package fix package to1_0_0 diff --git a/scalafix/input/src/main/scala/fix/v1_0_0/RenameFreeSuspend.scala b/scalafix/input/src/main/scala/fix/v1_0_0/RenameFreeSuspend.scala index ffa3e26a38..d478c32098 100644 --- a/scalafix/input/src/main/scala/fix/v1_0_0/RenameFreeSuspend.scala +++ b/scalafix/input/src/main/scala/fix/v1_0_0/RenameFreeSuspend.scala @@ -1,5 +1,5 @@ -/* -rewrite = "scala:fix.v1_0_0.RenameFreeSuspend" +/* +rule = "scala:fix.v1_0_0.RenameFreeSuspend" */ package fix package to1_0_0 diff --git a/scalafix/input/src/main/scala/fix/v1_0_0/RenameInjectProdAndCoproduct.scala b/scalafix/input/src/main/scala/fix/v1_0_0/RenameInjectProdAndCoproduct.scala index 08599b5fbf..5c69b50fc9 100644 --- a/scalafix/input/src/main/scala/fix/v1_0_0/RenameInjectProdAndCoproduct.scala +++ b/scalafix/input/src/main/scala/fix/v1_0_0/RenameInjectProdAndCoproduct.scala @@ -1,5 +1,5 @@ -/* -rewrite = "scala:fix.v1_0_0.RenameInjectProdAndCoproduct" +/* +rule = "scala:fix.v1_0_0.RenameInjectProdAndCoproduct" */ package fix package to1_0_0 diff --git a/scalafix/input/src/main/scala/fix/v1_0_0/RenameReducibleMethods.scala b/scalafix/input/src/main/scala/fix/v1_0_0/RenameReducibleMethods.scala index 4bbbb572da..38a00bb3ca 100644 --- a/scalafix/input/src/main/scala/fix/v1_0_0/RenameReducibleMethods.scala +++ b/scalafix/input/src/main/scala/fix/v1_0_0/RenameReducibleMethods.scala @@ -1,5 +1,5 @@ -/* -rewrite = "scala:fix.v1_0_0.RenameReducibleMethods" +/* +rule = "scala:fix.v1_0_0.RenameReducibleMethods" */ package fix package to1_0_0 diff --git a/scalafix/input/src/main/scala/fix/v1_0_0/RenameTupleApplySyntax.scala b/scalafix/input/src/main/scala/fix/v1_0_0/RenameTupleApplySyntax.scala index 02fd5a1779..bcc98f815d 100644 --- a/scalafix/input/src/main/scala/fix/v1_0_0/RenameTupleApplySyntax.scala +++ b/scalafix/input/src/main/scala/fix/v1_0_0/RenameTupleApplySyntax.scala @@ -1,5 +1,5 @@ /* -rewrite = "scala:fix.v1_0_0.RenameTupleApplySyntax" +rule = "scala:fix.v1_0_0.RenameTupleApplySyntax" */ package fix package to1_0_0 diff --git a/scalafix/input/src/main/scala/fix/v1_0_0/SimplifyEitherTLift.scala b/scalafix/input/src/main/scala/fix/v1_0_0/SimplifyEitherTLift.scala index 2fcee79eb0..c653b69bbc 100644 --- a/scalafix/input/src/main/scala/fix/v1_0_0/SimplifyEitherTLift.scala +++ b/scalafix/input/src/main/scala/fix/v1_0_0/SimplifyEitherTLift.scala @@ -1,5 +1,5 @@ /* -rewrite = "scala:fix.v1_0_0.SimplifyEitherTLift" +rule = "scala:fix.v1_0_0.SimplifyEitherTLift" */ package fix package to1_0_0 diff --git a/scalafix/rewrites/src/main/scala/fix/Cats_v1_0_0.scala b/scalafix/rewrites/src/main/scala/fix/Cats_v1_0_0.scala deleted file mode 100644 index 7ca608aadc..0000000000 --- a/scalafix/rewrites/src/main/scala/fix/Cats_v1_0_0.scala +++ /dev/null @@ -1,278 +0,0 @@ -package fix -package v1_0_0 - -import scalafix._ -import scalafix.syntax._ -import scala.meta._ - -object Utils { - - private[fix] def rename( - ctx: RuleCtx, - t: Term.Name, - renames: Map[String, String])(implicit index: SemanticdbIndex): Patch = { - renames.collect { - case (target, rename) if t.isSymbol(target) => - ctx.replaceTree(t, rename) - }.asPatch - } - - implicit class TermNameOps(t: Name) { - def isSymbol(s: String)(implicit index: SemanticdbIndex): Boolean = - t.symbol.exists(_.normalized.syntax == s) - - def isOneOfSymbols(symbols: Set[String])( - implicit index: SemanticdbIndex): Boolean = - t.symbol.exists(s => symbols.contains(s.normalized.syntax)) - } - - implicit class OptionTermNameOps(t: Option[Name]) { - def isSymbol(s: String)(implicit index: SemanticdbIndex): Boolean = - t.flatMap(_.symbol).exists(_.normalized.syntax == s) - } - -} -import Utils._ - -// ref: https://github.com/typelevel/cats/pull/1745 -case class RemoveCartesianBuilder(index: SemanticdbIndex) - extends SemanticRule(index, "RemoveCartesianBuilder") { - - private[this] val cartesianBuilders = - (1 to 22) - .map(arity => - s"_root_.cats.syntax.CartesianBuilder.CartesianBuilder$arity.`|@|`.") - .toSet + - "_root_.cats.syntax.CartesianOps.`|@|`." - - private[this] val partialApplies = Set( - s"_root_.cats.syntax.CartesianOps.`*>`.", - s"_root_.cats.syntax.CartesianOps.`<*`." - ) - - private[this] val renames: Map[String, String] = - (1 to 22) - .map { arity => - Seq( - s"_root_.cats.syntax.CartesianBuilder.CartesianBuilder$arity.map." -> "mapN", - s"_root_.cats.syntax.CartesianBuilder.CartesianBuilder$arity.imap." -> "imapN", - s"_root_.cats.syntax.CartesianBuilder.CartesianBuilder$arity.contramap." -> "contramapN" - ) - } - .flatten - .toMap - - // Hackish way to work around duplicate fixes due to recursion - val alreadyFixedOps = collection.mutable.Set.empty[Term.Name] - private[this] def replaceOpWithComma(ctx: RuleCtx, op: Term.Name): Patch = - if (op.isOneOfSymbols(cartesianBuilders) && !alreadyFixedOps.contains(op)) { - alreadyFixedOps += op - // remove the space before |@| - ctx.removeToken(ctx.tokenList.prev(op.tokens.head)) + - // replace |@| with , - ctx.replaceTree(op, ",") - } else { - Patch.empty - } - - private[this] def removeCartesianBuilderOp( - ctx: RuleCtx, - applyInfix: Term.ApplyInfix): Patch = { - applyInfix match { - case Term.ApplyInfix(lhs: Term.ApplyInfix, op, _, _) => - removeCartesianBuilderOp(ctx, lhs) + replaceOpWithComma(ctx, op) - case Term.ApplyInfix(_, op, _, _) => - replaceOpWithComma(ctx, op) - } - } - - private[this] def wrapInParensIfNeeded(ctx: RuleCtx, t: Term): Patch = { - if (t.tokens.head.is[Token.LeftParen] && t.tokens.last - .is[Token.RightParen]) { - Patch.empty - } else { - ctx.addLeft(t.tokens.head, "(") + ctx.addRight(t.tokens.last, ")") - } - } - - override def fix(ctx: RuleCtx): Patch = { - ctx.tree.collect { - case t: Term.ApplyInfix if t.op.isOneOfSymbols(cartesianBuilders) => - removeCartesianBuilderOp(ctx, t) - case t: Term.ApplyInfix if t.op.isOneOfSymbols(renames.keys.toSet) => - wrapInParensIfNeeded(ctx, t.lhs) - case t: Term.Name => rename(ctx, t, renames) - case t @ q"import cats.syntax.cartesian._" => - val usesPartialApplies = ctx.tree.collect { - case t: Term.Name if t.isOneOfSymbols(partialApplies) => () - }.length > 0 - if (usesPartialApplies) { - ctx.addRight(t.tokens.last, "\n import cats.syntax.apply._") - } else { - ctx.replaceTree(t, "import cats.syntax.apply._") - } - }.asPatch - } -} - -// ref: https://github.com/typelevel/cats/pull/1583 -case class RemoveUnapply(index: SemanticdbIndex) - extends SemanticRule(index, "RemoveUnapply") { - - private[this] val renames = Map( - "_root_.cats.Traverse.Ops.traverseU." -> "traverse", - "_root_.cats.Foldable.Ops.traverseU_." -> "traverse_", - "_root_.cats.Foldable.traverseU_." -> "traverse_", - "_root_.cats.Traverse.Ops.sequenceU." -> "sequence", - "_root_.cats.Foldable.Ops.sequenceU_." -> "sequence_", - "_root_.cats.Foldable.sequenceU_." -> "sequence_", - "_root_.cats.data.Func.appFuncU." -> "appFunc", - "_root_.cats.free.FreeT.liftTU." -> "liftT" - ) - - private[this] def importeeName(importee: Importee): Option[Name] = - importee match { - case Importee.Name(name) => Some(name) - case Importee.Rename(name, _) => Some(name) - case _ => None - } - - private[this] def removeImportee( - ctx: RuleCtx, - importee: Importee, - fixes: Map[String, String]): Patch = - fixes.collect { - case (target, _) if importeeName(importee).isSymbol(target) => - ctx.removeImportee(importee) - }.asPatch - - override def fix(ctx: RuleCtx): Patch = { - ctx.tree.collect { - case t: Term.Name => rename(ctx, t, renames) - case t: Importee.Name => removeImportee(ctx, t, renames) - }.asPatch - } -} - -// ref: https://github.com/typelevel/cats/pull/1709 -case class RenameFreeSuspend(index: SemanticdbIndex) - extends SemanticRule(index, "RenameFreeSuspend") { - - private[this] val renames = Map( - "_root_.cats.free.Free.suspend." -> "defer", - "_root_.cats.free.TrampolineFunctions.suspend." -> "defer" - ) - - override def fix(ctx: RuleCtx): Patch = { - ctx.tree.collect { - case t: Term.Name => rename(ctx, t, renames) - }.asPatch - } - -} - -// ref: https://github.com/typelevel/cats/pull/1611 -case class RenameReducibleMethods(index: SemanticdbIndex) - extends SemanticRule(index, "RenameReducibleMethods") { - - private[this] val renames = Map( - "_root_.cats.Reducible.traverse1_." -> "nonEmptyTraverse_", - "_root_.cats.Reducible.Ops.traverse1_." -> "nonEmptyTraverse_", - "_root_.cats.Reducible.intercalate1." -> "nonEmptyIntercalate", - "_root_.cats.Reducible.Ops.intercalate1." -> "nonEmptyIntercalate", - "_root_.cats.Reducible.sequence1_." -> "nonEmptySequence_", - "_root_.cats.Reducible.Ops.sequence1_." -> "nonEmptySequence_" - ) - - override def fix(ctx: RuleCtx): Patch = { - ctx.tree.collect { - case t: Term.Name => rename(ctx, t, renames) - }.asPatch - } - -} - -// ref: https://github.com/typelevel/cats/pull/1614 -case class SimplifyEitherTLift(index: SemanticdbIndex) - extends SemanticRule(index, "SimplifyEitherTLift") { - - private[this] val leftSymbol = "_root_.cats.data.EitherTFunctions.left." - private[this] val rightSymbol = "_root_.cats.data.EitherTFunctions.right." - - private[this] def removeWithLeadingComma(ctx: RuleCtx, t: Tree): Patch = - (for { - leadingComma <- ctx.tokenList.leading(t.tokens.head).find(_.syntax == ",") - } yield { - val leadingSpaces = ctx.tokenList.slice(leadingComma, t.tokens.head) - ctx.removeToken(leadingComma) + - leadingSpaces.map(ctx.removeToken).asPatch + - ctx.removeTokens(t.tokens) - }).asPatch - - override def fix(ctx: RuleCtx): Patch = { - ctx.tree.collect { - case Term.ApplyType(Term.Select(_, name), Seq(f, a, b)) - if name.isSymbol(leftSymbol) => - ctx.replaceTree(name, "leftT") + removeWithLeadingComma(ctx, a) - case Term.ApplyType(Term.Select(_, name), Seq(f, a, b)) - if name.isSymbol(rightSymbol) => - ctx.replaceTree(name, "pure") + removeWithLeadingComma(ctx, b) - }.asPatch - } - -} - -// ref: https://github.com/typelevel/cats/pull/1589 -// https://github.com/typelevel/cats/pull/1596 -case class RenameInjectProdAndCoproduct(index: SemanticdbIndex) - extends SemanticRule(index, "RenameInjectProdAndCoproduct") { - - override def fix(ctx: RuleCtx): Patch = { - ctx.replaceSymbols( - "_root_.cats.free.Inject." -> "_root_.cats.InjectK.", - "_root_.cats.data.Prod." -> "_root_.cats.data.Tuple2K.", - "_root_.cats.data.Coproduct." -> "_root_.cats.data.EitherK." - ) - } - -} - -// ref: https://github.com/typelevel/cats/pull/1487 -case class RenameTupleApplySyntax(index: SemanticdbIndex) - extends SemanticRule(index, "RenameTupleApplySyntax") { - - private[this] val renames: Map[String, String] = - (1 to 22) - .map { arity => - Seq( - s"_root_.cats.syntax.Tuple${arity}CartesianOps.map$arity." -> "mapN", - s"_root_.cats.syntax.Tuple${arity}CartesianOps.contramap$arity." -> "contramapN", - s"_root_.cats.syntax.Tuple${arity}CartesianOps.imap$arity." -> "imapN" - ) - } - .flatten - .toMap - - override def fix(ctx: RuleCtx): Patch = { - ctx.tree.collect { - case t: Term.Name => rename(ctx, t, renames) - case t @ q"import cats.syntax.tuple._" => - ctx.replaceTree(t, "import cats.syntax.apply._") - }.asPatch - } -} - -// ref: https://github.com/typelevel/cats/pull/1766 -case class RemoveSplit(index: SemanticdbIndex) - extends SemanticRule(index, "RemoveSplit") { - - override def fix(ctx: RuleCtx): Patch = { - ctx.replaceSymbols( - "_root_.cats.arrow.Split." -> "_root_.cats.arrow.Arrow." - ) + ctx.tree.collect { - case t @ q"import cats.syntax.split._" => - ctx.replaceTree(t, "import cats.syntax.arrow._") - }.asPatch - } - -} diff --git a/scalafix/rules/src/main/scala/fix/Cats_v1_0_0.scala b/scalafix/rules/src/main/scala/fix/Cats_v1_0_0.scala new file mode 100644 index 0000000000..8cc86d5aef --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/Cats_v1_0_0.scala @@ -0,0 +1,199 @@ +package fix +package v1_0_0 + +import scalafix._ +import scalafix.util.SymbolMatcher +import scala.meta._ +import scala.meta.contrib._ + +// ref: https://github.com/typelevel/cats/pull/1745 +case class RemoveCartesianBuilder(index: SemanticdbIndex) + extends SemanticRule(index, "RemoveCartesianBuilder") { + + private[this] val cartesianBuilders = SymbolMatcher.normalized( + Symbol("_root_.cats.syntax.CartesianOps.`|@|`.") :: + (1 to 22).toList.map(arity => + Symbol( + s"_root_.cats.syntax.CartesianBuilder.CartesianBuilder$arity.`|@|`.")): _* + ) + + private[this] val partialApplies = SymbolMatcher.normalized( + Symbol(s"_root_.cats.syntax.CartesianOps.`*>`."), + Symbol(s"_root_.cats.syntax.CartesianOps.`<*`.") + ) + + private[this] val renames: Map[String, String] = (1 to 22).flatMap { arity => + Seq( + s"_root_.cats.syntax.CartesianBuilder.CartesianBuilder$arity.map." -> "mapN", + s"_root_.cats.syntax.CartesianBuilder.CartesianBuilder$arity.imap." -> "imapN", + s"_root_.cats.syntax.CartesianBuilder.CartesianBuilder$arity.contramap." -> "contramapN" + ) + }.toMap + + private[this] val cartesianOps = + SymbolMatcher.normalized(renames.keys.map(Symbol.apply).toSeq: _*) + + private[this] def replaceOpWithComma(ctx: RuleCtx, op: Term.Name): Patch = + // replace |@| with , + ctx.replaceTree(op, ",") ++ + // remove the space before |@| + ctx.tokenList + .leading(op.tokens.head) + .takeWhile(_.is[Whitespace]) + .map(ctx.removeToken) + + private[this] def wrapInParensIfNeeded(ctx: RuleCtx, t: Term): Patch = { + for { + head <- t.tokens.headOption + if !head.is[Token.LeftParen] + last <- t.tokens.lastOption + if !last.is[Token.RightParen] + } yield + ctx.addLeft(head, "(") + + ctx.addRight(last, ")") + }.asPatch + + override def fix(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case Term.ApplyInfix(_, cartesianBuilders(op: Term.Name), _, _) => + replaceOpWithComma(ctx, op) + case Term.ApplyInfix(lhs, cartesianOps(_), _, _) => + wrapInParensIfNeeded(ctx, lhs) + case t @ q"import cats.syntax.cartesian._" => + val usesPartialApplies = ctx.tree.exists { + case partialApplies(_: Term.Name) => true + case _ => false + } + if (usesPartialApplies) { + ctx.addRight(t.tokens.last, "\n import cats.syntax.apply._") + } else { + ctx.replaceTree(t, "import cats.syntax.apply._") + } + }.asPatch + ctx.replaceSymbols(renames.toSeq: _*) + } +} + +// ref: https://github.com/typelevel/cats/pull/1583 +case class RemoveUnapply(index: SemanticdbIndex) + extends SemanticRule(index, "RemoveUnapply") { + + override def fix(ctx: RuleCtx): Patch = ctx.replaceSymbols( + "_root_.cats.Traverse.Ops.traverseU." -> "traverse", + "_root_.cats.Foldable.Ops.traverseU_." -> "traverse_", + "_root_.cats.Foldable.traverseU_." -> "traverse_", + "_root_.cats.Traverse.Ops.sequenceU." -> "sequence", + "_root_.cats.Foldable.Ops.sequenceU_." -> "sequence_", + "_root_.cats.Foldable.sequenceU_." -> "sequence_", + "_root_.cats.data.Func.appFuncU." -> "appFunc", + "_root_.cats.free.FreeT.liftTU." -> "liftT" + ) +} + +// ref: https://github.com/typelevel/cats/pull/1709 +case class RenameFreeSuspend(index: SemanticdbIndex) + extends SemanticRule(index, "RenameFreeSuspend") { + + override def fix(ctx: RuleCtx): Patch = ctx.replaceSymbols( + "_root_.cats.free.Free.suspend." -> "defer", + "_root_.cats.free.TrampolineFunctions.suspend." -> "defer" + ) + +} + +// ref: https://github.com/typelevel/cats/pull/1611 +case class RenameReducibleMethods(index: SemanticdbIndex) + extends SemanticRule(index, "RenameReducibleMethods") { + + override def fix(ctx: RuleCtx): Patch = ctx.replaceSymbols( + "_root_.cats.Reducible.traverse1_." -> "nonEmptyTraverse_", + "_root_.cats.Reducible.Ops.traverse1_." -> "nonEmptyTraverse_", + "_root_.cats.Reducible.intercalate1." -> "nonEmptyIntercalate", + "_root_.cats.Reducible.Ops.intercalate1." -> "nonEmptyIntercalate", + "_root_.cats.Reducible.sequence1_." -> "nonEmptySequence_", + "_root_.cats.Reducible.Ops.sequence1_." -> "nonEmptySequence_" + ) + +} + +// ref: https://github.com/typelevel/cats/pull/1614 +case class SimplifyEitherTLift(index: SemanticdbIndex) + extends SemanticRule(index, "SimplifyEitherTLift") { + + private[this] val leftSymbol = SymbolMatcher.normalized( + Symbol("_root_.cats.data.EitherTFunctions.left.") + ) + private[this] val rightSymbol = SymbolMatcher.normalized( + Symbol("_root_.cats.data.EitherTFunctions.right.") + ) + + private[this] def removeWithLeadingComma(ctx: RuleCtx, t: Tree): Patch = + (for { + leadingComma <- ctx.tokenList + .leading(t.tokens.head) + .find(_.is[Token.Comma]) + } yield { + val leadingSpaces = ctx.tokenList.slice(leadingComma, t.tokens.head) + ctx.removeToken(leadingComma) ++ + leadingSpaces.map(ctx.removeToken) + + ctx.removeTokens(t.tokens) + }).asPatch + + override def fix(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case Term.ApplyType(Term.Select(_, leftSymbol(name)), Seq(_, a, _)) => + ctx.replaceTree(name, "leftT") + removeWithLeadingComma(ctx, a) + case Term.ApplyType(Term.Select(_, rightSymbol(name)), Seq(_, _, b)) => + ctx.replaceTree(name, "pure") + removeWithLeadingComma(ctx, b) + }.asPatch + } + +} + +// ref: https://github.com/typelevel/cats/pull/1589 +// https://github.com/typelevel/cats/pull/1596 +case class RenameInjectProdAndCoproduct(index: SemanticdbIndex) + extends SemanticRule(index, "RenameInjectProdAndCoproduct") { + + override def fix(ctx: RuleCtx): Patch = ctx.replaceSymbols( + "_root_.cats.free.Inject." -> "_root_.cats.InjectK.", + "_root_.cats.data.Prod." -> "_root_.cats.data.Tuple2K.", + "_root_.cats.data.Coproduct." -> "_root_.cats.data.EitherK." + ) + +} + +// ref: https://github.com/typelevel/cats/pull/1487 +case class RenameTupleApplySyntax(index: SemanticdbIndex) + extends SemanticRule(index, "RenameTupleApplySyntax") { + + override def fix(ctx: RuleCtx): Patch = { + ctx.replaceSymbols( + (1 to 22).flatMap { arity => + Seq( + s"_root_.cats.syntax.Tuple${arity}CartesianOps.map$arity." -> "mapN", + s"_root_.cats.syntax.Tuple${arity}CartesianOps.contramap$arity." -> "contramapN", + s"_root_.cats.syntax.Tuple${arity}CartesianOps.imap$arity." -> "imapN" + ) + }: _* + ) ++ + ctx.tree.collect { + case t @ q"import cats.syntax.tuple._" => + ctx.replaceTree(t, "import cats.syntax.apply._") + } + } +} + +// ref: https://github.com/typelevel/cats/pull/1766 +case class RemoveSplit(index: SemanticdbIndex) + extends SemanticRule(index, "RemoveSplit") { + + override def fix(ctx: RuleCtx): Patch = { + ctx.replaceSymbols( + "_root_.cats.arrow.Split." -> "_root_.cats.arrow.Arrow." + ) + ctx.tree.collect { + case t @ q"import cats.syntax.split._" => + ctx.replaceTree(t, "import cats.syntax.arrow._") + }.asPatch + } + +}