All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wartremover.WartTraverser.scala Maven / Gradle / Ivy

package org.wartremover

import reflect.api.Universe
import reflect.macros.blackbox.Context

trait WartTraverser {
  def apply(u: WartUniverse): u.Traverser

  lazy val className = this.getClass.getName.stripSuffix("$")
  lazy val wartName = className.substring(className.lastIndexOf('.') + 1)

  def asMacro(c: Context)(expr: c.Expr[Any]): c.Expr[Any] = {

    object MacroUniverse extends WartUniverse {
      val universe: c.universe.type = c.universe
      def error(pos: universe.Position, message: String) = c.error(pos, message)
      def warning(pos: universe.Position, message: String) = c.warning(pos, message)
      val excludes: List[String] = List.empty // TODO: find a sensible way to initialize this field with useful data
    }

    apply(MacroUniverse).traverse(expr.tree)

    expr
  }

  def asAnnotationMacro(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    val block = Block(annottees.map(_.tree).toList, Literal(Constant(())))
    c.typecheck(block)

    annottees.foreach { expr =>
      asMacro(c)(expr)
    }

    c.Expr[Any](block)
  }

  def compose(o: WartTraverser): WartTraverser = new WartTraverser {
    def apply(u: WartUniverse): u.Traverser = {
      new u.Traverser {
        override def traverse(tree: u.universe.Tree): Unit = {
          WartTraverser.this(u).traverse(tree)
          o(u).traverse(tree)
        }
      }
    }
  }

  def isSyntheticPartialFunction(u: WartUniverse)(tree: u.universe.Tree): Boolean = {
    import u.universe._
    tree match {
      case ClassDef(_, className, _, Template((parents, _, _))) =>
        isAnonymousFunctionName(u)(className) && isSynthetic(u)(tree) && parents.exists {
          case t @ TypeTree() =>
            t.symbol.fullName == "scala.runtime.AbstractPartialFunction"
          case _ =>
            false
        }
      case _ =>
        false
    }
  }

  def isAnonymousFunctionName(u: WartUniverse)(t: u.universe.TypeName): Boolean =
    t == u.universe.TypeName("$anonfun")

  def isSynthetic(u: WartUniverse)(t: u.universe.Tree): Boolean = {
    // Unfortunately, Scala does not mark accessors as Synthetic (even though the documentation claims that they do).
    // A manually crafted getter/setter does not deserve a GETTER/SETTER flag in Scala compiler's eyes, so we can
    // "safely" rely on this
    Option(t.symbol).exists(s => s.isSynthetic || s.isImplementationArtifact || (s.isTerm && s.asTerm.isAccessor))
  }

  def isPrimitive(u: WartUniverse)(t: u.universe.Type): Boolean =
    t <:< u.universe.typeOf[Boolean] ||
      t <:< u.universe.typeOf[Byte] ||
      t <:< u.universe.typeOf[Short] ||
      t <:< u.universe.typeOf[Char] ||
      t <:< u.universe.typeOf[Int] ||
      t <:< u.universe.typeOf[Long] ||
      t <:< u.universe.typeOf[Float] ||
      t <:< u.universe.typeOf[Double]

  def hasTypeAscription(u: WartUniverse)(tree: u.universe.ValOrDefDef): Boolean = {
    import u.universe._
    tree.tpt.nonEmpty && (tree.tpt match {
      case t @ TypeTree() => !wasInferred(u)(t)
      case _ => false
    })
  }

  private def hasAccess(u: WartUniverse)(t: u.universe.ValOrDefDef, p: u.universe.Symbol => Boolean): Boolean = {
    // If this a field, then look at the getter's visibility
    val symbol = if (!t.symbol.isMethod && (t.symbol.owner.isType || t.symbol.owner.isModule)) {
      t.symbol.asTerm.getter
    } else t.symbol

    p(symbol)
  }

  def isPublic(u: WartUniverse)(t: u.universe.ValOrDefDef): Boolean = {
    hasAccess(u)(t, s => s.isPublic && (s != u.universe.NoSymbol))
  }

  def isPrivate(u: WartUniverse)(t: u.universe.ValOrDefDef): Boolean = {
    hasAccess(u)(t, s => s.isPrivate)
  }

  def wasInferred(u: WartUniverse)(t: u.universe.TypeTree): Boolean =
    t.original == null

  def isWartAnnotation(u: WartUniverse)(a: u.universe.Annotation): Boolean = {
    import u.universe._
    a.tree.tpe <:< typeTag[java.lang.SuppressWarnings].tpe &&
    a.tree.children.tail.exists {
      _.exists {
        case Literal(Constant(arg)) =>
          (arg == className) || (arg == "org.wartremover.warts.All")
        case _ =>
          false
      }
    } || isScalaAnnotationNowarn(u)(a)
  }

  private[this] def isScalaAnnotationNowarn(u: WartUniverse)(a: u.universe.Annotation): Boolean = {
    import u.universe._
    val nowarnClassOpt =
      try {
        Option(rootMirror.staticClass("scala.annotation.nowarn"))
      } catch {
        case _: ScalaReflectionException =>
          None
      }

    val compat = new org.wartremover.Compat[u.universe.type](u.universe)

    nowarnClassOpt.exists { nowarnClass =>
      (a.tree.tpe =:= nowarnClass.toType) && {
        a.tree.children match {
          case _ :: compat.NamedArg(Ident(TermName("value")), Literal(Constant(""))) :: Nil =>
            true
          case _ :: Nil =>
            true
          case _ =>
            false
        }
      }
    }
  }

  def hasWartAnnotation(u: WartUniverse)(tree: u.universe.Tree) = {
    import u.universe._
    tree match {
      case t: ValOrDefDef =>
        t.symbol.annotations.exists(isWartAnnotation(u)) ||
        (t.symbol != null && t.symbol.isTerm && t.symbol.asTerm.isAccessor &&
          t.symbol.asTerm.accessed.annotations.exists(isWartAnnotation(u)))
      case t: ImplDef => t.symbol.annotations.exists(isWartAnnotation(u))
      case t => false
    }
  }

  def error(u: WartUniverse)(pos: u.universe.Position, message: String): Unit = u.error(pos, message, wartName)

  def warning(u: WartUniverse)(pos: u.universe.Position, message: String): Unit = u.warning(pos, message, wartName)
}

object WartTraverser {
  def sumList(u: WartUniverse)(l: List[WartTraverser]): u.Traverser =
    l.reduceRight(_ compose _)(u)
}

trait WartUniverse {
  val universe: Universe
  type Traverser = universe.Traverser
  type TypeTag[T] = universe.TypeTag[T]
  protected def error(pos: universe.Position, message: String): Unit
  protected def warning(pos: universe.Position, message: String): Unit
  def logLevel: LogLevel = LogLevel.Disable
  def error(pos: universe.Position, message: String, wartName: String): Unit =
    error(pos, s"[wartremover:$wartName] $message")
  def warning(pos: universe.Position, message: String, wartName: String): Unit =
    warning(pos, s"[wartremover:$wartName] $message")
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy