<commit_before>package build

import sbt._
import Keys._

import scala.annotation.tailrec

import com.typesafe.tools.mima.plugin.MimaPlugin.autoImport._

import java.io.{
  BufferedOutputStream,
  FileOutputStream
}

import scala.collection.mutable
import scala.util.Properties

import org.scalajs.core.ir
import org.scalajs.core.ir.Utils.escapeJS

import org.scalajs.sbtplugin._
import org.scalajs.jsenv.{JSEnv, RetryingComJSEnv}
import org.scalajs.jsenv.rhino.RhinoJSEnv
import org.scalajs.jsenv.nodejs.{NodeJSEnv, JSDOMNodeJSEnv}
import org.scalajs.jsenv.phantomjs.PhantomJSEnv
import ScalaJSPlugin.autoImport._
import ExternalCompile.scalaJSExternalCompileSettings
import Loggers._

import org.scalajs.core.tools.io.MemVirtualJSFile
import org.scalajs.core.tools.sem._
import org.scalajs.core.tools.jsdep.ResolvedJSDependency
import org.scalajs.core.tools.json._
import org.scalajs.core.tools.linker.ModuleInitializer
import org.scalajs.core.tools.linker.backend.OutputMode

import sbtassembly.AssemblyPlugin.autoImport._

/* In sbt 0.13 the Build trait would expose all vals to the shell, where you
 * can use them in "set a := b" like expressions. This re-exposes them.
 */
object ExposedValues extends AutoPlugin {
  object autoImport {
    val makeCompliant = Build.makeCompliant
  }
}

object Build {

  val isGeneratingEclipse =
    Properties.envOrElse("GENERATING_ECLIPSE", "false").toBoolean

  val bintrayProjectName = settingKey[String](
      "Project name on Bintray")

  val fetchScalaSource = taskKey[File](
    "Fetches the scala source for the current scala version")
  val shouldPartest = settingKey[Boolean](
    "Whether we should partest the current scala version (and fail if we can't)")

  val previousVersion = "0.6.20"
  val previousSJSBinaryVersion =
    ScalaJSCrossVersion.binaryScalaJSVersion(previousVersion)
  val previousBinaryCrossVersion =
    CrossVersion.binaryMapped(v => s"sjs${previousSJSBinaryVersion}_$v")

  val scalaVersionsUsedForPublishing: Set[String] =
    Set("2.10.6", "2.11.11", "2.12.3", "2.13.0-M2")
  val newScalaBinaryVersionsInThisRelease: Set[String] =
    Set()

  val javaVersion = settingKey[Int](
    "The major Java SDK version that should be assumed for compatibility. " +
    "Defaults to what sbt is running with.")

  val javaDocBaseURL: String = "http://docs.oracle.com/javase/8/docs/api/"

  // set scalaJSSemantics in someProject ~= makeCompliant
  val makeCompliant: Semantics => Semantics = { semantics =>
    semantics
      .withAsInstanceOfs(CheckedBehavior.Compliant)
      .withArrayIndexOutOfBounds(CheckedBehavior.Compliant)
      .withModuleInit(CheckedBehavior.Compliant)
      .withStrictFloats(true)
  }

  private def includeIf(testDir: File, condition: Boolean): List[File] =
    if (condition) List(testDir)
    else Nil

  val previousArtifactSetting: Setting[_] = {
    mimaPreviousArtifacts ++= {
      val scalaV = scalaVersion.value
      val scalaBinaryV = scalaBinaryVersion.value
      if (!scalaVersionsUsedForPublishing.contains(scalaV)) {
        // This artifact will not be published. Binary compatibility is irrelevant.
        Set.empty
      } else if (newScalaBinaryVersionsInThisRelease.contains(scalaBinaryV)) {
        // New in this release, no binary compatibility to comply to
        Set.empty
      } else {
        val thisProjectID = projectID.value
        val previousCrossVersion = thisProjectID.crossVersion match {
          case ScalaJSCrossVersion.binary => previousBinaryCrossVersion
          case crossVersion               => crossVersion
        }
        /* Filter out e:info.apiURL as it expects 0.6.7-SNAPSHOT, whereas the
         * artifact we're looking for has 0.6.6 (for example).
         */
        val prevExtraAttributes =
          thisProjectID.extraAttributes.filterKeys(_ != "e:info.apiURL")
        val prevProjectID =
          (thisProjectID.organization % thisProjectID.name % previousVersion)
            .cross(previousCrossVersion)
            .extra(prevExtraAttributes.toSeq: _*)
        Set(CrossVersion(scalaV, scalaBinaryV)(prevProjectID).cross(CrossVersion.Disabled))
      }
    }
  }

  val commonSettings = Seq(
      scalaVersion := "2.11.11",
      organization := "org.scala-js",
      version := scalaJSVersion,

      normalizedName ~= {
        _.replace("scala.js", "scalajs").replace("scala-js", "scalajs")
      },

      homepage := Some(url("http://scala-js.org/")),
      licenses += ("BSD New",
          url("https://github.com/scala-js/scala-js/blob/master/LICENSE")),
      scmInfo := Some(ScmInfo(
          url("https://github.com/scala-js/scala-js"),
          "scm:git:<EMAIL>:scala-js/scala-js.git",
          Some("scm:git:<EMAIL>:scala-js/scala-js.git"))),

      shouldPartest := {
        val testListDir = (
          (resourceDirectory in (partestSuite, Test)).value / "scala"
            / "tools" / "partest" / "scalajs" / scalaVersion.value
        )
        testListDir.exists
      },

      scalacOptions ++= Seq(
          "-deprecation",
          "-unchecked",
          "-feature",
          "-encoding", "utf8"
      ),

      // Scaladoc linking
      apiURL := {
        val name = normalizedName.value
        Some(url(s"http://www.scala-js.org/api/$name/$scalaJSVersion/"))
      },
      autoAPIMappings := true,

      // Add Java Scaladoc mapping
      apiMappings += {
        val rtJar = {
          val bootClasspath = System.getProperty("sun.boot.class.path")
          if (bootClasspath != null) {
            // JDK <= 8, there is an rt.jar (or classes.jar) on the boot classpath
            val jars = bootClasspath.split(java.io.File.pathSeparator)
            def matches(path: String, name: String): Boolean =
              path.endsWith(s"${java.io.File.separator}$name.jar")
            val jar = jars.find(matches(_, "rt")) // most JREs
              .orElse(jars.find(matches(_, "classes"))) // Java 6 on Mac OS X
              .get
            file(jar)
          } else {
            // JDK >= 9, sbt gives us a fake rt.jar in `scala.ext.dirs`
            val scalaExtDirs = System.getProperty("scala.ext.dirs")
            file(scalaExtDirs) / "rt.jar"
          }
        }

        assert(rtJar.exists, s"$rtJar does not exist")
        rtJar -> url(javaDocBaseURL)
      },

      /* Add a second Java Scaladoc mapping for cases where Scala actually
       * understands the jrt:/ filesystem of Java 9.
       */
      apiMappings +=
        file("/modules/java.base") -> url(javaDocBaseURL),

      /* Patch the ScalaDoc we generate.
       *
       *  After executing the normal doc command, copy everything to the
       *  `patched-api` directory (same internal directory structure) while
       *  patching the following:
       *
       *  - Append `additional-doc-styles.css` to `lib/template.css`
       *  - Fix external links to the JavaDoc, i.e. change
       *    `${javaDocBaseURL}index.html#java.lang.String` to
       *    `${javaDocBaseURL}index.html?java/lang/String.html`
       */
      doc in Compile := {
        // Where to store the patched docs
        val outDir = crossTarget.value / "patched-api"

        // Find all files in the current docs
        val docPaths = {
          val docDir = (doc in Compile).value
          Path.selectSubpaths(docDir, new SimpleFileFilter(_.isFile)).toMap
        }

        /* File with our CSS styles (needs to be canonical so that the
         * comparison below works)
         */
        val additionalStylesFile =
          (root.base / "assets/additional-doc-styles.css").getCanonicalFile

        // Regex and replacement function for JavaDoc linking
        val javadocAPIRe =
          s"""\"(\\Q${javaDocBaseURL}index.html\\E)#([^"]*)\"""".r

        val logger = streams.value.log
        val errorsSeen = mutable.Set.empty[String]

        val fixJavaDocLink = { (m: scala.util.matching.Regex.Match) =>
          val frag = m.group(2)

          // Fail when encountering links to class members
          if (frag.contains("@") && !errorsSeen.contains(frag)) {
            errorsSeen += frag
            logger.error(s"Cannot fix JavaDoc link to member: $frag")
          }

          m.group(1) + "?" + frag.replace('.', '/') + ".html"
        }

        FileFunction.cached(streams.value.cacheDirectory,
            FilesInfo.lastModified, FilesInfo.exists) { files =>
          for {
            file <- files
            if file != additionalStylesFile
          } yield {
            val relPath = docPaths(file)
            val outFile = outDir / relPath

            if (relPath == "lib/template.css") {
              val styles = IO.read(additionalStylesFile)
              IO.copyFile(file, outFile)
              IO.append(outFile, styles)
            } else if (relPath.endsWith(".html")) {
              val content = IO.read(file)
              val patched = javadocAPIRe.replaceAllIn(content, fixJavaDocLink)
              IO.write(outFile, patched)
            } else {
              IO.copyFile(file, outFile)
            }

            outFile
          }
        } (docPaths.keySet + additionalStylesFile)

        if (errorsSeen.size > 0)
          throw new MessageOnlyException("ScalaDoc patching had errors")

        outDir
      }
  )

  val noClassFilesSettings: Setting[_] = (
      scalacOptions in (Compile, compile) ++= {
        if (isGeneratingEclipse) Seq()
        else Seq("-Yskip:cleanup,icode,jvm")
      }
  )

  val publishSettings = Seq(
      publishMavenStyle := true,
      publishTo := {
        val nexus = "https://oss.sonatype.org/"
        if (isSnapshot.value)
          Some("snapshots" at nexus + "content/repositories/snapshots")
        else
          Some("releases" at nexus + "service/local/staging/deploy/maven2")
      },
      pomExtra := (
          <developers>
            <developer>
              <id>sjrd</id>
              <name><NAME></name>
              <url>https://github.com/sjrd/</url>
            </developer>
            <developer>
              <id>gzm0</id>
              <name><NAME></name>
              <url>https://github.com/gzm0/</url>
            </developer>
            <developer>
              <id>nicolasstucki</id>
              <name><NAME></name>
              <url>https://github.com/nicolasstucki/</url>
            </developer>
          </developers>
      ),
      pomIncludeRepository := { _ => false }
  )

  val fatalWarningsSettings = Seq(
      // The pattern matcher used to exceed its analysis budget before 2.11.5
      scalacOptions ++= {
        scalaVersion.value.split('.') match {
          case Array("2", "10", _)                 => Nil
          case Array("2", "11", x)
              if x.takeWhile(_.isDigit).toInt <= 4 => Nil
          case _                                   => Seq("-Xfatal-warnings")
        }
      },

      scalacOptions in (Compile, doc) := {
        val baseOptions = (scalacOptions in (Compile, doc)).value

        /* - need JDK7 to link the doc to java.nio.charset.StandardCharsets
         * - in Scala 2.10, some ScalaDoc links fail
         */
        val fatalInDoc =
          javaVersion.value >= 7 && scalaBinaryVersion.value != "2.10"

        if (fatalInDoc) baseOptions
        else baseOptions.filterNot(_ == "-Xfatal-warnings")
      }
  )

  private def publishToBintraySettings = Seq(
      publishTo := {
        val proj = bintrayProjectName.value
        val ver = version.value
        if (isSnapshot.value) {
          None // Bintray does not support snapshots
        } else {
          val url = new java.net.URL(
              s"https://api.bintray.com/content/scala-js/scala-js-releases/$proj/$ver")
          val patterns = Resolver.ivyStylePatterns
          Some(Resolver.url("bintray", url)(patterns))
        }
      }
  )

  val publishIvySettings = (
      publishToBintraySettings
  ) ++ Seq(
      publishMavenStyle := false
  )

  val myScalaJSSettings = ScalaJSPluginInternal.scalaJSAbstractSettings ++ Seq(
      autoCompilerPlugins := true,
      scalaJSOptimizerOptions ~= (_.withCheckScalaJSIR(true)),

      // Link source maps
      scalacOptions ++= {
        if (isGeneratingEclipse) Seq()
        else if (scalaJSIsSnapshotVersion) Seq()
        else Seq(
          // Link source maps to github sources
          "-P:scalajs:mapSourceURI:" + root.base.toURI +
          "->https://raw.githubusercontent.com/scala-js/scala-js/v" +
          scalaJSVersion + "/"
        )
      },

      scalacOptions += "-P:scalajs:sjsDefinedByDefault"
  )

  private def parallelCollectionsDependencies(
      scalaVersion: String): Seq[ModuleID] = {
    CrossVersion.partialVersion(scalaVersion) match {
      case Some((2, n)) if n >= 13 =>
        Seq("org.scala-lang.modules" %% "scala-parallel-collections" % "0.1.2")

      case _ => Nil
    }
  }

  implicit class ProjectOps(val project: Project) extends AnyVal {
    /** Uses the Scala.js compiler plugin. */
    def withScalaJSCompiler: Project =
      if (isGeneratingEclipse) project
      else project.dependsOn(compiler % "plugin")

    def withScalaJSJUnitPlugin: Project = {
      project.settings(
        scalacOptions in Test ++= {
          if (isGeneratingEclipse) {
            Seq.empty
          } else {
            val jar = (packageBin in (jUnitPlugin, Compile)).value
            Seq(s"-Xplugin:$jar")
          }
        }
      )
    }

    /** Depends on library as if (exportJars in library) was set to false. */
    def dependsOnLibraryNoJar: Project = {
      if (isGeneratingEclipse) {
        project.dependsOn(library)
      } else {
        project.settings(
            internalDependencyClasspath in Compile ++= {
              val prods = (products in (library, Compile)).value
              val analysis = (compile in (library, Compile)).value
              prods.map(p => Classpaths.analyzed(p, analysis))
            }
        )
      }
    }

    /** Depends on the sources of another project. */
    def dependsOnSource(dependency: Project): Project = {
      if (isGeneratingEclipse) {
        project.dependsOn(dependency)
      } else {
        project.settings(
            unmanagedSourceDirectories in Compile +=
              (scalaSource in (dependency, Compile)).value
        )
      }
    }
  }

  val thisBuildSettings = (
      inScope(Global)(ScalaJSPlugin.globalSettings)
  ) ++ Seq(
      // Most of the projects cross-compile
      crossScalaVersions := Seq(
        "2.10.2",
        "2.10.3",
        "2.10.4",
        "2.10.5",
        "2.10.6",
        "2.11.0",
        "2.11.1",
        "2.11.2",
        "2.11.4",
        "2.11.5",
        "2.11.6",
        "2.11.7",
        "2.11.8",
        "2.11.11",
        "2.12.0",
        "2.12.1",
        "2.12.2",
        "2.12.3",
        "2.13.0-M2"
      ),
      // JDK version we are running with
      javaVersion in Global := {
        val fullVersion = System.getProperty("java.version")
        val v = fullVersion.stripPrefix("1.").takeWhile(_.isDigit).toInt
        sLog.value.info(s"Detected JDK version $v")
        v
      }
  )

  lazy val root: Project = Project(
      id = "scalajs",
      base = file("."),
      settings = commonSettings ++ Seq(
          name := "Scala.js",
          publishArtifact in Compile := false,

          clean := clean.dependsOn(
              clean in compiler,
              clean in irProject, clean in irProjectJS,
              clean in tools, clean in toolsJS,
              clean in jsEnvs, clean in jsEnvsTestKit, clean in jsEnvsTestSuite,
              clean in testAdapter, clean in plugin,
              clean in javalanglib, clean in javalib, clean in scalalib,
              clean in libraryAux, clean in library, clean in javalibEx,
              clean in stubs, clean in cli,
              clean in testInterface,
              clean in jUnitRuntime, clean in jUnitPlugin,
              clean in jUnitTestOutputsJS, clean in jUnitTestOutputsJVM,
              clean in examples, clean in helloworld,
              clean in reversi, clean in testingExample,
              clean in testSuite, clean in testSuiteJVM, clean in noIrCheckTest,
              clean in javalibExTestSuite,
              clean in partest, clean in partestSuite,
              clean in scalaTestSuite).value,

          publish := {},
          publishLocal := {}
      )
  )

  val commonIrProjectSettings = (
      commonSettings ++ publishSettings ++ fatalWarningsSettings
  ) ++ Seq(
      name := "Scala.js IR",
      previousArtifactSetting,
      mimaBinaryIssueFilters ++= BinaryIncompatibilities.IR,
      exportJars := true, // required so ScalaDoc linking works

      testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-a", "-s")
  )

  lazy val irProject: Project = Project(
      id = "ir",
      base = file("ir"),
      settings = commonIrProjectSettings ++ Seq(
          libraryDependencies +=
            "com.novocode" % "junit-interface" % "0.9" % "test"
      )
  )

  lazy val irProjectJS: Project = Project(
      id = "irJS",
      base = file("ir/.js"),
      settings = commonIrProjectSettings ++ myScalaJSSettings ++ Seq(
          crossVersion := ScalaJSCrossVersion.binary,
          unmanagedSourceDirectories in Compile +=
            (scalaSource in Compile in irProject).value,
          unmanagedSourceDirectories in Test +=
            (scalaSource in Test in irProject).value
      )
  ).withScalaJSCompiler.withScalaJSJUnitPlugin.dependsOn(
      javalibEx, jUnitRuntime % "test"
  )

  lazy val compiler: Project = Project(
      id = "compiler",
      base = file("compiler"),
      settings = commonSettings ++ publishSettings ++ Seq(
          name := "Scala.js compiler",
          crossVersion := CrossVersion.full, // because compiler api is not binary compatible
          libraryDependencies ++= Seq(
              "org.scala-lang" % "scala-compiler" % scalaVersion.value,
              "org.scala-lang" % "scala-reflect" % scalaVersion.value,
              "com.novocode" % "junit-interface" % "0.9" % "test"
          ),
          testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-a"),
          testOptions += Tests.Setup { () =>
            val testOutDir = (streams.value.cacheDirectory / "scalajs-compiler-test")
            IO.createDirectory(testOutDir)
            System.setProperty("scala.scalajs.compiler.test.output",
                testOutDir.getAbsolutePath)
            System.setProperty("scala.scalajs.compiler.test.scalajslib",
                (packageBin in (library, Compile)).value.getAbsolutePath)

            def scalaArtifact(name: String): String = {
              def isTarget(att: Attributed[File]) = {
                att.metadata.get(moduleID.key).exists { mId =>
                  mId.organization == "org.scala-lang" &&
                  mId.name == name &&
                  mId.revision == scalaVersion.value
                }
              }

              (managedClasspath in Test).value.find(isTarget).fold {
                streams.value.log.error(s"Couldn't find $name on the classpath")
                ""
              } { lib =>
                lib.data.getAbsolutePath
              }
            }

            System.setProperty("scala.scalajs.compiler.test.scalalib",
                scalaArtifact("scala-library"))

            System.setProperty("scala.scalajs.compiler.test.scalareflect",
                scalaArtifact("scala-reflect"))
          },
          exportJars := true
      )
  ).dependsOnSource(irProject)

  val commonToolsSettings = (
      commonSettings ++ publishSettings ++ fatalWarningsSettings
  ) ++ Seq(
      name := "Scala.js tools",

      unmanagedSourceDirectories in Compile +=
        baseDirectory.value.getParentFile / "shared/src/main/scala",
      unmanagedSourceDirectories in Test +=
        baseDirectory.value.getParentFile / "shared/src/test/scala",

      sourceGenerators in Compile += Def.task {
        ScalaJSEnvGenerator.generateEnvHolder(
          baseDirectory.value.getParentFile,
          (sourceManaged in Compile).value)
      }.taskValue,

      previousArtifactSetting,
      mimaBinaryIssueFilters ++= BinaryIncompatibilities.Tools,
      exportJars := true // required so ScalaDoc linking works
  )

  lazy val tools: Project = Project(
      id = "tools",
      base = file("tools/jvm"),
      settings = commonToolsSettings ++ Seq(
          libraryDependencies ++= Seq(
              "org.scala-js" % "closure-compiler-java-6" % "v20160517",
              "com.googlecode.json-simple" % "json-simple" % "1.1.1" exclude("junit", "junit"),
              "com.novocode" % "junit-interface" % "0.9" % "test"
          ) ++ (
              parallelCollectionsDependencies(scalaVersion.value)
          )
      )
  ).dependsOn(irProject)

  lazy val toolsJS: Project = Project(
      id = "toolsJS",
      base = file("tools/js"),
      settings = myScalaJSSettings ++ commonToolsSettings ++ Seq(
          crossVersion := ScalaJSCrossVersion.binary,

          /* We need RuntimeClassNameMapper.custom() in QuickLinker
           * TODO Remove this in 1.x.
           */
          scalacOptions in Test -= "-Xfatal-warnings",

          resourceGenerators in Test += Def.task {
            val base = (resourceManaged in Compile).value
            IO.createDirectory(base)
            val outFile = base / "js-test-definitions.js"

            val testDefinitions = {
              org.scalajs.build.HTMLRunnerTemplateAccess.renderTestDefinitions(
                  (loadedTestFrameworks in testSuite in Test).value,
                  (definedTests in testSuite in Test).value)
            }

            IO.write(outFile, testDefinitions)
            Seq(outFile)
          }.taskValue,

          // Give more memory to Node.js, and deactivate source maps
          jsEnv := {
            new NodeJSEnv(
                NodeJSEnv.Config()
                  .withArgs(List("--max_old_space_size=3072"))
                  .withSourceMap(false))
          },

          jsDependencies += ProvidedJS / "js-test-definitions.js" % "test"
      ) ++ inConfig(Test) {
        // Redefine test to run Node.js and link HelloWorld
        test := {
          val jsEnv = resolvedJSEnv.value
          if (!jsEnv.isInstanceOf[NodeJSEnv])
            throw new MessageOnlyException("toolsJS/test must be run with Node.js")

          /* Collect IR relevant files from the classpath
           * We assume here that the classpath is valid. This is checked by the
           * the scalaJSIR task.
           */
          val cp = Attributed.data(fullClasspath.value)

          // Files must be Jars, non-files must be dirs
          val (jars, dirs) = cp.filter(_.exists).partition(_.isFile)
          val irFiles = dirs.flatMap(dir => (dir ** "*.sjsir").get)

          def seqOfStringsToJSArrayCode(strings: Seq[String]): String =
            strings.map(s => "\"" + escapeJS(s) + "\"").mkString("[", ", ", "]")

          val irPaths = {
            val absolutePaths = (jars ++ irFiles).map(_.getAbsolutePath)
            seqOfStringsToJSArrayCode(absolutePaths)
          }

          val mainMethods = {
            /* Ideally we would read `scalaJSModuleInitializers in (testSuite, Test)`,
             * but we cannot convert the ModuleInitializers to strings to be
             * passed to the QuickLinker (because ModuleInitializer is a
             * write-only data structure). So we have some duplication.
             */
            val unescapedMainMethods = List(
                "org.scalajs.testsuite.compiler.ModuleInitializerInNoConfiguration.main",
                "org.scalajs.testsuite.compiler.ModuleInitializerInTestConfiguration.main2",
                "org.scalajs.testsuite.compiler.ModuleInitializerInTestConfiguration.main1",
                "org.scalajs.testsuite.compiler.ModuleInitializerInTestConfiguration.mainArgs1()",
                "org.scalajs.testsuite.compiler.ModuleInitializerInTestConfiguration.mainArgs2(foo,bar)"
            )
            seqOfStringsToJSArrayCode(unescapedMainMethods)
          }

          val scalaJSEnv = {
            s"""
            {"javaSystemProperties": {
              "scalajs.scalaVersion": "${scalaVersion.value}"
            }}
            """
          }

          val code = {
            s"""
            var linker = scalajs.QuickLinker;
            var lib = linker.linkTestSuiteNode($irPaths, $mainMethods);

            var __ScalaJSEnv = $scalaJSEnv;

            eval("(function() { 'use strict'; " +
              lib + ";" +
              "scalajs.TestRunner.runTests();" +
            "}).call(this);");
            """
          }

          val launcher = new MemVirtualJSFile("Generated launcher file")
            .withContent(code)

          val linked = scalaJSLinkedFile.value
          val libs = resolvedJSDependencies.value.data :+
              ResolvedJSDependency.minimal(linked)
          val runner = jsEnv.jsRunner(libs, launcher)

          runner.run(sbtLogger2ToolsLogger(streams.value.log), scalaJSConsole.value)
        }
      }
  ).withScalaJSCompiler.dependsOn(
      javalibEx, testSuite % "test->test", irProjectJS
  )

  lazy val jsEnvs: Project = Project(
      id = "jsEnvs",
      base = file("js-envs"),
      settings = (
          commonSettings ++ publishSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js JS Envs",
          libraryDependencies ++= Seq(
              "io.apigee" % "rhino" % "1.7R5pre4",
              "org.webjars" % "envjs" % "1.2"
          ) ++ ScalaJSPluginInternal.phantomJSJettyModules.map(_ % "provided"),
          previousArtifactSetting,
          mimaBinaryIssueFilters ++= BinaryIncompatibilities.JSEnvs
      )
  ).dependsOn(tools)

  lazy val jsEnvsTestKit: Project = Project(
      id = "jsEnvsTestKit",
      base = file("js-envs-test-kit"),
      settings = (
          commonSettings ++ publishSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js JS Envs Test Kit",
          libraryDependencies +=
            "junit" % "junit" % "4.8.2",
          previousArtifactSetting,
          mimaBinaryIssueFilters ++= BinaryIncompatibilities.JSEnvsTestKit
      )
  ).dependsOn(tools, jsEnvs)

  lazy val jsEnvsTestSuite: Project = Project(
      id = "jsEnvsTestSuite",
      base = file("js-envs-test-suite"),
      settings = (
          commonSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js JS Envs Test Suite",
          libraryDependencies ++= Seq(
              "com.novocode" % "junit-interface" % "0.9" % "test"
          ) ++ ScalaJSPluginInternal.phantomJSJettyModules.map(_ % "provided")
      )
  ).dependsOn(tools, jsEnvs, jsEnvsTestKit % "test")

  lazy val testAdapter = Project(
      id = "testAdapter",
      base = file("test-adapter"),
      settings = (
          commonSettings ++ publishSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js sbt test adapter",
          libraryDependencies += "org.scala-sbt" % "test-interface" % "1.0",
          libraryDependencies +=
            "com.novocode" % "junit-interface" % "0.11" % "test",

          previousArtifactSetting,
          mimaBinaryIssueFilters ++= BinaryIncompatibilities.TestAdapter,
          unmanagedSourceDirectories in Compile +=
            baseDirectory.value.getParentFile / "test-common/src/main/scala",
          unmanagedSourceDirectories in Test +=
            baseDirectory.value.getParentFile / "test-common/src/test/scala"
      )
  ).dependsOn(jsEnvs)

  lazy val plugin: Project = Project(
      id = "sbtPlugin",
      base = file("sbt-plugin"),
      settings = (
          commonSettings ++ publishIvySettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js sbt plugin",
          normalizedName := "sbt-scalajs",
          bintrayProjectName := "sbt-scalajs-plugin", // "sbt-scalajs" was taken
          sbtPlugin := true,
          scalaBinaryVersion :=
            CrossVersion.binaryScalaVersion(scalaVersion.value),
          previousArtifactSetting,
          mimaBinaryIssueFilters ++= BinaryIncompatibilities.SbtPlugin,

          /* TODO Remove this in 1.x, since there are no macros in sbt-plugin
           * anymore.
           */
          scalacOptions -= "-Xfatal-warnings",

          // Add API mappings for sbt (seems they don't export their API URL)
          apiMappings ++= {
            val deps = (externalDependencyClasspath in Compile).value

            val sbtJars = deps filter { attributed =>
              val p = attributed.data.getPath
              p.contains("/org.scala-sbt/") && p.endsWith(".jar")
            }

            val docUrl =
              url(s"http://www.scala-sbt.org/${sbtVersion.value}/api/")

            sbtJars.map(_.data -> docUrl).toMap
          }
      )
  ).dependsOn(tools, jsEnvs, testAdapter)

  lazy val delambdafySetting = {
    scalacOptions ++= (
        if (isGeneratingEclipse) Seq()
        else if (scalaBinaryVersion.value == "2.10") Seq()
        else Seq("-Ydelambdafy:method"))
  }

  private def serializeHardcodedIR(base: File,
      infoAndTree: (ir.Infos.ClassInfo, ir.Trees.ClassDef)): File = {
    // We assume that there are no weird characters in the full name
    val fullName = ir.Definitions.decodeClassName(infoAndTree._1.encodedName)
    val output = base / (fullName.replace('.', '/') + ".sjsir")

    if (!output.exists()) {
      IO.createDirectory(output.getParentFile)
      val stream = new BufferedOutputStream(new FileOutputStream(output))
      try {
        ir.InfoSerializers.serialize(stream, infoAndTree._1)
        ir.Serializers.serialize(stream, infoAndTree._2)
      } finally {
        stream.close()
      }
    }
    output
  }

  lazy val javalanglib: Project = Project(
      id = "javalanglib",
      base = file("javalanglib"),
      settings = (
          commonSettings ++ myScalaJSSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "java.lang library for Scala.js",
          publishArtifact in Compile := false,
          delambdafySetting,
          noClassFilesSettings,

          resourceGenerators in Compile += Def.task {
            val base = (resourceManaged in Compile).value
            Seq(
                serializeHardcodedIR(base, JavaLangObject.InfoAndTree),
                serializeHardcodedIR(base, JavaLangString.InfoAndTree)
            )
          }.taskValue
      ) ++ (
          scalaJSExternalCompileSettings
      )
  ).withScalaJSCompiler.dependsOnLibraryNoJar

  lazy val javalib: Project = Project(
      id = "javalib",
      base = file("javalib"),
      settings = (
          commonSettings ++ myScalaJSSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Java library for Scala.js",
          publishArtifact in Compile := false,
          delambdafySetting,
          noClassFilesSettings
      ) ++ (
          scalaJSExternalCompileSettings
      )
  ).withScalaJSCompiler.dependsOnLibraryNoJar

  lazy val scalalib: Project = Project(
      id = "scalalib",
      base = file("scalalib"),
      settings = commonSettings ++ Seq(
          /* Link source maps to the GitHub sources of the original scalalib
           * #2195 This must come *before* the option added by myScalaJSSettings
           * because mapSourceURI works on a first-match basis.
           */
          scalacOptions += {
            "-P:scalajs:mapSourceURI:" +
            (artifactPath in fetchScalaSource).value.toURI +
            "->https://raw.githubusercontent.com/scala/scala/v" +
            scalaVersion.value + "/src/library/"
          }
      ) ++ myScalaJSSettings ++ Seq(
          name := "Scala library for Scala.js",
          publishArtifact in Compile := false,
          delambdafySetting,
          noClassFilesSettings,

          // The Scala lib is full of warnings we don't want to see
          scalacOptions ~= (_.filterNot(
              Set("-deprecation", "-unchecked", "-feature") contains _)),

          // Tell the plugin to hack-fix bad classOf trees
          scalacOptions += "-P:scalajs:fixClassOf",

          libraryDependencies +=
            "org.scala-lang" % "scala-library" % scalaVersion.value classifier "sources",

          artifactPath in fetchScalaSource :=
            target.value / "scalaSources" / scalaVersion.value,

          /* Work around for #2649. We would like to always use `update`, but
           * that fails if the scalaVersion we're looking for happens to be the
           * version of Scala used by sbt itself. This is clearly a bug in sbt,
           * which we work around here by using `updateClassifiers` instead in
           * that case.
           */
          update in fetchScalaSource := Def.taskDyn {
            if (scalaVersion.value == scala.util.Properties.versionNumberString)
              updateClassifiers
            else
              update
          }.value,

          fetchScalaSource := {
            val s = streams.value
            val cacheDir = s.cacheDirectory
            val ver = scalaVersion.value
            val trgDir = (artifactPath in fetchScalaSource).value

            val report = (update in fetchScalaSource).value
            val scalaLibSourcesJar = report.select(
                configuration = Set("compile"),
                module = moduleFilter(name = "scala-library"),
                artifact = artifactFilter(classifier = "sources")).headOption.getOrElse {
              throw new Exception(
                  s"Could not fetch scala-library sources for version $ver")
            }

            FileFunction.cached(cacheDir / s"fetchScalaSource-$ver",
                FilesInfo.lastModified, FilesInfo.exists) { dependencies =>
              s.log.info(s"Unpacking Scala library sources to $trgDir...")

              if (trgDir.exists)
                IO.delete(trgDir)
              IO.createDirectory(trgDir)
              IO.unzip(scalaLibSourcesJar, trgDir)
            } (Set(scalaLibSourcesJar))

            trgDir
          },

          unmanagedSourceDirectories in Compile := {
            // Calculates all prefixes of the current Scala version
            // (including the empty prefix) to construct override
            // directories like the following:
            // - override-2.10.2-RC1
            // - override-2.10.2
            // - override-2.10
            // - override-2
            // - override
            val ver = scalaVersion.value
            val base = baseDirectory.value
            val parts = ver.split(Array('.','-'))
            val verList = parts.inits.map { ps =>
              val len = ps.mkString(".").length
              // re-read version, since we lost '.' and '-'
              ver.substring(0, len)
            }
            def dirStr(v: String) =
              if (v.isEmpty) "overrides" else s"overrides-$v"
            val dirs = verList.map(base / dirStr(_)).filter(_.exists)
            dirs.toSeq // most specific shadow less specific
          },

          // Compute sources
          // Files in earlier src dirs shadow files in later dirs
          sources in Compile := {
            // Sources coming from the sources of Scala
            val scalaSrcDir = fetchScalaSource.value

            // All source directories (overrides shadow scalaSrcDir)
            val sourceDirectories =
              (unmanagedSourceDirectories in Compile).value :+ scalaSrcDir

            // Filter sources with overrides
            def normPath(f: File): String =
              f.getPath.replace(java.io.File.separator, "/")

            val sources = mutable.ListBuffer.empty[File]
            val paths = mutable.Set.empty[String]

            for {
              srcDir <- sourceDirectories
              normSrcDir = normPath(srcDir)
              src <- (srcDir ** "*.scala").get
            } {
              val normSrc = normPath(src)
              val path = normSrc.substring(normSrcDir.length)
              val useless =
                path.contains("/scala/collection/parallel/") ||
                path.contains("/scala/util/parsing/")
              if (!useless) {
                if (paths.add(path))
                  sources += src
                else
                  streams.value.log.debug(s"not including $src")
              }
            }

            sources.result()
          },

          // Continuation plugin (when using 2.10.x)
          autoCompilerPlugins := true,
          libraryDependencies ++= {
            val ver = scalaVersion.value
            if (ver.startsWith("2.10."))
              Seq(compilerPlugin("org.scala-lang.plugins" % "continuations" % ver))
            else
              Nil
          },
          scalacOptions ++= {
            if (scalaVersion.value.startsWith("2.10."))
              Seq("-P:continuations:enable")
            else
              Nil
          }
      ) ++ (
          scalaJSExternalCompileSettings
      )
  ).withScalaJSCompiler.dependsOnLibraryNoJar

  lazy val libraryAux: Project = Project(
      id = "libraryAux",
      base = file("library-aux"),
      settings = (
          commonSettings ++ myScalaJSSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js aux library",
          publishArtifact in Compile := false,
          delambdafySetting,
          noClassFilesSettings
      ) ++ (
          scalaJSExternalCompileSettings
      )
  ).withScalaJSCompiler.dependsOnLibraryNoJar

  lazy val library: Project = Project(
      id = "library",
      base = file("library"),
      settings = (
          commonSettings ++ publishSettings ++ myScalaJSSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js library",
          delambdafySetting,
          exportJars := !isGeneratingEclipse,
          previousArtifactSetting,
          mimaBinaryIssueFilters ++= BinaryIncompatibilities.Library,
          libraryDependencies +=
            "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided",

          // js.JSApp is annotated with @JSExportDescendentObjects
          scalacOptions += "-P:scalajs:suppressExportDeprecations"
      ) ++ (
          scalaJSExternalCompileSettings
      ) ++ inConfig(Compile)(Seq(
          scalacOptions in doc ++= Seq("-implicits", "-groups"),

          // Filter doc sources to remove implementation details from doc.
          sources in doc := {
            def containsFileFilter(s: String): FileFilter = new FileFilter {
              override def accept(f: File): Boolean = {
                val path = f.getAbsolutePath.replace('\\', '/')
                path.contains(s)
              }
            }

            val filter: FileFilter = (
                AllPassFilter
                  -- containsFileFilter("/scala/scalajs/runtime/")
                  -- containsFileFilter("/scala/scalajs/js/annotation/internal/")
                  -- "*.nodoc.scala"
            )

            (sources in doc).value.filter(filter.accept)
          },

          /* Add compiled .class files to doc dependencyClasspath, so we can
           * still compile even with only part of the files being present.
           */
          dependencyClasspath in doc ++= exportedProducts.value,

          /* Add the .sjsir files from other lib projects
           * (but not .class files)
           */
          mappings in packageBin := {
            /* From library, we must take everyting, except the
             * java.nio.TypedArrayBufferBridge object, whose actual
             * implementation is in javalib.
             */
            val superMappings = (mappings in packageBin).value
            val libraryMappings = superMappings.filter(
                _._2.replace('\\', '/') !=
                  "scala/scalajs/js/typedarray/TypedArrayBufferBridge$.sjsir")

            val filter = ("*.sjsir": NameFilter)<commit_msg>Work around #3152: disable Scaladoc generation for `library` on JDK 9.<commit_after>package build

import sbt._
import Keys._

import scala.annotation.tailrec

import com.typesafe.tools.mima.plugin.MimaPlugin.autoImport._

import java.io.{
  BufferedOutputStream,
  FileOutputStream
}

import scala.collection.mutable
import scala.util.Properties

import org.scalajs.core.ir
import org.scalajs.core.ir.Utils.escapeJS

import org.scalajs.sbtplugin._
import org.scalajs.jsenv.{JSEnv, RetryingComJSEnv}
import org.scalajs.jsenv.rhino.RhinoJSEnv
import org.scalajs.jsenv.nodejs.{NodeJSEnv, JSDOMNodeJSEnv}
import org.scalajs.jsenv.phantomjs.PhantomJSEnv
import ScalaJSPlugin.autoImport._
import ExternalCompile.scalaJSExternalCompileSettings
import Loggers._

import org.scalajs.core.tools.io.MemVirtualJSFile
import org.scalajs.core.tools.sem._
import org.scalajs.core.tools.jsdep.ResolvedJSDependency
import org.scalajs.core.tools.json._
import org.scalajs.core.tools.linker.ModuleInitializer
import org.scalajs.core.tools.linker.backend.OutputMode

import sbtassembly.AssemblyPlugin.autoImport._

/* In sbt 0.13 the Build trait would expose all vals to the shell, where you
 * can use them in "set a := b" like expressions. This re-exposes them.
 */
object ExposedValues extends AutoPlugin {
  object autoImport {
    val makeCompliant = Build.makeCompliant
  }
}

object Build {

  val isGeneratingEclipse =
    Properties.envOrElse("GENERATING_ECLIPSE", "false").toBoolean

  val bintrayProjectName = settingKey[String](
      "Project name on Bintray")

  val fetchScalaSource = taskKey[File](
    "Fetches the scala source for the current scala version")
  val shouldPartest = settingKey[Boolean](
    "Whether we should partest the current scala version (and fail if we can't)")

  val previousVersion = "0.6.20"
  val previousSJSBinaryVersion =
    ScalaJSCrossVersion.binaryScalaJSVersion(previousVersion)
  val previousBinaryCrossVersion =
    CrossVersion.binaryMapped(v => s"sjs${previousSJSBinaryVersion}_$v")

  val scalaVersionsUsedForPublishing: Set[String] =
    Set("2.10.6", "2.11.11", "2.12.3", "2.13.0-M2")
  val newScalaBinaryVersionsInThisRelease: Set[String] =
    Set()

  val javaVersion = settingKey[Int](
    "The major Java SDK version that should be assumed for compatibility. " +
    "Defaults to what sbt is running with.")

  val javaDocBaseURL: String = "http://docs.oracle.com/javase/8/docs/api/"

  // set scalaJSSemantics in someProject ~= makeCompliant
  val makeCompliant: Semantics => Semantics = { semantics =>
    semantics
      .withAsInstanceOfs(CheckedBehavior.Compliant)
      .withArrayIndexOutOfBounds(CheckedBehavior.Compliant)
      .withModuleInit(CheckedBehavior.Compliant)
      .withStrictFloats(true)
  }

  private def includeIf(testDir: File, condition: Boolean): List[File] =
    if (condition) List(testDir)
    else Nil

  val previousArtifactSetting: Setting[_] = {
    mimaPreviousArtifacts ++= {
      val scalaV = scalaVersion.value
      val scalaBinaryV = scalaBinaryVersion.value
      if (!scalaVersionsUsedForPublishing.contains(scalaV)) {
        // This artifact will not be published. Binary compatibility is irrelevant.
        Set.empty
      } else if (newScalaBinaryVersionsInThisRelease.contains(scalaBinaryV)) {
        // New in this release, no binary compatibility to comply to
        Set.empty
      } else {
        val thisProjectID = projectID.value
        val previousCrossVersion = thisProjectID.crossVersion match {
          case ScalaJSCrossVersion.binary => previousBinaryCrossVersion
          case crossVersion               => crossVersion
        }
        /* Filter out e:info.apiURL as it expects 0.6.7-SNAPSHOT, whereas the
         * artifact we're looking for has 0.6.6 (for example).
         */
        val prevExtraAttributes =
          thisProjectID.extraAttributes.filterKeys(_ != "e:info.apiURL")
        val prevProjectID =
          (thisProjectID.organization % thisProjectID.name % previousVersion)
            .cross(previousCrossVersion)
            .extra(prevExtraAttributes.toSeq: _*)
        Set(CrossVersion(scalaV, scalaBinaryV)(prevProjectID).cross(CrossVersion.Disabled))
      }
    }
  }

  val commonSettings = Seq(
      scalaVersion := "2.11.11",
      organization := "org.scala-js",
      version := scalaJSVersion,

      normalizedName ~= {
        _.replace("scala.js", "scalajs").replace("scala-js", "scalajs")
      },

      homepage := Some(url("http://scala-js.org/")),
      licenses += ("BSD New",
          url("https://github.com/scala-js/scala-js/blob/master/LICENSE")),
      scmInfo := Some(ScmInfo(
          url("https://github.com/scala-js/scala-js"),
          "scm:git:<EMAIL>:scala-js/scala-js.git",
          Some("scm:git:<EMAIL>:scala-js/scala-js.git"))),

      shouldPartest := {
        val testListDir = (
          (resourceDirectory in (partestSuite, Test)).value / "scala"
            / "tools" / "partest" / "scalajs" / scalaVersion.value
        )
        testListDir.exists
      },

      scalacOptions ++= Seq(
          "-deprecation",
          "-unchecked",
          "-feature",
          "-encoding", "utf8"
      ),

      // Scaladoc linking
      apiURL := {
        val name = normalizedName.value
        Some(url(s"http://www.scala-js.org/api/$name/$scalaJSVersion/"))
      },
      autoAPIMappings := true,

      // Add Java Scaladoc mapping
      apiMappings += {
        val rtJar = {
          val bootClasspath = System.getProperty("sun.boot.class.path")
          if (bootClasspath != null) {
            // JDK <= 8, there is an rt.jar (or classes.jar) on the boot classpath
            val jars = bootClasspath.split(java.io.File.pathSeparator)
            def matches(path: String, name: String): Boolean =
              path.endsWith(s"${java.io.File.separator}$name.jar")
            val jar = jars.find(matches(_, "rt")) // most JREs
              .orElse(jars.find(matches(_, "classes"))) // Java 6 on Mac OS X
              .get
            file(jar)
          } else {
            // JDK >= 9, sbt gives us a fake rt.jar in `scala.ext.dirs`
            val scalaExtDirs = System.getProperty("scala.ext.dirs")
            file(scalaExtDirs) / "rt.jar"
          }
        }

        assert(rtJar.exists, s"$rtJar does not exist")
        rtJar -> url(javaDocBaseURL)
      },

      /* Add a second Java Scaladoc mapping for cases where Scala actually
       * understands the jrt:/ filesystem of Java 9.
       */
      apiMappings +=
        file("/modules/java.base") -> url(javaDocBaseURL),

      /* Patch the ScalaDoc we generate.
       *
       *  After executing the normal doc command, copy everything to the
       *  `patched-api` directory (same internal directory structure) while
       *  patching the following:
       *
       *  - Append `additional-doc-styles.css` to `lib/template.css`
       *  - Fix external links to the JavaDoc, i.e. change
       *    `${javaDocBaseURL}index.html#java.lang.String` to
       *    `${javaDocBaseURL}index.html?java/lang/String.html`
       */
      doc in Compile := {
        // Where to store the patched docs
        val outDir = crossTarget.value / "patched-api"

        // Find all files in the current docs
        val docPaths = {
          val docDir = (doc in Compile).value
          Path.selectSubpaths(docDir, new SimpleFileFilter(_.isFile)).toMap
        }

        /* File with our CSS styles (needs to be canonical so that the
         * comparison below works)
         */
        val additionalStylesFile =
          (root.base / "assets/additional-doc-styles.css").getCanonicalFile

        // Regex and replacement function for JavaDoc linking
        val javadocAPIRe =
          s"""\"(\\Q${javaDocBaseURL}index.html\\E)#([^"]*)\"""".r

        val logger = streams.value.log
        val errorsSeen = mutable.Set.empty[String]

        val fixJavaDocLink = { (m: scala.util.matching.Regex.Match) =>
          val frag = m.group(2)

          // Fail when encountering links to class members
          if (frag.contains("@") && !errorsSeen.contains(frag)) {
            errorsSeen += frag
            logger.error(s"Cannot fix JavaDoc link to member: $frag")
          }

          m.group(1) + "?" + frag.replace('.', '/') + ".html"
        }

        FileFunction.cached(streams.value.cacheDirectory,
            FilesInfo.lastModified, FilesInfo.exists) { files =>
          for {
            file <- files
            if file != additionalStylesFile
          } yield {
            val relPath = docPaths(file)
            val outFile = outDir / relPath

            if (relPath == "lib/template.css") {
              val styles = IO.read(additionalStylesFile)
              IO.copyFile(file, outFile)
              IO.append(outFile, styles)
            } else if (relPath.endsWith(".html")) {
              val content = IO.read(file)
              val patched = javadocAPIRe.replaceAllIn(content, fixJavaDocLink)
              IO.write(outFile, patched)
            } else {
              IO.copyFile(file, outFile)
            }

            outFile
          }
        } (docPaths.keySet + additionalStylesFile)

        if (errorsSeen.size > 0)
          throw new MessageOnlyException("ScalaDoc patching had errors")

        outDir
      }
  )

  val noClassFilesSettings: Setting[_] = (
      scalacOptions in (Compile, compile) ++= {
        if (isGeneratingEclipse) Seq()
        else Seq("-Yskip:cleanup,icode,jvm")
      }
  )

  val publishSettings = Seq(
      publishMavenStyle := true,
      publishTo := {
        val nexus = "https://oss.sonatype.org/"
        if (isSnapshot.value)
          Some("snapshots" at nexus + "content/repositories/snapshots")
        else
          Some("releases" at nexus + "service/local/staging/deploy/maven2")
      },
      pomExtra := (
          <developers>
            <developer>
              <id>sjrd</id>
              <name><NAME></name>
              <url>https://github.com/sjrd/</url>
            </developer>
            <developer>
              <id>gzm0</id>
              <name><NAME></name>
              <url>https://github.com/gzm0/</url>
            </developer>
            <developer>
              <id>nicolasstucki</id>
              <name><NAME></name>
              <url>https://github.com/nicolasstucki/</url>
            </developer>
          </developers>
      ),
      pomIncludeRepository := { _ => false }
  )

  val fatalWarningsSettings = Seq(
      // The pattern matcher used to exceed its analysis budget before 2.11.5
      scalacOptions ++= {
        scalaVersion.value.split('.') match {
          case Array("2", "10", _)                 => Nil
          case Array("2", "11", x)
              if x.takeWhile(_.isDigit).toInt <= 4 => Nil
          case _                                   => Seq("-Xfatal-warnings")
        }
      },

      scalacOptions in (Compile, doc) := {
        val baseOptions = (scalacOptions in (Compile, doc)).value

        /* - need JDK7 to link the doc to java.nio.charset.StandardCharsets
         * - in Scala 2.10, some ScalaDoc links fail
         */
        val fatalInDoc =
          javaVersion.value >= 7 && scalaBinaryVersion.value != "2.10"

        if (fatalInDoc) baseOptions
        else baseOptions.filterNot(_ == "-Xfatal-warnings")
      }
  )

  private def publishToBintraySettings = Seq(
      publishTo := {
        val proj = bintrayProjectName.value
        val ver = version.value
        if (isSnapshot.value) {
          None // Bintray does not support snapshots
        } else {
          val url = new java.net.URL(
              s"https://api.bintray.com/content/scala-js/scala-js-releases/$proj/$ver")
          val patterns = Resolver.ivyStylePatterns
          Some(Resolver.url("bintray", url)(patterns))
        }
      }
  )

  val publishIvySettings = (
      publishToBintraySettings
  ) ++ Seq(
      publishMavenStyle := false
  )

  val myScalaJSSettings = ScalaJSPluginInternal.scalaJSAbstractSettings ++ Seq(
      autoCompilerPlugins := true,
      scalaJSOptimizerOptions ~= (_.withCheckScalaJSIR(true)),

      // Link source maps
      scalacOptions ++= {
        if (isGeneratingEclipse) Seq()
        else if (scalaJSIsSnapshotVersion) Seq()
        else Seq(
          // Link source maps to github sources
          "-P:scalajs:mapSourceURI:" + root.base.toURI +
          "->https://raw.githubusercontent.com/scala-js/scala-js/v" +
          scalaJSVersion + "/"
        )
      },

      scalacOptions += "-P:scalajs:sjsDefinedByDefault"
  )

  private def parallelCollectionsDependencies(
      scalaVersion: String): Seq[ModuleID] = {
    CrossVersion.partialVersion(scalaVersion) match {
      case Some((2, n)) if n >= 13 =>
        Seq("org.scala-lang.modules" %% "scala-parallel-collections" % "0.1.2")

      case _ => Nil
    }
  }

  implicit class ProjectOps(val project: Project) extends AnyVal {
    /** Uses the Scala.js compiler plugin. */
    def withScalaJSCompiler: Project =
      if (isGeneratingEclipse) project
      else project.dependsOn(compiler % "plugin")

    def withScalaJSJUnitPlugin: Project = {
      project.settings(
        scalacOptions in Test ++= {
          if (isGeneratingEclipse) {
            Seq.empty
          } else {
            val jar = (packageBin in (jUnitPlugin, Compile)).value
            Seq(s"-Xplugin:$jar")
          }
        }
      )
    }

    /** Depends on library as if (exportJars in library) was set to false. */
    def dependsOnLibraryNoJar: Project = {
      if (isGeneratingEclipse) {
        project.dependsOn(library)
      } else {
        project.settings(
            internalDependencyClasspath in Compile ++= {
              val prods = (products in (library, Compile)).value
              val analysis = (compile in (library, Compile)).value
              prods.map(p => Classpaths.analyzed(p, analysis))
            }
        )
      }
    }

    /** Depends on the sources of another project. */
    def dependsOnSource(dependency: Project): Project = {
      if (isGeneratingEclipse) {
        project.dependsOn(dependency)
      } else {
        project.settings(
            unmanagedSourceDirectories in Compile +=
              (scalaSource in (dependency, Compile)).value
        )
      }
    }
  }

  val thisBuildSettings = (
      inScope(Global)(ScalaJSPlugin.globalSettings)
  ) ++ Seq(
      // Most of the projects cross-compile
      crossScalaVersions := Seq(
        "2.10.2",
        "2.10.3",
        "2.10.4",
        "2.10.5",
        "2.10.6",
        "2.11.0",
        "2.11.1",
        "2.11.2",
        "2.11.4",
        "2.11.5",
        "2.11.6",
        "2.11.7",
        "2.11.8",
        "2.11.11",
        "2.12.0",
        "2.12.1",
        "2.12.2",
        "2.12.3",
        "2.13.0-M2"
      ),
      // JDK version we are running with
      javaVersion in Global := {
        val fullVersion = System.getProperty("java.version")
        val v = fullVersion.stripPrefix("1.").takeWhile(_.isDigit).toInt
        sLog.value.info(s"Detected JDK version $v")
        v
      }
  )

  lazy val root: Project = Project(
      id = "scalajs",
      base = file("."),
      settings = commonSettings ++ Seq(
          name := "Scala.js",
          publishArtifact in Compile := false,

          clean := clean.dependsOn(
              clean in compiler,
              clean in irProject, clean in irProjectJS,
              clean in tools, clean in toolsJS,
              clean in jsEnvs, clean in jsEnvsTestKit, clean in jsEnvsTestSuite,
              clean in testAdapter, clean in plugin,
              clean in javalanglib, clean in javalib, clean in scalalib,
              clean in libraryAux, clean in library, clean in javalibEx,
              clean in stubs, clean in cli,
              clean in testInterface,
              clean in jUnitRuntime, clean in jUnitPlugin,
              clean in jUnitTestOutputsJS, clean in jUnitTestOutputsJVM,
              clean in examples, clean in helloworld,
              clean in reversi, clean in testingExample,
              clean in testSuite, clean in testSuiteJVM, clean in noIrCheckTest,
              clean in javalibExTestSuite,
              clean in partest, clean in partestSuite,
              clean in scalaTestSuite).value,

          publish := {},
          publishLocal := {}
      )
  )

  val commonIrProjectSettings = (
      commonSettings ++ publishSettings ++ fatalWarningsSettings
  ) ++ Seq(
      name := "Scala.js IR",
      previousArtifactSetting,
      mimaBinaryIssueFilters ++= BinaryIncompatibilities.IR,
      exportJars := true, // required so ScalaDoc linking works

      testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-a", "-s")
  )

  lazy val irProject: Project = Project(
      id = "ir",
      base = file("ir"),
      settings = commonIrProjectSettings ++ Seq(
          libraryDependencies +=
            "com.novocode" % "junit-interface" % "0.9" % "test"
      )
  )

  lazy val irProjectJS: Project = Project(
      id = "irJS",
      base = file("ir/.js"),
      settings = commonIrProjectSettings ++ myScalaJSSettings ++ Seq(
          crossVersion := ScalaJSCrossVersion.binary,
          unmanagedSourceDirectories in Compile +=
            (scalaSource in Compile in irProject).value,
          unmanagedSourceDirectories in Test +=
            (scalaSource in Test in irProject).value
      )
  ).withScalaJSCompiler.withScalaJSJUnitPlugin.dependsOn(
      javalibEx, jUnitRuntime % "test"
  )

  lazy val compiler: Project = Project(
      id = "compiler",
      base = file("compiler"),
      settings = commonSettings ++ publishSettings ++ Seq(
          name := "Scala.js compiler",
          crossVersion := CrossVersion.full, // because compiler api is not binary compatible
          libraryDependencies ++= Seq(
              "org.scala-lang" % "scala-compiler" % scalaVersion.value,
              "org.scala-lang" % "scala-reflect" % scalaVersion.value,
              "com.novocode" % "junit-interface" % "0.9" % "test"
          ),
          testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-a"),
          testOptions += Tests.Setup { () =>
            val testOutDir = (streams.value.cacheDirectory / "scalajs-compiler-test")
            IO.createDirectory(testOutDir)
            System.setProperty("scala.scalajs.compiler.test.output",
                testOutDir.getAbsolutePath)
            System.setProperty("scala.scalajs.compiler.test.scalajslib",
                (packageBin in (library, Compile)).value.getAbsolutePath)

            def scalaArtifact(name: String): String = {
              def isTarget(att: Attributed[File]) = {
                att.metadata.get(moduleID.key).exists { mId =>
                  mId.organization == "org.scala-lang" &&
                  mId.name == name &&
                  mId.revision == scalaVersion.value
                }
              }

              (managedClasspath in Test).value.find(isTarget).fold {
                streams.value.log.error(s"Couldn't find $name on the classpath")
                ""
              } { lib =>
                lib.data.getAbsolutePath
              }
            }

            System.setProperty("scala.scalajs.compiler.test.scalalib",
                scalaArtifact("scala-library"))

            System.setProperty("scala.scalajs.compiler.test.scalareflect",
                scalaArtifact("scala-reflect"))
          },
          exportJars := true
      )
  ).dependsOnSource(irProject)

  val commonToolsSettings = (
      commonSettings ++ publishSettings ++ fatalWarningsSettings
  ) ++ Seq(
      name := "Scala.js tools",

      unmanagedSourceDirectories in Compile +=
        baseDirectory.value.getParentFile / "shared/src/main/scala",
      unmanagedSourceDirectories in Test +=
        baseDirectory.value.getParentFile / "shared/src/test/scala",

      sourceGenerators in Compile += Def.task {
        ScalaJSEnvGenerator.generateEnvHolder(
          baseDirectory.value.getParentFile,
          (sourceManaged in Compile).value)
      }.taskValue,

      previousArtifactSetting,
      mimaBinaryIssueFilters ++= BinaryIncompatibilities.Tools,
      exportJars := true // required so ScalaDoc linking works
  )

  lazy val tools: Project = Project(
      id = "tools",
      base = file("tools/jvm"),
      settings = commonToolsSettings ++ Seq(
          libraryDependencies ++= Seq(
              "org.scala-js" % "closure-compiler-java-6" % "v20160517",
              "com.googlecode.json-simple" % "json-simple" % "1.1.1" exclude("junit", "junit"),
              "com.novocode" % "junit-interface" % "0.9" % "test"
          ) ++ (
              parallelCollectionsDependencies(scalaVersion.value)
          )
      )
  ).dependsOn(irProject)

  lazy val toolsJS: Project = Project(
      id = "toolsJS",
      base = file("tools/js"),
      settings = myScalaJSSettings ++ commonToolsSettings ++ Seq(
          crossVersion := ScalaJSCrossVersion.binary,

          /* We need RuntimeClassNameMapper.custom() in QuickLinker
           * TODO Remove this in 1.x.
           */
          scalacOptions in Test -= "-Xfatal-warnings",

          resourceGenerators in Test += Def.task {
            val base = (resourceManaged in Compile).value
            IO.createDirectory(base)
            val outFile = base / "js-test-definitions.js"

            val testDefinitions = {
              org.scalajs.build.HTMLRunnerTemplateAccess.renderTestDefinitions(
                  (loadedTestFrameworks in testSuite in Test).value,
                  (definedTests in testSuite in Test).value)
            }

            IO.write(outFile, testDefinitions)
            Seq(outFile)
          }.taskValue,

          // Give more memory to Node.js, and deactivate source maps
          jsEnv := {
            new NodeJSEnv(
                NodeJSEnv.Config()
                  .withArgs(List("--max_old_space_size=3072"))
                  .withSourceMap(false))
          },

          jsDependencies += ProvidedJS / "js-test-definitions.js" % "test"
      ) ++ inConfig(Test) {
        // Redefine test to run Node.js and link HelloWorld
        test := {
          val jsEnv = resolvedJSEnv.value
          if (!jsEnv.isInstanceOf[NodeJSEnv])
            throw new MessageOnlyException("toolsJS/test must be run with Node.js")

          /* Collect IR relevant files from the classpath
           * We assume here that the classpath is valid. This is checked by the
           * the scalaJSIR task.
           */
          val cp = Attributed.data(fullClasspath.value)

          // Files must be Jars, non-files must be dirs
          val (jars, dirs) = cp.filter(_.exists).partition(_.isFile)
          val irFiles = dirs.flatMap(dir => (dir ** "*.sjsir").get)

          def seqOfStringsToJSArrayCode(strings: Seq[String]): String =
            strings.map(s => "\"" + escapeJS(s) + "\"").mkString("[", ", ", "]")

          val irPaths = {
            val absolutePaths = (jars ++ irFiles).map(_.getAbsolutePath)
            seqOfStringsToJSArrayCode(absolutePaths)
          }

          val mainMethods = {
            /* Ideally we would read `scalaJSModuleInitializers in (testSuite, Test)`,
             * but we cannot convert the ModuleInitializers to strings to be
             * passed to the QuickLinker (because ModuleInitializer is a
             * write-only data structure). So we have some duplication.
             */
            val unescapedMainMethods = List(
                "org.scalajs.testsuite.compiler.ModuleInitializerInNoConfiguration.main",
                "org.scalajs.testsuite.compiler.ModuleInitializerInTestConfiguration.main2",
                "org.scalajs.testsuite.compiler.ModuleInitializerInTestConfiguration.main1",
                "org.scalajs.testsuite.compiler.ModuleInitializerInTestConfiguration.mainArgs1()",
                "org.scalajs.testsuite.compiler.ModuleInitializerInTestConfiguration.mainArgs2(foo,bar)"
            )
            seqOfStringsToJSArrayCode(unescapedMainMethods)
          }

          val scalaJSEnv = {
            s"""
            {"javaSystemProperties": {
              "scalajs.scalaVersion": "${scalaVersion.value}"
            }}
            """
          }

          val code = {
            s"""
            var linker = scalajs.QuickLinker;
            var lib = linker.linkTestSuiteNode($irPaths, $mainMethods);

            var __ScalaJSEnv = $scalaJSEnv;

            eval("(function() { 'use strict'; " +
              lib + ";" +
              "scalajs.TestRunner.runTests();" +
            "}).call(this);");
            """
          }

          val launcher = new MemVirtualJSFile("Generated launcher file")
            .withContent(code)

          val linked = scalaJSLinkedFile.value
          val libs = resolvedJSDependencies.value.data :+
              ResolvedJSDependency.minimal(linked)
          val runner = jsEnv.jsRunner(libs, launcher)

          runner.run(sbtLogger2ToolsLogger(streams.value.log), scalaJSConsole.value)
        }
      }
  ).withScalaJSCompiler.dependsOn(
      javalibEx, testSuite % "test->test", irProjectJS
  )

  lazy val jsEnvs: Project = Project(
      id = "jsEnvs",
      base = file("js-envs"),
      settings = (
          commonSettings ++ publishSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js JS Envs",
          libraryDependencies ++= Seq(
              "io.apigee" % "rhino" % "1.7R5pre4",
              "org.webjars" % "envjs" % "1.2"
          ) ++ ScalaJSPluginInternal.phantomJSJettyModules.map(_ % "provided"),
          previousArtifactSetting,
          mimaBinaryIssueFilters ++= BinaryIncompatibilities.JSEnvs
      )
  ).dependsOn(tools)

  lazy val jsEnvsTestKit: Project = Project(
      id = "jsEnvsTestKit",
      base = file("js-envs-test-kit"),
      settings = (
          commonSettings ++ publishSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js JS Envs Test Kit",
          libraryDependencies +=
            "junit" % "junit" % "4.8.2",
          previousArtifactSetting,
          mimaBinaryIssueFilters ++= BinaryIncompatibilities.JSEnvsTestKit
      )
  ).dependsOn(tools, jsEnvs)

  lazy val jsEnvsTestSuite: Project = Project(
      id = "jsEnvsTestSuite",
      base = file("js-envs-test-suite"),
      settings = (
          commonSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js JS Envs Test Suite",
          libraryDependencies ++= Seq(
              "com.novocode" % "junit-interface" % "0.9" % "test"
          ) ++ ScalaJSPluginInternal.phantomJSJettyModules.map(_ % "provided")
      )
  ).dependsOn(tools, jsEnvs, jsEnvsTestKit % "test")

  lazy val testAdapter = Project(
      id = "testAdapter",
      base = file("test-adapter"),
      settings = (
          commonSettings ++ publishSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js sbt test adapter",
          libraryDependencies += "org.scala-sbt" % "test-interface" % "1.0",
          libraryDependencies +=
            "com.novocode" % "junit-interface" % "0.11" % "test",

          previousArtifactSetting,
          mimaBinaryIssueFilters ++= BinaryIncompatibilities.TestAdapter,
          unmanagedSourceDirectories in Compile +=
            baseDirectory.value.getParentFile / "test-common/src/main/scala",
          unmanagedSourceDirectories in Test +=
            baseDirectory.value.getParentFile / "test-common/src/test/scala"
      )
  ).dependsOn(jsEnvs)

  lazy val plugin: Project = Project(
      id = "sbtPlugin",
      base = file("sbt-plugin"),
      settings = (
          commonSettings ++ publishIvySettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js sbt plugin",
          normalizedName := "sbt-scalajs",
          bintrayProjectName := "sbt-scalajs-plugin", // "sbt-scalajs" was taken
          sbtPlugin := true,
          scalaBinaryVersion :=
            CrossVersion.binaryScalaVersion(scalaVersion.value),
          previousArtifactSetting,
          mimaBinaryIssueFilters ++= BinaryIncompatibilities.SbtPlugin,

          /* TODO Remove this in 1.x, since there are no macros in sbt-plugin
           * anymore.
           */
          scalacOptions -= "-Xfatal-warnings",

          // Add API mappings for sbt (seems they don't export their API URL)
          apiMappings ++= {
            val deps = (externalDependencyClasspath in Compile).value

            val sbtJars = deps filter { attributed =>
              val p = attributed.data.getPath
              p.contains("/org.scala-sbt/") && p.endsWith(".jar")
            }

            val docUrl =
              url(s"http://www.scala-sbt.org/${sbtVersion.value}/api/")

            sbtJars.map(_.data -> docUrl).toMap
          }
      )
  ).dependsOn(tools, jsEnvs, testAdapter)

  lazy val delambdafySetting = {
    scalacOptions ++= (
        if (isGeneratingEclipse) Seq()
        else if (scalaBinaryVersion.value == "2.10") Seq()
        else Seq("-Ydelambdafy:method"))
  }

  private def serializeHardcodedIR(base: File,
      infoAndTree: (ir.Infos.ClassInfo, ir.Trees.ClassDef)): File = {
    // We assume that there are no weird characters in the full name
    val fullName = ir.Definitions.decodeClassName(infoAndTree._1.encodedName)
    val output = base / (fullName.replace('.', '/') + ".sjsir")

    if (!output.exists()) {
      IO.createDirectory(output.getParentFile)
      val stream = new BufferedOutputStream(new FileOutputStream(output))
      try {
        ir.InfoSerializers.serialize(stream, infoAndTree._1)
        ir.Serializers.serialize(stream, infoAndTree._2)
      } finally {
        stream.close()
      }
    }
    output
  }

  lazy val javalanglib: Project = Project(
      id = "javalanglib",
      base = file("javalanglib"),
      settings = (
          commonSettings ++ myScalaJSSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "java.lang library for Scala.js",
          publishArtifact in Compile := false,
          delambdafySetting,
          noClassFilesSettings,

          resourceGenerators in Compile += Def.task {
            val base = (resourceManaged in Compile).value
            Seq(
                serializeHardcodedIR(base, JavaLangObject.InfoAndTree),
                serializeHardcodedIR(base, JavaLangString.InfoAndTree)
            )
          }.taskValue
      ) ++ (
          scalaJSExternalCompileSettings
      )
  ).withScalaJSCompiler.dependsOnLibraryNoJar

  lazy val javalib: Project = Project(
      id = "javalib",
      base = file("javalib"),
      settings = (
          commonSettings ++ myScalaJSSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Java library for Scala.js",
          publishArtifact in Compile := false,
          delambdafySetting,
          noClassFilesSettings
      ) ++ (
          scalaJSExternalCompileSettings
      )
  ).withScalaJSCompiler.dependsOnLibraryNoJar

  lazy val scalalib: Project = Project(
      id = "scalalib",
      base = file("scalalib"),
      settings = commonSettings ++ Seq(
          /* Link source maps to the GitHub sources of the original scalalib
           * #2195 This must come *before* the option added by myScalaJSSettings
           * because mapSourceURI works on a first-match basis.
           */
          scalacOptions += {
            "-P:scalajs:mapSourceURI:" +
            (artifactPath in fetchScalaSource).value.toURI +
            "->https://raw.githubusercontent.com/scala/scala/v" +
            scalaVersion.value + "/src/library/"
          }
      ) ++ myScalaJSSettings ++ Seq(
          name := "Scala library for Scala.js",
          publishArtifact in Compile := false,
          delambdafySetting,
          noClassFilesSettings,

          // The Scala lib is full of warnings we don't want to see
          scalacOptions ~= (_.filterNot(
              Set("-deprecation", "-unchecked", "-feature") contains _)),

          // Tell the plugin to hack-fix bad classOf trees
          scalacOptions += "-P:scalajs:fixClassOf",

          libraryDependencies +=
            "org.scala-lang" % "scala-library" % scalaVersion.value classifier "sources",

          artifactPath in fetchScalaSource :=
            target.value / "scalaSources" / scalaVersion.value,

          /* Work around for #2649. We would like to always use `update`, but
           * that fails if the scalaVersion we're looking for happens to be the
           * version of Scala used by sbt itself. This is clearly a bug in sbt,
           * which we work around here by using `updateClassifiers` instead in
           * that case.
           */
          update in fetchScalaSource := Def.taskDyn {
            if (scalaVersion.value == scala.util.Properties.versionNumberString)
              updateClassifiers
            else
              update
          }.value,

          fetchScalaSource := {
            val s = streams.value
            val cacheDir = s.cacheDirectory
            val ver = scalaVersion.value
            val trgDir = (artifactPath in fetchScalaSource).value

            val report = (update in fetchScalaSource).value
            val scalaLibSourcesJar = report.select(
                configuration = Set("compile"),
                module = moduleFilter(name = "scala-library"),
                artifact = artifactFilter(classifier = "sources")).headOption.getOrElse {
              throw new Exception(
                  s"Could not fetch scala-library sources for version $ver")
            }

            FileFunction.cached(cacheDir / s"fetchScalaSource-$ver",
                FilesInfo.lastModified, FilesInfo.exists) { dependencies =>
              s.log.info(s"Unpacking Scala library sources to $trgDir...")

              if (trgDir.exists)
                IO.delete(trgDir)
              IO.createDirectory(trgDir)
              IO.unzip(scalaLibSourcesJar, trgDir)
            } (Set(scalaLibSourcesJar))

            trgDir
          },

          unmanagedSourceDirectories in Compile := {
            // Calculates all prefixes of the current Scala version
            // (including the empty prefix) to construct override
            // directories like the following:
            // - override-2.10.2-RC1
            // - override-2.10.2
            // - override-2.10
            // - override-2
            // - override
            val ver = scalaVersion.value
            val base = baseDirectory.value
            val parts = ver.split(Array('.','-'))
            val verList = parts.inits.map { ps =>
              val len = ps.mkString(".").length
              // re-read version, since we lost '.' and '-'
              ver.substring(0, len)
            }
            def dirStr(v: String) =
              if (v.isEmpty) "overrides" else s"overrides-$v"
            val dirs = verList.map(base / dirStr(_)).filter(_.exists)
            dirs.toSeq // most specific shadow less specific
          },

          // Compute sources
          // Files in earlier src dirs shadow files in later dirs
          sources in Compile := {
            // Sources coming from the sources of Scala
            val scalaSrcDir = fetchScalaSource.value

            // All source directories (overrides shadow scalaSrcDir)
            val sourceDirectories =
              (unmanagedSourceDirectories in Compile).value :+ scalaSrcDir

            // Filter sources with overrides
            def normPath(f: File): String =
              f.getPath.replace(java.io.File.separator, "/")

            val sources = mutable.ListBuffer.empty[File]
            val paths = mutable.Set.empty[String]

            for {
              srcDir <- sourceDirectories
              normSrcDir = normPath(srcDir)
              src <- (srcDir ** "*.scala").get
            } {
              val normSrc = normPath(src)
              val path = normSrc.substring(normSrcDir.length)
              val useless =
                path.contains("/scala/collection/parallel/") ||
                path.contains("/scala/util/parsing/")
              if (!useless) {
                if (paths.add(path))
                  sources += src
                else
                  streams.value.log.debug(s"not including $src")
              }
            }

            sources.result()
          },

          // Continuation plugin (when using 2.10.x)
          autoCompilerPlugins := true,
          libraryDependencies ++= {
            val ver = scalaVersion.value
            if (ver.startsWith("2.10."))
              Seq(compilerPlugin("org.scala-lang.plugins" % "continuations" % ver))
            else
              Nil
          },
          scalacOptions ++= {
            if (scalaVersion.value.startsWith("2.10."))
              Seq("-P:continuations:enable")
            else
              Nil
          }
      ) ++ (
          scalaJSExternalCompileSettings
      )
  ).withScalaJSCompiler.dependsOnLibraryNoJar

  lazy val libraryAux: Project = Project(
      id = "libraryAux",
      base = file("library-aux"),
      settings = (
          commonSettings ++ myScalaJSSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js aux library",
          publishArtifact in Compile := false,
          delambdafySetting,
          noClassFilesSettings
      ) ++ (
          scalaJSExternalCompileSettings
      )
  ).withScalaJSCompiler.dependsOnLibraryNoJar

  lazy val library: Project = Project(
      id = "library",
      base = file("library"),
      settings = (
          commonSettings ++ publishSettings ++ myScalaJSSettings ++ fatalWarningsSettings
      ) ++ Seq(
          name := "Scala.js library",
          delambdafySetting,
          exportJars := !isGeneratingEclipse,
          previousArtifactSetting,
          mimaBinaryIssueFilters ++= BinaryIncompatibilities.Library,
          libraryDependencies +=
            "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided",

          // js.JSApp is annotated with @JSExportDescendentObjects
          scalacOptions += "-P:scalajs:suppressExportDeprecations"
      ) ++ (
          scalaJSExternalCompileSettings
      ) ++ inConfig(Compile)(Seq(
          scalacOptions in doc ++= Seq("-implicits", "-groups"),

          // Filter doc sources to remove implementation details from doc.
          sources in doc := {
            val prev = (sources in doc).value

            if (javaVersion.value < 9) {
              def containsFileFilter(s: String): FileFilter = new FileFilter {
                override def accept(f: File): Boolean = {
                  val path = f.getAbsolutePath.replace('\\', '/')
                  path.contains(s)
                }
              }

              val filter: FileFilter = (
                  AllPassFilter
                    -- containsFileFilter("/scala/scalajs/runtime/")
                    -- containsFileFilter("/scala/scalajs/js/annotation/internal/")
                    -- "*.nodoc.scala"
              )

              (sources in doc).value.filter(filter.accept)
            } else {
              /* Work around #3152: library/doc crashes with
               *   <Cannot read source file>
               * on JDK 9.
               */
              Nil
            }
          },

          /* Add compiled .class files to doc dependencyClasspath, so we can
           * still compile even with only part of the files being present.
           */
          dependencyClasspath in doc ++= exportedProducts.value,

          /* Add the .sjsir files from other lib projects
           * (but not .class files)
           */
          mappings in packageBin := {
            /* From library, we must take everyting, except the
             * java.nio.TypedArrayBufferBridge object, whose actual
             * implementation is in javalib.
             */
            val superMappings = (mappings in packageBin).value
            val libraryMappings = superMappings.filter(
                _._2.replace('\\', '/') !=
                  "scala/scalajs/js/typedarray/TypedArrayBufferBridge$.sjsir")

            val filter = ("*.sjsir": NameFilter)
{'fraction_non_alphanumeric': 0.10840037418147802, 'fraction_numerical': 0.005999376364203305, 'mean_word_length': 1.964759826942277, 'pattern_counts': {'":': 6, '<': 77, '<?xml version=': 0, '>': 130, 'https://': 18, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3905886', 'n_tokens_mistral': 23426, 'n_tokens_neox': 22439, 'n_words': 5051}