Skip to content

Commit

Permalink
Merge pull request #175 from brbrown25/ISSUE-128
Browse files Browse the repository at this point in the history
feat(cats-effect): Adding in fix for UUID.randomUUID -> IO.randomUUID…
  • Loading branch information
rossabaker authored May 14, 2024
2 parents 9f01e9d + 9f9e363 commit 37e6db8
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 2 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ rules = [
TypelevelUnusedShowInterpolator
TypelevelFs2SyncCompiler
TypelevelHttp4sLiteralsSyntax
TypelevelIORandomUUID
]
```

Expand All @@ -60,6 +61,7 @@ Not all rules function with Scala 3 yet.
| TypelevelUnusedShowInterpolator | :white_check_mark: | :x: |
| TypelevelFs2SyncCompiler | :white_check_mark: | :x: |
| TypelevelHttp4sLiteralsSyntax | :white_check_mark: | :white_check_mark: |
| TypelevelIORandomUUID | :white_check_mark: | :x: |

## Rules for cats

Expand Down Expand Up @@ -151,6 +153,23 @@ EitherT(IO.println("foo").attempt).value
IO.println("foo").timeout(50.millis).attemptNarrow[TimeoutException]
```

### TypelevelIORandomUUID

This rule detects usages of UUID.randomUUID wrapped in an IO or IO.blocking call and rewrites them automatically to IO.randomUUID calls.

**Examples**

```scala
val test = IO(UUID.randomUUID())
```

would be rewritten to
```scala
val test = IO.randomUUID
```

This rule works on variable declarations, usaged within methods as well as for comprehensions.

## Rules for fs2

### TypelevelFs2SyncCompiler
Expand Down
7 changes: 6 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ lazy val catsEffect = scalafixProject("cats-effect")
"org.typelevel" %% "cats-effect" % CatsEffectVersion
)
)

.outputSettings(
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % CatsVersion,
"org.typelevel" %% "cats-effect" % CatsEffectVersion
)
)
// typelevel/fs2 Scalafix rules
lazy val fs2 = scalafixProject("fs2")
.rulesSettings(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
rule = TypelevelIORandomUUID
*/
package fix

import cats.effect.*
import java.util.UUID

object IORandomUUIDTests {
IO.randomUUID
IO(UUID.randomUUID())
IO.blocking(UUID.randomUUID())
val test = IO(UUID.randomUUID())
for {
ioa <- IO(UUID.randomUUID())
iob = IO(UUID.randomUUID())
} yield (ioa, iob)
def generateUUID1(): IO[UUID] = {
IO.blocking(UUID.randomUUID())
}
def generateUUID2(): IO[UUID] = {
IO.randomUUID
}
def generateUUID3(): IO[UUID] = {
IO(UUID.randomUUID())
}
def generateUUID4(): IO[(UUID, IO[UUID])] = {
for {
ioa <- IO(UUID.randomUUID())
iob = IO(UUID.randomUUID())
} yield (ioa, iob)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package fix

import cats.effect.*
import java.util.UUID

object IORandomUUIDTests {
IO.randomUUID
IO.randomUUID
IO.randomUUID
val test = IO.randomUUID
for {
ioa <- IO.randomUUID
iob = IO.randomUUID
} yield (ioa, iob)
def generateUUID1(): IO[UUID] = {
IO.randomUUID
}
def generateUUID2(): IO[UUID] = {
IO.randomUUID
}
def generateUUID3(): IO[UUID] = {
IO.randomUUID
}
def generateUUID4(): IO[(UUID, IO[UUID])] = {
for {
ioa <- IO.randomUUID
iob = IO.randomUUID
} yield (ioa, iob)
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
org.typelevel.fix.UnusedIO
org.typelevel.fix.UnusedIO
org.typelevel.fix.IORandomUUID
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2022 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.typelevel.fix

import scala.meta.*
import scalafix.v1.*

class IORandomUUID extends SemanticRule("TypelevelIORandomUUID") {
def checkSignature(parent: Stat, tree: Term)(implicit doc: SemanticDocument): Patch = {
tree.symbol.info.flatMap { info =>
info.signature match {
case MethodSignature(_, _, TypeRef(_, sym, _))
if IOSym.matches(sym) && containsJavaUUID(parent) =>
Some(Patch.replaceTree(parent, "IO.randomUUID"))
case _ =>
None
}
}.asPatch
}

def containsJavaUUID(outer: Stat)(implicit doc: SemanticDocument): Boolean = {
outer.children.exists(_.children.map(_.symbol).exists(JavaUUIDSym.matches))
}

def checkDiscardedStat(outer: Stat)(implicit doc: SemanticDocument): Patch = {
def checkInner(stat: Stat): Patch = {
lazy val self: PartialFunction[Stat, Patch] = {
case Term.Apply.Initial(fn @ Term.Name(_), _)
if IOCompanionSym.matches(fn) && containsJavaUUID(outer) =>
Patch.replaceTree(outer, "IO.randomUUID").atomic
case Term.Apply.Initial(Term.Select(_, method), _) =>
checkSignature(outer, method)
}

self.lift(stat).asPatch
}

checkInner(outer)
}

override def fix(implicit doc: SemanticDocument): Patch = {
doc.tree.collect {
case Term.Block(body) =>
body.map(checkDiscardedStat).asPatch
case Defn.Val(_, _, _, expr) =>
checkDiscardedStat(expr)
case Template.Initial(_, _, _, stats) =>
stats.map(checkDiscardedStat).asPatch
case Term.ForYield(enums, _) =>
enums.collect {
case Enumerator.Generator(_, rhs) =>
checkDiscardedStat(rhs)
case Enumerator.Val(_, rhs) =>
checkDiscardedStat(rhs)
}.asPatch
}.asPatch
}

val IOSym = SymbolMatcher.exact("cats/effect/IO#")
val IOCompanionSym = SymbolMatcher.exact("cats/effect/IO.")
val JavaUUIDSym = SymbolMatcher.exact("java/util/UUID#randomUUID().")
}

0 comments on commit 37e6db8

Please sign in to comment.