
acyclic.plugin.PluginPhase.scala Maven / Gradle / Ivy
The newest version!
package acyclic.plugin
import acyclic.file
import collection.mutable
import scala.tools.nsc.{Global, Phase}
import tools.nsc.plugins.PluginComponent
/**
* - Break dependency graph into strongly connected components
* - Turn acyclic packages into virtual "files" in the dependency graph, as
* aggregates of all the files within them
* - Any strongly connected component which includes an acyclic.file or
* acyclic.pkg is a failure
* - Pick an arbitrary cycle and report it
* - Don't report more than one cycle per file/pkg, to avoid excessive spam
*/
class PluginPhase(val global: Global, cycleReporter: Seq[(Value, Set[Int])] => Unit)
extends PluginComponent
with GraphAnalysis { t =>
import global._
val runsAfter = List("typer")
override val runsRightAfter = Some("typer")
val phaseName = "acyclic"
def pkgName(unit: CompilationUnit) = {
unit.body
.collect{case x: PackageDef => x.pid.toString}
.flatMap(_.split('.'))
}
def units = global.currentRun.units.toSet
def findAcyclics() = {
val acyclicNodePaths = for {
unit <- units
if unit.body.children.collect{
case Import(expr, List(sel)) =>
expr.symbol.toString == "package acyclic" && sel.name.toString == "file"
}.exists(x => x)
} yield {
Value.File(unit.source.path, pkgName(unit))
}
val acyclicPkgNames = for {
unit <- units
pkgObject <- unit.body.collect{case x: ModuleDef if x.name.toString == "package" => x }
if pkgObject.impl.children.collect{case Import(expr, List(sel)) =>
expr.symbol.toString == "package acyclic" && sel.name.toString == "pkg"
}.exists(x => x)
} yield {
Value.Pkg(
pkgObject.symbol
.enclosingPackageClass
.fullName
.split('.')
.toList
)
}
(acyclicNodePaths, acyclicPkgNames)
}
override def newPhase(prev: Phase): Phase = new Phase(prev) {
override def run() {
val unitMap = units.map(u => u.source.path -> u).toMap
val nodes = for (unit <- units) yield {
val deps = DependencyExtraction(t.global)(unit)
val connections = for{
(sym, tree) <- deps
if sym != NoSymbol
if sym.sourceFile != null
if sym.sourceFile.path != unit.source.path
} yield (sym.sourceFile.path, tree)
Node[Value.File](
Value.File(unit.source.path, pkgName(unit)),
connections.groupBy(c => Value.File(c._1, pkgName(unitMap(c._1))): Value)
.mapValues(_.map(_._2))
)
}
val nodeMap = nodes.map(n => n.value -> n).toMap
val (acyclicFiles, acyclicPkgs) = findAcyclics()
val allAcyclics = Set.empty[Value] ++ acyclicFiles ++ acyclicPkgs
// synthetic nodes for packages, which aggregate the dependencies of
// their contents
val pkgNodes = acyclicPkgs.map{ value =>
Node(
value,
nodes.filter(_.value.pkg.startsWith(value.pkg))
.flatMap(_.dependencies.toSeq)
.groupBy(_._1)
.mapValues(_.flatMap(_._2).toSet)
)
}
val linkedNodes: Set[DepNode] = (nodes ++ pkgNodes).map{ d =>
val extraLinks = for{
(value: Value.File, pos) <- d.dependencies
acyclicPkg <- acyclicPkgs
if nodeMap(value).value.pkg.startsWith(acyclicPkg.pkg)
if !d.value.pkg.startsWith(acyclicPkg.pkg)
} yield (acyclicPkg, pos)
d.copy(dependencies = d.dependencies ++ extraLinks)
}
// only care about cycles with size > 1 here
val components = DepNode.stronglyConnectedComponents(linkedNodes)
.filter(_.size > 1)
val usedNodes = mutable.Set.empty[DepNode]
for{
c <- components
n <- c
if !usedNodes.contains(n)
if allAcyclics.contains(n.value)
}{
val cycle = DepNode.smallestCycle(n, c)
val cycleInfo =
(cycle :+ cycle.head).sliding(2)
.map{ case Seq(a, b) => (a.value, a.dependencies(b.value))}
.toSeq
cycleReporter(
cycleInfo.map{ case (a, b) => a -> b.map(_.pos.line).toSet }
)
global.error("Unwanted cyclic dependency")
for (Seq((value, locs), (nextValue, _)) <- (cycleInfo :+ cycleInfo.head).sliding(2)){
global.inform("")
value match{
case Value.Pkg(pkg) => global.inform(s"package ${pkg.mkString(".")}")
case Value.File(_, _) =>
}
units.find(_.source.path == locs.head.pos.source.path)
.get
.echo(locs.head.pos, "")
val otherLines = locs.tail
.map(_.pos.line)
.filter(_ != locs.head.pos.line)
global.inform("symbol: " + locs.head.symbol.toString)
if (!otherLines.isEmpty){
global.inform("More dependencies at lines " + otherLines.mkString(" "))
}
}
global.inform("")
usedNodes ++= cycle
}
}
def name: String = "acyclic"
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy