mirror of https://github.com/sbt/sbt.git
118 lines
4.6 KiB
Scala
118 lines
4.6 KiB
Scala
package xsbt
|
|
|
|
import scala.tools.nsc._
|
|
|
|
/**
|
|
* Extracts simple names used in given compilation unit.
|
|
*
|
|
* Extracts simple (unqualified) names mentioned in given in non-definition position by collecting
|
|
* all symbols associated with non-definition trees and extracting names from all collected symbols.
|
|
*
|
|
* If given symbol is mentioned both in definition and in non-definition position (e.g. in member
|
|
* selection) then that symbol is collected. It means that names of symbols defined and used in the
|
|
* same compilation unit are extracted. We've considered not extracting names of those symbols
|
|
* as an optimization strategy. It turned out that this is not correct. Check
|
|
* https://github.com/gkossakowski/sbt/issues/3 for an example of scenario where it matters.
|
|
*
|
|
* All extracted names are returned in _decoded_ form. This way we stay consistent with the rest
|
|
* of incremental compiler which works with names in decoded form.
|
|
*
|
|
* Names mentioned in Import nodes are handled properly but require some special logic for two
|
|
* reasons:
|
|
*
|
|
* 1. import node itself has a term symbol associated with it with a name `<import`>.
|
|
* I (gkossakowski) tried to track down what role this symbol serves but I couldn't.
|
|
* It doesn't look like there are many places in Scala compiler that refer to
|
|
* that kind of symbols explicitly.
|
|
* 2. ImportSelector is not subtype of Tree therefore is not processed by `Tree.foreach`
|
|
*
|
|
* Another type of tree nodes that requires special handling is TypeTree. TypeTree nodes
|
|
* has a little bit odd representation:
|
|
*
|
|
* 1. TypeTree.hasSymbol always returns false even when TypeTree.symbol
|
|
* returns a symbol
|
|
* 2. The original tree from which given TypeTree was derived is stored
|
|
* in TypeTree.original but Tree.forech doesn't walk into original
|
|
* tree so we missed it
|
|
*
|
|
* The tree walking algorithm walks into TypeTree.original explicitly.
|
|
*
|
|
*/
|
|
class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) extends Compat {
|
|
import global._
|
|
|
|
def extract(unit: CompilationUnit): Set[String] = {
|
|
val tree = unit.body
|
|
val extractedByTreeWalk = extractByTreeWalk(tree)
|
|
extractedByTreeWalk
|
|
}
|
|
|
|
private def extractByTreeWalk(tree: Tree): Set[String] = {
|
|
val namesBuffer = collection.mutable.ListBuffer.empty[String]
|
|
def addSymbol(symbol: Symbol): Unit = {
|
|
val symbolNameAsString = symbol.name.decode.trim
|
|
namesBuffer += symbolNameAsString
|
|
}
|
|
|
|
def handleTreeNode(node: Tree): Unit = {
|
|
def handleMacroExpansion(original: Tree): Unit = original.foreach(handleTreeNode)
|
|
|
|
def handleClassicTreeNode(node: Tree): Unit = node match {
|
|
case _: DefTree | _: Template => ()
|
|
// turns out that Import node has a TermSymbol associated with it
|
|
// I (Grzegorz) tried to understand why it's there and what does it represent but
|
|
// that logic was introduced in 2005 without any justification I'll just ignore the
|
|
// import node altogether and just process the selectors in the import node
|
|
case Import(_, selectors: List[ImportSelector]) =>
|
|
def usedNameInImportSelector(name: Name): Unit =
|
|
if ((name != null) && (name != nme.WILDCARD)) namesBuffer += name.toString
|
|
selectors foreach { selector =>
|
|
usedNameInImportSelector(selector.name)
|
|
usedNameInImportSelector(selector.rename)
|
|
}
|
|
// TODO: figure out whether we should process the original tree or walk the type
|
|
// the argument for processing the original tree: we process what user wrote
|
|
// the argument for processing the type: we catch all transformations that typer applies
|
|
// to types but that might be a bad thing because it might expand aliases eagerly which
|
|
// not what we need
|
|
case t: TypeTree if t.original != null =>
|
|
t.original.foreach(handleTreeNode)
|
|
case t if t.hasSymbol && eligibleAsUsedName(t.symbol) =>
|
|
addSymbol(t.symbol)
|
|
case _ => ()
|
|
}
|
|
|
|
node match {
|
|
case MacroExpansionOf(original) =>
|
|
handleClassicTreeNode(node)
|
|
handleMacroExpansion(original)
|
|
case _ =>
|
|
handleClassicTreeNode(node)
|
|
}
|
|
}
|
|
|
|
tree.foreach(handleTreeNode)
|
|
namesBuffer.toSet
|
|
}
|
|
|
|
|
|
/**
|
|
* Needed for compatibility with Scala 2.8 which doesn't define `tpnme`
|
|
*/
|
|
private object tpnme {
|
|
val EMPTY = nme.EMPTY.toTypeName
|
|
val EMPTY_PACKAGE_NAME = nme.EMPTY_PACKAGE_NAME.toTypeName
|
|
}
|
|
|
|
private def eligibleAsUsedName(symbol: Symbol): Boolean = {
|
|
def emptyName(name: Name): Boolean = name match {
|
|
case nme.EMPTY | nme.EMPTY_PACKAGE_NAME | tpnme.EMPTY | tpnme.EMPTY_PACKAGE_NAME => true
|
|
case _ => false
|
|
}
|
|
|
|
(symbol != NoSymbol) &&
|
|
!symbol.isSynthetic &&
|
|
!emptyName(symbol.name)
|
|
}
|
|
}
|