![JAR search and dependency download from the Maven repository](/logo.png)
sbt.internal.LintUnused.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of main_2.12 Show documentation
Show all versions of main_2.12 Show documentation
sbt is an interactive build tool
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
package internal
import Keys._
import Def.{ Setting, ScopedKey }
import sbt.internal.util.{ FilePosition, NoPosition, SourcePosition }
import java.io.File
import Scope.Global
import sbt.SlashSyntax0._
import sbt.Def._
object LintUnused {
lazy val lintSettings: Seq[Setting[_]] = Seq(
lintIncludeFilter := {
val includes = includeLintKeys.value.map(_.scopedKey.key.label)
keyName => includes(keyName)
},
lintExcludeFilter := {
val excludedPrefixes = List("release", "sonatype", "watch", "whitesource")
val excludes = excludeLintKeys.value.map(_.scopedKey.key.label)
keyName => excludes(keyName) || excludedPrefixes.exists(keyName.startsWith(_))
},
excludeLintKeys := Set(
aggregate,
concurrentRestrictions,
commands,
crossScalaVersions,
crossSbtVersions,
initialize,
lintUnusedKeysOnLoad,
onLoad,
onLoadMessage,
onUnload,
sbt.nio.Keys.watchTriggers,
serverConnectionType,
serverIdleTimeout,
shellPrompt,
),
includeLintKeys := Set(
scalacOptions,
javacOptions,
javaOptions,
incOptions,
compileOptions,
packageOptions,
mainClass,
mappings,
testOptions,
classpathConfiguration,
ivyConfiguration,
),
Keys.lintUnused := lintUnusedTask.evaluated,
Keys.lintUnusedKeysOnLoad := true,
)
// input task version of the lintUnused
def lintUnusedTask: Def.Initialize[InputTask[Unit]] = Def.inputTask {
val _ = Def.spaceDelimited().parsed // not used yet
val state = Keys.state.value
val log = streams.value.log
val includeKeys = (Global / lintIncludeFilter).value
val excludeKeys = (Global / lintExcludeFilter).value
val result = lintUnused(state, includeKeys, excludeKeys)
if (result.isEmpty) log.success("ok")
else lintResultLines(result) foreach { log.warn(_) }
}
// function version of the lintUnused, based on just state
def lintUnusedFunc(s: State): State = {
val log = s.log
val extracted = Project.extract(s)
val includeKeys = extracted.get((Global / lintIncludeFilter))
val excludeKeys = extracted.get((Global / lintExcludeFilter))
if (extracted.get((Global / lintUnusedKeysOnLoad))) {
val result = lintUnused(s, includeKeys, excludeKeys)
lintResultLines(result) foreach { log.warn(_) }
}
s
}
def lintResultLines(
result: Seq[(ScopedKey[_], String, Vector[SourcePosition])]
): Vector[String] = {
import scala.collection.mutable.ListBuffer
val buffer = ListBuffer.empty[String]
if (result.isEmpty) Vector.empty
else {
val size = result.size
if (size == 1) buffer.append("there's a key that's not used by any other settings/tasks:")
else buffer.append(s"there are $size keys that are not used by any other settings/tasks:")
buffer.append(" ")
result foreach {
case (_, str, positions) =>
buffer.append(s"* $str")
positions foreach {
case pos: FilePosition => buffer.append(s" +- ${pos.path}:${pos.startLine}")
case _ => ()
}
}
buffer.append(" ")
buffer.append(
"note: a setting might still be used by a command; to exclude a key from this `lintUnused` check"
)
buffer.append(
"either append it to `Global / excludeLintKeys` or call .withRank(KeyRanks.Invisible) on the key"
)
buffer.toVector
}
}
def lintUnused(
state: State,
includeKeys: String => Boolean,
excludeKeys: String => Boolean
): Seq[(ScopedKey[_], String, Vector[SourcePosition])] = {
val extracted = Project.extract(state)
val structure = extracted.structure
val display = Def.showShortKey(None) // extracted.showKey
val comp = structure.compiledMap
val cMap = Def.flattenLocals(comp)
val used: Set[ScopedKey[_]] = cMap.values.flatMap(_.dependencies).toSet
val unused: Seq[ScopedKey[_]] = cMap.keys.filter(!used.contains(_)).toSeq
val withDefinedAts: Seq[UnusedKey] = unused map { u =>
val definingScope = structure.data.definingScope(u.scope, u.key)
val definingScoped = definingScope match {
case Some(sc) => ScopedKey(sc, u.key)
case _ => u
}
val definedAt = comp.get(definingScoped) match {
case Some(c) => definedAtString(c.settings.toVector)
case _ => Vector.empty
}
val data = Project.scopedKeyData(structure, u.scope, u.key)
UnusedKey(u, definedAt, data)
}
def isIncludeKey(u: UnusedKey): Boolean = includeKeys(u.scoped.key.label)
def isExcludeKey(u: UnusedKey): Boolean = excludeKeys(u.scoped.key.label)
def isSettingKey(u: UnusedKey): Boolean = u.data match {
case Some(data) => data.settingValue.isDefined
case _ => false
}
def isLocallyDefined(u: UnusedKey): Boolean = u.positions exists {
case pos: FilePosition => pos.path.contains(File.separator)
case _ => false
}
def isInvisible(u: UnusedKey): Boolean = u.scoped.key.rank == KeyRanks.Invisible
val unusedKeys = withDefinedAts collect {
case u
if !isExcludeKey(u) && !isInvisible(u)
&& (isSettingKey(u) || isIncludeKey(u))
&& isLocallyDefined(u) =>
u
}
(unusedKeys map { u =>
(u.scoped, display.show(u.scoped), u.positions)
}).sortBy(_._2)
}
private[this] case class UnusedKey(
scoped: ScopedKey[_],
positions: Vector[SourcePosition],
data: Option[ScopedKeyData[_]]
)
private def definedAtString(settings: Vector[Setting[_]]): Vector[SourcePosition] = {
settings flatMap { setting =>
setting.pos match {
case NoPosition => Vector.empty
case pos => Vector(pos)
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy