mirror of https://github.com/sbt/sbt.git
comments and reorganization in SameAPI/ShowAPI
This commit is contained in:
parent
af4f41e052
commit
80780d9bec
|
|
@ -8,6 +8,27 @@ import xsbti.api._
|
|||
import Function.tupled
|
||||
import scala.collection.{immutable, mutable}
|
||||
|
||||
object TopLevel
|
||||
{
|
||||
class Changes(val newTypeNames: Set[String], val removedTypeNames: Set[String], val newValueNames: Set[String], val removedValueNames: Set[String])
|
||||
/** Identifies removed and new top-level definitions by name. */
|
||||
def nameChanges(a: Iterable[Source], b: Iterable[Source]): Changes =
|
||||
{
|
||||
def definitions(i: Iterable[Source]) = SameAPI.separateDefinitions(i.toSeq.flatMap( _.definitions ))
|
||||
def names(s: Iterable[Definition]): Set[String] = Set() ++ s.map(_.name)
|
||||
def changes(s: Set[String], t: Set[String]) = (s -- t, t -- s)
|
||||
|
||||
val (avalues, atypes) = definitions(a)
|
||||
val (bvalues, btypes) = definitions(b)
|
||||
|
||||
val (newTypes, removedTypes) = changes(names(atypes), names(btypes))
|
||||
val (newValues, removedValues) = changes(names(avalues), names(bvalues))
|
||||
|
||||
new Changes(newTypes, removedTypes, newValues, removedValues)
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks the API of two source files for equality.*/
|
||||
object SameAPI
|
||||
{
|
||||
def apply(a: Source, b: Source) =
|
||||
|
|
@ -20,24 +41,54 @@ object SameAPI
|
|||
println("\n=========== API #2 ================")
|
||||
println(ShowAPI.show(b))
|
||||
|
||||
val result = (new SameAPI(a,b)).check
|
||||
val result = (new SameAPI(a,b, false)).check
|
||||
val end = System.currentTimeMillis
|
||||
println(" API comparison took: " + (end - start) / 1000.0 + " s")
|
||||
result
|
||||
}
|
||||
|
||||
def separateDefinitions(s: Seq[Definition]): (Seq[Definition], Seq[Definition]) =
|
||||
s.toArray.partition(isValueDefinition)
|
||||
def isValueDefinition(d: Definition): Boolean =
|
||||
d match
|
||||
{
|
||||
case _: FieldLike | _: Def=> true
|
||||
case c: ClassLike => isValue(c.definitionType)
|
||||
case _ => false
|
||||
}
|
||||
def isValue(d: DefinitionType): Boolean =
|
||||
d == DefinitionType.Module || d == DefinitionType.PackageModule
|
||||
/** Puts the given definitions in a map according to their names.*/
|
||||
def byName(s: Seq[Definition]): scala.collection.Map[String, List[Definition]] =
|
||||
{
|
||||
val map = new mutable.HashMap[String, List[Definition]]
|
||||
for(d <- s; name = d.name)
|
||||
map(name) = d :: map.getOrElse(name, Nil)
|
||||
map.readOnly
|
||||
}
|
||||
}
|
||||
private class SameAPI(a: Source, b: Source)
|
||||
/** Used to implement API equality. All comparisons must be done between constructs in source files `a` and `b`. For example, when doing:
|
||||
* `sameDefinitions(as, bs)`, `as` must be definitions from source file `a` and `bs` must be definitions from source file `b`. This is in order
|
||||
* to properly handle type parameters, which must be computed for each source file and then referenced during comparison.
|
||||
*
|
||||
* If `includePrivate` is true, `private` and `private[this]` members are included in the comparison. Otherwise, those members are excluded.
|
||||
*/
|
||||
private class SameAPI(a: Source, b: Source, includePrivate: Boolean)
|
||||
{
|
||||
import SameAPI._
|
||||
/** de Bruijn levels for type parameters in source `a`*/
|
||||
private lazy val tagsA = TagTypeVariables(a)
|
||||
/** de Bruijn levels for type parameters in source `a`*/
|
||||
private lazy val tagsB = TagTypeVariables(b)
|
||||
|
||||
def debug(flag: Boolean, msg: => String): Boolean =
|
||||
{
|
||||
if(!flag) println(msg)
|
||||
//if(!flag) println(msg)
|
||||
flag
|
||||
}
|
||||
|
||||
lazy val check: Boolean =
|
||||
/** Returns true if source `a` has the same API as source `b`.*/
|
||||
def check: Boolean =
|
||||
{
|
||||
samePackages(a, b) &&
|
||||
debug(sameDefinitions(a, b), "Definitions differed")
|
||||
|
|
@ -49,32 +100,34 @@ private class SameAPI(a: Source, b: Source)
|
|||
Set() ++ s.packages.map(_.name)
|
||||
|
||||
def sameDefinitions(a: Source, b: Source): Boolean =
|
||||
sameDefinitions(a.definitions, b.definitions)
|
||||
def sameDefinitions(a: Seq[Definition], b: Seq[Definition]): Boolean =
|
||||
sameDefinitions(a.definitions, b.definitions, true)
|
||||
def sameDefinitions(a: Seq[Definition], b: Seq[Definition], topLevel: Boolean): Boolean =
|
||||
{
|
||||
val (avalues, atypes) = separateDefinitions(filterDefinitions(a))
|
||||
val (bvalues, btypes) = separateDefinitions(filterDefinitions(b))
|
||||
val (avalues, atypes) = separateDefinitions(filterDefinitions(a, topLevel))
|
||||
val (bvalues, btypes) = separateDefinitions(filterDefinitions(b, topLevel))
|
||||
debug(sameDefinitions(byName(avalues), byName(bvalues)), "Value definitions differed") &&
|
||||
debug(sameDefinitions(byName(atypes), byName(btypes)), "Type definitions differed")
|
||||
}
|
||||
def separateDefinitions(s: Seq[Definition]): (Seq[Definition], Seq[Definition]) =
|
||||
s.toArray.partition(isValueDefinition)
|
||||
def sameDefinitions(a: scala.collection.Map[String, List[Definition]], b: scala.collection.Map[String, List[Definition]]): Boolean =
|
||||
debug(sameStrings(a.keySet, b.keySet), "\tDefinition strings differed") && zippedEntries(a,b).forall(tupled(sameNamedDefinitions))
|
||||
|
||||
def filterDefinitions(d: Seq[Definition]) = d.filter(isNonPrivate)
|
||||
/** Removes definitions that should not be considered for API equality.
|
||||
* All top-level definitions are always considered: 'private' only means package-private.
|
||||
* Other definitions are considered if they are not qualified with 'private[this]' or 'private'.*/
|
||||
def filterDefinitions(d: Seq[Definition], topLevel: Boolean) = if(topLevel || includePrivate) d else d.filter(isNonPrivate)
|
||||
def isNonPrivate(d: Definition): Boolean = isNonPrivate(d.access)
|
||||
def isNonPrivate(a: Access): Boolean =
|
||||
a match
|
||||
/** Returns false if the `access` is `Private` and qualified, true otherwise.*/
|
||||
def isNonPrivate(access: Access): Boolean =
|
||||
access match
|
||||
{
|
||||
case p: Private if p.qualifier.isInstanceOf[IdQualifier] => false
|
||||
case p: Private if !p.qualifier.isInstanceOf[IdQualifier] => false
|
||||
case _ => true
|
||||
}
|
||||
|
||||
/** Checks that the definitions in `a` are the same as those in `b`, ignoring order.
|
||||
* Each list is assumed to have already been checked to have the same names (by `sameDefinitions`, for example).*/
|
||||
def sameNamedDefinitions(a: List[Definition], b: List[Definition]): Boolean =
|
||||
{
|
||||
def show(x: List[Definition]) = x.map(DefaultShowAPI.apply).mkString("{\n\t", "\n\t", "\n}\n")
|
||||
//println("Comparing \n\t" + show(a) + "\nagainst\n\t" + show(b))
|
||||
def sameDefs(a: List[Definition], b: List[Definition]): Boolean =
|
||||
{
|
||||
a match
|
||||
|
|
@ -92,28 +145,10 @@ private class SameAPI(a: Source, b: Source)
|
|||
case Nil => true
|
||||
}
|
||||
}
|
||||
//if(a.length > 1) println("Comparing\n" + a.mkString("\n\t") + "\nagainst\n" + b.mkString("\n\t") + "\n\n")
|
||||
debug((a.length == b.length), "\t\tLength differed for " + a.headOption.map(_.name).getOrElse("empty")) && sameDefs(a, b)
|
||||
}
|
||||
|
||||
def isValueDefinition(d: Definition): Boolean =
|
||||
d match
|
||||
{
|
||||
case _: FieldLike | _: Def=> true
|
||||
case c: ClassLike => isValue(c.definitionType)
|
||||
case _ => false
|
||||
}
|
||||
def isValue(d: DefinitionType): Boolean =
|
||||
d == DefinitionType.Module || d == DefinitionType.PackageModule
|
||||
def byName(s: Seq[Definition]): scala.collection.Map[String, List[Definition]] =
|
||||
{
|
||||
val map = new mutable.HashMap[String, List[Definition]]
|
||||
for(d <- s; name = d.name)
|
||||
map(name) = d :: map.getOrElse(name, Nil)
|
||||
map.readOnly
|
||||
}
|
||||
|
||||
// doesn't check name
|
||||
/** Checks that the two definitions are the same, other than their name.*/
|
||||
def sameDefinitionContent(a: Definition, b: Definition): Boolean =
|
||||
//a.name == b.name &&
|
||||
debug(sameAccess(a.access, b.access), "Access differed") &&
|
||||
|
|
@ -276,7 +311,7 @@ private class SameAPI(a: Source, b: Source)
|
|||
sameMembers(a.inherited, b.inherited)
|
||||
|
||||
def sameMembers(a: Seq[Definition], b: Seq[Definition]): Boolean =
|
||||
sameDefinitions(a, b)
|
||||
sameDefinitions(a, b, false)
|
||||
|
||||
def sameSimpleType(a: SimpleType, b: SimpleType): Boolean =
|
||||
(a, b) match
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ object ShowAPI
|
|||
def concat[A](list: Seq[A], as: Show[A], sep: String): String = mapSeq(list, as).mkString(sep)
|
||||
def commas[A](list: Seq[A], as: Show[A]): String = concat(list, as, ", ")
|
||||
def spaced[A](list: Seq[A], as: Show[A]): String = concat(list, as, " ")
|
||||
def lines[A](list: Seq[A], as: Show[A]): String = mapSeq(list, as).mkString("", "\n", "\n")
|
||||
def lines[A](list: Seq[A], as: Show[A]): String = mapSeq(list, as).mkString("\n")
|
||||
def mapSeq[A](list: Seq[A], as: Show[A]): Seq[String] = list.map(as.show)
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ trait ShowBase
|
|||
new Show[Variance] { def show(v: Variance) = v match { case Invariant => ""; case Covariant => "+"; case Contravariant => "-" } }
|
||||
|
||||
implicit def showSource(implicit ps: Show[Package], ds: Show[Definition]): Show[Source] =
|
||||
new Show[Source] { def show(a: Source) = lines(a.packages, ps) + lines(a.definitions, ds) }
|
||||
new Show[Source] { def show(a: Source) = lines(a.packages, ps) + "\n" + lines(a.definitions, ds) }
|
||||
|
||||
implicit def showPackage: Show[Package] =
|
||||
new Show[Package] { def show(pkg: Package) = "package " + pkg.name }
|
||||
|
|
@ -66,7 +66,7 @@ trait ShowBase
|
|||
def show(a: Access) =
|
||||
a match
|
||||
{
|
||||
case p: Public => "public"
|
||||
case p: Public => ""
|
||||
case q: Qualified => sq.show(q)
|
||||
}
|
||||
}
|
||||
|
|
@ -147,7 +147,8 @@ trait ShowDefinitions
|
|||
def parameterizedDef(d: ParameterizedDefinition, label: String)(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation], tp: Show[Seq[TypeParameter]]): String =
|
||||
definitionBase(d, label)(acs, ms, ans) + tp.show(d.typeParameters)
|
||||
def definitionBase(d: Definition, label: String)(implicit acs: Show[Access], ms: Show[Modifiers], ans: Show[Annotation]): String =
|
||||
spaced(d.annotations, ans) + " " + acs.show(d.access) + " " + ms.show(d.modifiers) + " " + label + " " + d.name
|
||||
space(spaced(d.annotations, ans)) + space(acs.show(d.access)) + space(ms.show(d.modifiers)) + space(label) + d.name
|
||||
def space(s: String) = if(s.isEmpty) s else s + " "
|
||||
}
|
||||
trait ShowDefinition
|
||||
{
|
||||
|
|
@ -209,7 +210,7 @@ trait ShowTypes
|
|||
implicit def showStructure(implicit t: Show[Type], d: Show[Definition]): Show[Structure] =
|
||||
new Show[Structure] {
|
||||
def show(s: Structure) =
|
||||
concat(s.parents, t, " with ") + "\n{\n\tDeclared:" + lines(s.declared, d) + "\n\tInherited:" + lines(s.inherited, d) + "\n}"
|
||||
concat(s.parents, t, " with ") + "\n{\n Declared:\n" + lines(s.declared, d) + "\n Inherited:\n" + lines(s.inherited, d) + "\n}"
|
||||
}
|
||||
implicit def showAnnotated(implicit as: Show[Annotation], t: Show[SimpleType]): Show[Annotated] =
|
||||
new Show[Annotated] { def show(a: Annotated) = spaced(a.annotations, as) + " " + t.show(a.baseType) }
|
||||
|
|
|
|||
Loading…
Reference in New Issue