Skip to content

Commit 6722d01

Browse files
committed
Check cycles on package level
1 parent fbb60d1 commit 6722d01

File tree

15 files changed

+104
-13
lines changed

15 files changed

+104
-13
lines changed

acyclic/src-2/acyclic/plugin/Plugin.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,24 @@ class TestPlugin(val global: Global, cycleReporter: Seq[(Value, SortedSet[Int])]
1010
val name = "acyclic"
1111

1212
var force = false
13+
var forcePkg = false
1314
var fatal = true
1415

1516
// Yeah processOptions is deprecated but keep using it anyway for 2.10.x compatibility
1617
override def processOptions(options: List[String], error: String => Unit): Unit = {
1718
if (options.contains("force")) {
1819
force = true
1920
}
21+
if (options.contains("forcePkg")) {
22+
forcePkg = true
23+
}
2024
if (options.contains("warn")) {
2125
fatal = false
2226
}
2327
}
2428
val description = "Allows the developer to prohibit inter-file dependencies"
2529

2630
val components = List[tools.nsc.plugins.PluginComponent](
27-
new PluginPhase(this.global, cycleReporter, force, fatal)
31+
new PluginPhase(this.global, cycleReporter, force, forcePkg, fatal)
2832
)
2933
}

acyclic/src-2/acyclic/plugin/PluginPhase.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class PluginPhase(
1919
val global: Global,
2020
cycleReporter: Seq[(Value, SortedSet[Int])] => Unit,
2121
force: => Boolean,
22+
forcePkg: => Boolean,
2223
fatal: => Boolean
2324
) extends PluginComponent { t =>
2425

@@ -33,6 +34,7 @@ class PluginPhase(
3334
private object base extends BasePluginPhase[CompilationUnit, Tree, Symbol] with GraphAnalysis[Tree] {
3435
protected val cycleReporter = t.cycleReporter
3536
protected lazy val force = t.force
37+
protected lazy val forcePkg = t.forcePkg
3638
protected lazy val fatal = t.fatal
3739

3840
def treeLine(tree: Tree): Int = tree.pos.line
@@ -50,6 +52,8 @@ class PluginPhase(
5052
unit.body.collect { case x: PackageDef => x.pid.toString }.flatMap(_.split('.'))
5153
def findPkgObjects(tree: Tree): List[Tree] = tree.collect { case x: ModuleDef if x.name.toString == "package" => x }
5254
def pkgObjectName(pkgObject: Tree): String = pkgObject.symbol.enclosingPackageClass.fullName
55+
def findPkgs(tree: Tree): List[Tree] = tree.collect { case x: PackageDef if x.symbol.fullName != "<empty>" => x }
56+
def pkgName(pkg: Tree): String = pkg.symbol.fullName
5357
def hasAcyclicImport(tree: Tree, selector: String): Boolean =
5458
tree.collect {
5559
case Import(expr, List(sel)) => expr.symbol.toString == "package acyclic" && sel.name.toString == selector

acyclic/src-3/acyclic/plugin/Plugin.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class TestPlugin(cycleReporter: Seq[(Value, SortedSet[Int])] => Unit = _ => ())
1212
val description = "Allows the developer to prohibit inter-file dependencies"
1313

1414
var force = false
15+
var forcePkg = false
1516
var fatal = true
1617
var alreadyRun = false
1718

@@ -22,7 +23,7 @@ class TestPlugin(cycleReporter: Seq[(Value, SortedSet[Int])] => Unit = _ => ())
2223
override def run(using Context): Unit = {
2324
if (!alreadyRun) {
2425
alreadyRun = true
25-
new acyclic.plugin.PluginPhase(cycleReporter, force, fatal).run()
26+
new acyclic.plugin.PluginPhase(cycleReporter, force, forcePkg, fatal).run()
2627
}
2728
}
2829
}
@@ -31,6 +32,9 @@ class TestPlugin(cycleReporter: Seq[(Value, SortedSet[Int])] => Unit = _ => ())
3132
if (options.contains("force")) {
3233
force = true
3334
}
35+
if (options.contains("forcePkg")) {
36+
forcePkg = true
37+
}
3438
if (options.contains("warn")) {
3539
fatal = false
3640
}

acyclic/src-3/acyclic/plugin/PluginPhase.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import dotty.tools.dotc.{CompilationUnit, report}
77
import dotty.tools.dotc.core.Contexts.Context
88
import dotty.tools.dotc.core.Symbols.{NoSymbol, Symbol}
99
import dotty.tools.dotc.util.NoSource
10+
import dotty.tools.dotc.core.Flags.Package
1011

1112
/**
1213
* - Break dependency graph into strongly connected components
@@ -20,6 +21,7 @@ import dotty.tools.dotc.util.NoSource
2021
class PluginPhase(
2122
protected val cycleReporter: Seq[(Value, SortedSet[Int])] => Unit,
2223
protected val force: Boolean,
24+
protected val forcePkg: Boolean,
2325
protected val fatal: Boolean
2426
)(using ctx: Context) extends BasePluginPhase[CompilationUnit, tpd.Tree, Symbol], GraphAnalysis[tpd.Tree] {
2527

@@ -53,6 +55,14 @@ class PluginPhase(
5355
)
5456
}
5557

58+
private val pkgAccumulator = new tpd.TreeAccumulator[List[tpd.Tree]] {
59+
def apply(acc: List[tpd.Tree], tree: tpd.Tree)(using Context): List[tpd.Tree] =
60+
foldOver(
61+
if (tree.symbol.is(Package) && tree.symbol.fullName.toString != "<empty>") tree :: acc else acc,
62+
tree
63+
)
64+
}
65+
5666
private def hasAcyclicImportAccumulator(selector: String) = new tpd.TreeAccumulator[Boolean] {
5767
def apply(acc: Boolean, tree: tpd.Tree)(using Context): Boolean = tree match {
5868
case tpd.Import(expr, List(sel)) =>
@@ -71,6 +81,8 @@ class PluginPhase(
7181
def unitPkgName(unit: CompilationUnit): List[String] = pkgNameAccumulator(Nil, unit.tpdTree).reverse.flatMap(_.split('.'))
7282
def findPkgObjects(tree: tpd.Tree): List[tpd.Tree] = pkgObjectAccumulator(Nil, tree).reverse
7383
def pkgObjectName(pkgObject: tpd.Tree): String = pkgObject.symbol.enclosingPackageClass.fullName.toString
84+
def findPkgs(tree: tpd.Tree): List[tpd.Tree] = pkgAccumulator(Nil, tree).reverse
85+
def pkgName(pkg: tpd.Tree): String = pkg.symbol.fullName.toString
7486
def hasAcyclicImport(tree: tpd.Tree, selector: String): Boolean = hasAcyclicImportAccumulator(selector)(false, tree)
7587

7688
def extractDependencies(unit: CompilationUnit): Seq[(Symbol, tpd.Tree)] = DependencyExtraction(unit)

acyclic/src/acyclic/plugin/BasePluginPhase.scala

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import scala.collection.{mutable, SortedSet}
66
trait BasePluginPhase[CompilationUnit, Tree, Symbol] { self: GraphAnalysis[Tree] =>
77
protected val cycleReporter: Seq[(Value, SortedSet[Int])] => Unit
88
protected def force: Boolean
9+
protected def forcePkg: Boolean
910
protected def fatal: Boolean
1011

1112
def treeLine(tree: Tree): Int
@@ -22,6 +23,8 @@ trait BasePluginPhase[CompilationUnit, Tree, Symbol] { self: GraphAnalysis[Tree]
2223
def unitPkgName(unit: CompilationUnit): List[String]
2324
def findPkgObjects(tree: Tree): List[Tree]
2425
def pkgObjectName(pkgObject: Tree): String
26+
def findPkgs(tree: Tree): List[Tree]
27+
def pkgName(tree: Tree): String
2528
def hasAcyclicImport(tree: Tree, selector: String): Boolean
2629

2730
def extractDependencies(unit: CompilationUnit): Seq[(Symbol, Tree)]
@@ -39,15 +42,22 @@ trait BasePluginPhase[CompilationUnit, Tree, Symbol] { self: GraphAnalysis[Tree]
3942
} yield {
4043
Value.File(unitPath(unit), unitPkgName(unit))
4144
}
42-
43-
val acyclicPkgNames = for {
44-
unit <- units
45-
pkgObject <- findPkgObjects(unitTree(unit))
46-
if hasAcyclicImport(pkgObject, "pkg")
47-
} yield Value.Pkg(pkgObjectName(pkgObject).split('.').toList)
45+
val stdPackages = if (forcePkg) packages else Seq.empty[Value.Pkg]
46+
val acyclicPkgNames = packageObjects ++ stdPackages
4847
(skipNodePaths, acyclicNodePaths, acyclicPkgNames)
4948
}
5049

50+
private def packageObjects = for {
51+
unit <- units
52+
pkgObject <- findPkgObjects(unitTree(unit))
53+
if hasAcyclicImport(pkgObject, "pkg")
54+
} yield Value.Pkg(pkgObjectName(pkgObject).split('.').toList)
55+
56+
private def packages: Seq[Value.Pkg] = for {
57+
unit <- units
58+
pkgs <- findPkgs(unitTree(unit))
59+
} yield Value.Pkg(pkgName(pkgs).split('.').toList)
60+
5161
final def runAllUnits(): Unit = {
5262
val unitMap = units.map(u => unitPath(u) -> u).toMap
5363
val nodes = for (unit <- units) yield {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package forcepkg.cyclicpackage
2+
package a
3+
4+
class A1 extends b.B1{
5+
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package forcepkg.cyclicpackage.a
2+
3+
class A2 {
4+
5+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package forcepkg.cyclicpackage.b
2+
3+
class B1
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package forcepkg.cyclicpackage
2+
package b
3+
4+
class B2 extends a.A2
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package forcepkg.simple
2+
3+
4+
class A {
5+
val b: B = null
6+
}

0 commit comments

Comments
 (0)