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

scalafix.internal.rule.DisableSyntax.scala Maven / Gradle / Ivy

package scalafix.internal.rule

import scala.meta._
import metaconfig.{Conf, Configured}
import scalafix.Patch
import scalafix.rule.{Rule, RuleCtx}
import scalafix.lint.LintMessage
import scalafix.lint.LintCategory
import scalafix.internal.config.{DisableSyntaxConfig, Keyword}

final case class DisableSyntax(
    config: DisableSyntaxConfig = DisableSyntaxConfig())
    extends Rule("DisableSyntax")
    with Product {

  override def description: String =
    "Linter that reports an error on a configurable set of keywords and syntax."

  override def init(config: Conf): Configured[Rule] =
    config
      .getOrElse("disableSyntax", "DisableSyntax")(DisableSyntaxConfig.default)
      .map(DisableSyntax(_))

  private def checkRegex(ctx: RuleCtx): Seq[LintMessage] = {
    def pos(offset: Int): Position =
      Position.Range(ctx.input, offset, offset)
    val regexLintMessages = Seq.newBuilder[LintMessage]
    config.regex.foreach { regex =>
      val matcher = regex.value.matcher(ctx.input.chars)
      val pattern = regex.value.pattern
      val message = regex.message.getOrElse(s"$pattern is disabled")
      while (matcher.find()) {
        regexLintMessages +=
          errorCategory
            .copy(id = regex.id.getOrElse(pattern))
            .at(message, pos(matcher.start))
      }
    }
    regexLintMessages.result()
  }

  private def checkTokens(ctx: RuleCtx): Seq[LintMessage] = {
    ctx.tree.tokens.collect {
      case token @ Keyword(keyword) if config.isDisabled(keyword) =>
        errorCategory
          .copy(id = s"keywords.$keyword")
          .at(s"$keyword is disabled", token.pos)
      case token @ Token.Semicolon() if config.noSemicolons =>
        error("noSemicolons", token)
      case token @ Token.Tab() if config.noTabs =>
        error("noTabs", token)
      case token @ Token.Xml.Start() if config.noXml =>
        error("noXml", token)
    }
  }

  private def checkTree(ctx: RuleCtx): Seq[LintMessage] = {
    object AbstractWithVals {
      def unapply(t: Tree): Option[List[Defn.Val]] = {
        val stats = t match {
          case Defn.Class(mods, _, _, _, templ)
              if mods.exists(_.is[Mod.Abstract]) =>
            templ.stats
          case Defn.Trait(_, _, _, _, templ) => templ.stats
          case _ => List.empty
        }
        val vals = stats.flatMap {
          case v: Defn.Val => Some(v)
          case _ => None
        }
        if (vals.isEmpty) None else Some(vals)
      }
    }

    object DefaultArgs {
      def unapply(t: Tree): Option[List[Term]] = {
        t match {
          case d: Defn.Def => {
            val defaults =
              for {
                params <- d.paramss
                param <- params
                default <- param.default.toList
              } yield default

            Some(defaults)
          }
          case _ => None
        }
      }
    }

    def hasNonImplicitParam(d: Defn.Def): Boolean =
      d.paramss.exists(_.exists(_.mods.forall(!_.is[Mod.Implicit])))

    val DefaultMatcher: PartialFunction[Tree, Seq[LintMessage]] = {
      case t @ mod"+" if config.noCovariantTypes =>
        Seq(
          errorCategory
            .copy(id = "covariant")
            .at(
              "Covariant types could lead to error-prone situations.",
              t.pos
            )
        )
      case t @ mod"-" if config.noContravariantTypes =>
        Seq(
          errorCategory
            .copy(id = "contravariant")
            .at(
              "Contravariant types could lead to error-prone situations.",
              t.pos
            )
        )
      case t @ AbstractWithVals(vals) if config.noValInAbstract =>
        vals.map { v =>
          errorCategory
            .copy(id = "valInAbstract")
            .at(
              "val definitions in traits/abstract classes may cause initialization bugs",
              v.pos)
        }
      case t @ Defn.Object(mods, _, _)
          if mods.exists(_.is[Mod.Implicit]) && config.noImplicitObject =>
        Seq(
          errorCategory
            .copy(id = "implicitObject")
            .at("implicit objects may cause implicit resolution errors", t.pos)
        )
      case t @ Defn.Def(mods, _, _, paramss, _, _)
          if mods.exists(_.is[Mod.Implicit]) &&
            hasNonImplicitParam(t) &&
            config.noImplicitConversion =>
        Seq(
          errorCategory
            .copy(id = "implicitConversion")
            .at(
              "implicit conversions weaken type safety and always can be replaced by explicit conversions",
              t.pos)
        )
      case DefaultArgs(params) if config.noDefaultArgs =>
        params
          .map { m =>
            errorCategory
              .copy(id = "defaultArgs")
              .at(
                "Default args makes it hard to use methods as functions.",
                m.pos)
          }
    }
    val FinalizeMatcher = DisableSyntax.FinalizeMatcher("noFinalize")
    ctx.tree.collect(DefaultMatcher.orElse(FinalizeMatcher)).flatten
  }

  private def fixTree(ctx: RuleCtx): Patch = {
    ctx.tree.collect {
      case t @ Defn.Val(mods, _, _, _)
          if config.noFinalVal &&
            mods.exists(_.is[Mod.Final]) =>
        val finalTokens =
          mods.find(_.is[Mod.Final]).map(_.tokens.toList).getOrElse(List.empty)
        ctx.removeTokens(finalTokens) +
          ctx.removeTokens(finalTokens.flatMap(ctx.tokenList.trailingSpaces))
    }.asPatch
  }

  override def fix(ctx: RuleCtx): Patch = {
    val lints =
      (checkTree(ctx) ++ checkTokens(ctx) ++ checkRegex(ctx)).map(ctx.lint)
    fixTree(ctx) ++ lints
  }

  private val errorCategory: LintCategory =
    LintCategory.error(
      "Some constructs are unsafe to use and should be avoided")

  private def error(keyword: String, token: Token): LintMessage =
    errorCategory.copy(id = keyword).at(s"$keyword is disabled", token.pos)
}

object DisableSyntax {

  private val FinalizeError =
    LintCategory.error(
      explain = """|there is no guarantee that finalize will be called and
                   |overriding finalize incurs a performance penalty""".stripMargin
    )

  def FinalizeMatcher(id: String): PartialFunction[Tree, List[LintMessage]] = {
    case Defn.Def(_, name @ q"finalize", _, Nil | Nil :: Nil, _, _) =>
      FinalizeError
        .copy(id = id)
        .at("finalize should not be used", name.pos) :: Nil
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy