-
Notifications
You must be signed in to change notification settings - Fork 162
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #471 from scala-js/topic/mima
Poor Man's MiMa
- Loading branch information
Showing
15 changed files
with
49,318 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
Hi there! | ||
|
||
Before submitting a PR containing any Scala changes, please make sure you... | ||
|
||
* run `sbt prePR` | ||
* commit changes to `api-reports` | ||
|
||
Thanks for contributing! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,20 +21,33 @@ jobs: | |
env: | ||
SCALAJS_VERSION: "${{ matrix.scalajsversion == '0.6.x' && '0.6.28' || '' }}" | ||
steps: | ||
|
||
- uses: actions/checkout@v2 | ||
- uses: olafurpg/setup-scala@v13 | ||
with: | ||
java-version: "[email protected]" | ||
- uses: coursier/cache-action@v6 | ||
|
||
- name: Hacks for Scala 2.10 | ||
if: matrix.scalaversion == '2.10.7' | ||
run: ./prepareForScala210.sh | ||
|
||
- name: Build | ||
run: sbt "++${{ matrix.scalaversion }}" package | ||
|
||
- name: Test generate documentation | ||
run: sbt "++${{ matrix.scalaversion }}" doc | ||
|
||
- name: Build examples | ||
run: sbt "++${{ matrix.scalaversion }}" example/compile | ||
- name: scalafmt | ||
|
||
- name: Validate formatting | ||
run: sbt "++${{ matrix.scalaversion }}" scalafmtCheck | ||
|
||
- name: Validate api report | ||
if: matrix.scalajsversion == '1.x' && matrix.scalaversion != '2.11.12' | ||
run: ./api-reports/validate "${{ matrix.scalaversion }}" | ||
|
||
readme: | ||
runs-on: ubuntu-latest | ||
steps: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
.idea_modules | ||
.metals | ||
.project | ||
.sbtboot | ||
.settings/ | ||
.vscode | ||
metals.sbt | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
rules = [ | ||
GenerateApiReport, | ||
] |
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
#!/bin/bash | ||
set -euo pipefail | ||
cd "$(dirname "$0")" | ||
|
||
[ $# -ne 1 ] && echo "Usage: $0 <scala version>" && exit 1 | ||
|
||
series="${1%.*}" | ||
file="${series/./_}.txt" | ||
echo -n "Validating $file ... " | ||
|
||
help='Run `sbt +compile` and check in the differences to the '"$(basename "$0") directory" | ||
|
||
if [ ! -e "$file" ]; then | ||
echo "file not found. $help" | ||
exit 2 | ||
elif [ -n "$(git status --porcelain -- "$file")" ]; then | ||
echo "out-of-date. $help" | ||
exit 3 | ||
else | ||
echo "ok" | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/bin/bash | ||
set -euo pipefail | ||
cd "$(dirname "$0")" | ||
|
||
sed -i -e '/delete if Scala 2.10/d' *.sbt project/*.sbt | ||
rm scalafix.sbt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
ThisBuild / semanticdbEnabled := true | ||
ThisBuild / semanticdbVersion := "4.4.27" | ||
ThisBuild / scalafixScalaBinaryVersion := CrossVersion.binaryScalaVersion(scalaVersion.value) | ||
|
||
ThisBuild / scalacOptions ++= { | ||
if (scalaVersion.value startsWith "2") | ||
"-Yrangepos" :: Nil | ||
else | ||
Nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
org.scalajs.dom.scalafix.GenerateApiReport |
127 changes: 127 additions & 0 deletions
127
scalafix/src/main/scala/org/scalajs/dom/scalafix/GenerateApiReport.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package org.scalajs.dom.scalafix | ||
|
||
import java.nio.charset.StandardCharsets | ||
import java.nio.file.{Paths, Files} | ||
import scala.meta._ | ||
import scalafix.v1._ | ||
|
||
class GenerateApiReport extends SemanticRule("GenerateApiReport") { | ||
import MutableState.{global => state, ScopeType} | ||
|
||
private[this] def enabled = state ne null | ||
|
||
override def beforeStart(): Unit = { | ||
Util.scalaSeriesVer match { | ||
case "2.11" => // disabled - can't read classfiles | ||
case _ => MutableState.global = new MutableState // can't set state= in early Scala versions | ||
} | ||
} | ||
|
||
override def fix(implicit doc: SemanticDocument): Patch = { | ||
|
||
if (enabled) | ||
doc.tree.traverse { | ||
case a: Defn.Class => process(a.symbol, a.templ, ScopeType.Class) | ||
case a: Defn.Object => process(a.symbol, a.templ, ScopeType.Object) | ||
case a: Defn.Trait => process(a.symbol, a.templ, ScopeType.Trait) | ||
case a: Pkg.Object => process(a.symbol, a.templ, ScopeType.Object) | ||
case _ => | ||
} | ||
|
||
Patch.empty | ||
} | ||
|
||
private def process(sym: Symbol, body: Template, typ: ScopeType)(implicit doc: SemanticDocument): Unit = { | ||
// Skip non-public scopes | ||
val info = sym.info.get | ||
if (!info.isPublic && !info.isPackageObject) | ||
return | ||
|
||
val parents = Util.parents(sym).iterator.map(Util.typeSymbol).toList | ||
val domParents = parents.iterator.filter(isScalaJsDom).toSet | ||
val isJsType = parents.exists(isScalaJs) | ||
val s = state.register(sym, isJsType, typ, domParents) | ||
|
||
def letsSeeHowLazyWeCanBeLol(t: Tree): Unit = { | ||
// Skip non-public members | ||
if (!t.symbol.info.get.isPublic) | ||
return | ||
|
||
// Remove definition bodies | ||
val t2: Tree = | ||
t match { | ||
case Defn.Def(mods, name, tparams, paramss, Some(tpe), _) => Decl.Def(mods, name, tparams, paramss, tpe) | ||
case Defn.Val(mods, pats, Some(tpe), _) => Decl.Val(mods, pats, tpe) | ||
case Defn.Var(mods, pats, Some(tpe), _) => Decl.Var(mods, pats, tpe) | ||
case _ => t | ||
} | ||
|
||
val desc = | ||
t2 | ||
.toString | ||
.replace('\n', ' ') | ||
.replace("=", " = ") | ||
.replace("@inline ", "") | ||
.replaceAll(", *", ", ") | ||
.replaceAll(" {2,}", " ") | ||
.trim | ||
.stripSuffix(" = js.native") | ||
.replaceAll(" = js.native(?=[^\n])", "?") | ||
|
||
s.add(desc) | ||
} | ||
|
||
body.traverse { | ||
|
||
// Skip inner members that we collect at the outer scope | ||
case _: Defn.Class => | ||
case _: Defn.Object => | ||
case _: Defn.Trait => | ||
case _: Pkg.Object => | ||
|
||
case d: Decl => letsSeeHowLazyWeCanBeLol(d) | ||
case d: Defn => letsSeeHowLazyWeCanBeLol(d) | ||
|
||
case _ => | ||
} | ||
} | ||
|
||
private def isScalaJs(sym: Symbol): Boolean = | ||
sym.toString startsWith "scala/scalajs/js/" | ||
|
||
private def isScalaJsDom(sym: Symbol): Boolean = | ||
sym.toString startsWith "org/scalajs/dom/" | ||
|
||
override def afterComplete(): Unit = | ||
if (enabled) { | ||
saveReport() | ||
MutableState.global = null // can't set state= in early Scala versions | ||
} | ||
|
||
private def saveReport(): Unit = { | ||
val scalaVer = Util.scalaSeriesVer.replace('.', '_') | ||
val projectRoot = System.getProperty("user.dir") | ||
val reportFile = Paths.get(s"$projectRoot/api-reports/$scalaVer.txt") | ||
val api = state.result().iterator.map(_.stripPrefix("org/scalajs/dom/")).mkString("\n") | ||
|
||
val content = | ||
s"""|scala-js-dom API | ||
|================ | ||
| | ||
|This is generated automatically on compile via custom Scalafix rule '${name.value}'. | ||
| | ||
|Flags: | ||
| [J-] = JavaScript type | ||
| [S-] = Scala type | ||
| [-${ScopeType.Class.id}] = Class | ||
| [-${ScopeType.Trait.id}] = Trait | ||
| [-${ScopeType.Object.id}] = Object | ||
| | ||
| | ||
|$api | ||
|""".stripMargin | ||
|
||
println(s"[info] Generating $reportFile") | ||
Files.write(reportFile, content.getBytes(StandardCharsets.UTF_8)) | ||
} | ||
} |
108 changes: 108 additions & 0 deletions
108
scalafix/src/main/scala/org/scalajs/dom/scalafix/MutableState.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package org.scalajs.dom.scalafix | ||
|
||
import scala.annotation.tailrec | ||
import scala.collection.immutable.SortedSet | ||
import scala.collection.mutable | ||
import scala.meta._ | ||
import scalafix.v1._ | ||
|
||
final class MutableState { | ||
import MutableState._ | ||
|
||
private[this] val scopes = mutable.Map.empty[Symbol, Scope] | ||
|
||
def register(sym: Symbol, isJsType: Boolean, scopeType: ScopeType, parents: Set[Symbol]): Scope = synchronized { | ||
scopes.get(sym) match { | ||
case None => | ||
val s = Scope(sym)(scopeType, parents) | ||
scopes.update(sym, s) | ||
s.isJsType = isJsType | ||
s | ||
case Some(s) => | ||
s | ||
} | ||
} | ||
|
||
private def scopeParents(root: Scope): List[Scope] = { | ||
@tailrec | ||
def go(s: Scope, seen: Set[Symbol], queue: Set[Symbol], results: List[Scope]): List[Scope] = | ||
if (!seen.contains(s.symbol)) | ||
go(s, seen + s.symbol, queue ++ s.parents, s :: results) | ||
else if (queue.nonEmpty) { | ||
val sym = queue.head | ||
val nextQueue = queue - sym | ||
scopes.get(sym) match { | ||
case Some(scope) => go(scope, seen, nextQueue, results) | ||
case None => go(s, seen, nextQueue, results) | ||
} | ||
} else | ||
results | ||
|
||
go(root, Set.empty, Set.empty, Nil) | ||
} | ||
|
||
def result(): Array[String] = synchronized { | ||
// Because - comes before . in ASCII this little hack affects the ordering so that A[X] comes before A.B[X] | ||
val sortHack = "-OMG-" | ||
|
||
val b = SortedSet.newBuilder[String] | ||
|
||
// Pass 1 | ||
for (root <- scopes.valuesIterator) { | ||
if (!root.isJsType && scopeParents(root).exists(_.isJsType)) | ||
root.isJsType = true | ||
} | ||
|
||
// Pass 2 | ||
for (root <- scopes.valuesIterator) { | ||
val name = root.symbol.value.stripSuffix("#").stripSuffix(".") | ||
val prefix = { | ||
val lang = if (root.isJsType) "J" else "S" | ||
val typ = root.scopeType.id | ||
s"$name$sortHack[$lang$typ] " | ||
} | ||
|
||
var membersFound = false | ||
for { | ||
s <- root :: scopeParents(root) | ||
v <- s.directMembers | ||
} { | ||
membersFound = true | ||
b += prefix + v | ||
} | ||
|
||
if (!membersFound && !name.endsWith("/package")) | ||
b += prefix.trim | ||
} | ||
|
||
val array = b.result().toArray | ||
for (i <- array.indices) | ||
array(i) = array(i).replace(sortHack, "") | ||
array | ||
} | ||
} | ||
|
||
object MutableState { | ||
var global: MutableState = null | ||
|
||
sealed abstract class ScopeType(final val id: String) | ||
object ScopeType { | ||
case object Class extends ScopeType("C") | ||
case object Trait extends ScopeType("T") | ||
case object Object extends ScopeType("O") | ||
} | ||
|
||
final case class Scope(symbol: Symbol) | ||
(val scopeType: ScopeType, | ||
val parents: Set[Symbol]) { | ||
|
||
private[MutableState] val directMembers = mutable.Set.empty[String] | ||
private[MutableState] var isJsType = false | ||
|
||
def add(ov: Option[String]): Unit = | ||
ov.foreach(add(_)) | ||
|
||
def add(v: String): Unit = | ||
synchronized(directMembers += v) | ||
} | ||
} |
Oops, something went wrong.