Is there a way to prevent usage of a specific function provided by a dependency?

456 views Asked by At

My application has a compile dependency to a library A. There is a specific function in this library which I know that causes performance problems. But it is not deprecated and if you don't have this prior information you would think that it is safe to use it. I'm looking for a way to somehow deprecate this function or prevent it from being called directly.

Would it be possible to achieve this via static analysis tools or builtin compiler flags?

2

There are 2 answers

0
fcs On BEST ANSWER

I have checked scalafix but it was too complicated to setup for just this kind of linting. I ended up using wartremover and adding a custom wart.

Custom warts sit in a subproject as shown in this example.

lazy val myWarts = project.in(file("my-warts")).settings(
  commonSettings,
  libraryDependencies ++= Seq(
  "org.wartremover" % "wartremover" % wartremover.Wart.PluginVersion cross CrossVersion.full
  )
)

lazy val main = project.in(file("main")).settings(
  commonSettings,
  wartremoverWarnings += Wart.custom("mywarts.ExtractOrElse"),
  wartremoverClasspaths ++= {
    (fullClasspath in (myWarts, Compile)).value.map(_.data.toURI.toString)
  }
)

I have created a custom wart by simply modifying builtin EitherProjectionPartial wart.

object ExtractOrElse extends WartTraverser {
  def apply(u: WartUniverse): u.Traverser = {
    import u.universe._

    val extractableJsonAstNode = rootMirror.staticClass("org.json4s.ExtractableJsonAstNode")
    new u.Traverser {
      override def traverse(tree: Tree): Unit = {
        tree match {
          // Ignore trees marked by SuppressWarnings
          case t if hasWartAnnotation(u)(t) =>
          case Select(left, TermName("extractOrElse")) if left.tpe.baseType(extractableJsonAstNode) != NoType =>
            error(u)(tree.pos, "extractOrElse is deprecated - use toOption.map or extract[Option[A]] instead")
            super.traverse(tree)
          case _ => super.traverse(tree)
        }
      }
    }
  }
}
0
Dmytro Mitin On

You can write a rule for Scalafix

https://scalacenter.github.io/scalafix/docs/developers/setup.html

For example let's deprecate scala.Predef.println

class DeprecateFunction extends SemanticRule("DeprecateFunction") {

  case class Deprecation(position: Position) extends Diagnostic {
    override def message = "Use loggers instead of println"
    override def severity = LintSeverity.Warning
  }

  val deprecatedFunction = SymbolMatcher.normalized("scala.Predef.println")

  override def fix(implicit doc: SemanticDocument): Patch = {
    doc.tree.collect {
      case deprecatedFunction(t: Name) =>
        Patch.lint(Deprecation(t.pos))
    }.asPatch
  }
}

Example:

object Scalafixdemo {
  println(1)
}

Output:

[IJ]sbt:scalafix> scalafix --rules=file:rules/src/main/scala/fix/DeprecateFunction.scala
[info] Running scalafix on 1 Scala sources
[warn] .../scalafix/input/src/main/scala/fix/Scalafixdemo.scala:8:3: warning: [DeprecateFunction] Use loggers instead of println
[warn]   println(1)
[warn]   ^^^^^^^
[success] Total time: 2 s, completed 27.05.2020 21:12:13