Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check cycles on package level #30

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/main/scala/acyclic/plugin/Plugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@ class TestPlugin(val global: Global,
val name = "acyclic"

var force = false
var forcePkg = false
// Yeah processOptions is deprecated but keep using it anyway for 2.10.x compatibility
override def processOptions(options: List[String], error: String => Unit): Unit = {
if (options.contains("force")) {
force = true
}
if(options.contains("forcePkg")){
forcePkg = true
}
}
val description = "Allows the developer to prohibit inter-file dependencies"


val components = List[tools.nsc.plugins.PluginComponent](
new PluginPhase(this.global, cycleReporter, force)
new PluginPhase(this.global, cycleReporter, force, forcePkg)
)
}
25 changes: 20 additions & 5 deletions src/main/scala/acyclic/plugin/PluginPhase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import tools.nsc.plugins.PluginComponent
*/
class PluginPhase(val global: Global,
cycleReporter: Seq[(Value, SortedSet[Int])] => Unit,
force: => Boolean)
force: => Boolean,
forcePkg: => Boolean)
extends PluginComponent
with GraphAnalysis { t =>

Expand Down Expand Up @@ -58,11 +59,16 @@ class PluginPhase(val global: Global,
} yield {
Value.File(unit.source.path, pkgName(unit))
}
val stdPackages = if(forcePkg) packages else Seq.empty[Value.Pkg]
val acyclicPkgNames = packageObjects ++ stdPackages
(skipNodePaths, acyclicNodePaths, acyclicPkgNames)
}

val acyclicPkgNames = for {
private def packageObjects = {
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)) =>
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 {
Expand All @@ -74,7 +80,16 @@ class PluginPhase(val global: Global,
.toList
)
}
(skipNodePaths, acyclicNodePaths, acyclicPkgNames)
}

private def packages: Seq[Value.Pkg] = {
units.map(x => x.body).collect { case y: PackageDef => y }.map(z => z.symbol.fullName).filter(x => x != "<empty>")
.distinct
.map(c => Value.Pkg(
c
.split('.')
.toList
))
}

override def newPhase(prev: Phase): Phase = new Phase(prev) {
Expand Down
6 changes: 6 additions & 0 deletions src/test/resources/forcepkg/cyclicpackage/a/A1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package forcepkg.cyclicpackage
package a

class A1 extends b.B1{

}
5 changes: 5 additions & 0 deletions src/test/resources/forcepkg/cyclicpackage/a/A2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package forcepkg.cyclicpackage.a

class A2 {

}
3 changes: 3 additions & 0 deletions src/test/resources/forcepkg/cyclicpackage/b/B1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package forcepkg.cyclicpackage.b

class B1
4 changes: 4 additions & 0 deletions src/test/resources/forcepkg/cyclicpackage/b/B2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package forcepkg.cyclicpackage
package b

class B2 extends a.A2
6 changes: 6 additions & 0 deletions src/test/resources/forcepkg/simple/A.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package forcepkg.simple


class A {
val b: B = null
}
6 changes: 6 additions & 0 deletions src/test/resources/forcepkg/simple/B.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package forcepkg.simple

class B {
val a1: A = new A
val a2: A = new A
}
17 changes: 16 additions & 1 deletion src/test/scala/acyclic/CycleTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package acyclic

import utest._
import TestUtils.{make, makeFail}
import scala.tools.nsc.util.ScalaClassLoader.URLClassLoader
import acyclic.plugin.Value.{Pkg, File}
import scala.collection.SortedSet
import acyclic.file
Expand Down Expand Up @@ -61,6 +60,22 @@ object CycleTests extends TestSuite{
))
'pass-make("force/simple")
'skip-make("force/skip", force = true)
"mutualcyclic"-make("success/pkg/mutualcyclic", force = true)
}
'forcepkg{
'fail-makeFail("forcepkg/cyclicpackage", force = false, forcePkg = true)(
Seq(
Pkg("forcepkg.cyclicpackage.b") -> SortedSet(4),
Pkg("forcepkg.cyclicpackage.a") -> SortedSet(4)
)
)
'success-make("forcepkg/simple", force = false, forcePkg = true)
'fail-makeFail("forcepkg/simple", force = true, forcePkg = true)(
Seq(
File("B.scala") -> SortedSet(4, 5),
File("A.scala") -> SortedSet(5)
)
)
}
}
}
Expand Down
10 changes: 6 additions & 4 deletions src/test/scala/acyclic/TestUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ object TestUtils {
*/
def make(path: String,
extraIncludes: Seq[String] = Seq("src/main/scala/acyclic/package.scala"),
force: Boolean = false) = {
force: Boolean = false,
forcePkg: Boolean = false) = {
val src = "src/test/resources/" + path
val sources = getFilePaths(src) ++ extraIncludes

Expand All @@ -41,7 +42,8 @@ object TestUtils {

settings.classpath.value = ClassPath.join(entries ++ sclpath : _*)

if (force) settings.pluginOptions.value = List("acyclic:force")
if (force) settings.pluginOptions.value ++= List("acyclic:force")
if (forcePkg) settings.pluginOptions.value ++= List("acyclic:forcePkg")

var cycles: Option[Seq[Seq[(acyclic.plugin.Value, SortedSet[Int])]]] = None
lazy val compiler = new Global(settings, new ConsoleReporter(settings)){
Expand All @@ -58,13 +60,13 @@ object TestUtils {
if (vd.toList.isEmpty) throw CompilationException(cycles.get)
}

def makeFail(path: String, force: Boolean = false)(expected: Seq[(Value, SortedSet[Int])]*) = {
def makeFail(path: String, force: Boolean = false, forcePkg: Boolean = false)(expected: Seq[(Value, SortedSet[Int])]*) = {
def canonicalize(cycle: Seq[(Value, SortedSet[Int])]): Seq[(Value, SortedSet[Int])] = {
val startIndex = cycle.indexOf(cycle.minBy(_._1.toString))
cycle.toList.drop(startIndex) ++ cycle.toList.take(startIndex)
}

val ex = intercept[CompilationException]{ make(path, force = force) }
val ex = intercept[CompilationException]{ make(path, force = force, forcePkg = forcePkg) }
val cycles = ex.cycles
.map(canonicalize)
.map(
Expand Down