From 0819dec5cf07f770b236540b6fb1a39d6cdadef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 8 Jun 2021 15:58:05 +0200 Subject: [PATCH 01/48] Towards 1.6.1. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 2 +- project/BinaryIncompatibilities.scala | 3 --- project/Build.scala | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index c813313ff1..20b0242721 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.6.0", + current = "1.6.1-SNAPSHOT", binaryEmitted = "1.6" ) diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index cb70351fd1..50ed423e45 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -23,9 +23,6 @@ object BinaryIncompatibilities { ) val Library = Seq( - // New method in a sealed trait (even a JS trait), not an issue - ProblemFilters.exclude[ReversedMissingMethodProblem]( - "scala.scalajs.runtime.LinkingInfo.esVersion"), ) val TestInterface = Seq( diff --git a/project/Build.scala b/project/Build.scala index e14df546f9..02fd2448b5 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -235,7 +235,7 @@ object Build { val packageMinilib = taskKey[File]("Produces the minilib jar.") val previousVersions = List("1.0.0", "1.0.1", "1.1.0", "1.1.1", - "1.2.0", "1.3.0", "1.3.1", "1.4.0", "1.5.0", "1.5.1") + "1.2.0", "1.3.0", "1.3.1", "1.4.0", "1.5.0", "1.5.1", "1.6.0") val previousVersion = previousVersions.last val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") From 6b9364181c86eddce701d82e3e213cbdb625a32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 17 Apr 2021 13:03:47 +0200 Subject: [PATCH 02/48] Add `linkerInterfaceJS/test` to the CI. It was somehow missing before. --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2853b2237d..f55590060e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -341,7 +341,7 @@ def Tasks = [ npm install && sbt ++$scala linker$v/test && sbt linkerPrivateLibrary/test && - sbt ++$scala irJS$v/test linkerJS$v/test && + sbt ++$scala irJS$v/test linkerJS$v/test linkerInterfaceJS$v/test && sbt 'set scalaJSStage in Global := FullOptStage' \ 'set scalaJSStage in testSuite.v$v := FastOptStage' \ ++$scala irJS$v/test linkerJS$v/test linkerInterfaceJS$v/test && From de11c5507ad538b8d0a5a5ffd3fc59d4fc9710ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 17 Apr 2021 13:04:16 +0200 Subject: [PATCH 03/48] Improve `String.codePointAt` to use the native method in ES2015+. --- .../src/main/scala/java/lang/_String.scala | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/javalanglib/src/main/scala/java/lang/_String.scala b/javalanglib/src/main/scala/java/lang/_String.scala index 86c6a30286..aea1913bd4 100644 --- a/javalanglib/src/main/scala/java/lang/_String.scala +++ b/javalanglib/src/main/scala/java/lang/_String.scala @@ -18,6 +18,8 @@ import java.util.Comparator import scala.scalajs.js import scala.scalajs.js.annotation._ +import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo.ESVersion import java.nio.ByteBuffer import java.nio.charset.Charset @@ -53,15 +55,21 @@ final class _String private () // scalastyle:ignore } def codePointAt(index: Int): Int = { - val high = charAt(index) - if (index+1 < length()) { - val low = charAt(index+1) - if (Character.isSurrogatePair(high, low)) - Character.toCodePoint(high, low) - else - high.toInt + if (linkingInfo.esVersion >= ESVersion.ES2015) { + this.asInstanceOf[js.Dynamic] + .codePointAt(index.asInstanceOf[js.Dynamic]) + .asInstanceOf[Int] } else { - high.toInt + val high = charAt(index) + if (index+1 < length()) { + val low = charAt(index+1) + if (Character.isSurrogatePair(high, low)) + Character.toCodePoint(high, low) + else + high.toInt + } else { + high.toInt + } } } From a24ce747f658d823a394c3b5de8232db8eca0259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 9 Jun 2021 15:18:55 +0200 Subject: [PATCH 04/48] Remove ClassTag.empty{,Wrapped}Array in the override for 2.13. Those methods do not exist in `ClassTag` upstream in 2.13.x. --- .../overrides-2.13/scala/reflect/ClassTag.scala | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/scalalib/overrides-2.13/scala/reflect/ClassTag.scala b/scalalib/overrides-2.13/scala/reflect/ClassTag.scala index 650cb2af4d..4dbc089bd0 100644 --- a/scalalib/overrides-2.13/scala/reflect/ClassTag.scala +++ b/scalalib/overrides-2.13/scala/reflect/ClassTag.scala @@ -49,22 +49,6 @@ import scala.runtime.BoxedUnit */ @scala.annotation.implicitNotFound(msg = "No ClassTag available for ${T}") trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serializable { - - /* Scala.js deviation: emptyArray and emptyWrappedArray are - * `@transient lazy val`s on the JVM, but we make them `def`s. On the JVM, - * instances of `ClassTag` are cached, so that makes sense. On JS, however, - * `ClassTag`s are usually stack-allocated instead, hence the lazy vals - * contribute more useless code than actual caching. `def`s are more - * appropriate. - */ - private[scala] def emptyArray: Array[T] = { - val componentType = - if (runtimeClass eq classOf[Void]) classOf[BoxedUnit] else runtimeClass - java.lang.reflect.Array.newInstance(componentType, 0).asInstanceOf[Array[T]] - } - private[scala] def emptyWrappedArray: mutable.WrappedArray[T] = - mutable.WrappedArray.make[T](emptyArray) - // please, don't add any APIs here, like it was with `newWrappedArray` and `newArrayBuilder` // class tags, and all tags in general, should be as minimalistic as possible From a91fe8b6a3760410890c4f345b08511489875e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 9 Jun 2021 15:23:12 +0200 Subject: [PATCH 05/48] Fix #4507: Fix ClassTag.emptyArray for classTag[Unit]. This is port of the upstream commit https://github.com/scala/scala/commit/5e6676d99669fdfe8493cb75a692f90608c873e1 --- project/Build.scala | 7 +-- .../scala/reflect/ClassTag.scala | 2 +- .../scalalib/WrappedArrayBuilderTest.scala | 45 +++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 test-suite/shared/src/test/scala-old-collections/org/scalajs/testsuite/scalalib/WrappedArrayBuilderTest.scala diff --git a/project/Build.scala b/project/Build.scala index 02fd2448b5..ab5f94321a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1748,9 +1748,10 @@ object Build { val scalaV = scalaVersion.value val isScalaAtLeast212 = !scalaV.startsWith("2.11.") - List(sharedTestDir / "scala", sharedTestDir / "require-scala2") ++ - includeIf(sharedTestDir / "require-jdk11", javaV >= 11) ++ - includeIf(testDir / "require-2.12", isJSTest && isScalaAtLeast212) ++ + List(sharedTestDir / "scala", sharedTestDir / "require-scala2") ::: + collectionsEraDependentDirectory(scalaV, sharedTestDir) :: + includeIf(sharedTestDir / "require-jdk11", javaV >= 11) ::: + includeIf(testDir / "require-2.12", isJSTest && isScalaAtLeast212) ::: includeIf(testDir / "require-scala2", isJSTest) }, diff --git a/scalalib/overrides-2.12/scala/reflect/ClassTag.scala b/scalalib/overrides-2.12/scala/reflect/ClassTag.scala index af8fe44665..f9dc5dd716 100644 --- a/scalalib/overrides-2.12/scala/reflect/ClassTag.scala +++ b/scalalib/overrides-2.12/scala/reflect/ClassTag.scala @@ -47,7 +47,7 @@ trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serial */ private[scala] def emptyArray: Array[T] = { val componentType = - if (runtimeClass eq classOf[Void]) classOf[BoxedUnit] else runtimeClass + if (runtimeClass eq java.lang.Void.TYPE) classOf[BoxedUnit] else runtimeClass java.lang.reflect.Array.newInstance(componentType, 0).asInstanceOf[Array[T]] } private[scala] def emptyWrappedArray: mutable.WrappedArray[T] = diff --git a/test-suite/shared/src/test/scala-old-collections/org/scalajs/testsuite/scalalib/WrappedArrayBuilderTest.scala b/test-suite/shared/src/test/scala-old-collections/org/scalajs/testsuite/scalalib/WrappedArrayBuilderTest.scala new file mode 100644 index 0000000000..8f18df84c4 --- /dev/null +++ b/test-suite/shared/src/test/scala-old-collections/org/scalajs/testsuite/scalalib/WrappedArrayBuilderTest.scala @@ -0,0 +1,45 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.testsuite.scalalib + +import scala.collection.mutable +import scala.reflect.ClassTag + +import org.junit.Assert._ +import org.junit.Test + +class WrappedArrayBuilderTest { + + @Test def emptyResult_Issue4507(): Unit = { + def test[A](implicit ct: ClassTag[A]): Unit = { + val result = new mutable.WrappedArrayBuilder(ct).result() + assertEquals(0, result.size) + + val expectedComponentType: Class[_] = + if (ct.runtimeClass == classOf[Unit]) classOf[scala.runtime.BoxedUnit] + else ct.runtimeClass + assertSame(expectedComponentType, result.array.getClass().getComponentType()) + } + + test[Int] + test[String] + test[List[Int]] + test[Char] + test[Unit] + test[scala.runtime.BoxedUnit] + test[java.lang.Void] + test[Array[Int]] + test[Array[String]] + } + +} From b47b90b3672fea9c87fd1fe6685eeac8cd26f46a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 9 Jun 2021 17:36:30 +0200 Subject: [PATCH 06/48] Upgrade to Scala 2.12.14. --- Jenkinsfile | 7 +- .../{2.12.13 => 2.12.14}/BlacklistedTests.txt | 7 + .../{2.12.13 => 2.12.14}/neg/t11952b.check | 0 .../neg/t6446-additional.check | 0 .../{2.12.13 => 2.12.14}/neg/t6446-list.check | 0 .../neg/t6446-missing.check | 0 .../neg/t6446-show-phases.check | 0 .../neg/t7494-no-options.check | 0 .../run/Course-2002-01.check | 0 .../run/Course-2002-02.check | 0 .../run/Course-2002-04.check | 0 .../run/Course-2002-08.check | 0 .../run/Course-2002-09.check | 0 .../run/Course-2002-10.check | 0 .../{2.12.13 => 2.12.14}/run/Meter.check | 0 .../run/MeterCaseClass.check | 0 .../run/anyval-box-types.check | 0 .../scalajs/{2.12.13 => 2.12.14}/run/bugs.sem | 0 .../run/caseClassHash.check | 0 .../{2.12.13 => 2.12.14}/run/classof.check | 0 .../{2.12.13 => 2.12.14}/run/deeps.check | 0 .../run/dynamic-anyval.check | 0 .../run/impconvtimes.check | 0 .../{2.12.13 => 2.12.14}/run/imports.check | 0 .../run/interpolation.check | 0 .../run/interpolationMultiline1.check | 0 .../{2.12.13 => 2.12.14}/run/issue192.sem | 0 .../run/macro-bundle-static.check | 0 .../run/macro-bundle-toplevel.check | 0 .../run/macro-bundle-whitebox-decl.check | 0 .../{2.12.13 => 2.12.14}/run/misc.check | 0 .../{2.12.13 => 2.12.14}/run/promotion.check | 0 .../{2.12.13 => 2.12.14}/run/runtime.check | 0 .../{2.12.13 => 2.12.14}/run/spec-self.check | 0 .../{2.12.13 => 2.12.14}/run/structural.check | 0 .../{2.12.13 => 2.12.14}/run/t0421-new.check | 0 .../{2.12.13 => 2.12.14}/run/t0421-old.check | 0 .../{2.12.13 => 2.12.14}/run/t1503.sem | 0 .../{2.12.13 => 2.12.14}/run/t3702.check | 0 .../{2.12.13 => 2.12.14}/run/t4148.sem | 0 .../{2.12.13 => 2.12.14}/run/t4617.check | 0 .../{2.12.13 => 2.12.14}/run/t5356.check | 0 .../{2.12.13 => 2.12.14}/run/t5552.check | 0 .../{2.12.13 => 2.12.14}/run/t5568.check | 0 .../{2.12.13 => 2.12.14}/run/t5629b.check | 0 .../{2.12.13 => 2.12.14}/run/t5680.check | 0 .../{2.12.13 => 2.12.14}/run/t5866.check | 0 .../run/t6318_primitives.check | 0 .../{2.12.13 => 2.12.14}/run/t6662.check | 0 .../{2.12.13 => 2.12.14}/run/t7657.check | 0 .../{2.12.13 => 2.12.14}/run/t7763.sem | 0 .../{2.12.13 => 2.12.14}/run/t8570a.check | 0 .../{2.12.13 => 2.12.14}/run/t8764.check | 0 .../{2.12.13 => 2.12.14}/run/t9387b.check | 0 .../{2.12.13 => 2.12.14}/run/t9656.check | 0 .../run/try-catch-unify.check | 0 .../run/virtpatmat_switch.check | 0 .../run/virtpatmat_typetag.check | 0 project/Build.scala | 14 +- project/MultiScalaProject.scala | 2 +- .../change-config-and-source/build.sbt | 2 +- .../incremental/change-config/build.sbt | 2 +- .../incremental/fix-compile-error/build.sbt | 2 +- .../linker/concurrent-linker-use/build.sbt | 2 +- .../sbt-test/linker/custom-linker/build.sbt | 4 +- .../no-root-dependency-resolution/build.sbt | 2 +- .../linker/non-existent-classpath/build.sbt | 2 +- .../sbt-test/settings/cross-version/build.sbt | 2 +- .../settings/legacy-link-empty/build.sbt | 2 +- .../settings/legacy-link-tasks/build.sbt | 2 +- .../sbt-test/settings/module-init/build.sbt | 2 +- .../sbt-test/settings/source-map/build.sbt | 2 +- .../testing/multi-framework/build.sbt | 2 +- .../resources/2.12.14/BlacklistedTests.txt | 197 ++++++++++++++++++ 74 files changed, 230 insertions(+), 25 deletions(-) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/BlacklistedTests.txt (99%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/neg/t11952b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/neg/t6446-additional.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/neg/t6446-list.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/neg/t6446-missing.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/neg/t6446-show-phases.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/neg/t7494-no-options.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/Course-2002-01.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/Course-2002-02.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/Course-2002-04.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/Course-2002-08.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/Course-2002-09.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/Course-2002-10.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/Meter.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/MeterCaseClass.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/anyval-box-types.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/bugs.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/caseClassHash.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/classof.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/deeps.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/dynamic-anyval.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/impconvtimes.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/imports.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/interpolation.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/interpolationMultiline1.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/issue192.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/macro-bundle-static.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/macro-bundle-toplevel.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/macro-bundle-whitebox-decl.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/misc.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/promotion.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/runtime.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/spec-self.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/structural.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t0421-new.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t0421-old.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t1503.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t3702.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t4148.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t4617.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t5356.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t5552.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t5568.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t5629b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t5680.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t5866.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t6318_primitives.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t6662.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t7657.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t7763.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t8570a.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t8764.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t9387b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/t9656.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/try-catch-unify.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/virtpatmat_switch.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.12.13 => 2.12.14}/run/virtpatmat_typetag.check (100%) create mode 100644 scala-test-suite/src/test/resources/2.12.14/BlacklistedTests.txt diff --git a/Jenkinsfile b/Jenkinsfile index f55590060e..09c75f9e64 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -408,8 +408,8 @@ def otherJavaVersions = ["11"] def allJavaVersions = otherJavaVersions.clone() allJavaVersions << mainJavaVersion -def mainScalaVersion = "2.12.13" -def mainScalaVersions = ["2.11.12", "2.12.13", "2.13.5"] +def mainScalaVersion = "2.12.14" +def mainScalaVersions = ["2.11.12", "2.12.14", "2.13.5"] def otherScalaVersions = [ "2.11.12", "2.12.1", @@ -424,6 +424,7 @@ def otherScalaVersions = [ "2.12.10", "2.12.11", "2.12.12", + "2.12.13", "2.13.0", "2.13.1", "2.13.2", @@ -458,7 +459,7 @@ allESVersions.each { esVersion -> quickMatrix.add([task: "test-suite-custom-esversion-force-polyfills", scala: mainScalaVersion, java: mainJavaVersion, esVersion: esVersion, testSuite: "testSuite"]) } allJavaVersions.each { javaVersion -> - quickMatrix.add([task: "tools-sbtplugin", scala: "2.12.13", java: javaVersion]) + quickMatrix.add([task: "tools-sbtplugin", scala: "2.12.14", java: javaVersion]) quickMatrix.add([task: "tools", scala: "2.11.12", java: javaVersion]) quickMatrix.add([task: "tools", scala: "2.13.5", java: javaVersion]) } diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/BlacklistedTests.txt b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/BlacklistedTests.txt similarity index 99% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/BlacklistedTests.txt rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/BlacklistedTests.txt index bcf2e9a40f..fbc3b2f582 100644 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/BlacklistedTests.txt +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/BlacklistedTests.txt @@ -993,6 +993,7 @@ run/t8918-unary-ids.scala run/t1931.scala run/t8935-class.scala run/t8935-object.scala +run/t12354.scala # partest.JavapTest run/t8608-no-format.scala @@ -1070,6 +1071,9 @@ run/t3899 run/t11373 run/t8928 run/indy-meth-refs-j +run/t12019 +run/t12038a +run/t12038b # Using scala-script run/t7791-script-linenums.scala @@ -1098,6 +1102,9 @@ run/t3493.scala # Custom invoke dynamic node run/indy-via-macro +run/indy-via-macro-class-constant-bsa +run/indy-via-macro-method-type-bsa +run/indy-via-macro-reflector run/indy-via-macro-with-dynamic-args # Crashes our optimizer because of artificially insane amount of inlining diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/neg/t11952b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/neg/t11952b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/neg/t11952b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/neg/t11952b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/neg/t6446-additional.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/neg/t6446-additional.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/neg/t6446-additional.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/neg/t6446-additional.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/neg/t6446-list.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/neg/t6446-list.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/neg/t6446-list.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/neg/t6446-list.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/neg/t6446-missing.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/neg/t6446-missing.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/neg/t6446-missing.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/neg/t6446-missing.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/neg/t6446-show-phases.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/neg/t6446-show-phases.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/neg/t6446-show-phases.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/neg/t6446-show-phases.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/neg/t7494-no-options.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/neg/t7494-no-options.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/neg/t7494-no-options.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/neg/t7494-no-options.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Course-2002-01.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Course-2002-01.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Course-2002-01.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Course-2002-01.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Course-2002-02.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Course-2002-02.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Course-2002-02.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Course-2002-02.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Course-2002-04.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Course-2002-04.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Course-2002-04.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Course-2002-04.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Course-2002-08.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Course-2002-08.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Course-2002-08.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Course-2002-08.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Course-2002-09.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Course-2002-09.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Course-2002-09.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Course-2002-09.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Course-2002-10.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Course-2002-10.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Course-2002-10.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Course-2002-10.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Meter.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Meter.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/Meter.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/Meter.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/MeterCaseClass.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/MeterCaseClass.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/MeterCaseClass.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/MeterCaseClass.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/anyval-box-types.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/anyval-box-types.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/anyval-box-types.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/anyval-box-types.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/bugs.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/bugs.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/bugs.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/bugs.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/caseClassHash.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/caseClassHash.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/caseClassHash.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/caseClassHash.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/classof.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/classof.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/classof.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/classof.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/deeps.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/deeps.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/deeps.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/deeps.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/dynamic-anyval.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/dynamic-anyval.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/dynamic-anyval.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/dynamic-anyval.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/impconvtimes.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/impconvtimes.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/impconvtimes.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/impconvtimes.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/imports.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/imports.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/imports.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/imports.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/interpolation.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/interpolation.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/interpolation.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/interpolation.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/interpolationMultiline1.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/interpolationMultiline1.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/interpolationMultiline1.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/interpolationMultiline1.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/issue192.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/issue192.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/issue192.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/issue192.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/macro-bundle-static.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/macro-bundle-static.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/macro-bundle-static.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/macro-bundle-static.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/macro-bundle-toplevel.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/macro-bundle-toplevel.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/macro-bundle-toplevel.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/macro-bundle-toplevel.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/macro-bundle-whitebox-decl.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/macro-bundle-whitebox-decl.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/macro-bundle-whitebox-decl.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/macro-bundle-whitebox-decl.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/misc.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/misc.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/misc.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/misc.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/promotion.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/promotion.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/promotion.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/promotion.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/runtime.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/runtime.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/runtime.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/runtime.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/spec-self.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/spec-self.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/spec-self.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/spec-self.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/structural.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/structural.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/structural.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/structural.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t0421-new.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t0421-new.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t0421-new.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t0421-new.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t0421-old.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t0421-old.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t0421-old.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t0421-old.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t1503.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t1503.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t1503.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t1503.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t3702.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t3702.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t3702.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t3702.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t4148.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t4148.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t4148.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t4148.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t4617.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t4617.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t4617.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t4617.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t5356.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t5356.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t5356.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t5356.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t5552.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t5552.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t5552.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t5552.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t5568.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t5568.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t5568.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t5568.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t5629b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t5629b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t5629b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t5629b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t5680.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t5680.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t5680.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t5680.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t5866.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t5866.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t5866.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t5866.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t6318_primitives.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t6318_primitives.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t6318_primitives.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t6318_primitives.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t6662.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t6662.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t6662.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t6662.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t7657.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t7657.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t7657.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t7657.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t7763.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t7763.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t7763.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t7763.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t8570a.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t8570a.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t8570a.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t8570a.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t8764.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t8764.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t8764.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t8764.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t9387b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t9387b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t9387b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t9387b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t9656.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t9656.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/t9656.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/t9656.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/try-catch-unify.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/try-catch-unify.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/try-catch-unify.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/try-catch-unify.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/virtpatmat_switch.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/virtpatmat_switch.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/virtpatmat_switch.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/virtpatmat_switch.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/virtpatmat_typetag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/virtpatmat_typetag.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.13/run/virtpatmat_typetag.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/run/virtpatmat_typetag.check diff --git a/project/Build.scala b/project/Build.scala index ab5f94321a..3d7512d9c1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -241,7 +241,7 @@ object Build { val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") val scalaVersionsUsedForPublishing: Set[String] = - Set("2.11.12", "2.12.13", "2.13.5") + Set("2.11.12", "2.12.14", "2.13.5") val newScalaBinaryVersionsInThisRelease: Set[String] = Set() @@ -848,7 +848,7 @@ object Build { MyScalaJSPlugin ).settings( commonSettings, - scalaVersion := "2.12.13", + scalaVersion := "2.12.14", fatalWarningsSettings, name := "Scala.js linker private library", publishArtifact in Compile := false, @@ -1040,7 +1040,7 @@ object Build { name := "Scala.js sbt plugin", normalizedName := "sbt-scalajs", sbtPlugin := true, - crossScalaVersions := Seq("2.12.13"), + crossScalaVersions := Seq("2.12.14"), scalaVersion := crossScalaVersions.value.head, sbtVersion := "1.0.0", scalaBinaryVersion := @@ -1674,11 +1674,11 @@ object Build { fullLinkGz = 28000 to 29000, )) - case "2.12.13" => + case "2.12.14" => Some(ExpectedSizes( - fastLink = 780000 to 781000, - fullLink = 149000 to 150000, - fastLinkGz = 91000 to 92000, + fastLink = 782000 to 783000, + fullLink = 150000 to 151000, + fastLinkGz = 92000 to 93000, fullLinkGz = 36000 to 37000, )) diff --git a/project/MultiScalaProject.scala b/project/MultiScalaProject.scala index 0714eaab16..e02c9ba9a6 100644 --- a/project/MultiScalaProject.scala +++ b/project/MultiScalaProject.scala @@ -79,7 +79,7 @@ object MultiScalaProject { private final val versions = Map[String, Seq[String]]( "2.11" -> Seq("2.11.12"), - "2.12" -> Seq("2.12.1", "2.12.2", "2.12.3", "2.12.4", "2.12.5", "2.12.6", "2.12.7", "2.12.8", "2.12.9", "2.12.10", "2.12.11", "2.12.12", "2.12.13"), + "2.12" -> Seq("2.12.1", "2.12.2", "2.12.3", "2.12.4", "2.12.5", "2.12.6", "2.12.7", "2.12.8", "2.12.9", "2.12.10", "2.12.11", "2.12.12", "2.12.13", "2.12.14"), "2.13" -> Seq("2.13.0", "2.13.1", "2.13.2", "2.13.3", "2.13.4", "2.13.5"), ) diff --git a/sbt-plugin/src/sbt-test/incremental/change-config-and-source/build.sbt b/sbt-plugin/src/sbt-test/incremental/change-config-and-source/build.sbt index 024fea3318..aba5223780 100644 --- a/sbt-plugin/src/sbt-test/incremental/change-config-and-source/build.sbt +++ b/sbt-plugin/src/sbt-test/incremental/change-config-and-source/build.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.12.13" +scalaVersion := "2.12.14" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/incremental/change-config/build.sbt b/sbt-plugin/src/sbt-test/incremental/change-config/build.sbt index 024fea3318..aba5223780 100644 --- a/sbt-plugin/src/sbt-test/incremental/change-config/build.sbt +++ b/sbt-plugin/src/sbt-test/incremental/change-config/build.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.12.13" +scalaVersion := "2.12.14" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/incremental/fix-compile-error/build.sbt b/sbt-plugin/src/sbt-test/incremental/fix-compile-error/build.sbt index 024fea3318..aba5223780 100644 --- a/sbt-plugin/src/sbt-test/incremental/fix-compile-error/build.sbt +++ b/sbt-plugin/src/sbt-test/incremental/fix-compile-error/build.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.12.13" +scalaVersion := "2.12.14" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/linker/concurrent-linker-use/build.sbt b/sbt-plugin/src/sbt-test/linker/concurrent-linker-use/build.sbt index 61ec96e17d..331e5f2aec 100644 --- a/sbt-plugin/src/sbt-test/linker/concurrent-linker-use/build.sbt +++ b/sbt-plugin/src/sbt-test/linker/concurrent-linker-use/build.sbt @@ -11,7 +11,7 @@ lazy val concurrentUseOfLinkerTest = taskKey[Any]("") name := "Scala.js sbt test" version := scalaJSVersion -scalaVersion := "2.12.13" +scalaVersion := "2.12.14" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/linker/custom-linker/build.sbt b/sbt-plugin/src/sbt-test/linker/custom-linker/build.sbt index 3d079c99b1..e8440d7cb7 100644 --- a/sbt-plugin/src/sbt-test/linker/custom-linker/build.sbt +++ b/sbt-plugin/src/sbt-test/linker/custom-linker/build.sbt @@ -13,14 +13,14 @@ inThisBuild(Def.settings( version := scalaJSVersion, - scalaVersion := "2.12.13", + scalaVersion := "2.12.14", )) lazy val check = taskKey[Any]("") lazy val customLinker = project.in(file("custom-linker")) .settings( - scalaVersion := "2.12.13", // needs to match sbt's version + scalaVersion := "2.12.14", // needs to match the minor version of Scala used by sbt libraryDependencies += "org.scala-js" %% "scalajs-linker" % scalaJSVersion, ) diff --git a/sbt-plugin/src/sbt-test/linker/no-root-dependency-resolution/build.sbt b/sbt-plugin/src/sbt-test/linker/no-root-dependency-resolution/build.sbt index 7122c28656..3f7b9c33ce 100644 --- a/sbt-plugin/src/sbt-test/linker/no-root-dependency-resolution/build.sbt +++ b/sbt-plugin/src/sbt-test/linker/no-root-dependency-resolution/build.sbt @@ -1,7 +1,7 @@ name := "Scala.js sbt test" version in ThisBuild := scalaJSVersion -scalaVersion in ThisBuild := "2.12.13" +scalaVersion in ThisBuild := "2.12.14" // Disable the IvyPlugin on the root project disablePlugins(sbt.plugins.IvyPlugin) diff --git a/sbt-plugin/src/sbt-test/linker/non-existent-classpath/build.sbt b/sbt-plugin/src/sbt-test/linker/non-existent-classpath/build.sbt index 9fed925fe8..81b9691eae 100644 --- a/sbt-plugin/src/sbt-test/linker/non-existent-classpath/build.sbt +++ b/sbt-plugin/src/sbt-test/linker/non-existent-classpath/build.sbt @@ -1,5 +1,5 @@ version := scalaJSVersion -scalaVersion := "2.12.13" +scalaVersion := "2.12.14" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/settings/cross-version/build.sbt b/sbt-plugin/src/sbt-test/settings/cross-version/build.sbt index 11a70133c4..b27d1988bd 100644 --- a/sbt-plugin/src/sbt-test/settings/cross-version/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/cross-version/build.sbt @@ -3,7 +3,7 @@ import org.scalajs.sbtplugin.ScalaJSCrossVersion val check = taskKey[Unit]("Run checks of this test") version := scalaJSVersion -scalaVersion := "2.12.13" +scalaVersion := "2.12.14" lazy val js = project.enablePlugins(ScalaJSPlugin).settings( check := { diff --git a/sbt-plugin/src/sbt-test/settings/legacy-link-empty/build.sbt b/sbt-plugin/src/sbt-test/settings/legacy-link-empty/build.sbt index 367bc6693f..c9ca9f5263 100644 --- a/sbt-plugin/src/sbt-test/settings/legacy-link-empty/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/legacy-link-empty/build.sbt @@ -1,4 +1,4 @@ version := scalaJSVersion -scalaVersion := "2.12.13" +scalaVersion := "2.12.14" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/settings/legacy-link-tasks/build.sbt b/sbt-plugin/src/sbt-test/settings/legacy-link-tasks/build.sbt index 1173c04ed0..289c969ea4 100644 --- a/sbt-plugin/src/sbt-test/settings/legacy-link-tasks/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/legacy-link-tasks/build.sbt @@ -1,7 +1,7 @@ val checkNoClosure = taskKey[Unit]("Check that fullOptJS wasn't run with closure") version := scalaJSVersion -scalaVersion := "2.12.13" +scalaVersion := "2.12.14" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/settings/module-init/build.sbt b/sbt-plugin/src/sbt-test/settings/module-init/build.sbt index f3f1715694..38039cbc09 100644 --- a/sbt-plugin/src/sbt-test/settings/module-init/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/module-init/build.sbt @@ -3,7 +3,7 @@ import org.scalajs.linker.interface.ModuleInitializer val check = taskKey[Unit]("Run checks of this test") version := scalaJSVersion -scalaVersion := "2.12.13" +scalaVersion := "2.12.14" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/settings/source-map/build.sbt b/sbt-plugin/src/sbt-test/settings/source-map/build.sbt index ef6de1d45b..2be732ca0b 100644 --- a/sbt-plugin/src/sbt-test/settings/source-map/build.sbt +++ b/sbt-plugin/src/sbt-test/settings/source-map/build.sbt @@ -3,7 +3,7 @@ import org.scalajs.linker.interface.ModuleInitializer val check = taskKey[Unit]("Run checks of this test") version := scalaJSVersion -scalaVersion := "2.12.13" +scalaVersion := "2.12.14" enablePlugins(ScalaJSPlugin) diff --git a/sbt-plugin/src/sbt-test/testing/multi-framework/build.sbt b/sbt-plugin/src/sbt-test/testing/multi-framework/build.sbt index edaf4afe43..f2ae42a727 100644 --- a/sbt-plugin/src/sbt-test/testing/multi-framework/build.sbt +++ b/sbt-plugin/src/sbt-test/testing/multi-framework/build.sbt @@ -1,5 +1,5 @@ inThisBuild(version := scalaJSVersion) -inThisBuild(scalaVersion := "2.12.13") +inThisBuild(scalaVersion := "2.12.14") lazy val root = project.in(file(".")). aggregate(multiTestJS, multiTestJVM) diff --git a/scala-test-suite/src/test/resources/2.12.14/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.14/BlacklistedTests.txt new file mode 100644 index 0000000000..96a6beba8b --- /dev/null +++ b/scala-test-suite/src/test/resources/2.12.14/BlacklistedTests.txt @@ -0,0 +1,197 @@ +## Do not compile +scala/lang/annotations/BytecodeTest.scala +scala/lang/annotations/RunTest.scala +scala/lang/traits/BytecodeTest.scala +scala/lang/traits/RunTest.scala +scala/lang/primitives/NaNTest.scala +scala/lang/primitives/BoxUnboxTest.scala +scala/collection/SeqTest.scala +scala/collection/Sizes.scala +scala/collection/immutable/HashMapTest.scala +scala/collection/immutable/HashSetTest.scala +scala/collection/immutable/ListMapTest.scala +scala/collection/immutable/MapHashcodeTest.scala +scala/collection/immutable/SetTest.scala +scala/collection/immutable/SeqTest.scala +scala/collection/immutable/SmallMapTest.scala +scala/collection/immutable/SortedMapTest.scala +scala/collection/immutable/SortedSetTest.scala +scala/collection/immutable/TreeMapTest.scala +scala/collection/immutable/TreeSetTest.scala +scala/collection/mutable/ListBufferTest.scala +scala/reflect/ClassOfTest.scala +scala/reflect/QTest.scala +scala/reflect/io/AbstractFileTest.scala +scala/reflect/io/ZipArchiveTest.scala +scala/reflect/internal/util/AbstractFileClassLoaderTest.scala +scala/reflect/internal/util/FileUtilsTest.scala +scala/reflect/internal/util/SourceFileTest.scala +scala/reflect/internal/util/StringOpsTest.scala +scala/reflect/internal/util/WeakHashSetTest.scala +scala/reflect/internal/LongNamesTest.scala +scala/reflect/internal/MirrorsTest.scala +scala/reflect/internal/NamesTest.scala +scala/reflect/internal/PositionsTest.scala +scala/reflect/internal/PrintersTest.scala +scala/reflect/internal/ScopeTest.scala +scala/reflect/internal/TreeGenTest.scala +scala/reflect/internal/TypesTest.scala +scala/reflect/macros/AttachmentsTest.scala +scala/reflect/runtime/ReflectionUtilsShowTest.scala +scala/reflect/runtime/ThreadSafetyTest.scala +scala/runtime/BooleanBoxingTest.scala +scala/runtime/ByteBoxingTest.scala +scala/runtime/CharBoxingTest.scala +scala/runtime/DoubleBoxingTest.scala +scala/runtime/IntBoxingTest.scala +scala/runtime/FloatBoxingTest.scala +scala/runtime/LongBoxingTest.scala +scala/runtime/ShortBoxingTest.scala +scala/tools/cmd/CommandLineParserTest.scala +scala/tools/nsc/Build.scala +scala/tools/nsc/DeterminismTest.scala +scala/tools/nsc/DeterminismTester.scala +scala/tools/nsc/FileUtils.scala +scala/tools/nsc/GlobalCustomizeClassloaderTest.scala +scala/tools/nsc/PickleWriteTest.scala +scala/tools/nsc/PipelineMainTest.scala +scala/tools/nsc/async/AnnotationDrivenAsync.scala +scala/tools/nsc/async/CustomFuture.scala +scala/tools/nsc/backend/jvm/BTypesTest.scala +scala/tools/nsc/backend/jvm/BytecodeTest.scala +scala/tools/nsc/backend/jvm/DefaultMethodTest.scala +scala/tools/nsc/backend/jvm/DirectCompileTest.scala +scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala +scala/tools/nsc/backend/jvm/IndyLambdaDirectTest.scala +scala/tools/nsc/backend/jvm/IndyLambdaTest.scala +scala/tools/nsc/backend/jvm/IndySammyTest.scala +scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala +scala/tools/nsc/backend/jvm/LineNumberTest.scala +scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala +scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala +scala/tools/nsc/backend/jvm/PerRunInitTest.scala +scala/tools/nsc/backend/jvm/StringConcatTest.scala +scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxAndInlineTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala +scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala +scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala +scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala +scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala +scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala +scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala +scala/tools/nsc/ScriptRunnerTest.scala +scala/tools/nsc/classpath/AggregateClassPathTest.scala +scala/tools/nsc/classpath/JrtClassPathTest.scala +scala/tools/nsc/classpath/MultiReleaseJarTest.scala +scala/tools/nsc/classpath/PathResolverBaseTest.scala +scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala +scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala +scala/tools/nsc/doc/html/HtmlDocletTest.scala +scala/tools/nsc/interpreter/CompletionTest.scala +scala/tools/nsc/interpreter/ScriptedTest.scala +scala/tools/nsc/interpreter/TabulatorTest.scala +scala/tools/nsc/parser/ParserTest.scala +scala/tools/nsc/reporters/ConsoleReporterTest.scala +scala/tools/nsc/reporters/WConfTest.scala +scala/tools/nsc/settings/ScalaVersionTest.scala +scala/tools/nsc/settings/SettingsTest.scala +scala/tools/nsc/symtab/CannotHaveAttrsTest.scala +scala/tools/nsc/symtab/FlagsTest.scala +scala/tools/nsc/symtab/FreshNameExtractorTest.scala +scala/tools/nsc/symtab/StdNamesTest.scala +scala/tools/nsc/symtab/SymbolLoadersAssociatedFileTest.scala +scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala +scala/tools/nsc/symtab/SymbolTableTest.scala +scala/tools/nsc/symtab/classfile/PicklerTest.scala +scala/tools/nsc/transform/MixinTest.scala +scala/tools/nsc/transform/SpecializationTest.scala +scala/tools/nsc/transform/ThicketTransformerTest.scala +scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala +scala/tools/nsc/transform/patmat/SolvingTest.scala +scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala +scala/tools/nsc/typechecker/Implicits.scala +scala/tools/nsc/typechecker/NamerTest.scala +scala/tools/nsc/typechecker/ParamAliasTest.scala +scala/tools/nsc/typechecker/TypedTreeTest.scala +scala/tools/nsc/util/StackTraceTest.scala +scala/tools/testing/AllocationTest.scala +scala/tools/testing/BytecodeTesting.scala +scala/tools/testing/JOL.scala +scala/tools/testing/RunTesting.scala +scala/tools/testing/VirtualCompilerTesting.scala + +## Do not link +scala/MatchErrorSerializationTest.scala +scala/PartialFunctionSerializationTest.scala +scala/lang/stringinterpol/StringContextTest.scala +scala/collection/IteratorTest.scala +scala/collection/NewBuilderTest.scala +scala/collection/ParallelConsistencyTest.scala +scala/collection/SetMapRulesTest.scala +scala/collection/SeqViewTest.scala +scala/collection/SetMapConsistencyTest.scala +scala/collection/concurrent/TrieMapTest.scala +scala/collection/convert/WrapperSerializationTest.scala +scala/collection/immutable/ListTest.scala +scala/collection/immutable/RedBlackTreeSerialFormat.scala +scala/collection/immutable/StreamTest.scala +scala/collection/immutable/StringLikeTest.scala +scala/collection/mutable/AnyRefMapTest.scala +scala/collection/mutable/ArrayBufferTest.scala +scala/collection/mutable/MutableListTest.scala +scala/collection/mutable/OpenHashMapTest.scala +scala/collection/mutable/PriorityQueueTest.scala +scala/collection/parallel/TaskTest.scala +scala/collection/parallel/immutable/ParRangeTest.scala +scala/concurrent/FutureTest.scala +scala/concurrent/duration/SerializationTest.scala +scala/concurrent/impl/DefaultPromiseTest.scala +scala/io/SourceTest.scala +scala/runtime/ScalaRunTimeTest.scala +scala/sys/process/PipedProcessTest.scala +scala/sys/process/ProcessTest.scala +scala/tools/testing/AssertUtilTest.scala +scala/tools/testing/AssertThrowsTest.scala +scala/util/SpecVersionTest.scala +scala/util/SystemPropertiesTest.scala + +## Tests fail + +# Reflection +scala/reflect/ClassTagTest.scala + +# Regex +scala/util/matching/CharRegexTest.scala +scala/util/matching/RegexTest.scala + +# Require strict-floats +scala/math/BigDecimalTest.scala + +# Difference of getClass() on primitive values +scala/collection/immutable/RangeTest.scala + +# Test fails only some times with +# 'set scalaJSOptimizerOptions in scalaTestSuite ~= (_.withDisableOptimizer(true))' +# and' 'set scalaJSUseRhino in Global := false' +scala/collection/immutable/PagedSeqTest.scala + +# Bugs +scala/collection/convert/MapWrapperTest.scala + +# Tests passed but are too slow (timeouts) +scala/collection/immutable/ListSetTest.scala +scala/util/SortingTest.scala From 4a38ac78e153ab2ba3bfc88f755ef0179c8bc330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 9 Jun 2021 18:04:54 +0200 Subject: [PATCH 07/48] Upgrade to Scala 2.13.6. --- Jenkinsfile | 7 +- .../{2.13.5 => 2.13.6}/BlacklistedTests.txt | 15 +- .../{2.13.5 => 2.13.6}/neg/t11952b.check | 0 .../neg/t6446-additional.check | 0 .../{2.13.5 => 2.13.6}/neg/t6446-list.check | 0 .../neg/t6446-missing.check | 0 .../neg/t6446-show-phases.check | 0 .../neg/t7494-no-options.check | 0 .../run/Course-2002-01.check | 0 .../run/Course-2002-02.check | 0 .../run/Course-2002-04.check | 0 .../run/Course-2002-08.check | 0 .../run/Course-2002-09.check | 0 .../run/Course-2002-10.check | 0 .../{2.13.5 => 2.13.6}/run/Meter.check | 0 .../run/MeterCaseClass.check | 0 .../run/anyval-box-types.check | 0 .../scalajs/{2.13.5 => 2.13.6}/run/bugs.sem | 0 .../run/caseClassHash.check | 0 .../{2.13.5 => 2.13.6}/run/classof.check | 0 .../{2.13.5 => 2.13.6}/run/deeps.check | 0 .../run/dynamic-anyval.check | 0 .../{2.13.5 => 2.13.6}/run/impconvtimes.check | 0 .../{2.13.5 => 2.13.6}/run/imports.check | 0 .../run/interpolation.check | 6 + .../run/interpolationMultiline1.check | 0 .../{2.13.5 => 2.13.6}/run/issue192.sem | 0 .../run/macro-bundle-static.check | 0 .../run/macro-bundle-toplevel.check | 0 .../run/macro-bundle-whitebox-decl.check | 0 ...expand-varargs-implicit-over-varargs.check | 0 .../scalajs/{2.13.5 => 2.13.6}/run/misc.check | 0 .../{2.13.5 => 2.13.6}/run/promotion.check | 0 .../{2.13.5 => 2.13.6}/run/runtime.check | 0 .../run/sammy_vararg_cbn.check | 0 .../{2.13.5 => 2.13.6}/run/spec-self.check | 0 .../run/string-switch.check | 0 .../{2.13.5 => 2.13.6}/run/structural.check | 0 .../{2.13.5 => 2.13.6}/run/t0421-new.check | 0 .../{2.13.5 => 2.13.6}/run/t0421-old.check | 0 .../scalajs/{2.13.5 => 2.13.6}/run/t1503.sem | 0 .../{2.13.5 => 2.13.6}/run/t3702.check | 0 .../scalajs/{2.13.5 => 2.13.6}/run/t4148.sem | 0 .../{2.13.5 => 2.13.6}/run/t4617.check | 0 .../{2.13.5 => 2.13.6}/run/t5356.check | 0 .../{2.13.5 => 2.13.6}/run/t5552.check | 0 .../{2.13.5 => 2.13.6}/run/t5568.check | 0 .../{2.13.5 => 2.13.6}/run/t5629b.check | 0 .../{2.13.5 => 2.13.6}/run/t5680.check | 0 .../{2.13.5 => 2.13.6}/run/t5866.check | 0 .../{2.13.5 => 2.13.6}/run/t5966.check | 0 .../{2.13.5 => 2.13.6}/run/t6265.check | 0 .../run/t6318_primitives.check | 0 .../{2.13.5 => 2.13.6}/run/t6662.check | 0 .../{2.13.5 => 2.13.6}/run/t7657.check | 0 .../scalajs/{2.13.5 => 2.13.6}/run/t7763.sem | 0 .../{2.13.5 => 2.13.6}/run/t8570a.check | 0 .../{2.13.5 => 2.13.6}/run/t8764.check | 0 .../{2.13.5 => 2.13.6}/run/t9387b.check | 0 .../run/try-catch-unify.check | 0 .../run/virtpatmat_switch.check | 0 .../run/virtpatmat_typetag.check | 0 project/Build.scala | 6 +- project/MultiScalaProject.scala | 2 +- .../src/sbt-test/cross-version/2.13/build.sbt | 2 +- .../resources/2.13.6/BlacklistedTests.txt | 224 ++++++++++++++++++ 66 files changed, 250 insertions(+), 12 deletions(-) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/BlacklistedTests.txt (99%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/neg/t11952b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/neg/t6446-additional.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/neg/t6446-list.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/neg/t6446-missing.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/neg/t6446-show-phases.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/neg/t7494-no-options.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/Course-2002-01.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/Course-2002-02.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/Course-2002-04.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/Course-2002-08.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/Course-2002-09.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/Course-2002-10.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/Meter.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/MeterCaseClass.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/anyval-box-types.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/bugs.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/caseClassHash.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/classof.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/deeps.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/dynamic-anyval.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/impconvtimes.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/imports.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/interpolation.check (86%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/interpolationMultiline1.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/issue192.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/macro-bundle-static.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/macro-bundle-toplevel.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/macro-bundle-whitebox-decl.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/macro-expand-varargs-implicit-over-varargs.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/misc.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/promotion.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/runtime.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/sammy_vararg_cbn.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/spec-self.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/string-switch.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/structural.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t0421-new.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t0421-old.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t1503.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t3702.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t4148.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t4617.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t5356.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t5552.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t5568.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t5629b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t5680.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t5866.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t5966.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t6265.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t6318_primitives.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t6662.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t7657.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t7763.sem (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t8570a.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t8764.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/t9387b.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/try-catch-unify.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/virtpatmat_switch.check (100%) rename partest-suite/src/test/resources/scala/tools/partest/scalajs/{2.13.5 => 2.13.6}/run/virtpatmat_typetag.check (100%) create mode 100644 scala-test-suite/src/test/resources/2.13.6/BlacklistedTests.txt diff --git a/Jenkinsfile b/Jenkinsfile index 09c75f9e64..986ea834db 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -409,7 +409,7 @@ def allJavaVersions = otherJavaVersions.clone() allJavaVersions << mainJavaVersion def mainScalaVersion = "2.12.14" -def mainScalaVersions = ["2.11.12", "2.12.14", "2.13.5"] +def mainScalaVersions = ["2.11.12", "2.12.14", "2.13.6"] def otherScalaVersions = [ "2.11.12", "2.12.1", @@ -429,7 +429,8 @@ def otherScalaVersions = [ "2.13.1", "2.13.2", "2.13.3", - "2.13.4" + "2.13.4", + "2.13.5" ] def allESVersions = [ @@ -461,7 +462,7 @@ allESVersions.each { esVersion -> allJavaVersions.each { javaVersion -> quickMatrix.add([task: "tools-sbtplugin", scala: "2.12.14", java: javaVersion]) quickMatrix.add([task: "tools", scala: "2.11.12", java: javaVersion]) - quickMatrix.add([task: "tools", scala: "2.13.5", java: javaVersion]) + quickMatrix.add([task: "tools", scala: "2.13.6", java: javaVersion]) } // The 'full' matrix diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/BlacklistedTests.txt b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/BlacklistedTests.txt similarity index 99% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/BlacklistedTests.txt rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/BlacklistedTests.txt index eb485f2111..bfc3dce90f 100644 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/BlacklistedTests.txt +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/BlacklistedTests.txt @@ -674,6 +674,8 @@ run/inlineHandlers.scala run/optimizer-array-load.scala run/t6827.scala run/t8601.scala +run/t5887.scala +run/t11534c.scala # Expecting exceptions that are linking errors in Scala.js (e.g. NoSuchMethodException) run/t10334.scala @@ -824,6 +826,8 @@ run/t11899.scala run/t11915.scala run/t11991.scala run/t12276.scala +run/interpolation-repl.scala +run/t12292.scala # Using Scala Script (partest.ScriptTest) @@ -859,7 +863,6 @@ run/settings-parse.scala run/sm-interpolator.scala run/t1501.scala run/t1500.scala -run/sammy_java8.scala run/t1618.scala run/t2464 run/t4072.scala @@ -875,11 +878,9 @@ run/t6745-2.scala run/t7096.scala run/t7271.scala run/t7337.scala -run/t7398.scala run/t7569.scala run/t7852.scala run/t7817-tree-gen.scala -run/t7825.scala run/extend-global.scala run/nowarn.scala @@ -946,6 +947,9 @@ run/t11385.scala run/t11731.scala run/t11746.scala run/t11815.scala +run/splain.scala +run/splain-tree.scala +run/splain-truncrefined.scala # Using partest.StoreReporterDirectTest run/t10171 @@ -962,7 +966,6 @@ run/StubErrorTypeclass.scala run/StubErrorTypeDef.scala # partest.CompilerTest -run/t8852a.scala run/t12062.scala # partest.ASMConverters @@ -1082,6 +1085,7 @@ run/t12019 run/t12038a run/t12038b run/t12195 +run/t12380 # Using scala-script run/t7791-script-linenums.scala @@ -1115,6 +1119,9 @@ run/indy-via-macro-with-dynamic-args # Crashes our optimizer because of artificially insane amount of inlining run/t10594.scala +# Relies on a main() method that does not return Unit +run/t7448.scala + ### Incorrect partests ### # Badly uses constract of Console.print (no flush) run/t429.scala diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/neg/t11952b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t11952b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/neg/t11952b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t11952b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/neg/t6446-additional.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-additional.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/neg/t6446-additional.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-additional.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/neg/t6446-list.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-list.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/neg/t6446-list.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-list.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/neg/t6446-missing.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-missing.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/neg/t6446-missing.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-missing.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/neg/t6446-show-phases.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-show-phases.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/neg/t6446-show-phases.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t6446-show-phases.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/neg/t7494-no-options.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t7494-no-options.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/neg/t7494-no-options.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/neg/t7494-no-options.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Course-2002-01.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-01.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Course-2002-01.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-01.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Course-2002-02.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-02.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Course-2002-02.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-02.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Course-2002-04.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-04.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Course-2002-04.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-04.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Course-2002-08.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-08.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Course-2002-08.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-08.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Course-2002-09.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-09.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Course-2002-09.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-09.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Course-2002-10.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-10.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Course-2002-10.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Course-2002-10.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Meter.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Meter.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/Meter.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/Meter.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/MeterCaseClass.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/MeterCaseClass.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/MeterCaseClass.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/MeterCaseClass.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/anyval-box-types.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/anyval-box-types.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/anyval-box-types.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/anyval-box-types.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/bugs.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/bugs.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/bugs.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/bugs.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/caseClassHash.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/caseClassHash.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/caseClassHash.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/caseClassHash.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/classof.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/classof.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/classof.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/classof.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/deeps.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/deeps.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/deeps.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/deeps.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/dynamic-anyval.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/dynamic-anyval.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/dynamic-anyval.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/dynamic-anyval.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/impconvtimes.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/impconvtimes.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/impconvtimes.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/impconvtimes.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/imports.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/imports.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/imports.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/imports.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/interpolation.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/interpolation.check similarity index 86% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/interpolation.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/interpolation.check index 9c4a77715b..2b13e6ba43 100644 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/interpolation.check +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/interpolation.check @@ -30,3 +30,9 @@ Best price: 13.35 0 00 +"everybody loves escaped quotes" is a common sentiment. +hi"$" +hi"$" +hi"$" +hi"$" +hi"$" diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/interpolationMultiline1.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/interpolationMultiline1.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/interpolationMultiline1.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/interpolationMultiline1.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/issue192.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/issue192.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/issue192.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/issue192.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/macro-bundle-static.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-bundle-static.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/macro-bundle-static.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-bundle-static.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/macro-bundle-toplevel.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-bundle-toplevel.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/macro-bundle-toplevel.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-bundle-toplevel.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/macro-bundle-whitebox-decl.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-bundle-whitebox-decl.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/macro-bundle-whitebox-decl.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-bundle-whitebox-decl.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/macro-expand-varargs-implicit-over-varargs.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-expand-varargs-implicit-over-varargs.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/macro-expand-varargs-implicit-over-varargs.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/macro-expand-varargs-implicit-over-varargs.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/misc.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/misc.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/misc.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/misc.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/promotion.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/promotion.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/promotion.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/promotion.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/runtime.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/runtime.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/runtime.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/runtime.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/sammy_vararg_cbn.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/sammy_vararg_cbn.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/sammy_vararg_cbn.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/sammy_vararg_cbn.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/spec-self.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/spec-self.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/spec-self.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/spec-self.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/string-switch.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/string-switch.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/string-switch.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/string-switch.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/structural.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/structural.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/structural.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/structural.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t0421-new.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t0421-new.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t0421-new.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t0421-new.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t0421-old.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t0421-old.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t0421-old.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t0421-old.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t1503.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t1503.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t1503.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t1503.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t3702.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t3702.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t3702.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t3702.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t4148.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t4148.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t4148.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t4148.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t4617.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t4617.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t4617.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t4617.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5356.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5356.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5356.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5356.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5552.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5552.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5552.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5552.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5568.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5568.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5568.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5568.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5629b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5629b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5629b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5629b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5680.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5680.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5680.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5680.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5866.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5866.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5866.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5866.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5966.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5966.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t5966.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t5966.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t6265.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t6265.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t6265.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t6265.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t6318_primitives.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t6318_primitives.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t6318_primitives.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t6318_primitives.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t6662.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t6662.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t6662.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t6662.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t7657.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t7657.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t7657.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t7657.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t7763.sem b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t7763.sem similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t7763.sem rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t7763.sem diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t8570a.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t8570a.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t8570a.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t8570a.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t8764.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t8764.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t8764.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t8764.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t9387b.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t9387b.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/t9387b.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/t9387b.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/try-catch-unify.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/try-catch-unify.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/try-catch-unify.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/try-catch-unify.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/virtpatmat_switch.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/virtpatmat_switch.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/virtpatmat_switch.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/virtpatmat_switch.check diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/virtpatmat_typetag.check b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/virtpatmat_typetag.check similarity index 100% rename from partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.5/run/virtpatmat_typetag.check rename to partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/run/virtpatmat_typetag.check diff --git a/project/Build.scala b/project/Build.scala index 3d7512d9c1..4f1e36ee98 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -241,7 +241,7 @@ object Build { val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") val scalaVersionsUsedForPublishing: Set[String] = - Set("2.11.12", "2.12.14", "2.13.5") + Set("2.11.12", "2.12.14", "2.13.6") val newScalaBinaryVersionsInThisRelease: Set[String] = Set() @@ -1682,9 +1682,9 @@ object Build { fullLinkGz = 36000 to 37000, )) - case "2.13.5" => + case "2.13.6" => Some(ExpectedSizes( - fastLink = 776000 to 777000, + fastLink = 777000 to 778000, fullLink = 169000 to 170000, fastLinkGz = 98000 to 99000, fullLinkGz = 43000 to 44000, diff --git a/project/MultiScalaProject.scala b/project/MultiScalaProject.scala index e02c9ba9a6..25dec79af6 100644 --- a/project/MultiScalaProject.scala +++ b/project/MultiScalaProject.scala @@ -80,7 +80,7 @@ object MultiScalaProject { private final val versions = Map[String, Seq[String]]( "2.11" -> Seq("2.11.12"), "2.12" -> Seq("2.12.1", "2.12.2", "2.12.3", "2.12.4", "2.12.5", "2.12.6", "2.12.7", "2.12.8", "2.12.9", "2.12.10", "2.12.11", "2.12.12", "2.12.13", "2.12.14"), - "2.13" -> Seq("2.13.0", "2.13.1", "2.13.2", "2.13.3", "2.13.4", "2.13.5"), + "2.13" -> Seq("2.13.0", "2.13.1", "2.13.2", "2.13.3", "2.13.4", "2.13.5", "2.13.6"), ) private final val ideVersion = "2.12" diff --git a/sbt-plugin/src/sbt-test/cross-version/2.13/build.sbt b/sbt-plugin/src/sbt-test/cross-version/2.13/build.sbt index 4d1c5149dc..41e3af019d 100644 --- a/sbt-plugin/src/sbt-test/cross-version/2.13/build.sbt +++ b/sbt-plugin/src/sbt-test/cross-version/2.13/build.sbt @@ -2,6 +2,6 @@ enablePlugins(ScalaJSPlugin) enablePlugins(ScalaJSJUnitPlugin) version := scalaJSVersion -scalaVersion := "2.13.5" +scalaVersion := "2.13.6" scalaJSUseMainModuleInitializer := true diff --git a/scala-test-suite/src/test/resources/2.13.6/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.6/BlacklistedTests.txt new file mode 100644 index 0000000000..2fdcb65859 --- /dev/null +++ b/scala-test-suite/src/test/resources/2.13.6/BlacklistedTests.txt @@ -0,0 +1,224 @@ +## Do not compile +scala/ExtractorTest.scala +scala/OptionTest.scala +scala/SerializationStabilityTest.scala +scala/StringTest.scala +scala/collection/FactoriesTest.scala +scala/collection/LazyZipOpsTest.scala +scala/collection/SeqTest.scala +scala/collection/immutable/HashMapTest.scala +scala/collection/immutable/HashSetTest.scala +scala/collection/immutable/IndexedSeqTest.scala +scala/collection/immutable/IntMapTest.scala +scala/collection/immutable/ListMapTest.scala +scala/collection/immutable/LongMapTest.scala +scala/collection/immutable/MapHashcodeTest.scala +scala/collection/immutable/SeqTest.scala +scala/collection/immutable/SmallMapTest.scala +scala/collection/immutable/SortedMapTest.scala +scala/collection/immutable/SortedSetTest.scala +scala/collection/immutable/TreeMapTest.scala +scala/collection/immutable/TreeSetTest.scala +scala/lang/annotations/BytecodeTest.scala +scala/lang/annotations/RunTest.scala +scala/lang/traits/BytecodeTest.scala +scala/lang/traits/RunTest.scala +scala/lang/primitives/NaNTest.scala +scala/reflect/ClassOfTest.scala +scala/reflect/FieldAccessTest.scala +scala/reflect/QTest.scala +scala/reflect/macros/AttachmentsTest.scala +scala/reflect/io/ZipArchiveTest.scala +scala/reflect/internal/InferTest.scala +scala/reflect/internal/LongNamesTest.scala +scala/reflect/internal/MirrorsTest.scala +scala/reflect/internal/NamesTest.scala +scala/reflect/internal/PositionsTest.scala +scala/reflect/internal/PrintersTest.scala +scala/reflect/internal/ScopeTest.scala +scala/reflect/internal/SubstMapTest.scala +scala/reflect/internal/TreeGenTest.scala +scala/reflect/internal/TypesTest.scala +scala/reflect/internal/util/AbstractFileClassLoaderTest.scala +scala/reflect/internal/util/FileUtilsTest.scala +scala/reflect/internal/util/SourceFileTest.scala +scala/reflect/internal/util/StringOpsTest.scala +scala/reflect/internal/util/WeakHashSetTest.scala +scala/reflect/io/AbstractFileTest.scala +scala/reflect/runtime/ThreadSafetyTest.scala +scala/runtime/BooleanBoxingTest.scala +scala/runtime/ByteBoxingTest.scala +scala/runtime/CharBoxingTest.scala +scala/runtime/DoubleBoxingTest.scala +scala/runtime/IntBoxingTest.scala +scala/runtime/FloatBoxingTest.scala +scala/runtime/LongBoxingTest.scala +scala/runtime/ShortBoxingTest.scala +scala/tools/cmd/CommandLineParserTest.scala +scala/tools/nsc/Build.scala +scala/tools/nsc/DeterminismTest.scala +scala/tools/nsc/DeterminismTester.scala +scala/tools/nsc/FileUtils.scala +scala/tools/nsc/GlobalCustomizeClassloaderTest.scala +scala/tools/nsc/PhaseAssemblyTest.scala +scala/tools/nsc/PickleWriteTest.scala +scala/tools/nsc/PipelineMainTest.scala +scala/tools/nsc/ScriptRunnerTest.scala +scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala +scala/tools/nsc/async/CustomFuture.scala +scala/tools/nsc/backend/jvm/BTypesTest.scala +scala/tools/nsc/backend/jvm/BytecodeTest.scala +scala/tools/nsc/backend/jvm/DefaultMethodTest.scala +scala/tools/nsc/backend/jvm/DirectCompileTest.scala +scala/tools/nsc/backend/jvm/GenericSignaturesTest.scala +scala/tools/nsc/backend/jvm/IndyLambdaTest.scala +scala/tools/nsc/backend/jvm/IndySammyTest.scala +scala/tools/nsc/backend/jvm/InnerClassAttributeTest.scala +scala/tools/nsc/backend/jvm/LineNumberTest.scala +scala/tools/nsc/backend/jvm/NestedClassesCollectorTest.scala +scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala +scala/tools/nsc/backend/jvm/PerRunInitTest.scala +scala/tools/nsc/backend/jvm/StringConcatTest.scala +scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/TypeFlowAnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala +scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxAndInlineTest.scala +scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala +scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala +scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala +scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala +scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala +scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +scala/tools/nsc/backend/jvm/opt/InlineSourceMatcherTest.scala +scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala +scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala +scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala +scala/tools/nsc/classpath/AggregateClassPathTest.scala +scala/tools/nsc/classpath/JrtClassPathTest.scala +scala/tools/nsc/classpath/MultiReleaseJarTest.scala +scala/tools/nsc/classpath/PathResolverBaseTest.scala +scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala +scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala +scala/tools/nsc/doc/html/HtmlDocletTest.scala +scala/tools/nsc/doc/html/StringLiteralTest.scala +scala/tools/nsc/interpreter/CompletionTest.scala +scala/tools/nsc/interpreter/ScriptedTest.scala +scala/tools/nsc/interpreter/TabulatorTest.scala +scala/tools/nsc/parser/ParserTest.scala +scala/tools/nsc/reporters/ConsoleReporterTest.scala +scala/tools/nsc/reporters/PositionFilterTest.scala +scala/tools/nsc/reporters/WConfTest.scala +scala/tools/nsc/settings/ScalaVersionTest.scala +scala/tools/nsc/settings/SettingsTest.scala +scala/tools/nsc/settings/TargetTest.scala +scala/tools/nsc/symtab/CannotHaveAttrsTest.scala +scala/tools/nsc/symtab/FlagsTest.scala +scala/tools/nsc/symtab/FreshNameExtractorTest.scala +scala/tools/nsc/symtab/StdNamesTest.scala +scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala +scala/tools/nsc/symtab/SymbolTableTest.scala +scala/tools/nsc/symtab/classfile/PicklerTest.scala +scala/tools/nsc/transform/ErasureTest.scala +scala/tools/nsc/transform/MixinTest.scala +scala/tools/nsc/transform/ReleaseFenceTest.scala +scala/tools/nsc/transform/SpecializationTest.scala +scala/tools/nsc/transform/ThicketTransformerTest.scala +scala/tools/nsc/transform/UncurryTest.scala +scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala +scala/tools/nsc/transform/patmat/SolvingTest.scala +scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala +scala/tools/nsc/typechecker/ConstantFolderTest.scala +scala/tools/nsc/typechecker/ImplicitsTest.scala +scala/tools/nsc/typechecker/InferencerTest.scala +scala/tools/nsc/typechecker/NamerTest.scala +scala/tools/nsc/typechecker/OverridingPairsTest.scala +scala/tools/nsc/typechecker/ParamAliasTest.scala +scala/tools/nsc/typechecker/TypedTreeTest.scala +scala/tools/nsc/util/StackTraceTest.scala +scala/tools/testkit/ReflectUtilTest.scala +scala/util/ChainingOpsTest.scala + +## Do not link +scala/CollectTest.scala +scala/MatchErrorSerializationTest.scala +scala/PartialFunctionSerializationTest.scala +scala/lang/stringinterpol/StringContextTest.scala +scala/collection/IterableTest.scala +scala/collection/IteratorTest.scala +scala/collection/NewBuilderTest.scala +scala/collection/SeqViewTest.scala +scala/collection/SetMapConsistencyTest.scala +scala/collection/SetMapRulesTest.scala +scala/collection/Sizes.scala +scala/collection/concurrent/TrieMapTest.scala +scala/collection/convert/WrapperSerializationTest.scala +scala/collection/immutable/ChampMapSmokeTest.scala +scala/collection/immutable/ChampSetSmokeTest.scala +scala/collection/immutable/LazyListGCTest.scala +scala/collection/immutable/LazyListLazinessTest.scala +scala/collection/immutable/ListTest.scala +scala/collection/immutable/SerializationTest.scala +scala/collection/immutable/StreamTest.scala +scala/collection/immutable/StringLikeTest.scala +scala/collection/immutable/VectorTest.scala +scala/collection/mutable/AnyRefMapTest.scala +scala/collection/mutable/ArrayBufferTest.scala +scala/collection/mutable/ListBufferTest.scala +scala/collection/mutable/OpenHashMapTest.scala +scala/collection/mutable/PriorityQueueTest.scala +scala/collection/mutable/SerializationTest.scala +scala/concurrent/FutureTest.scala +scala/concurrent/duration/SerializationTest.scala +scala/concurrent/impl/DefaultPromiseTest.scala +scala/io/SourceTest.scala +scala/jdk/AccumulatorTest.scala +scala/jdk/DurationConvertersTest.scala +scala/jdk/FunctionConvertersTest.scala +scala/jdk/OptionConvertersTest.scala +scala/jdk/StepperConversionTest.scala +scala/jdk/StepperTest.scala +scala/jdk/StreamConvertersTest.scala +scala/jdk/StreamConvertersTypingTest.scala +scala/math/OrderingTest.scala +scala/runtime/ScalaRunTimeTest.scala +scala/sys/env.scala +scala/sys/process/ParserTest.scala +scala/sys/process/PipedProcessTest.scala +scala/sys/process/ProcessBuilderTest.scala +scala/sys/process/ProcessTest.scala +scala/tools/testkit/AssertUtilTest.scala +scala/util/PropertiesTest.scala +scala/util/SpecVersionTest.scala +scala/util/SystemPropertiesTest.scala + +## Tests fail + +# Reflection +scala/reflect/ClassTagTest.scala + +# Regex +scala/util/matching/CharRegexTest.scala +scala/util/matching/RegexTest.scala + +# Require strict-floats +scala/math/BigDecimalTest.scala + +# Tests passed but are too slow (timeouts) +scala/collection/immutable/ListSetTest.scala +scala/util/SortingTest.scala + +# Relies on undefined behavior +scala/collection/MapTest.scala +scala/collection/StringOpsTest.scala +scala/collection/StringParsersTest.scala +scala/collection/convert/CollectionConvertersTest.scala +scala/collection/convert/MapWrapperTest.scala From 6a632ceb4a8a1cb1c0323a4481eeebee41c77130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 10 Jun 2021 15:58:48 +0200 Subject: [PATCH 08/48] Upgrade to GCC v20210601. JSModule was renamed to JSChunk. Number nodes cannot contain NaNs, Infinities nor negative values anymore. Instead, we have to create NAME nodes for `NaN` and `Infinity`, and NEG nodes for negative values. --- .../closure/ClosureAstTransformer.scala | 26 +++++++++++++++++-- .../closure/ClosureLinkerBackend.scala | 18 ++++++------- project/Build.scala | 2 +- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala index e994e2c7c9..39b8b74cb0 100644 --- a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala +++ b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureAstTransformer.scala @@ -26,6 +26,7 @@ import com.google.javascript.jscomp.parsing.parser.FeatureSet import scala.annotation.tailrec +import java.lang.{Double => JDouble} import java.net.URI private[closure] object ClosureAstTransformer { @@ -372,9 +373,9 @@ private class ClosureAstTransformer(featureSet: FeatureSet, case BooleanLiteral(value) => if (value) new Node(Token.TRUE) else new Node(Token.FALSE) case IntLiteral(value) => - Node.newNumber(value) + mkNumberLiteral(value) case DoubleLiteral(value) => - Node.newNumber(value) + mkNumberLiteral(value) case StringLiteral(value) => Node.newString(value) case VarRef(ident) => @@ -541,6 +542,27 @@ private class ClosureAstTransformer(featureSet: FeatureSet, } // Helpers for IR + + private def mkNumberLiteral(value: Double)(implicit pos: Position): Node = { + /* Since GCC v20210601, Number nodes can only hold finite non-negative + * values. NaNs and Infinities must be represented as text nodes, and + * negative values as a NEG unary operator of a positive value. + */ + + if (JDouble.isNaN(value)) { + setNodePosition(Node.newString(Token.NAME, "NaN"), pos) + } else { + val absValueNode = + if (JDouble.isInfinite(value)) Node.newString(Token.NAME, "Infinity") + else Node.newNumber(Math.abs(value)) + val positionedAbsValueNode = setNodePosition(absValueNode, pos) + if (value < 0.0 || (value == 0.0 && 1.0 / value < 0.0)) + setNodePosition(new Node(Token.NEG, positionedAbsValueNode), pos) + else + positionedAbsValueNode + } + } + @inline private def mkUnaryOp(op: UnaryOp.Code, lhs: Node): Node = { import ir.Trees.JSUnaryOp._ diff --git a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala index b6dc6486b4..69a21f9ca0 100644 --- a/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala +++ b/linker/jvm/src/main/scala/org/scalajs/linker/backend/closure/ClosureLinkerBackend.scala @@ -101,8 +101,8 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) val gccResult = for { sjsModule <- moduleSet.modules.headOption } yield { - val closureModule = logger.time("Closure: Create trees)") { - buildModule(emitterResult.body(sjsModule.id)) + val closureChunk = logger.time("Closure: Create trees)") { + buildChunk(emitterResult.body(sjsModule.id)) } logger.time("Closure: Compiler pass") { @@ -116,7 +116,7 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) ClosureSource.fromCode("ScalaJSExportExterns.js", makeExternsForExports(emitterResult.topLevelVarDecls, sjsModule))) - compile(externs, closureModule, options, logger) + compile(externs, closureChunk, options, logger) } } @@ -125,23 +125,23 @@ final class ClosureLinkerBackend(config: LinkerBackendImpl.Config) } } - private def buildModule(tree: js.Tree): JSModule = { + private def buildChunk(tree: js.Tree): JSChunk = { val root = ClosureAstTransformer.transformScript(tree, languageMode.toFeatureSet(), config.relativizeSourceMapBase) - val module = new JSModule("Scala.js") - module.add(new CompilerInput(new SyntheticAst(root))) - module + val chunk = new JSChunk("Scala.js") + chunk.add(new CompilerInput(new SyntheticAst(root))) + chunk } - private def compile(externs: java.util.List[ClosureSource], module: JSModule, + private def compile(externs: java.util.List[ClosureSource], chunk: JSChunk, options: ClosureOptions, logger: Logger) = { val compiler = new ClosureCompiler compiler.setErrorManager(new SortingErrorManager(new HashSet(Arrays.asList( new LoggerErrorReportGenerator(logger))))) val result = - compiler.compileModules(externs, Arrays.asList(module), options) + compiler.compileModules(externs, Arrays.asList(chunk), options) if (!result.success) { throw new LinkingException( diff --git a/project/Build.scala b/project/Build.scala index ab5f94321a..4bf02290ec 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -927,7 +927,7 @@ object Build { commonLinkerSettings _ ).settings( libraryDependencies ++= Seq( - "com.google.javascript" % "closure-compiler" % "v20210406", + "com.google.javascript" % "closure-compiler" % "v20210601", "com.google.jimfs" % "jimfs" % "1.1" % "test" ) ++ ( parallelCollectionsDependencies(scalaVersion.value) From 65a16827c79f43c44850534755bba4ed27910f49 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 12 Jun 2021 18:21:19 +0200 Subject: [PATCH 09/48] Fix #4511: Report an error on duplicate non-static getters --- .../org/scalajs/nscplugin/GenJSExports.scala | 32 ++++++++--------- .../scalajs/nscplugin/test/JSExportTest.scala | 10 +++--- .../nscplugin/test/NonNativeJSTypeTest.scala | 34 +++++++++++++++++++ 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala index c8ac8d5ff8..344cbd0003 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala @@ -297,17 +297,8 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { val (getter, setters) = alts.partition(_.tpe.params.isEmpty) // We can have at most one getter - if (getter.size > 1) { - /* Member export of properties should be caught earlier, so if we get - * here with a non-static export, something went horribly wrong. - */ - assert(static, - s"Found more than one instance getter to export for name $jsName.") - for (duplicate <- getter.tail) { - reporter.error(duplicate.pos, - s"Duplicate static getter export with name '${jsName.displayName}'") - } - } + if (getter.size > 1) + reportCannotDisambiguateError(jsName, alts) val getterBody = getter.headOption.map { getterSym => genApplyForSym(new FormalArgsRegistry(0, false), getterSym, static) @@ -492,7 +483,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { // 2. The optional argument count restriction has triggered // 3. We only have (more than once) repeated parameters left // Therefore, we should fail - reportCannotDisambiguateError(jsName, alts) + reportCannotDisambiguateError(jsName, alts.map(_.sym)) js.Undefined() } else { val altsByTypeTest = groupByWithoutHashCode(alts) { exported => @@ -566,7 +557,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { } private def reportCannotDisambiguateError(jsName: JSName, - alts: List[Exported]): Unit = { + alts: List[Symbol]): Unit = { val currentClass = currentClassSym.get /* Find a position that is in the current class for decent error reporting. @@ -575,21 +566,26 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { * same error in all compilers. */ val validPositions = alts.collect { - case alt if alt.sym.owner == currentClass => alt.sym.pos + case alt if alt.owner == currentClass => alt.pos } val pos = if (validPositions.isEmpty) currentClass.pos else validPositions.maxBy(_.point) val kind = - if (isNonNativeJSClass(currentClass)) "method" - else "exported method" + if (jsInterop.isJSGetter(alts.head)) "getter" + else if (jsInterop.isJSSetter(alts.head)) "setter" + else "method" + + val fullKind = + if (isNonNativeJSClass(currentClass)) kind + else "exported " + kind val displayName = jsName.displayName - val altsTypesInfo = alts.map(_.sym.tpe.toString).sorted.mkString("\n ") + val altsTypesInfo = alts.map(_.tpe.toString).sorted.mkString("\n ") reporter.error(pos, - s"Cannot disambiguate overloads for $kind $displayName with types\n" + + s"Cannot disambiguate overloads for $fullKind $displayName with types\n" + s" $altsTypesInfo") } diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala index 42ad6ebadb..b61acd71ed 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala @@ -1597,9 +1597,11 @@ class JSExportTest extends DirectTest with TestHelpers { def bar: Int = 2 } """ hasErrors - """ - |newSource1.scala:7: error: Duplicate static getter export with name 'foo' - | def foo: Int = 1 + s""" + |newSource1.scala:10: error: Cannot disambiguate overloads for exported getter foo with types + | ${methodSig("()", "Int")} + | ${methodSig("()", "Int")} + | def bar: Int = 2 | ^ """ } @@ -1618,7 +1620,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors s""" - |newSource1.scala:10: error: Cannot disambiguate overloads for exported method foo with types + |newSource1.scala:10: error: Cannot disambiguate overloads for exported setter foo with types | ${methodSig("(v: Int)", "Unit")} | ${methodSig("(v: Int)", "Unit")} | def bar_=(v: Int): Unit = () diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala index 9c55202e9a..bef90e8475 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala @@ -858,4 +858,38 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers { """ } + @Test // #4511 + def noConflictingProperties: Unit = { + """ + class A extends js.Object { + def a: Unit = () + + @JSName("a") + def b: Unit = () + } + """ hasErrors + s""" + |newSource1.scala:9: error: Cannot disambiguate overloads for getter a with types + | ${methodSig("()", "Unit")} + | ${methodSig("()", "Unit")} + | def b: Unit = () + | ^ + """ + + """ + class A extends js.Object { + class B extends js.Object + + object B + } + """ hasErrors + s""" + |newSource1.scala:8: error: Cannot disambiguate overloads for getter B with types + | ${methodSig("()", "A$B.type")} + | ${methodSig("()", "Object")} + | object B + | ^ + """ + } + } From 8ccd35cee9297d9b07f786678772c06044f54249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 17 Jun 2021 14:26:23 +0200 Subject: [PATCH 10/48] Use sbt 1.5.4 and Scala 3.0.0 final for the Scala 3.x scripted tests. Drop sbt-dotty, since its functionality has been absorbed by sbt 1.5.x itself. --- sbt-plugin/src/sbt-test/scala3/basic/build.sbt | 6 +++--- .../src/sbt-test/scala3/basic/project/build.properties | 2 +- sbt-plugin/src/sbt-test/scala3/basic/project/plugins.sbt | 1 - sbt-plugin/src/sbt-test/scala3/junit/build.sbt | 2 +- .../src/sbt-test/scala3/junit/project/build.properties | 2 +- sbt-plugin/src/sbt-test/scala3/junit/project/plugins.sbt | 1 - 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/sbt-plugin/src/sbt-test/scala3/basic/build.sbt b/sbt-plugin/src/sbt-test/scala3/basic/build.sbt index 5571e715cf..b632f8bb20 100644 --- a/sbt-plugin/src/sbt-test/scala3/basic/build.sbt +++ b/sbt-plugin/src/sbt-test/scala3/basic/build.sbt @@ -1,10 +1,10 @@ enablePlugins(ScalaJSPlugin) -scalaVersion := "3.0.0-M1" +scalaVersion := "3.0.0" -// Test withDottyCompat for %%% dependencies +// Test CrossVersion.for3Use2_13 for %%% dependencies libraryDependencies += - ("org.scala-js" %%% "scalajs-ir" % scalaJSVersion).withDottyCompat(scalaVersion.value) + ("org.scala-js" %%% "scalajs-ir" % scalaJSVersion).cross(CrossVersion.for3Use2_13) scalaJSUseMainModuleInitializer := true diff --git a/sbt-plugin/src/sbt-test/scala3/basic/project/build.properties b/sbt-plugin/src/sbt-test/scala3/basic/project/build.properties index c19c768d69..9edb75b77c 100644 --- a/sbt-plugin/src/sbt-test/scala3/basic/project/build.properties +++ b/sbt-plugin/src/sbt-test/scala3/basic/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.2 +sbt.version=1.5.4 diff --git a/sbt-plugin/src/sbt-test/scala3/basic/project/plugins.sbt b/sbt-plugin/src/sbt-test/scala3/basic/project/plugins.sbt index 4cbc417648..960f526aa2 100644 --- a/sbt-plugin/src/sbt-test/scala3/basic/project/plugins.sbt +++ b/sbt-plugin/src/sbt-test/scala3/basic/project/plugins.sbt @@ -1,4 +1,3 @@ val scalaJSVersion = sys.props("plugin.version") addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion) -addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.4.5") diff --git a/sbt-plugin/src/sbt-test/scala3/junit/build.sbt b/sbt-plugin/src/sbt-test/scala3/junit/build.sbt index 8711174f8e..56d9ab8e10 100644 --- a/sbt-plugin/src/sbt-test/scala3/junit/build.sbt +++ b/sbt-plugin/src/sbt-test/scala3/junit/build.sbt @@ -1,6 +1,6 @@ enablePlugins(ScalaJSPlugin, ScalaJSJUnitPlugin) -scalaVersion := "3.0.0-M1" +scalaVersion := "3.0.0" // Work around #4368 ThisBuild / useCoursier := false diff --git a/sbt-plugin/src/sbt-test/scala3/junit/project/build.properties b/sbt-plugin/src/sbt-test/scala3/junit/project/build.properties index c19c768d69..9edb75b77c 100644 --- a/sbt-plugin/src/sbt-test/scala3/junit/project/build.properties +++ b/sbt-plugin/src/sbt-test/scala3/junit/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.2 +sbt.version=1.5.4 diff --git a/sbt-plugin/src/sbt-test/scala3/junit/project/plugins.sbt b/sbt-plugin/src/sbt-test/scala3/junit/project/plugins.sbt index 4cbc417648..960f526aa2 100644 --- a/sbt-plugin/src/sbt-test/scala3/junit/project/plugins.sbt +++ b/sbt-plugin/src/sbt-test/scala3/junit/project/plugins.sbt @@ -1,4 +1,3 @@ val scalaJSVersion = sys.props("plugin.version") addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion) -addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.4.5") From 8204fc44df3fa2e6e22fcc19d160c1f11046dd44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 17 Jun 2021 15:23:46 +0200 Subject: [PATCH 11/48] Define BoxedClasses.TYPE constants as `def`s instead of `final val`s. This allows the JVM bytecode generator not to crash on those definitions. --- javalanglib/src/main/scala/java/lang/Boolean.scala | 5 ++++- javalanglib/src/main/scala/java/lang/Byte.scala | 6 +++++- javalanglib/src/main/scala/java/lang/Character.scala | 6 +++++- javalanglib/src/main/scala/java/lang/Double.scala | 6 +++++- javalanglib/src/main/scala/java/lang/Float.scala | 6 +++++- javalanglib/src/main/scala/java/lang/Integer.scala | 6 +++++- javalanglib/src/main/scala/java/lang/Long.scala | 6 +++++- javalanglib/src/main/scala/java/lang/Short.scala | 6 +++++- javalanglib/src/main/scala/java/lang/Void.scala | 5 ++++- 9 files changed, 43 insertions(+), 9 deletions(-) diff --git a/javalanglib/src/main/scala/java/lang/Boolean.scala b/javalanglib/src/main/scala/java/lang/Boolean.scala index f18040673a..d2e530c570 100644 --- a/javalanglib/src/main/scala/java/lang/Boolean.scala +++ b/javalanglib/src/main/scala/java/lang/Boolean.scala @@ -41,7 +41,10 @@ final class Boolean private () } object Boolean { - final val TYPE = scala.Predef.classOf[scala.Boolean] + /* TYPE should be a `final val`, but that crashes the JVM back-end, so we + * use a 'def' instead, which is binary compatible. + */ + def TYPE: Class[_] = scala.Predef.classOf[scala.Boolean] /* TRUE and FALSE are supposed to be vals. However, they are better * optimized as defs, because they end up being just the constant true and diff --git a/javalanglib/src/main/scala/java/lang/Byte.scala b/javalanglib/src/main/scala/java/lang/Byte.scala index eb599cbdbf..e56d2f9a96 100644 --- a/javalanglib/src/main/scala/java/lang/Byte.scala +++ b/javalanglib/src/main/scala/java/lang/Byte.scala @@ -45,7 +45,11 @@ final class Byte private () extends Number with Comparable[Byte] { } object Byte { - final val TYPE = scala.Predef.classOf[scala.Byte] + /* TYPE should be a `final val`, but that crashes the JVM back-end, so we + * use a 'def' instead, which is binary compatible. + */ + def TYPE: Class[_] = scala.Predef.classOf[scala.Byte] + final val SIZE = 8 final val BYTES = 1 diff --git a/javalanglib/src/main/scala/java/lang/Character.scala b/javalanglib/src/main/scala/java/lang/Character.scala index e85ea5bebf..7333603838 100644 --- a/javalanglib/src/main/scala/java/lang/Character.scala +++ b/javalanglib/src/main/scala/java/lang/Character.scala @@ -49,7 +49,11 @@ class Character private () } object Character { - final val TYPE = scala.Predef.classOf[scala.Char] + /* TYPE should be a `final val`, but that crashes the JVM back-end, so we + * use a 'def' instead, which is binary compatible. + */ + def TYPE: Class[_] = scala.Predef.classOf[scala.Char] + final val MIN_VALUE = '\u0000' final val MAX_VALUE = '\uffff' final val SIZE = 16 diff --git a/javalanglib/src/main/scala/java/lang/Double.scala b/javalanglib/src/main/scala/java/lang/Double.scala index 97d37ac165..066b722fe1 100644 --- a/javalanglib/src/main/scala/java/lang/Double.scala +++ b/javalanglib/src/main/scala/java/lang/Double.scala @@ -52,7 +52,11 @@ final class Double private () extends Number with Comparable[Double] { } object Double { - final val TYPE = scala.Predef.classOf[scala.Double] + /* TYPE should be a `final val`, but that crashes the JVM back-end, so we + * use a 'def' instead, which is binary compatible. + */ + def TYPE: Class[_] = scala.Predef.classOf[scala.Double] + final val POSITIVE_INFINITY = 1.0 / 0.0 final val NEGATIVE_INFINITY = 1.0 / -0.0 final val NaN = 0.0 / 0.0 diff --git a/javalanglib/src/main/scala/java/lang/Float.scala b/javalanglib/src/main/scala/java/lang/Float.scala index 5b308fa225..0c74c67a3d 100644 --- a/javalanglib/src/main/scala/java/lang/Float.scala +++ b/javalanglib/src/main/scala/java/lang/Float.scala @@ -54,7 +54,11 @@ final class Float private () extends Number with Comparable[Float] { } object Float { - final val TYPE = scala.Predef.classOf[scala.Float] + /* TYPE should be a `final val`, but that crashes the JVM back-end, so we + * use a 'def' instead, which is binary compatible. + */ + def TYPE: Class[_] = scala.Predef.classOf[scala.Float] + final val POSITIVE_INFINITY = 1.0f / 0.0f final val NEGATIVE_INFINITY = 1.0f / -0.0f final val NaN = 0.0f / 0.0f diff --git a/javalanglib/src/main/scala/java/lang/Integer.scala b/javalanglib/src/main/scala/java/lang/Integer.scala index 2e5a94d5c9..a363bdcd56 100644 --- a/javalanglib/src/main/scala/java/lang/Integer.scala +++ b/javalanglib/src/main/scala/java/lang/Integer.scala @@ -45,7 +45,11 @@ final class Integer private () extends Number with Comparable[Integer] { } object Integer { - final val TYPE = scala.Predef.classOf[scala.Int] + /* TYPE should be a `final val`, but that crashes the JVM back-end, so we + * use a 'def' instead, which is binary compatible. + */ + def TYPE: Class[_] = scala.Predef.classOf[scala.Int] + final val MIN_VALUE = -2147483648 final val MAX_VALUE = 2147483647 final val SIZE = 32 diff --git a/javalanglib/src/main/scala/java/lang/Long.scala b/javalanglib/src/main/scala/java/lang/Long.scala index a555db6d75..a8abf8cce0 100644 --- a/javalanglib/src/main/scala/java/lang/Long.scala +++ b/javalanglib/src/main/scala/java/lang/Long.scala @@ -49,7 +49,11 @@ final class Long private () extends Number with Comparable[Long] { } object Long { - final val TYPE = scala.Predef.classOf[scala.Long] + /* TYPE should be a `final val`, but that crashes the JVM back-end, so we + * use a 'def' instead, which is binary compatible. + */ + def TYPE: Class[_] = scala.Predef.classOf[scala.Long] + final val MIN_VALUE = -9223372036854775808L final val MAX_VALUE = 9223372036854775807L final val SIZE = 64 diff --git a/javalanglib/src/main/scala/java/lang/Short.scala b/javalanglib/src/main/scala/java/lang/Short.scala index 33546a2f07..928c3f6121 100644 --- a/javalanglib/src/main/scala/java/lang/Short.scala +++ b/javalanglib/src/main/scala/java/lang/Short.scala @@ -44,7 +44,11 @@ final class Short private () extends Number with Comparable[Short] { } object Short { - final val TYPE = scala.Predef.classOf[scala.Short] + /* TYPE should be a `final val`, but that crashes the JVM back-end, so we + * use a 'def' instead, which is binary compatible. + */ + def TYPE: Class[_] = scala.Predef.classOf[scala.Short] + final val SIZE = 16 final val BYTES = 2 diff --git a/javalanglib/src/main/scala/java/lang/Void.scala b/javalanglib/src/main/scala/java/lang/Void.scala index 4073bc1a3f..00a98113c8 100644 --- a/javalanglib/src/main/scala/java/lang/Void.scala +++ b/javalanglib/src/main/scala/java/lang/Void.scala @@ -29,5 +29,8 @@ final class Void private () extends AnyRef { } object Void { - final val TYPE = scala.Predef.classOf[scala.Unit] + /* TYPE should be a `final val`, but that crashes the JVM back-end, so we + * use a 'def' instead, which is binary compatible. + */ + def TYPE: Class[_] = scala.Predef.classOf[scala.Unit] } From 317da523dacd89b28801dae028391fab5ee44d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 17 Jun 2021 15:44:35 +0200 Subject: [PATCH 12/48] Explicitly declare the class BoxesRunTime. This allows the JVM bytecode generator not to crash when compiling the scalalib. --- scalalib/overrides/scala/runtime/BoxesRunTime.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scalalib/overrides/scala/runtime/BoxesRunTime.scala b/scalalib/overrides/scala/runtime/BoxesRunTime.scala index 4f0f5d67b1..48412eb778 100644 --- a/scalalib/overrides/scala/runtime/BoxesRunTime.scala +++ b/scalalib/overrides/scala/runtime/BoxesRunTime.scala @@ -2,6 +2,11 @@ package scala.runtime import scala.math.ScalaNumber +/* The declaration of the class is only to make the JVM back-end happy when + * compiling the scalalib. + */ +final class BoxesRunTime + object BoxesRunTime { def boxToBoolean(b: Boolean): java.lang.Boolean = b.asInstanceOf[java.lang.Boolean] From f659f7a36b3be724e616dce42094f7648d04ee93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 21 Jun 2021 10:57:37 +0200 Subject: [PATCH 13/48] Ensure that there is a trailing '/' for mapSourceURI settings. If the base directory does not exist yet at the time the setting is constructed, the URI did not contain a trailing '/'. That could cause spurious full recompilation during the *second* run after a 'clean'. We now ensure that there is always a trailing '/'. --- project/Build.scala | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 5752c1d184..3d1ebe5c66 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -101,6 +101,20 @@ object MyScalaJSPlugin extends AutoPlugin { } } + def mapSourceURISetting(baseDir: File, targetURI: String): String = { + /* Ensure that there is a trailing '/', otherwise we can get no '/' + * before the first compilation (because the directory does not exist yet) + * but a '/' after the first compilation, causing a full recompilation on + * the *second* run after 'clean' (but not third and following). + */ + val baseDirURI0 = baseDir.toURI.toString + val baseDirURI = + if (baseDirURI0.endsWith("/")) baseDirURI0 + else baseDirURI0 + "/" + + s"mapSourceURI:$baseDirURI->$targetURI" + } + override def globalSettings: Seq[Setting[_]] = Def.settings( fullClasspath in scalaJSLinkerImpl := { (fullClasspath in (Build.linker.v2_12, Runtime)).value @@ -194,10 +208,9 @@ object MyScalaJSPlugin extends AutoPlugin { Nil } else { addScalaJSCompilerOption(Def.setting { - "mapSourceURI:" + - (baseDirectory in LocalProject("scalajs")).value.toURI + - "->https://raw.githubusercontent.com/scala-js/scala-js/v" + - scalaJSVersion + "/" + mapSourceURISetting( + (baseDirectory in LocalProject("scalajs")).value, + s"https://raw.githubusercontent.com/scala-js/scala-js/v$scalaJSVersion/") }) }, @@ -223,7 +236,12 @@ object MyScalaJSPlugin extends AutoPlugin { } object Build { - import MyScalaJSPlugin.{addScalaJSCompilerOption, addScalaJSCompilerOptionInConfig, isGeneratingForIDE} + import MyScalaJSPlugin.{ + addScalaJSCompilerOption, + addScalaJSCompilerOptionInConfig, + mapSourceURISetting, + isGeneratingForIDE + } val scalastyleCheck = taskKey[Unit]("Run scalastyle") @@ -1234,12 +1252,9 @@ object Build { if (isGeneratingForIDE) { prev } else { - val option = { - "-P:scalajs:mapSourceURI:" + - (artifactPath in fetchScalaSource).value.toURI + - "->https://raw.githubusercontent.com/scala/scala/v" + - scalaVersion.value + "/src/library/" - } + val option = mapSourceURISetting( + (artifactPath in fetchScalaSource).value, + s"https://raw.githubusercontent.com/scala/scala/v${scalaVersion.value}/src/library/") option +: prev } }, From b23f9adc9270efc29dc19bda9858149f1f2e5020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 17 Jun 2021 17:39:02 +0200 Subject: [PATCH 14/48] Enable JVM codegen and disable ExternalCompile. Now that we got rid of the idiosynchrasies of our build that prevented us from using the JVM back-end, and hence sbt's integration with the compiler, we can enable them. We configure the incremental compiler to recompile everything or nothing on the javalib and scalalib projects, because the incremental compiler can still get confused about those. --- project/Build.scala | 41 ++++++----- project/ExternalCompile.scala | 130 ---------------------------------- 2 files changed, 20 insertions(+), 151 deletions(-) delete mode 100644 project/ExternalCompile.scala diff --git a/project/Build.scala b/project/Build.scala index 3d1ebe5c66..b5d2742eab 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -28,7 +28,6 @@ import org.scalajs.jsenv.nodejs.NodeJSEnv import ScalaJSPlugin.autoImport.{ModuleKind => _, _} import org.scalastyle.sbt.ScalastylePlugin.autoImport.scalastyle -import ExternalCompile.scalaJSExternalCompileSettings import Loggers._ import org.scalajs.linker.interface._ @@ -482,13 +481,6 @@ object Build { } ) - val noClassFilesSettings: Setting[_] = { - scalacOptions in (Compile, compile) += { - if (isGeneratingForIDE) "-Yskip:jvm" - else "-Ystop-after:jscode" - } - } - val publishSettings = Seq( publishMavenStyle := true, publishTo := { @@ -569,6 +561,13 @@ object Build { } ) + val recompileAllOrNothingSettings = Def.settings( + /* Recompile all sources when at least 1/10,000 of the source files have + * changed, i.e., as soon as at least one source file changed. + */ + incOptions ~= { _.withRecompileAllFraction(0.0001) }, + ) + private def parallelCollectionsDependencies( scalaVersion: String): Seq[ModuleID] = { CrossVersion.partialVersion(scalaVersion) match { @@ -1161,7 +1160,8 @@ object Build { publishArtifact in Compile := false, delambdafySetting, ensureSAMSupportSetting, - noClassFilesSettings, + + recompileAllOrNothingSettings, /* When writing code in the java.lang package, references to things * like `Boolean` or `Double` refer to `j.l.Boolean` or `j.l.Double`. @@ -1184,7 +1184,6 @@ object Build { Seq(output) }.taskValue, - scalaJSExternalCompileSettings, cleanIRSettings, ).withScalaJSCompiler.dependsOnLibraryNoJar @@ -1199,8 +1198,8 @@ object Build { publishArtifact in Compile := false, delambdafySetting, ensureSAMSupportSetting, - noClassFilesSettings, - scalaJSExternalCompileSettings, + + recompileAllOrNothingSettings, /* Do not import `Predef._` so that we have a better control of when * we rely on the Scala library. @@ -1262,7 +1261,8 @@ object Build { publishArtifact in Compile := false, NoIDEExport.noIDEExportSettings, delambdafySetting, - noClassFilesSettings, + + recompileAllOrNothingSettings, // Ignore scalastyle for this project scalastyleCheck := {}, @@ -1386,8 +1386,6 @@ object Build { headerSources in Compile := Nil, headerSources in Test := Nil, - - scalaJSExternalCompileSettings ).withScalaJSCompiler.dependsOnLibraryNoJar lazy val libraryAux: MultiScalaProject = MultiScalaProject( @@ -1401,8 +1399,8 @@ object Build { publishArtifact in Compile := false, NoIDEExport.noIDEExportSettings, delambdafySetting, - noClassFilesSettings, - scalaJSExternalCompileSettings + + recompileAllOrNothingSettings, ).withScalaJSCompiler.dependsOnLibraryNoJar lazy val library: MultiScalaProject = MultiScalaProject( @@ -1421,8 +1419,6 @@ object Build { previousArtifactSetting, mimaBinaryIssueFilters ++= BinaryIncompatibilities.Library, - scalaJSExternalCompileSettings, - test in Test := { streams.value.log.warn("Skipping library/test. Run testSuite/test to test library.") }, @@ -2164,9 +2160,12 @@ object Build { publishArtifact in Compile := false, delambdafySetting, ensureSAMSupportSetting, - noClassFilesSettings, - scalaJSExternalCompileSettings, + + // Ensure that .class files are not used in downstream projects exportJars := true, + Compile / packageBin / mappings ~= { + _.filter(!_._2.endsWith(".class")) + }, /* Do not import `Predef._` so that we have a better control of when * we rely on the Scala library. diff --git a/project/ExternalCompile.scala b/project/ExternalCompile.scala deleted file mode 100644 index 13fd69218a..0000000000 --- a/project/ExternalCompile.scala +++ /dev/null @@ -1,130 +0,0 @@ -package build - -import sbt._ -import Keys._ - -import org.scalajs.sbtplugin.ScalaJSPlugin - -object ExternalCompile { - - private val isWindows = - System.getProperty("os.name").toLowerCase().indexOf("win") >= 0 - - val scalaJSExternalCompileConfigSettings: Seq[Setting[_]] = inTask(compile)( - Defaults.runnerTask - ) ++ Seq( - fork in compile := true, - trapExit in compile := true, - javaOptions in compile += "-Xmx512M", - - javaOptions in compile ++= { - val scalaExtDirs = System.getProperty("scala.ext.dirs") - if (scalaExtDirs != null && (fork in compile).value) - Seq("-Dscala.ext.dirs=" + scalaExtDirs) - else - Nil - }, - - compile := { - val inputs = (compileInputs in compile).value - val run = (runner in compile).value - - val classpath = inputs.options.classpath - val classesDirectory = inputs.options.classesDirectory - val sources = inputs.options.sources - val options = inputs.options.scalacOptions - - val s = streams.value - val logger = s.log - val cacheDir = s.cacheDirectory - - // Discover classpaths - - def cpToString(cp: Seq[File]) = - cp.map(_.getAbsolutePath).mkString(java.io.File.pathSeparator) - - val compilerCp = inputs.compilers.scalac.scalaInstance.allJars - val cpStr = cpToString(classpath) - - // List all my dependencies (recompile if any of these changes) - - val allMyDependencies = classpath filterNot (_ == classesDirectory) flatMap { cpFile => - if (cpFile.isDirectory) (cpFile ** "*.class").get - else Seq(cpFile) - } - - // Compile - - val outputDirectory = cacheDir / "compile-out" - val cachedCompile = FileFunction.cached(cacheDir / "compile-cache", - FilesInfo.lastModified, FilesInfo.exists) { dependencies => - - logger.info( - "Compiling %d Scala sources to %s..." format ( - sources.size, classesDirectory)) - - if (outputDirectory.exists) - IO.delete(outputDirectory) - IO.createDirectory(outputDirectory) - - val sourcesArgs = sources.map(_.getAbsolutePath()).toList - - /* run.run() below in doCompileJS() will emit a call to its - * logger.info("Running scala.tools.nsc.scalajs.Main [...]") - * which we do not want to see. We use this patched logger to - * filter out that particular message. - */ - val patchedLogger = new Logger { - def log(level: Level.Value, message: => String) = { - val msg = message - if (level != Level.Info || - !msg.startsWith("Running (fork) scala.tools.nsc.Main")) - logger.log(level, msg) - } - def success(message: => String) = logger.success(message) - def trace(t: => Throwable) = logger.trace(t) - } - - def doCompile(sourcesArgs: List[String]): Unit = { - run.run("scala.tools.nsc.Main", compilerCp, - "-cp" :: cpStr :: - "-d" :: outputDirectory.getAbsolutePath() :: - options ++: - sourcesArgs, - patchedLogger).get - } - - /* Crude way of overcoming the Windows limitation on command line - * length. - */ - if ((fork in compile).value && isWindows && - (sourcesArgs.map(_.length).sum > 1536)) { - IO.withTemporaryFile("sourcesargs", ".txt") { sourceListFile => - IO.writeLines(sourceListFile, sourcesArgs) - doCompile(List("@"+sourceListFile.getAbsolutePath())) - } - } else { - doCompile(sourcesArgs) - } - - // Copy to classes directory. - val mappings = (outputDirectory ** AllPassFilter) - .pair(Path.rebase(outputDirectory, classesDirectory)) - Sync.sync(s.cacheStoreFactory.make("compile-copy"))(mappings) - - mappings.unzip._2.toSet - } - - cachedCompile((sources ++ allMyDependencies).toSet) - - // We do not have dependency analysis when compiling externally - sbt.internal.inc.Analysis.Empty - } - ) - - val scalaJSExternalCompileSettings = ( - inConfig(Compile)(scalaJSExternalCompileConfigSettings) ++ - inConfig(Test)(scalaJSExternalCompileConfigSettings) - ) - -} From f0de4dfc20262824b46a8c379fb6733ea2c50b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 21 Jun 2021 11:41:48 +0200 Subject: [PATCH 15/48] Add explicit build.properties in sbt tests that were lacking them. This way, we make sure that all our scripted tests keep using sbt 1.2.8 (except the two tests using 1.5.4 for Scala 3). --- .../src/sbt-test/cross-version/2.11/project/build.properties | 1 + .../change-config-and-source/project/build.properties | 1 + .../sbt-test/incremental/change-config/project/build.properties | 1 + .../incremental/fix-compile-error/project/build.properties | 1 + 4 files changed, 4 insertions(+) create mode 100644 sbt-plugin/src/sbt-test/cross-version/2.11/project/build.properties create mode 100644 sbt-plugin/src/sbt-test/incremental/change-config-and-source/project/build.properties create mode 100644 sbt-plugin/src/sbt-test/incremental/change-config/project/build.properties create mode 100644 sbt-plugin/src/sbt-test/incremental/fix-compile-error/project/build.properties diff --git a/sbt-plugin/src/sbt-test/cross-version/2.11/project/build.properties b/sbt-plugin/src/sbt-test/cross-version/2.11/project/build.properties new file mode 100644 index 0000000000..c0bab04941 --- /dev/null +++ b/sbt-plugin/src/sbt-test/cross-version/2.11/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.8 diff --git a/sbt-plugin/src/sbt-test/incremental/change-config-and-source/project/build.properties b/sbt-plugin/src/sbt-test/incremental/change-config-and-source/project/build.properties new file mode 100644 index 0000000000..c0bab04941 --- /dev/null +++ b/sbt-plugin/src/sbt-test/incremental/change-config-and-source/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.8 diff --git a/sbt-plugin/src/sbt-test/incremental/change-config/project/build.properties b/sbt-plugin/src/sbt-test/incremental/change-config/project/build.properties new file mode 100644 index 0000000000..c0bab04941 --- /dev/null +++ b/sbt-plugin/src/sbt-test/incremental/change-config/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.8 diff --git a/sbt-plugin/src/sbt-test/incremental/fix-compile-error/project/build.properties b/sbt-plugin/src/sbt-test/incremental/fix-compile-error/project/build.properties new file mode 100644 index 0000000000..c0bab04941 --- /dev/null +++ b/sbt-plugin/src/sbt-test/incremental/fix-compile-error/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.8 From b44f23179260e000db8e1d065fed9271b7b92717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 21 Jun 2021 11:49:11 +0200 Subject: [PATCH 16/48] Upgrade our build to sbt 1.5.4. As a side effect, this solves the issue that testSuite2_*/test was always logging debug messages. Now they are only displayed with the `-v` option. This will dramatically reduce the disk space used by logs on the CI servers. The sbt plugin is still compiled against sbt 1.0.0 artifacts, and the scripted tests are still executed against sbt 1.2.8. Hopefully that is enough to ensure that we do not break sbt 1.{2,3,4}.x users. --- .gitignore | 1 + Jenkinsfile | 2 ++ .../org/scalajs/linker/backend/emitter/VarGen.scala | 12 ++++++++++++ project/build.properties | 2 +- project/build.sbt | 11 ++++++++--- 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index fff654072f..5c978cc5cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Fundamental to the build target/ +/.bsp/ /scalalib/fetchedSources/ /partest/fetchedSources/ /linker-interface/**/scalajs-logging-src/ diff --git a/Jenkinsfile b/Jenkinsfile index 986ea834db..d77c63151a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -57,6 +57,7 @@ LOCAL_HOME="/localhome/jenkins" LOC_SBT_BASE="$LOCAL_HOME/scala-js-sbt-homes" LOC_SBT_BOOT="$LOC_SBT_BASE/sbt-boot" LOC_IVY_HOME="$LOC_SBT_BASE/sbt-home" +LOC_CS_CACHE="$LOC_SBT_BASE/coursier/cache" TEST_LOCAL_IVY_HOME="$(pwd)/.ivy2-test-local" rm -rf $TEST_LOCAL_IVY_HOME @@ -64,6 +65,7 @@ mkdir $TEST_LOCAL_IVY_HOME ln -s "$LOC_IVY_HOME/cache" "$TEST_LOCAL_IVY_HOME/cache" export SBT_OPTS="-J-Xmx5G -J-XX:MaxPermSize=512M -Dsbt.boot.directory=$LOC_SBT_BOOT -Dsbt.ivy.home=$TEST_LOCAL_IVY_HOME -Divy.home=$TEST_LOCAL_IVY_HOME -Dsbt.global.base=$LOC_SBT_BASE" +export COURSIER_CACHE="$LOC_CS_CACHE" export NODE_PATH="$HOME/node_modules/" diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala index d3982e7fbe..d3d3db9710 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/VarGen.scala @@ -208,11 +208,23 @@ private[emitter] final class VarGen(jsGen: JSGen, nameGen: NameGen, def typeRefVar(field: String, typeRef: NonArrayTypeRef)( implicit moduleContext: ModuleContext, globalKnowledge: GlobalKnowledge, pos: Position): Tree = { + /* Explicitly bringing `PrimRefScope` and `ClassScope` as local implicit + * vals should not be necessary, and used not to be there. When upgrading + * to sbt 1.5.4 from 1.2.8, compilation started failing because of missing + * implicit `Scope[PrimRef]` and `Scope[ClassName]` in this method, in + * *some* environments (depending on machine, OS, JDK version, in or out + * IDE, regular compile versus Scaladoc, etc., perhaps the phase of the + * moon, for all I could tell). It is not even clear that the sbt version + * is actually relevant. + * Regardless, the explicit vals reliably fix the issue. + */ typeRef match { case primRef: PrimRef => + implicit val primRefScope: Scope[PrimRef] = Scope.PrimRefScope globalVar(field, primRef) case ClassRef(className) => + implicit val classScope: Scope[ClassName] = Scope.ClassScope globalVar(field, className) } } diff --git a/project/build.properties b/project/build.properties index c0bab04941..9edb75b77c 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.2.8 +sbt.version=1.5.4 diff --git a/project/build.sbt b/project/build.sbt index aefa10ef96..48b286a57a 100644 --- a/project/build.sbt +++ b/project/build.sbt @@ -1,6 +1,6 @@ addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.0.0") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.18") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1") addSbtPlugin("org.scalastyle" % "scalastyle-sbt-plugin" % "1.0.0") @@ -13,7 +13,7 @@ libraryDependencies += "org.eclipse.jgit" % "org.eclipse.jgit.pgm" % "3.2.0.2013 libraryDependencies += "org.scala-js" %% "scalajs-js-envs" % "1.1.1" libraryDependencies += "org.scala-js" %% "scalajs-env-nodejs" % "1.1.1" -unmanagedSourceDirectories in Compile ++= { +Compile / unmanagedSourceDirectories ++= { val root = baseDirectory.value.getParentFile Seq( root / "ir/shared/src/main/scala", @@ -26,7 +26,12 @@ unmanagedSourceDirectories in Compile ++= { ) } -unmanagedResourceDirectories in Compile += { +Compile / unmanagedResourceDirectories += { val root = baseDirectory.value.getParentFile root / "test-adapter/src/main/resources" } + +/* Don't warn for using the 'in' syntax instead of the '/' syntax. + * We cannot get rid of it in the sbt plugin, whose sources we use in the build. + */ +scalacOptions += "-Wconf:msg=method in in trait ScopingSetting is deprecated:s" From fe3553199d22697374a1b339fc28b23b6b786065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 25 Jun 2021 09:47:48 +0200 Subject: [PATCH 17/48] Fix #4518: Tell MiMa not to fail on empty mimaPreviousArtifacts. --- project/Build.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index b5d2742eab..b66d21b89d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -292,7 +292,12 @@ object Build { if (condition) List(testDir) else Nil - val previousArtifactSetting: Setting[_] = { + val previousArtifactSetting: Seq[Setting[_]] = Def.settings( + /* Do not fail mimaReportBinaryIssues when mimaPreviousArtifacts is empty. + * We specifically set it to empty below when binary compat is irrelevant. + */ + mimaFailOnNoPrevious := false, + mimaPreviousArtifacts ++= { val scalaV = scalaVersion.value val scalaBinaryV = scalaBinaryVersion.value @@ -319,8 +324,8 @@ object Build { .extra(prevExtraAttributes.toSeq: _*) Set(prevProjectID) } - } - } + }, + ) val commonSettings = Seq( organization := "org.scala-js", From 29a6ee48f44225f105f6d00a8c4a1890af6dd8f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 25 Jun 2021 11:28:07 +0200 Subject: [PATCH 18/48] Fix #4368: Revert "Work around #4368: Disable coursier in the scripted tests using sbt 1.4.x." This reverts commit 8ad2dce29c4c1c639e3d7a6e1a0f1c46f69fde63. It seems we managed to fix the issue in b44f23179260e000db8e1d065fed9271b7b92717 by using the `COURSIER_CACHE` environment variable, rather than an option to sbt. --- sbt-plugin/src/sbt-test/scala3/basic/build.sbt | 3 --- sbt-plugin/src/sbt-test/scala3/junit/build.sbt | 3 --- 2 files changed, 6 deletions(-) diff --git a/sbt-plugin/src/sbt-test/scala3/basic/build.sbt b/sbt-plugin/src/sbt-test/scala3/basic/build.sbt index b632f8bb20..47788c2ad9 100644 --- a/sbt-plugin/src/sbt-test/scala3/basic/build.sbt +++ b/sbt-plugin/src/sbt-test/scala3/basic/build.sbt @@ -7,6 +7,3 @@ libraryDependencies += ("org.scala-js" %%% "scalajs-ir" % scalaJSVersion).cross(CrossVersion.for3Use2_13) scalaJSUseMainModuleInitializer := true - -// Work around #4368 -ThisBuild / useCoursier := false diff --git a/sbt-plugin/src/sbt-test/scala3/junit/build.sbt b/sbt-plugin/src/sbt-test/scala3/junit/build.sbt index 56d9ab8e10..809933c9ef 100644 --- a/sbt-plugin/src/sbt-test/scala3/junit/build.sbt +++ b/sbt-plugin/src/sbt-test/scala3/junit/build.sbt @@ -1,6 +1,3 @@ enablePlugins(ScalaJSPlugin, ScalaJSJUnitPlugin) scalaVersion := "3.0.0" - -// Work around #4368 -ThisBuild / useCoursier := false From be9e6fec2ce0aad79efa7f500a0d1a9c5db07e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 5 Jul 2021 14:25:34 +0200 Subject: [PATCH 19/48] Move stdlib-specific reachability test to LibraryReachabilityTest. These tests are not testing the Analyzer, but rather the arrangement of the standard library. --- .../org/scalajs/linker/AnalyzerTest.scala | 67 ---------- .../linker/LibraryReachabilityTest.scala | 122 ++++++++++++++++++ 2 files changed, 122 insertions(+), 67 deletions(-) create mode 100644 linker/shared/src/test/scala/org/scalajs/linker/LibraryReachabilityTest.scala diff --git a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala index 254329b9d3..a37511159b 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala @@ -587,73 +587,6 @@ class AnalyzerTest { } } - @Test - def juPropertiesNotReachableWhenUsingGetSetClearProperty(): AsyncResult = await { - val systemMod = LoadModule("java.lang.System$") - val emptyStr = str("") - val StringType = ClassType(BoxedStringClass) - - val classDefs = Seq( - classDef("A", superClass = Some(ObjectClass), memberDefs = List( - trivialCtor("A"), - MethodDef(EMF, m("test", Nil, V), NON, Nil, NoType, Some(Block( - Apply(EAF, systemMod, m("getProperty", List(T), T), List(emptyStr))(StringType), - Apply(EAF, systemMod, m("getProperty", List(T, T), T), List(emptyStr))(StringType), - Apply(EAF, systemMod, m("setProperty", List(T, T), T), List(emptyStr))(StringType), - Apply(EAF, systemMod, m("clearProperty", List(T), T), List(emptyStr))(StringType) - )))(EOH, None) - )) - ) - - for { - analysis <- computeAnalysis(classDefs, - reqsFactory.instantiateClass("A", NoArgConstructorName) ++ - reqsFactory.callMethod("A", m("test", Nil, V)), - stdlib = TestIRRepo.fulllib) - } yield { - assertNoError(analysis) - - val juPropertiesClass = analysis.classInfos("java.util.Properties") - assertFalse(juPropertiesClass.isAnySubclassInstantiated) - assertFalse(juPropertiesClass.areInstanceTestsUsed) - assertFalse(juPropertiesClass.isDataAccessed) - } - } - - @Test - def jmBigNumbersNotInstantiatedWhenUsingStringFormat(): AsyncResult = await { - val StringType = ClassType(BoxedStringClass) - val formatMethod = m("format", List(T, ArrayTypeRef(O, 1)), T) - - val classDefs = Seq( - classDef("A", superClass = Some(ObjectClass), memberDefs = List( - trivialCtor("A"), - MethodDef(EMF, m("test", Nil, V), NON, Nil, NoType, Some(Block( - ApplyStatic(EAF, BoxedStringClass, formatMethod, List(str("hello %d"), int(42)))(StringType) - )))(EOH, None) - )) - ) - - for { - analysis <- computeAnalysis(classDefs, - reqsFactory.instantiateClass("A", NoArgConstructorName) ++ - reqsFactory.callMethod("A", m("test", Nil, V)), - stdlib = TestIRRepo.fulllib) - } yield { - assertNoError(analysis) - - val jmBigIntegerClass = analysis.classInfos("java.math.BigInteger") - assertFalse(jmBigIntegerClass.isAnySubclassInstantiated) - assertFalse(jmBigIntegerClass.isDataAccessed) - assertTrue(jmBigIntegerClass.areInstanceTestsUsed) - - val jmBigDecimalClass = analysis.classInfos("java.math.BigDecimal") - assertFalse(jmBigDecimalClass.isAnySubclassInstantiated) - assertFalse(jmBigDecimalClass.isDataAccessed) - assertTrue(jmBigDecimalClass.areInstanceTestsUsed) - } - } - @Test // #3571 def specificReflectiveProxy(): AsyncResult = await { val fooAMethodName = m("foo", Nil, ClassRef("A")) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LibraryReachabilityTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LibraryReachabilityTest.scala new file mode 100644 index 0000000000..d211f568a3 --- /dev/null +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibraryReachabilityTest.scala @@ -0,0 +1,122 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker + +import scala.concurrent._ + +import org.junit.Test +import org.junit.Assert._ + +import org.scalajs.ir.Names._ +import org.scalajs.ir.Trees._ +import org.scalajs.ir.Types._ + +import org.scalajs.junit.async._ + +import org.scalajs.linker.analyzer._ +import org.scalajs.linker.frontend.IRLoader +import org.scalajs.linker.interface._ +import org.scalajs.linker.standard._ + +import org.scalajs.linker.testutils._ +import org.scalajs.linker.testutils.TestIRBuilder._ + +class LibraryReachabilityTest { + import scala.concurrent.ExecutionContext.Implicits.global + import LibraryReachabilityTest._ + + @Test + def juPropertiesNotReachableWhenUsingGetSetClearProperty(): AsyncResult = await { + val systemMod = LoadModule("java.lang.System$") + val emptyStr = str("") + val StringType = ClassType(BoxedStringClass) + + val classDefs = Seq( + classDef("A", superClass = Some(ObjectClass), memberDefs = List( + trivialCtor("A"), + MethodDef(EMF, m("test", Nil, V), NON, Nil, NoType, Some(Block( + Apply(EAF, systemMod, m("getProperty", List(T), T), List(emptyStr))(StringType), + Apply(EAF, systemMod, m("getProperty", List(T, T), T), List(emptyStr))(StringType), + Apply(EAF, systemMod, m("setProperty", List(T, T), T), List(emptyStr))(StringType), + Apply(EAF, systemMod, m("clearProperty", List(T), T), List(emptyStr))(StringType) + )))(EOH, None) + )) + ) + + for { + analysis <- computeAnalysis(classDefs, + reqsFactory.instantiateClass("A", NoArgConstructorName) ++ + reqsFactory.callMethod("A", m("test", Nil, V))) + } yield { + val juPropertiesClass = analysis.classInfos("java.util.Properties") + assertFalse(juPropertiesClass.isAnySubclassInstantiated) + assertFalse(juPropertiesClass.areInstanceTestsUsed) + assertFalse(juPropertiesClass.isDataAccessed) + } + } + + @Test + def jmBigNumbersNotInstantiatedWhenUsingStringFormat(): AsyncResult = await { + val StringType = ClassType(BoxedStringClass) + val formatMethod = m("format", List(T, ArrayTypeRef(O, 1)), T) + + val classDefs = Seq( + classDef("A", superClass = Some(ObjectClass), memberDefs = List( + trivialCtor("A"), + MethodDef(EMF, m("test", Nil, V), NON, Nil, NoType, Some(Block( + ApplyStatic(EAF, BoxedStringClass, formatMethod, List(str("hello %d"), int(42)))(StringType) + )))(EOH, None) + )) + ) + + for { + analysis <- computeAnalysis(classDefs, + reqsFactory.instantiateClass("A", NoArgConstructorName) ++ + reqsFactory.callMethod("A", m("test", Nil, V))) + } yield { + val jmBigIntegerClass = analysis.classInfos("java.math.BigInteger") + assertFalse(jmBigIntegerClass.isAnySubclassInstantiated) + assertFalse(jmBigIntegerClass.isDataAccessed) + assertTrue(jmBigIntegerClass.areInstanceTestsUsed) + + val jmBigDecimalClass = analysis.classInfos("java.math.BigDecimal") + assertFalse(jmBigDecimalClass.isAnySubclassInstantiated) + assertFalse(jmBigDecimalClass.isDataAccessed) + assertTrue(jmBigDecimalClass.areInstanceTestsUsed) + } + } +} + +object LibraryReachabilityTest { + private val reqsFactory = SymbolRequirement.factory("unit test") + + def computeAnalysis(classDefs: Seq[ClassDef], + symbolRequirements: SymbolRequirement = reqsFactory.none(), + moduleInitializers: Seq[ModuleInitializer] = Nil, + config: StandardConfig = StandardConfig())( + implicit ec: ExecutionContext): Future[Analysis] = { + for { + baseFiles <- TestIRRepo.fulllib + irLoader <- new IRLoader().update(classDefs.map(MemClassDefIRFile(_)) ++ baseFiles) + analysis <- Analyzer.computeReachability( + CommonPhaseConfig.fromStandardConfig(config), moduleInitializers, + symbolRequirements, allowAddingSyntheticMethods = true, + checkAbstractReachability = true, irLoader) + } yield { + if (analysis.errors.nonEmpty) + fail(analysis.errors.mkString("Unexpected errors:\n", "\n", "")) + + analysis + } + } +} From 799f50d32f489ab74f82fe06ff3db71cdb9db085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 6 Jul 2021 10:08:44 +0200 Subject: [PATCH 20/48] Do not explicitly set the Scala version in the AppVeyor build. Instead, use the default one from the sbt build. This version number has been out of date several times in the past, because we always forget to update it. It is easier not to have to update it at all. --- appveyor.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 552755b627..d1baab4712 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,6 @@ environment: global: NODEJS_VERSION: "14" JAVA_HOME: C:\Program Files\Java\jdk1.8.0 - SCALA_VERSION: 2.12.12 install: - ps: Install-Product node $env:NODEJS_VERSION - npm install @@ -15,7 +14,7 @@ build: off test_script: # Very far from testing everything, but at least it is a good sanity check # For slow things (partest and scripted), we execute only one test - - cmd: sbt ";clean;++%SCALA_VERSION%;testSuite2_12/test;linker2_12/test;partestSuite2_12/testOnly -- --fastOpt run/option-fold.scala" + - cmd: sbt ";clean;testSuite2_12/test;linker2_12/test;partestSuite2_12/testOnly -- --fastOpt run/option-fold.scala" cache: - C:\sbt - C:\Users\appveyor\.ivy2\cache From ecb47df58dd2d4157fff7d7df602ad3172ac76bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 6 Jul 2021 10:31:44 +0200 Subject: [PATCH 21/48] Introduce build-level vals for the default Scala versions. This avoids repeating them in several places the build, potentially creating mismatches. --- project/Build.scala | 19 +++++++++++++------ project/MultiScalaProject.scala | 9 +++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index b66d21b89d..5831c72cd5 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -242,6 +242,13 @@ object Build { isGeneratingForIDE } + import MultiScalaProject.{ + Default2_11ScalaVersion, + Default2_12ScalaVersion, + Default2_13ScalaVersion, + DefaultScalaVersion + } + val scalastyleCheck = taskKey[Unit]("Run scalastyle") val fetchScalaSource = taskKey[File]( @@ -258,7 +265,7 @@ object Build { val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") val scalaVersionsUsedForPublishing: Set[String] = - Set("2.11.12", "2.12.14", "2.13.6") + Set(Default2_11ScalaVersion, Default2_12ScalaVersion, Default2_13ScalaVersion) val newScalaBinaryVersionsInThisRelease: Set[String] = Set() @@ -870,7 +877,7 @@ object Build { MyScalaJSPlugin ).settings( commonSettings, - scalaVersion := "2.12.14", + scalaVersion := DefaultScalaVersion, fatalWarningsSettings, name := "Scala.js linker private library", publishArtifact in Compile := false, @@ -1062,7 +1069,7 @@ object Build { name := "Scala.js sbt plugin", normalizedName := "sbt-scalajs", sbtPlugin := true, - crossScalaVersions := Seq("2.12.14"), + crossScalaVersions := Seq(DefaultScalaVersion), scalaVersion := crossScalaVersions.value.head, sbtVersion := "1.0.0", scalaBinaryVersion := @@ -1682,7 +1689,7 @@ object Build { MyScalaJSPlugin.expectedSizes := { scalaVersion.value match { - case "2.11.12" => + case Default2_11ScalaVersion => Some(ExpectedSizes( fastLink = 520000 to 521000, fullLink = 108000 to 109000, @@ -1690,7 +1697,7 @@ object Build { fullLinkGz = 28000 to 29000, )) - case "2.12.14" => + case Default2_12ScalaVersion => Some(ExpectedSizes( fastLink = 782000 to 783000, fullLink = 150000 to 151000, @@ -1698,7 +1705,7 @@ object Build { fullLinkGz = 36000 to 37000, )) - case "2.13.6" => + case Default2_13ScalaVersion => Some(ExpectedSizes( fastLink = 777000 to 778000, fullLink = 169000 to 170000, diff --git a/project/MultiScalaProject.scala b/project/MultiScalaProject.scala index 25dec79af6..d97e9e622d 100644 --- a/project/MultiScalaProject.scala +++ b/project/MultiScalaProject.scala @@ -83,6 +83,15 @@ object MultiScalaProject { "2.13" -> Seq("2.13.0", "2.13.1", "2.13.2", "2.13.3", "2.13.4", "2.13.5", "2.13.6"), ) + val Default2_11ScalaVersion = versions("2.11").last + val Default2_12ScalaVersion = versions("2.12").last + val Default2_13ScalaVersion = versions("2.13").last + + /** The default Scala version is the default 2.12 Scala version, because it + * must work for sbt plugins. + */ + val DefaultScalaVersion = Default2_12ScalaVersion + private final val ideVersion = "2.12" private def projectID(id: String, major: String) = id + major.replace('.', '_') From df811970a3fe196e6b6ffdfc06a51854e077cbf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 9 Jun 2021 09:48:41 +0200 Subject: [PATCH 22/48] Bump the version to 1.7.0-SNAPSHOT. For the upcoming changes to `java.util.regex.*`. --- ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index 20b0242721..967ea502da 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.6.1-SNAPSHOT", + current = "1.7.0-SNAPSHOT", binaryEmitted = "1.6" ) From ed71c83e3c0b6d7e89cb47aa0c17ba62e2d69d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 7 Jul 2021 23:57:39 +0200 Subject: [PATCH 23/48] Fix #1201: Correct regex support. Previously, `java.util.regex.Pattern` was implemented without much concern for correctness wrt. the Java semantics of regular expressions. Patterns were passed through to the native `RegExp` with minimal preprocessing. This could cause several kinds of incompatibilities: - Throwing `ParseError`s for features not supported by JS regexes, - Or worse, silently compile with different semantics. In this commit, we correctly implement virtually all the features of Java regular expressions by compiling Java patterns down to JS patterns with the same semantics. This change introduces a significant code size regression for code bases that were already using `Pattern` and/or Scala's `Regex`. Therefore, we went to great lengths to optimize the compiler for code size, in particular in the default ES 2015 configuration. This means that some code is not as idiomatic as it could be. The impact of this commit on a typical output is approximately 65 KB for fastOpt and 12 KB for fullOpt. The `README.md` file contains an extensive explanation of the design of the compiler, and of how it compiles each kind of Java pattern. In addition to fixing the mega-issue #1201, this commit fixes the smaller issues #105, #1677, #1847, #2082 and #3959, which had been closed as duplicates of #1201. --- .../java/util/regex/GroupStartMapper.scala | 180 +- .../main/scala/java/util/regex/Matcher.scala | 103 +- .../main/scala/java/util/regex/Pattern.scala | 224 +- .../java/util/regex/PatternCompiler.scala | 1842 ++++++++++++ .../util/regex/PatternSyntaxException.scala | 56 + .../src/main/scala/java/util/regex/README.md | 324 +++ .../org/scalajs/linker/LibrarySizeTest.scala | 132 + .../linker/testutils/TestIRBuilder.scala | 1 + .../scalajs/2.11.12/BlacklistedTests.txt | 3 - .../scalajs/2.12.14/BlacklistedTests.txt | 3 - .../scalajs/2.13.6/BlacklistedTests.txt | 3 - project/Build.scala | 16 +- project/NodeJSEnvForcePolyfills.scala | 2 + .../resources/2.11.12/BlacklistedTests.txt | 4 - .../resources/2.12.1/BlacklistedTests.txt | 5 +- .../resources/2.12.10/BlacklistedTests.txt | 5 +- .../resources/2.12.11/BlacklistedTests.txt | 5 +- .../resources/2.12.12/BlacklistedTests.txt | 5 +- .../resources/2.12.13/BlacklistedTests.txt | 5 +- .../resources/2.12.14/BlacklistedTests.txt | 5 +- .../resources/2.12.2/BlacklistedTests.txt | 5 +- .../resources/2.12.3/BlacklistedTests.txt | 5 +- .../resources/2.12.4/BlacklistedTests.txt | 5 +- .../resources/2.12.5/BlacklistedTests.txt | 5 +- .../resources/2.12.6/BlacklistedTests.txt | 5 +- .../resources/2.12.7/BlacklistedTests.txt | 5 +- .../resources/2.12.8/BlacklistedTests.txt | 5 +- .../resources/2.12.9/BlacklistedTests.txt | 5 +- .../resources/2.13.0/BlacklistedTests.txt | 5 +- .../resources/2.13.1/BlacklistedTests.txt | 5 +- .../resources/2.13.2/BlacklistedTests.txt | 5 +- .../resources/2.13.3/BlacklistedTests.txt | 5 +- .../resources/2.13.4/BlacklistedTests.txt | 5 +- .../resources/2.13.5/BlacklistedTests.txt | 4 - .../resources/2.13.6/BlacklistedTests.txt | 4 - .../scalajs/testsuite/utils/Platform.scala | 11 + .../scalajs/testsuite/utils/Platform.scala | 6 + .../regex/PatternSyntaxExceptionTest.scala | 114 + .../javalib/util/regex/RegexEngineTest.scala | 2526 +++++++++++++++++ .../javalib/util/regex/RegexMatcherTest.scala | 94 +- .../javalib/util/regex/RegexPatternTest.scala | 37 +- 41 files changed, 5441 insertions(+), 343 deletions(-) create mode 100644 javalib/src/main/scala/java/util/regex/PatternCompiler.scala create mode 100644 javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala create mode 100644 javalib/src/main/scala/java/util/regex/README.md create mode 100644 linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala create mode 100644 test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex/PatternSyntaxExceptionTest.scala create mode 100644 test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex/RegexEngineTest.scala diff --git a/javalib/src/main/scala/java/util/regex/GroupStartMapper.scala b/javalib/src/main/scala/java/util/regex/GroupStartMapper.scala index d9abb9417f..d32d890ffc 100644 --- a/javalib/src/main/scala/java/util/regex/GroupStartMapper.scala +++ b/javalib/src/main/scala/java/util/regex/GroupStartMapper.scala @@ -38,21 +38,38 @@ import scala.scalajs.js * - It computes the start of every group thanks to the groups before it * - It builds and returns the mapping of previous group number -> start * + * The `pattern` that is parsed by `GroupStartMapper` is the *compiled* JS + * pattern produced by `PatternCompiler`, not the original Java pattern. This + * means that we can simplify a number of things with the knowledge that: + * + * - the pattern is well-formed, + * - it contains no named group or named back references, and + * - a '\' is always followed by an ASCII character that is: + * - a digit, for a back reference, + * - one of `^ $ \ . * + ? ( ) [ ] { } |`, for an escape, + * - 'b' or 'B' for a word boundary, + * - 'd' or 'D' for a digit character class (used in `[\d\D]` for any code point), or + * - 'p' or 'P' followed by a `{}`-enclosed name that contains only ASCII word characters. + * * @author Mikaël Mayer */ private[regex] class GroupStartMapper private (pattern: String, flags: String, node: GroupStartMapper.Node, groupCount: Int, - allMatchingRegex: js.RegExp) { + jsRegExpForFind: js.RegExp, jsRegExpForMatches: js.RegExp) { import GroupStartMapper._ - def apply(string: String, start: Int): js.Array[Int] = { - allMatchingRegex.lastIndex = start - val allMatchResult = allMatchingRegex.exec(string) - if (allMatchResult == null) { + def apply(forMatches: Boolean, string: String, index: Int): js.Array[Int] = { + val regExp = + if (forMatches) jsRegExpForMatches + else jsRegExpForFind + + regExp.lastIndex = index + val allMatchResult = regExp.exec(string) + if (allMatchResult == null || allMatchResult.index != index) { throw new AssertionError( - s"[Internal error] Executed '$allMatchingRegex' on " + - s"'$string' at position $start, got an error.\n" + + s"[Internal error] Executed '$regExp' on " + + s"'$string' at position $index, got an error.\n" + s"Original pattern '$pattern' with flags '$flags' did match however.") } @@ -65,7 +82,7 @@ private[regex] class GroupStartMapper private (pattern: String, flags: String, i += 1 } - node.propagateFromStart(allMatchResult, groupStartMap, start) + node.propagateFromStart(allMatchResult, groupStartMap, index) groupStartMap } @@ -76,10 +93,12 @@ private[regex] object GroupStartMapper { val parser = new Parser(pattern) val node = parser.parseTopLevel() node.setNewGroup(1) - val allMatchingRegex = - new js.RegExp(node.buildRegex(parser.groupNodeMap), flags) + val allMatchingPattern = node.buildRegex(parser.groupNodeMap) + val jsRegExpForFind = new js.RegExp(allMatchingPattern, flags + "g") + val jsRegExpForMatches = + new js.RegExp(Pattern.wrapJSPatternForMatches(allMatchingPattern), flags) new GroupStartMapper(pattern, flags, node, parser.parsedGroupCount, - allMatchingRegex) + jsRegExpForFind, jsRegExpForMatches) } /** Node of the regex tree. */ @@ -98,48 +117,36 @@ private[regex] object GroupStartMapper { def buildRegex(groupNodeMap: js.Array[Node]): String - /* When assigning group positions. I could not choose between assigning - * group numbers from left to right or from right to left, because there - * both failed in one case each. Normally, both directions give the same - * result. But there are corner cases. - * - * Consider the following regex matching `abbbbbbc` - * - * (?=ab*(c))ab - * - * After conversion, this becomes: - * - * (?=(ab*)(c))(ab) - * - * To recover the position of the group (c), we cannot do anything but - * compute it from the length of (ab*), that is, propagate the start, - * compute the length, and return the end, and this, recursively. This is - * what we need to do in a forward-matching regex. - * - * However, consider the following regex matching `abcbdbe` - * - * a(b.)* + /* The overall algorithm consists in, given known start and end positions + * of a parent node, determine the positions of its children. This is done + * in the main polymorphic method `propagate`, which each node implements. * - * After conversion, it is transformed to: + * For some kinds of parent nodes, even when we know both their start and + * end positions, we can only determine one side of their children. + * Obvious examples are look-around nodes. Since they are zero-length, + * their start and end are always equal, but correspond to different sides + * of their children: * - * (a)((b.)*) + * - For look-ahead nodes (?=X) and (?!X), they correspond to the *start* of X. + * - For look-behind nodes (?<=X) and (? end - matched.length) propagate(matchResult, groupStartMap, start, end) - start } /** Propagates the appropriate positions to the descendants of this node * from its start position. * - * @return the end position of this node + * @return the end position of this node, as a convenience for `SequenceNode.propagate` */ final def propagateFromStart(matchResult: js.RegExp.ExecResult, groupStartMap: js.Array[Int], start: Int): Int = { @@ -194,12 +198,16 @@ private[regex] object GroupStartMapper { */ if (matchResult(newGroup).isDefined) groupStartMap(number) = start - inner.propagateFromStart(matchResult, groupStartMap, start) + inner.propagate(matchResult, groupStartMap, start, end) } } - /** A zero-length test of the form `(?= )` or `(?! )`. */ - private final class ZeroLengthTestNode(val indicator: String, val inner: Node) + /** A look-around group of the form `(?= )`, `(?! )`, `(?<= )` or `(? // Complete one alternative alternatives.push(completeSequence(sequence)) @@ -384,15 +403,25 @@ private[regex] object GroupStartMapper { case '(' => val indicator = pattern.substring(pIndex + 1, pIndex + 3) if (indicator == "?=" || indicator == "?!") { - // Non-capturing test group + // Look-ahead group pIndex += 3 val inner = parseInsideParensAndClosingParen() - new ZeroLengthTestNode(indicator, inner) + new LookAroundNode(isLookBehind = false, indicator, inner) + } else if (indicator == "?<") { + // Look-behind group, which must be ?<= or ?= '0' && c <= '9' - if (isDigit(pattern.charAt(pIndex + 1))) { + val startIndex = pIndex + val c = pattern.charAt(startIndex + 1) + pIndex += 2 + + if (isDigit(c)) { // it is a back reference; parse all following digits - val startIndex = pIndex - pIndex += 2 while (isDigit(pattern.charAt(pIndex))) pIndex += 1 new BackReferenceNode( Integer.parseInt(pattern.substring(startIndex + 1, pIndex))) } else { - // it is a character escape - val e = pattern.substring(pIndex, pIndex + 2) - pIndex += 2 - new LeafRegexNode(e) + // it is a character escape, or one of \b, \B, \d, \D, \p{...} or \P{...} + if (c == 'p' || c == 'P') { + while (pattern.charAt(pIndex) != '}') + pIndex += 1 + pIndex += 1 + } + new LeafRegexNode(pattern.substring(startIndex, pIndex)) } case '[' => - // parse until the corresponding ']' + // parse until the corresponding ']' (here surrogate pairs don't matter) @tailrec def loop(pIndex: Int): Int = { pattern.charAt(pIndex) match { - case '\\' => loop(pIndex + 2) + case '\\' => loop(pIndex + 2) // this is also fine for \p{...} and \P{...} case ']' => pIndex + 1 case _ => loop(pIndex + 1) } @@ -439,9 +473,9 @@ private[regex] object GroupStartMapper { new LeafRegexNode(regex) case _ => - val e = pattern.substring(pIndex, pIndex + 1) - pIndex += 1 - new LeafRegexNode(e) + val start = pIndex + pIndex += Character.charCount(dispatchCP) + new LeafRegexNode(pattern.substring(start, pIndex)) } if (baseNode ne null) { // null if we just completed an alternative diff --git a/javalib/src/main/scala/java/util/regex/Matcher.scala b/javalib/src/main/scala/java/util/regex/Matcher.scala index c4a7792805..106ba6c3d2 100644 --- a/javalib/src/main/scala/java/util/regex/Matcher.scala +++ b/javalib/src/main/scala/java/util/regex/Matcher.scala @@ -33,7 +33,7 @@ final class Matcher private[regex] ( // Match result (updated by successful matches) private var lastMatch: js.RegExp.ExecResult = null - private var lastMatchIsValid = false + private var lastMatchIsForMatches = false private var canStillFind = true // Append state (updated by replacement methods) @@ -43,13 +43,9 @@ final class Matcher private[regex] ( def matches(): Boolean = { resetMatch() - find() - // TODO this check is wrong with non-greedy patterns - // Further, it might be wrong to just use ^$ delimiters for two reasons: - // - They might already be there - // - They might not behave as expected when newline characters are present - if ((lastMatch ne null) && (ensureLastMatch.index != 0 || group().length() != inputstr.length())) - resetMatch() + + lastMatch = pattern().execMatches(inputstr) + lastMatchIsForMatches = true lastMatch ne null } @@ -62,14 +58,14 @@ final class Matcher private[regex] ( } def find(): Boolean = if (canStillFind) { - lastMatchIsValid = true - lastMatch = regexp.exec(inputstr) + lastMatch = pattern().execFind(regexp, inputstr) if (lastMatch ne null) { if (lastMatch(0).get.isEmpty) regexp.lastIndex += 1 } else { canStillFind = false } + lastMatchIsForMatches = false startOfGroupCache = null lastMatch ne null } else false @@ -153,7 +149,6 @@ final class Matcher private[regex] ( private def resetMatch(): Matcher = { regexp.lastIndex = 0 lastMatch = null - lastMatchIsValid = false canStillFind = true appendPos = 0 startOfGroupCache = null @@ -186,34 +181,51 @@ final class Matcher private[regex] ( lastMatch } - def groupCount(): Int = Matcher.getGroupCount(lastMatch, pattern()) + def groupCount(): Int = pattern().groupCount def start(): Int = ensureLastMatch.index + regionStart() def end(): Int = start() + group().length def group(): String = ensureLastMatch(0).get + private def startInternal(compiledGroup: Int): Int = { + val s = startOfGroup(compiledGroup) + if (s == -1) -1 + else s + regionStart() + } + def start(group: Int): Int = { if (group == 0) start() - else startOfGroup(group) + else startInternal(pattern().numberedGroup(group)) } - def end(group: Int): Int = { - val s = start(group) + def start(name: String): Int = + startInternal(pattern().namedGroup(name)) + + private def endInternal(compiledGroup: Int): Int = { + val s = startOfGroup(compiledGroup) if (s == -1) -1 - else s + this.group(group).length + else s + ensureLastMatch(compiledGroup).get.length + regionStart() } - def group(group: Int): String = ensureLastMatch(group).orNull + def end(group: Int): Int = + if (group == 0) end() + else endInternal(pattern().numberedGroup(group)) - def group(name: String): String = { - ensureLastMatch - throw new IllegalArgumentException - } + def end(name: String): Int = + endInternal(pattern().namedGroup(name)) + + def group(group: Int): String = + ensureLastMatch(pattern().numberedGroup(group)).orNull + + def group(name: String): String = + ensureLastMatch(pattern().namedGroup(name)).orNull // Seal the state - def toMatchResult(): MatchResult = - new SealedResult(inputstr, lastMatch, pattern(), regionStart(), startOfGroupCache) + def toMatchResult(): MatchResult = { + new SealedResult(inputstr, lastMatch, lastMatchIsForMatches, pattern(), + regionStart(), startOfGroupCache) + } // Other query state methods @@ -247,7 +259,7 @@ final class Matcher private[regex] ( /** Returns a mapping from the group number to the respective start position. */ private def startOfGroup: js.Array[Int] = { if (startOfGroupCache eq null) - startOfGroupCache = pattern0.groupStartMapper(inputstr, start()) + startOfGroupCache = pattern0.groupStartMapper(lastMatchIsForMatches, inputstr, ensureLastMatch.index) startOfGroupCache } } @@ -267,21 +279,13 @@ object Matcher { result } - private def getGroupCount(lastMatch: js.RegExp.ExecResult, - pattern: Pattern): Int = { - /* `pattern.groupCount` has the answer, but it can require some - * computation to get it, so try and use lastMatch's group count if we can. - */ - if (lastMatch != null) lastMatch.length - 1 - else pattern.groupCount - } - private final class SealedResult(inputstr: String, - lastMatch: js.RegExp.ExecResult, pattern: Pattern, - regionStart: Int, private var startOfGroupCache: js.Array[Int]) + lastMatch: js.RegExp.ExecResult, lastMatchIsForMatches: Boolean, + pattern: Pattern, regionStart: Int, + private var startOfGroupCache: js.Array[Int]) extends MatchResult { - def groupCount(): Int = getGroupCount(lastMatch, pattern) + def groupCount(): Int = pattern.groupCount def start(): Int = ensureLastMatch.index + regionStart def end(): Int = start() + group().length @@ -289,22 +293,37 @@ object Matcher { private def startOfGroup: js.Array[Int] = { if (startOfGroupCache eq null) - startOfGroupCache = pattern.groupStartMapper(inputstr, start()) + startOfGroupCache = pattern.groupStartMapper(lastMatchIsForMatches, inputstr, ensureLastMatch.index) startOfGroupCache } + /* Note that MatchResult does *not* define the named versions of `group`, + * `start` and `end`, so we don't have them here either. + */ + + private def startInternal(compiledGroup: Int): Int = { + val s = startOfGroup(compiledGroup) + if (s == -1) -1 + else s + regionStart + } + def start(group: Int): Int = { if (group == 0) start() - else startOfGroup(group) + else startInternal(pattern.numberedGroup(group)) } - def end(group: Int): Int = { - val s = start(group) + private def endInternal(compiledGroup: Int): Int = { + val s = startOfGroup(compiledGroup) if (s == -1) -1 - else s + this.group(group).length + else s + ensureLastMatch(compiledGroup).get.length + regionStart } - def group(group: Int): String = ensureLastMatch(group).orNull + def end(group: Int): Int = + if (group == 0) end() + else endInternal(pattern.numberedGroup(group)) + + def group(group: Int): String = + ensureLastMatch(pattern.numberedGroup(group)).orNull private def ensureLastMatch: js.RegExp.ExecResult = { if (lastMatch == null) diff --git a/javalib/src/main/scala/java/util/regex/Pattern.scala b/javalib/src/main/scala/java/util/regex/Pattern.scala index 67a9f2211a..7c1afbaf0d 100644 --- a/javalib/src/main/scala/java/util/regex/Pattern.scala +++ b/javalib/src/main/scala/java/util/regex/Pattern.scala @@ -12,52 +12,135 @@ package java.util.regex -import scala.annotation.switch +import scala.annotation.tailrec import scala.scalajs.js -import java.util.ScalaOps._ +import PatternCompiler.Support._ -final class Pattern private (jsRegExp: js.RegExp, _pattern: String, _flags: Int) - extends Serializable { +final class Pattern private[regex] ( + _pattern: String, + _flags: Int, + jsPattern: String, + jsFlags: String, + sticky: Boolean, + private[regex] val groupCount: Int, + groupNumberMap: js.Array[Int], + namedGroups: js.Dictionary[Int] +) extends Serializable { import Pattern._ - def pattern(): String = _pattern - def flags(): Int = _flags - - private def jsPattern: String = jsRegExp.source + @inline private def jsFlagsForFind: String = + jsFlags + (if (sticky && supportsSticky) "gy" else "g") - private def jsFlags: String = { - (if (jsRegExp.global) "g" else "") + - (if (jsRegExp.ignoreCase) "i" else "") + - (if (jsRegExp.multiline) "m" else "") - } - - private[regex] lazy val groupCount: Int = - new js.RegExp("|" + jsPattern).exec("").length - 1 + /** Compile the native RegExp once. + * + * In `newJSRegExp()`, we clone that native RegExp using + * `new js.RegExp(jsRegExpBlueprint)`, which the JS engine hopefully + * optimizes by reusing the compiled internal representation of the RegExp. + * Otherwise, well, there's not much we can do about it. + */ + private[this] val jsRegExpBlueprint = + new js.RegExp(jsPattern, jsFlagsForFind) + + /** Another version of the RegExp that is used by `Matcher.matches()`. + * + * It forces `^` and `$` at the beginning and end of the pattern so that + * only entire inputs are matched. In addition, it does not have the 'g' + * flag, so that it can be repeatedly used without managing `lastIndex`. + * + * Since that RegExp is only used locally within `execMatches()`, we can + * always reuse the same instance. + */ + private[this] val jsRegExpForMatches: js.RegExp = + new js.RegExp(wrapJSPatternForMatches(jsPattern), jsFlags) private[regex] lazy val groupStartMapper: GroupStartMapper = GroupStartMapper(jsPattern, jsFlags) - override def toString(): String = pattern() - private[regex] def newJSRegExp(): js.RegExp = { - val r = new js.RegExp(jsRegExp) - if (r ne jsRegExp) { + val r = new js.RegExp(jsRegExpBlueprint) + if (r ne jsRegExpBlueprint) { r } else { /* Workaround for the PhantomJS 1.x bug * https://github.com/ariya/phantomjs/issues/11494 - * which causes new js.RegExp(jsRegExp) to return the same object, - * rather than a new one. - * We therefore reconstruct the pattern and flags used to create - * jsRegExp and create a new one from there. + * which causes new js.RegExp(jsRegExpBlueprint) to return the same + * object, rather than a new one. + * In that case, we reconstruct a new js.RegExp from scratch. */ - new js.RegExp(jsPattern, jsFlags) + new js.RegExp(jsPattern, jsFlagsForFind) } } + private[regex] def execMatches(input: String): js.RegExp.ExecResult = + jsRegExpForMatches.exec(input) + + private[regex] def execFind(regexp: js.RegExp, input: String): js.RegExp.ExecResult = { + if (!supportsSticky && sticky) { + val start = regexp.lastIndex + val mtch = regexp.exec(input) + if (mtch == null || mtch.index > start) + null + else + mtch + } else if (supportsUnicode) { + regexp.exec(input) + } else { + /* When the native RegExp does not support the 'u' flag (introduced in + * ECMAScript 2015), it can find a match starting in the middle of a + * surrogate pair. This can happen if the pattern can match a substring + * starting with a lone low surrogate. However, that is not valid, + * because surrogate pairs must always stick together. + * + * In all the other situations, the `PatternCompiler` makes sure that + * surrogate pairs are always matched together or not at all, but it + * cannot avoid this specific situation because there is no look-behind + * support in that case either. So we take care of it now by skipping + * matches that start in the middle of a surrogate pair. + */ + @tailrec + def loop(): js.RegExp.ExecResult = { + val start = regexp.lastIndex + val mtch = regexp.exec(input) + if (mtch == null) { + null + } else { + val index = mtch.index + if (index > start && index < input.length() && + Character.isLowSurrogate(input.charAt(index)) && + Character.isHighSurrogate(input.charAt(index - 1))) { + regexp.lastIndex = index + 1 + loop() + } else { + mtch + } + } + } + loop() + } + } + + private[regex] def numberedGroup(group: Int): Int = { + if (group < 0 || group > groupCount) + throw new IndexOutOfBoundsException(group.toString()) + groupNumberMap(group) + } + + private[regex] def namedGroup(name: String): Int = { + groupNumberMap(namedGroups.getOrElse(name, { + throw new IllegalArgumentException(s"No group with name <$name>") + })) + } + + // Public API --------------------------------------------------------------- + + def pattern(): String = _pattern + def flags(): Int = _flags + + override def toString(): String = pattern() + def matcher(input: CharSequence): Matcher = new Matcher(this, input, 0, input.length) @@ -123,27 +206,8 @@ object Pattern { final val CANON_EQ = 0x80 final val UNICODE_CHARACTER_CLASS = 0x100 - def compile(regex: String, flags: Int): Pattern = { - val (jsPattern, flags1) = { - if ((flags & LITERAL) != 0) { - (quote(regex), flags) - } else { - trySplitHack(regex, flags) orElse - tryFlagHack(regex, flags) getOrElse - (regex, flags) - } - } - - val jsFlags = { - "g" + - (if ((flags1 & CASE_INSENSITIVE) != 0) "i" else "") + - (if ((flags1 & MULTILINE) != 0) "m" else "") - } - - val jsRegExp = new js.RegExp(jsPattern, jsFlags) - - new Pattern(jsRegExp, regex, flags1) - } + def compile(regex: String, flags: Int): Pattern = + PatternCompiler.compile(regex, flags) def compile(regex: String): Pattern = compile(regex, 0) @@ -152,66 +216,18 @@ object Pattern { compile(regex).matcher(input).matches() def quote(s: String): String = { - var result = "" - var i = 0 - while (i < s.length) { - val c = s.charAt(i) - result += ((c: @switch) match { - case '\\' | '.' | '(' | ')' | '[' | ']' | '{' | '}' | '|' - | '?' | '*' | '+' | '^' | '$' => "\\"+c - case _ => c - }) - i += 1 + var result = "\\Q" + var start = 0 + var end = s.indexOf("\\E", start) + while (end >= 0) { + result += s.substring(start, end) + "\\E\\\\E\\Q" + start = end + 2 + end = s.indexOf("\\E", start) } - result - } - - /** This is a hack to support StringLike.split(). - * It replaces occurrences of \Q\E by quoted() - */ - @inline - private def trySplitHack(pat: String, flags: Int) = { - val m = splitHackPat.exec(pat) - if (m != null) - Some((quote(m(1).get), flags)) - else - None + result + s.substring(start) + "\\E" } @inline - private def tryFlagHack(pat: String, flags0: Int) = { - val m = flagHackPat.exec(pat) - if (m != null) { - val newPat = pat.substring(m(0).get.length) // cut off the flag specifiers - var flags = flags0 - for (chars <- m(1)) { - for (i <- 0 until chars.length()) - flags |= charToFlag(chars.charAt(i)) - } - for (chars <- m(2)) { - for (i <- 0 until chars.length()) - flags &= ~charToFlag(chars.charAt(i)) - } - Some((newPat, flags)) - } else - None - } - - private def charToFlag(c: Char) = (c: @switch) match { - case 'i' => CASE_INSENSITIVE - case 'd' => UNIX_LINES - case 'm' => MULTILINE - case 's' => DOTALL - case 'u' => UNICODE_CASE - case 'x' => COMMENTS - case 'U' => UNICODE_CHARACTER_CLASS - case _ => throw new IllegalArgumentException("bad in-pattern flag") - } - - /** matches \Q\E to support StringLike.split */ - private val splitHackPat = new js.RegExp("^\\\\Q(.|\\n|\\r)\\\\E$") - - /** regex to match flag specifiers in regex. E.g. (?u), (?-i), (?U-i) */ - private val flagHackPat = - new js.RegExp("^\\(\\?([idmsuxU]*)(?:-([idmsuxU]*))?\\)") + private[regex] def wrapJSPatternForMatches(jsPattern: String): String = + "^(?:" + jsPattern + ")$" // the group is needed if there is a top-level | in jsPattern } diff --git a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala new file mode 100644 index 0000000000..c9c2717b6c --- /dev/null +++ b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala @@ -0,0 +1,1842 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util.regex + +import scala.annotation.{switch, tailrec} + +import java.lang.Character.{ + charCount, + isBmpCodePoint, + highSurrogate, + lowSurrogate, + MIN_HIGH_SURROGATE, + MAX_HIGH_SURROGATE, + MIN_LOW_SURROGATE, + MAX_LOW_SURROGATE +} + +import java.util.ScalaOps._ + +import scala.scalajs.js +import scala.scalajs.LinkingInfo.{ESVersion, esVersion} + +/** Compiler from Java regular expressions to JavaScript regular expressions. + * + * See `README.md` in this directory for the design. + * + * !!! PLEASE (re-)read the README before modifying this class. !!! + * + * There are very intricate concerns that are cross-cutting all over the + * class, and assumptions are not local! + */ +private[regex] object PatternCompiler { + import Pattern._ + + def compile(regex: String, flags: Int): Pattern = + new PatternCompiler(regex, flags).compile() + + /** RegExp to match leading embedded flag specifiers in a pattern. + * + * E.g. (?u), (?-i), (?U-i) + */ + private val leadingEmbeddedFlagSpecifierRegExp = + new js.RegExp("^\\(\\?([idmsuxU]*)(?:-([idmsuxU]*))?\\)") + + /** RegExp to renumber backreferences (used for possessive quantifiers). */ + private val renumberingRegExp = + new js.RegExp("(\\\\+)(\\d+)", "g") + + /** Returns the flag that corresponds to an embedded flag specifier. */ + private def charToFlag(c: Char): Int = (c: @switch) match { + case 'i' => CASE_INSENSITIVE + case 'd' => UNIX_LINES + case 'm' => MULTILINE + case 's' => DOTALL + case 'u' => UNICODE_CASE + case 'x' => COMMENTS + case 'U' => UNICODE_CHARACTER_CLASS + case _ => throw new IllegalArgumentException("bad in-pattern flag") + } + + private def featureTest(flags: String): Boolean = { + try { + new js.RegExp("", flags) + true + } catch { + case _: js.JavaScriptException => + false + } + } + + /** Cache for `Support.supportsUnicode`. */ + private val _supportsUnicode = + (esVersion >= ESVersion.ES2015) || featureTest("u") + + /** Cache for `Support.supportsSticky`. */ + private val _supportsSticky = + (esVersion >= ESVersion.ES2015) || featureTest("y") + + /** Cache for `Support.supportsDotAll`. */ + private val _supportsDotAll = + (esVersion >= ESVersion.ES2018) || featureTest("us") + + /** Feature-test methods. + * + * They are located in a separate object so that the methods can be fully + * inlined and optimized away, without leaving a `LoadModule` of the + * enclosing object behind, depending on the target ES version. + */ + private[regex] object Support { + /** Tests whether the underlying JS RegExp supports the 'u' flag. */ + @inline + def supportsUnicode: Boolean = + (esVersion >= ESVersion.ES2015) || _supportsUnicode + + /** Tests whether the underlying JS RegExp supports the 'y' flag. */ + @inline + def supportsSticky: Boolean = + (esVersion >= ESVersion.ES2015) || _supportsSticky + + /** Tests whether the underlying JS RegExp supports the 's' flag. */ + @inline + def supportsDotAll: Boolean = + (esVersion >= ESVersion.ES2018) || _supportsDotAll + + /** Tests whether features requiring support for the 'u' flag are enabled. + * + * They are enabled if and only if the project is configured to rely on + * ECMAScript 2015 features. + */ + @inline + def enableUnicodeCaseInsensitive: Boolean = + esVersion >= ESVersion.ES2015 + + /** Tests whether features requiring \p{} and/or look-behind assertions are enabled. + * + * They are enabled if and only if the project is configured to rely on + * ECMAScript 2018 features. + */ + @inline + def enableUnicodeCharacterClassesAndLookBehinds: Boolean = + esVersion >= ESVersion.ES2018 + } + + import Support._ + + // Helpers to deal with surrogate pairs when the 'u' flag is not supported + + private def codePointNotAmong(characters: String): String = { + if (supportsUnicode) { + if (characters != "") + "[^" + characters + "]" + else if (supportsDotAll) + "." // we always add the 's' flag when it is supported, so we can use "." here + else + "[\\d\\D]" // In theory, "[^]" works, but XRegExp does not trust JS engines on that, so we don't either + } else { + val highCharRange = s"$MIN_HIGH_SURROGATE-$MAX_HIGH_SURROGATE" + val lowCharRange = s"$MIN_LOW_SURROGATE-$MAX_LOW_SURROGATE" + val highCPOrSupplementaryCP = s"[$highCharRange](?:[$lowCharRange]|(?![$lowCharRange]))" + s"(?:[^$characters$highCharRange]|$highCPOrSupplementaryCP)" + } + } + + // Other helpers + + /** Helpers that are always inlined; kept in a separate object so that they + * can be inlined without cost. + */ + private object InlinedHelpers { + /* isHighSurrogateCP, isLowSurrogateCP and toCodePointCP are like the + * non-CP equivalents in Character, but they take Int code point + * parameters. The implementation strategy is the same as the methods for + * Chars. The magical constants are copied from Character and extended to + * 32 bits. + */ + + private final val HighSurrogateCPMask = 0xfffffc00 // ffff 111111 00 00000000 + private final val HighSurrogateCPID = 0x0000d800 // 0000 110110 00 00000000 + private final val LowSurrogateCPMask = 0xfffffc00 // ffff 111111 00 00000000 + private final val LowSurrogateCPID = 0x0000dc00 // 0000 110111 00 00000000 + private final val SurrogateUsefulPartMask = 0x000003ff // 0000 000000 11 11111111 + + private final val HighSurrogateShift = 10 + private final val HighSurrogateAddValue = 0x10000 >> HighSurrogateShift + + @inline def isHighSurrogateCP(cp: Int): Boolean = + (cp & HighSurrogateCPMask) == HighSurrogateCPID + + @inline def isLowSurrogateCP(cp: Int): Boolean = + (cp & LowSurrogateCPMask) == LowSurrogateCPID + + @inline def toCodePointCP(high: Int, low: Int): Int = { + (((high & SurrogateUsefulPartMask) + HighSurrogateAddValue) << HighSurrogateShift) | + (low & SurrogateUsefulPartMask) + } + + @inline def isLetter(c: Char): Boolean = + (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + + @inline def isDigit(c: Char): Boolean = + c >= '0' && c <= '9' + + @inline def isLetterOrDigit(c: Char): Boolean = + isLetter(c) || isDigit(c) + + @inline def isHexDigit(c: Char): Boolean = + isDigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') + + @inline def parseInt(s: String, radix: Int): Int = + js.Dynamic.global.parseInt(s, radix).asInstanceOf[Int] + } + + import InlinedHelpers._ + + private def codePointToString(codePoint: Int): String = { + if (esVersion >= ESVersion.ES2015) { + js.Dynamic.global.String.fromCodePoint(codePoint).asInstanceOf[String] + } else { + if (isBmpCodePoint(codePoint)) { + js.Dynamic.global.String.fromCharCode(codePoint).asInstanceOf[String] + } else { + js.Dynamic.global.String + .fromCharCode(highSurrogate(codePoint).toInt, lowSurrogate(codePoint).toInt) + .asInstanceOf[String] + } + } + } + + // Everything for compiling character classes + + /* This should be a sealed class with subclasses that we pattern-match on. + * However, to cut costs in terms of code size, we use a single class with a + * `kind` field. + */ + private final class CompiledCharClass(val kind: Int, val data: String) { + import CompiledCharClass._ + + lazy val negated: CompiledCharClass = + new CompiledCharClass(kind ^ 1, data) + } + + // This object is entirely inlined and DCE'ed. Keep it that way. + private object CompiledCharClass { + /** Represents `\p{data}`. */ + final val PosP = 0 + + /** Represents `\P{data}`. */ + final val NegP = 1 + + /** Represents `[data]`. */ + final val PosClass = 2 + + /** Represents `[^data]`. */ + final val NegClass = 3 + + @inline def posP(name: String): CompiledCharClass = + new CompiledCharClass(PosP, name) + + @inline def negP(name: String): CompiledCharClass = + new CompiledCharClass(NegP, name) + + @inline def posClass(content: String): CompiledCharClass = + new CompiledCharClass(PosClass, content) + + @inline def negClass(content: String): CompiledCharClass = + new CompiledCharClass(NegClass, content) + } + + private val ASCIIDigit = CompiledCharClass.posClass("0-9") + private val UnicodeDigit = CompiledCharClass.posP("Nd") + + private val UniversalHorizontalWhiteSpace = + CompiledCharClass.posClass("\t \u00A0\u1680\u180E\u2000-\u200A\u202F\u205F\u3000") + + private val ASCIIWhiteSpace = CompiledCharClass.posClass("\t-\r ") + private val UnicodeWhitespace = CompiledCharClass.posP("White_Space") + + private val UniversalVerticalWhiteSpace = CompiledCharClass.posClass("\n-\r\u0085\u2028\u2029") + + private val ASCIIWordChar = CompiledCharClass.posClass("a-zA-Z_0-9") + private val UnicodeWordChar = + CompiledCharClass.posClass("\\p{Alphabetic}\\p{Mn}\\p{Me}\\p{Mc}\\p{Nd}\\p{Pc}\\p{Join_Control}") + + /** Mapping from POSIX character class to the character set to use when + * `UNICODE_CHARACTER_CLASSES` is *not* set. + * + * This is a `js.Dictionary` because it can be used even when compiling to + * ECMAScript 5.1. + */ + private val asciiPOSIXCharacterClasses = { + import CompiledCharClass._ + + js.Dictionary( + ("Lower", posClass("a-z")), + ("Upper", posClass("A-Z")), + ("ASCII", posClass("\u0000-\u007f")), + ("Alpha", posClass("A-Za-z")), // [\p{Lower}\p{Upper}] + ("Digit", posClass("0-9")), + ("Alnum", posClass("0-9A-Za-z")), // [\p{Alpha}\p{Digit}] + ("Punct", posClass("!-/:-@[-`{-~")), // One of !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ + ("Graph", posClass("!-~")), // [\p{Alnum}\p{Punct}] + ("Print", posClass(" -~")), // [\p{Graph}\x20] + ("Blank", posClass("\t ")), + ("Cntrl", posClass("\u0000-\u001f\u007f")), + ("XDigit", posClass("0-9A-Fa-f")), + ("Space", posClass("\t-\r ")) // [ \t\n\x0B\f\r] + ) + } + + /** Mapping of predefined character classes to the corresponding character + * set. + * + * Mappings that also exist in `asciiPOSIXCharacterClasses` must be + * preferred when `UNICODE_CHARACTER_CLASSES` is not set. + * + * This is a `js.Map` (and a lazy val) because it is only used when `\\p` is + * already known to be supported by the underlying `js.RegExp` (ES 2018), + * and we assume that that implies that `js.Map` is supported (ES 2015). + */ + private lazy val predefinedPCharacterClasses: js.Map[String, CompiledCharClass] = { + import CompiledCharClass._ + + val result = new js.Map[String, CompiledCharClass]() + + // General categories + + val generalCategories = js.Array( + "Lu", "Ll", "Lt", "LC", "Lm", "Lo", "L", + "Mn", "Mc", "Me", "M", + "Nd", "Nl", "No", "N", + "Pc", "Pd", "Ps", "Pe", "Pi", "Pf", "Po", "P", + "Sm", "Sc", "Sk", "So", "S", + "Zs", "Zl", "Zp", "Z", + "Cc", "Cf", "Cs", "Co", "Cn", "C" + ) + + for (gc <- generalCategories) { + val compiled = posP(gc) + result(gc) = compiled + result("Is" + gc) = compiled + result("general_category=" + gc) = compiled + result("gc=" + gc) = compiled + } + + // Binary properties + + result("IsAlphabetic") = posP("Alphabetic") + result("IsIdeographic") = posP("Ideographic") + result("IsLetter") = posP("Letter") + result("IsLowercase") = posP("Lowercase") + result("IsUppercase") = posP("Uppercase") + result("IsTitlecase") = posP("Lt") + result("IsPunctuation") = posP("Punctuation") + result("IsControl") = posP("Control") + result("IsWhite_Space") = posP("White_Space") + result("IsDigit") = posP("Nd") + result("IsHex_Digit") = posP("Hex_Digit") + result("IsJoin_Control") = posP("Join_Control") + result("IsNoncharacter_Code_Point") = posP("Noncharacter_Code_Point") + result("IsAssigned") = posP("Assigned") + + // java.lang.Character classes + + result("javaAlphabetic") = posP("Alphabetic") + result("javaDefined") = negP("Cn") + result("javaDigit") = posP("Nd") + result("javaIdentifierIgnorable") = posClass("\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}") + result("javaIdeographic") = posP("Ideographic") + result("javaISOControl") = posClass("\u0000-\u001F\u007F-\u009F") + result("javaJavaIdentifierPart") = + posClass("\\p{L}\\p{Sc}\\p{Pc}\\p{Nd}\\p{Nl}\\p{Mn}\\p{Mc}\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}") + result("javaJavaIdentifierStart") = posClass("\\p{L}\\p{Sc}\\p{Pc}\\p{Nl}") + result("javaLetterOrDigit") = posClass("\\p{L}\\p{Nd}") + result("javaLowerCase") = posP("Lowercase") + result("javaMirrored") = posP("Bidi_Mirrored") + result("javaSpaceChar") = posP("Z") + result("javaTitleCase") = posP("Lt") + result("javaUnicodeIdentifierPart") = + posClass("\\p{ID_Continue}\u2E2F\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}") + result("javaUnicodeIdentifierStart") = posClass("\\p{ID_Start}\u2E2F") + result("javaUpperCase") = posP("Uppercase") + + // [\t-\r\u001C-\u001F\\p{Z}&&[^\u00A0\u2007\u202F]] + result("javaWhitespace") = + posClass("\t-\r\u001C-\u001F \u1680\u2000-\u2006\u2008-\u200A\u205F\u3000\\p{Zl}\\p{Zp}") + + /* POSIX character classes with Unicode compatibility + * (resolved from the original definitions, which are in comments) + */ + + result("Lower") = posP("Lower") // \p{IsLowercase} + result("Upper") = posP("Upper") // \p{IsUppercase} + result("ASCII") = posClass("\u0000-\u007f") + result("Alpha") = posP("Alpha") // \p{IsAlphabetic} + result("Digit") = posP("Nd") // \p{IsDigit} + result("Alnum") = posClass("\\p{Alpha}\\p{Nd}") // [\p{IsAlphabetic}\p{IsDigit}] + result("Punct") = posP("P") // \p{IsPunctuation} + + // [^\p{IsWhite_Space}\p{gc=Cc}\p{gc=Cs}\p{gc=Cn}] + result("Graph") = negClass("\\p{White_Space}\\p{Cc}\\p{Cs}\\p{Cn}") + + /* [\p{Graph}\p{Blank}&&[^\p{Cntrl}]] + * === (by definition of Cntrl) + * [\p{Graph}\p{Blank}&&[^\p{Cc}]] + * === (because Graph already excludes anything in the Cc category) + * [\p{Graph}[\p{Blank}&&[^\p{Cc}]]] + * === (by the resolved definition of Blank below) + * [\p{Graph}[\t\p{Zs}&&[^\p{Cc}]]] + * === (by the fact that \t is a Cc, and general categories are disjoint) + * [\p{Graph}\p{Zs}] + * === (by definition of Graph) + * [[^\p{IsWhite_Space}\p{Cc}\p{Cs}\p{Cn}]\p{Zs}] + * === (see the excerpt from PropList.txt below) + * [[^\x09-\x0d\x85\p{Zs}\p{Zl}\p{Zp}\p{Cc}\p{Cs}\p{Cn}]\p{Zs}] + * === (canceling \p{Zs}) + * [^\x09-\x0d\x85\p{Zl}\p{Zp}\p{Cc}\p{Cs}\p{Cn}] + * === (because \x09-\x0d and \x85 are all in the Cc category) + * [^\p{Zl}\p{Zp}\p{Cc}\p{Cs}\p{Cn}] + */ + result("Print") = negClass("\\p{Zl}\\p{Zp}\\p{Cc}\\p{Cs}\\p{Cn}") + + /* [\p{IsWhite_Space}&&[^\p{gc=Zl}\p{gc=Zp}\x0a\x0b\x0c\x0d\x85]] + * === (see the excerpt from PropList.txt below) + * [[\x09-\x0d\x85\p{gc=Zs}\p{gc=Zl}\p{gc=Zp}]&&[^\p{gc=Zl}\p{gc=Zp}\x0a\x0b\x0c\x0d\x85]] + * === (by simplification) + * [\x09\p{gc=Zs}] + */ + result("Blank") = posClass("\t\\p{Zs}") + + result("Cntrl") = posP("Cc") // \p{gc=Cc} + result("XDigit") = posClass("\\p{Nd}\\p{Hex}") // [\p{gc=Nd}\p{IsHex_Digit}] + result("Space") = posP("White_Space") // \p{IsWhite_Space} + + result + } + + /* Excerpt from PropList.txt v13.0.0: + * + * 0009..000D ; White_Space # Cc [5] .. + * 0020 ; White_Space # Zs SPACE + * 0085 ; White_Space # Cc + * 00A0 ; White_Space # Zs NO-BREAK SPACE + * 1680 ; White_Space # Zs OGHAM SPACE MARK + * 2000..200A ; White_Space # Zs [11] EN QUAD..HAIR SPACE + * 2028 ; White_Space # Zl LINE SEPARATOR + * 2029 ; White_Space # Zp PARAGRAPH SEPARATOR + * 202F ; White_Space # Zs NARROW NO-BREAK SPACE + * 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE + * 3000 ; White_Space # Zs IDEOGRAPHIC SPACE + * + * Note that *all* the code points with general category Zs, Zl or Zp are + * listed here. In addition, we have 0009-000D and 0085 from the Cc category. + * Therefore, the following equivalence holds: + * + * \p{IsWhite_Space} === [\x09-\x0d\x85\p{gc=Zs}\p{gc=Zl}\p{gc=Zp}] + * + * That equivalence is known to be true as of Unicode 13.0.0, and seems to + * have been true for a number of past versions as well. We rely on it to + * define \p{Print} and \p{Blank} above. Those would become buggy if a future + * version of Unicode invalidates that assumption. + */ + + private val scriptCanonicalizeRegExp = new js.RegExp("(?:^|_)[a-z]", "g") + + /** A cache for verified and canonicalized script names. + * + * This is a `js.Map` (and a lazy val) because it is only used when `\\p` is + * already known to be supported by the underlying `js.RegExp` (ES 2018), + * and we assume that that implies that `js.Map` is supported (ES 2015). + */ + private lazy val canonicalizedScriptNameCache: js.Map[String, String] = { + val result = new js.Map[String, String]() + + /* SignWriting is an exception. It has an uppercase 'W' even though it is + * not after '_'. We add the exception to the map immediately. + */ + result("signwriting") = "SignWriting" + + result + } + + @inline + private final class CodePointRange(val start: Int, val end: Int) { + def isEmpty: Boolean = start > end + def nonEmpty: Boolean = start <= end + + /** Computes the intersection of two *non-empty* ranges. + * + * This method makes no guarantee about its result if either or both input + * ranges are empty. + * + * The result range may be empty. + */ + def intersect(that: CodePointRange): CodePointRange = + CodePointRange(Math.max(this.start, that.start), Math.min(this.end, that.end)) + + def shift(offset: Int): CodePointRange = + CodePointRange(start + offset, end + offset) + } + + private object CodePointRange { + @inline + def apply(start: Int, end: Int): CodePointRange = + new CodePointRange(start, end) + + @inline + def BmpBelowHighSurrogates: CodePointRange = + CodePointRange(0, Character.MIN_HIGH_SURROGATE - 1) + + @inline + def HighSurrogates: CodePointRange = + CodePointRange(Character.MIN_HIGH_SURROGATE, Character.MAX_HIGH_SURROGATE) + + @inline + def BmpAboveHighSurrogates: CodePointRange = + CodePointRange(Character.MAX_HIGH_SURROGATE + 1, Character.MAX_VALUE) + + @inline + def Supplementaries: CodePointRange = + CodePointRange(Character.MIN_SUPPLEMENTARY_CODE_POINT, Character.MAX_CODE_POINT) + } + + private final class CharacterClassBuilder(asciiCaseInsensitive: Boolean, isNegated: Boolean) { + private var conjunction = "" + private var thisConjunct = "" + private var thisSegment = "" + + def finish(): String = { + val conjunct = conjunctResult() + if (conjunction == "") conjunct else s"(?:$conjunction$conjunct)" + } + + def startNewConjunct(): Unit = { + val conjunct = conjunctResult() + conjunction += (if (isNegated) conjunct + "|" else s"(?=$conjunct)") + thisConjunct = "" + thisSegment = "" + } + + private def addAlternative(alt: String): Unit = { + if (thisConjunct == "") + thisConjunct = alt + else + thisConjunct += "|" + alt + } + + private def conjunctResult(): String = { + if (isNegated) { + val negThisSegment = codePointNotAmong(thisSegment) + if (thisConjunct == "") + negThisSegment + else + s"(?:(?!$thisConjunct)$negThisSegment)" + } else if (thisSegment == "") { + if (thisConjunct == "") + "[^\\d\\D]" // impossible to satisfy + else + s"(?:$thisConjunct)" + } else { + if (thisConjunct == "") + s"[$thisSegment]" + else + s"(?:$thisConjunct|[$thisSegment])" + } + } + + private def literalCodePoint(codePoint: Int): String = { + val s = codePointToString(codePoint) + if (codePoint == ']' || codePoint == '\\' || codePoint == '-' || codePoint == '^') + "\\" + s + else + s + } + + def addCharacterClass(cls: String): Unit = + addAlternative(cls) + + def addCharacterClass(cls: CompiledCharClass): Unit = { + cls.kind match { + case CompiledCharClass.PosP => + thisSegment += "\\p{" + cls.data + "}" + case CompiledCharClass.NegP => + thisSegment += "\\P{" + cls.data + "}" + case CompiledCharClass.PosClass => + thisSegment += cls.data + case CompiledCharClass.NegClass => + addAlternative(codePointNotAmong(cls.data)) + } + } + + def addCodePointsInString(str: String, start: Int, end: Int): Unit = { + var i = start + while (i != end) { + val codePoint = str.codePointAt(i) + addSingleCodePoint(codePoint) + i += charCount(codePoint) + } + } + + def addSingleCodePoint(codePoint: Int): Unit = { + val s = literalCodePoint(codePoint) + + if (supportsUnicode || (isBmpCodePoint(codePoint) && !isHighSurrogateCP(codePoint))) { + if (isLowSurrogateCP(codePoint)) { + // Put low surrogates at the beginning so that they do not merge with high surrogates + thisSegment = s + thisSegment + } else { + thisSegment += s + } + } else { + if (isBmpCodePoint(codePoint)) { + // It is a high surrogate + addAlternative(s"(?:$s(?![$MIN_LOW_SURROGATE-$MAX_LOW_SURROGATE]))") + } else { + // It is a supplementary code point + addAlternative(s) + } + } + + if (asciiCaseInsensitive) { + if (codePoint >= 'A' && codePoint <= 'Z') + thisSegment += codePointToString(codePoint - 'A' + 'a') + else if (codePoint >= 'a' && codePoint <= 'z') + thisSegment += codePointToString(codePoint - 'a' + 'A') + } + } + + def addCodePointRange(startCodePoint: Int, endCodePoint: Int): Unit = { + def literalRange(range: CodePointRange): String = + literalCodePoint(range.start) + "-" + literalCodePoint(range.end) + + val range = CodePointRange(startCodePoint, endCodePoint) + + if (supportsUnicode || range.end < MIN_HIGH_SURROGATE) { + val s = literalRange(range) + + if (isLowSurrogateCP(range.start)) { + /* Put ranges whose start code point is a low surrogate at the + * beginning, so that they cannot merge with a high surrogate. Since + * the numeric values of high surrogates is *less than* that of low + * surrogates, the `range.end` cannot be a high surrogate here, and + * so there is no danger of it merging with a low surrogate already + * present at the beginning of `thisSegment`. + */ + thisSegment = s + thisSegment + } else { + thisSegment += s + } + } else { + /* Here be dragons. We need to split the range into several ranges that + * we can separately compile. + * + * Since the 'u' flag is not used when we get here, the RegExp engine + * treats surrogate chars as individual chars in all cases. Therefore, + * we do not need to protect low surrogates. + */ + + val bmpBelowHighSurrogates = range.intersect(CodePointRange.BmpBelowHighSurrogates) + if (bmpBelowHighSurrogates.nonEmpty) + thisSegment += literalRange(bmpBelowHighSurrogates) + + val highSurrogates = range.intersect(CodePointRange.HighSurrogates) + if (highSurrogates.nonEmpty) + addAlternative("[" + literalRange(highSurrogates) + "]" + s"(?![$MIN_LOW_SURROGATE-$MAX_LOW_SURROGATE])") + + val bmpAboveHighSurrogates = range.intersect(CodePointRange.BmpAboveHighSurrogates) + if (bmpAboveHighSurrogates.nonEmpty) + thisSegment += literalRange(bmpAboveHighSurrogates) + + val supplementaries = range.intersect(CodePointRange.Supplementaries) + if (supplementaries.nonEmpty) { + val startHigh = highSurrogate(supplementaries.start) + val startLow = lowSurrogate(supplementaries.start) + + val endHigh = highSurrogate(supplementaries.end) + val endLow = lowSurrogate(supplementaries.end) + + if (startHigh == endHigh) { + addAlternative( + codePointToString(startHigh) + "[" + literalRange(CodePointRange(startLow, endLow)) + "]") + } else { + addAlternative( + codePointToString(startHigh) + "[" + literalRange(CodePointRange(startLow, MAX_LOW_SURROGATE)) + "]") + + val middleHighs = CodePointRange(startHigh + 1, endHigh - 1) + if (middleHighs.nonEmpty) + addAlternative(s"[${literalRange(middleHighs)}][$MIN_LOW_SURROGATE-$MAX_LOW_SURROGATE]") + + addAlternative( + codePointToString(endHigh) + "[" + literalRange(CodePointRange(MIN_LOW_SURROGATE, endLow)) + "]") + } + } + } + + if (asciiCaseInsensitive) { + val uppercases = range.intersect(CodePointRange('A', 'Z')) + if (uppercases.nonEmpty) + thisSegment += literalRange(uppercases.shift('a' - 'A')) + + val lowercases = range.intersect(CodePointRange('a', 'z')) + if (lowercases.nonEmpty) + thisSegment += literalRange(lowercases.shift('A' - 'a')) + } + } + } +} + +private final class PatternCompiler(private val pattern: String, private var flags: Int) { + import PatternCompiler._ + import PatternCompiler.Support._ + import PatternCompiler.InlinedHelpers._ + import Pattern._ + + /** Whether the result `Pattern` must be sticky. */ + private var sticky: Boolean = false + + /** The parse index, within `pattern`. */ + private var pIndex: Int = 0 + + /** The number of capturing groups in the compiled pattern. + * + * This is different than `originalGroupCount` when there are atomic groups + * (or possessive quantifiers, which are sugar for atomic groups). + */ + private var compiledGroupCount: Int = 0 + + /** Map from original group number to compiled group number. + * + * It contains a mapping for the entire match, which is group 0. + */ + private val groupNumberMap = js.Array[Int](0) + + /** The number of capturing groups found so far in the original pattern. + * + * This is `groupNumberMap.length - 1`, because `groupNumberMap` contains + * the mapping for the entire match, which is group 0. + */ + @inline private def originalGroupCount = groupNumberMap.length - 1 + + /** Map from group name to original group number. + * + * We store *original* group numbers, rather than compiled group numbers, + * in order to make the renumbering caused by possessive quantifiers easier. + */ + private val namedGroups = js.Dictionary.empty[Int] + + @inline private def hasFlag(flag: Int): Boolean = (flags & flag) != 0 + + @inline private def unixLines: Boolean = hasFlag(UNIX_LINES) + @inline private def comments: Boolean = hasFlag(COMMENTS) + @inline private def dotAll: Boolean = hasFlag(DOTALL) + + @inline + private def asciiCaseInsensitive: Boolean = + (flags & (CASE_INSENSITIVE | UNICODE_CASE)) == CASE_INSENSITIVE + + @inline + private def unicodeCaseInsensitive: Boolean = { + enableUnicodeCaseInsensitive && // for dead code elimination + (flags & (CASE_INSENSITIVE | UNICODE_CASE)) == (CASE_INSENSITIVE | UNICODE_CASE) + } + + @inline + private def unicodeCaseOrUnicodeCharacterClass: Boolean = { + enableUnicodeCaseInsensitive && // for dead code elimination + (flags & (UNICODE_CASE | UNICODE_CHARACTER_CLASS)) != 0 + } + + @inline + private def multiline: Boolean = { + enableUnicodeCharacterClassesAndLookBehinds && // for dead code elimination + hasFlag(MULTILINE) + } + + @inline + private def unicodeCharacterClass: Boolean = { + enableUnicodeCharacterClassesAndLookBehinds && // for dead code elimination + hasFlag(UNICODE_CHARACTER_CLASS) + } + + def compile(): Pattern = { + // UNICODE_CHARACTER_CLASS implies UNICODE_CASE, even for LITERAL + if (hasFlag(UNICODE_CHARACTER_CLASS)) + flags |= UNICODE_CASE + + val isLiteral = hasFlag(LITERAL) + + if (!isLiteral) + processLeadingEmbeddedFlags() + + if (hasFlag(CANON_EQ)) + parseError("CANON_EQ is not supported") + + if (!enableUnicodeCharacterClassesAndLookBehinds) { + if (hasFlag(MULTILINE)) + parseErrorRequireESVersion("MULTILINE", "2018") + if (hasFlag(UNICODE_CHARACTER_CLASS)) + parseErrorRequireESVersion("UNICODE_CHARACTER_CLASS", "2018") + } + + if (!enableUnicodeCaseInsensitive) { + if (hasFlag(UNICODE_CASE)) + parseErrorRequireESVersion("UNICODE_CASE", "2015") + } + + val jsPattern = if (isLiteral) { + literal(pattern) + } else { + if (pattern.substring(pIndex, pIndex + 2) == "\\G") { + sticky = true + pIndex += 2 + } + compileTopLevel() + } + + val jsFlags = { + // We always use the 'u' and 's' flags when they are supported. + val baseJSFlags = { + if (supportsDotAll) "us" + else if (supportsUnicode) "u" + else "" + } + + // We add the 'i' flag when using Unicode-aware case insensitive matching. + if (unicodeCaseInsensitive) baseJSFlags + "i" + else baseJSFlags + } + + new Pattern(pattern, flags, jsPattern, jsFlags, sticky, originalGroupCount, + groupNumberMap, namedGroups) + } + + private def parseError(desc: String): Nothing = + throw new PatternSyntaxException(desc, pattern, pIndex) + + @inline + private def requireES2018Features(purpose: String): Unit = { + if (!enableUnicodeCharacterClassesAndLookBehinds) + parseErrorRequireESVersion(purpose, "2018") + } + + @noinline + private def parseErrorRequireESVersion(purpose: String, es: String): Nothing = { + parseError( + s"$purpose is not supported because it requires RegExp features of ECMAScript $es.\n" + + s"If you only target environments with ES$es+, you can enable ES$es features with\n" + + s" scalaJSLinkerConfig ~= { _.withESFeatures(_.withESVersion(ESVersion.ES$es)) }\n" + + "or an equivalent configuration depending on your build tool.") + } + + private def processLeadingEmbeddedFlags(): Unit = { + val m = leadingEmbeddedFlagSpecifierRegExp.exec(pattern) + if (m != null) { + for (chars <- m(1)) { + for (i <- 0 until chars.length()) + flags |= charToFlag(chars.charAt(i)) + } + + // If U was in the flags, we need to enable UNICODE_CASE as well + if (hasFlag(UNICODE_CHARACTER_CLASS)) + flags |= UNICODE_CASE + + for (chars <- m(2)) { + for (i <- 0 until chars.length()) + flags &= ~charToFlag(chars.charAt(i)) + } + + /* The way things are done here, it is possible to *remove* + * `UNICODE_CASE` from the set of flags while leaving + * `UNICODE_CHARACTER_CLASS` in. This creates a somewhat inconsistent + * state, but it matches what the JVM does, as illustrated in the test + * `RegexPatternTest.flags()`. + */ + + // Advance past the embedded flags + pIndex += m(0).get.length() + } + } + + // The predefined character class for \w, depending on the UNICODE_CHARACTER_CLASS flag + + @inline + private def wordCharClass: CompiledCharClass = + if (unicodeCharacterClass) UnicodeWordChar + else ASCIIWordChar + + // Meat of the compilation + + private def literal(s: String): String = { + var result = "" + val len = s.length() + var i = 0 + while (i != len) { + val cp = s.codePointAt(i) + result += literal(cp) + i += charCount(cp) + } + result + } + + private def literal(cp: Int): String = { + val s = codePointToString(cp) + + if (cp < 0x80) { + /* SyntaxCharacter :: one of + * ^ $ \ . * + ? ( ) [ ] { } | + */ + (cp: @switch) match { + case '^' | '$' | '\\' | '.' | '*' | '+' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '|' => + "\\" + s + case _ => + if (!asciiCaseInsensitive) + s + else if (cp >= 'A' && cp <= 'Z') + "[" + s + codePointToString(cp + ('a' - 'A')) + "]" + else if (cp >= 'a' && cp <= 'z') + "[" + codePointToString(cp + ('A' - 'a')) + s + "]" + else + s + } + } else { + if (supportsUnicode) { + /* We wrap low surrogates with `(?:x)` to ensure that we do not + * artificially create a surrogate pair in the compiled pattern where + * none existed in the source pattern. + * Consider the source pattern `\x{D834}\x{DD1E}`, for example. + * If low surrogates were not wrapped, it would be compiled to a + * surrogate pair, which would match the input string `"𝄞"` although it + * is not supposed to. + */ + if (isLowSurrogateCP(cp)) + s"(?:$s)" + else + s + } else { + if (isHighSurrogateCP(cp)) + s"(?:$s(?![$MIN_LOW_SURROGATE-$MAX_LOW_SURROGATE]))" + else if (isBmpCodePoint(cp)) + s + else + s"(?:$s)" // group a surrogate pair so that it is repeated as a whole + } + } + } + + @inline + private def compileTopLevel(): String = + compileTopLevelOrInsideGroup(insideGroup = false) + + @inline + private def compileInsideGroup(): String = + compileTopLevelOrInsideGroup(insideGroup = true) + + /** The main parsing method. + * + * It follows a recursive descent approach. It is recursive for any + * `(...)`-enclosed subpattern, and flat for other kinds of patterns. + */ + private def compileTopLevelOrInsideGroup(insideGroup: Boolean): String = { + // scalastyle:off return + // the 'return' is in the case ')' + + val pattern = this.pattern // local copy + val len = pattern.length() + + var result = "" + + while (pIndex != len) { + val dispatchCP = pattern.codePointAt(pIndex) + (dispatchCP: @switch) match { + // Cases that mess with the control flow and/or that cannot be repeated + + case ')' => + if (!insideGroup) + parseError("Unmatched closing ')'") + pIndex += 1 + return result + + case '|' => + if (sticky && !insideGroup) + parseError("\\G is not supported when there is an alternative at the top level") + pIndex += 1 + result += "|" + + // experimentally, this is the set of chars considered as whitespace for comments + case ' ' | '\t' | '\n' | '\u000B' | '\f' | '\r' if comments => + pIndex += 1 + + case '#' if comments => + skipSharpComment() + + case '?' | '*' | '+' | '{' => + parseError("Dangling meta character '" + codePointToString(dispatchCP) + "'") + + // Regular cases, which can be repeated + + case _ => + // Record the current compiledGroupCount, for possessive quantifiers + val compiledGroupCountBeforeThisToken = compiledGroupCount + + val compiledToken = (dispatchCP: @switch) match { + case '\\' => compileEscape() + case '[' => compileCharacterClass() + case '(' => compileGroup() + case '^' => compileCaret() + case '$' => compileDollar() + case '.' => compileDot() + + case _ => + pIndex += charCount(dispatchCP) + literal(dispatchCP) + } + + result += compileRepeater(compiledGroupCountBeforeThisToken, compiledToken) + } + } + + if (insideGroup) + parseError("Unclosed group") + + result + // scalastyle:on return + } + + /** Skip a '#' comment. + * + * Pre-condition: `comments && pattern.charAt(pIndex) == '#'` is true + */ + private def skipSharpComment(): Unit = { + val pattern = this.pattern // local copy + val len = pattern.length() + + @inline def isEOL(c: Char): Boolean = + c == '\n' || c == '\r' || c == '\u0085' || c == '\u2028' || c == '\u2029' + + while (pIndex != len && !isEOL(pattern.charAt(pIndex))) + pIndex += 1 + } + + /** Skip all comments. + * + * Pre-condition: `comments` is true + */ + @noinline + private def skipComments(): Unit = { + val pattern = this.pattern // local copy + val len = pattern.length() + + @inline @tailrec + def loop(): Unit = { + if (pIndex != len) { + (pattern.charAt(pIndex): @switch) match { + case ' ' | '\t' | '\n' | '\u000B' | '\f' | '\r' => + pIndex += 1 + loop() + case '#' => + skipSharpComment() + loop() + case _ => + () + } + } + } + + loop() + } + + private def compileRepeater(compiledGroupCountBeforeThisToken: Int, compiledToken: String): String = { + val pattern = this.pattern // local copy + val len = pattern.length() + + val startOfRepeater = pIndex + val repeaterDispatchChar = + if (startOfRepeater == len) '.' + else pattern.charAt(startOfRepeater) + + @inline def hasRepeater: Boolean = { + repeaterDispatchChar == '?' || repeaterDispatchChar == '*' || + repeaterDispatchChar == '+' || repeaterDispatchChar == '{' + } + + if (hasRepeater) { + // There is a repeater + val baseRepeater = parseBaseRepeater(repeaterDispatchChar) + + if (pIndex != len) { + pattern.charAt(pIndex) match { + case '+' => + // Possessive quantifier + pIndex += 1 + buildPossessiveQuantifier(compiledGroupCountBeforeThisToken, compiledToken, baseRepeater) + case '?' => + // Lazy quantifier + pIndex += 1 + compiledToken + baseRepeater + "?" + case _ => + // Greedy quantifier + compiledToken + baseRepeater + } + } else { + // Greedy quantifier + compiledToken + baseRepeater + } + } else { + // No repeater + compiledToken + } + } + + private def parseBaseRepeater(repeaterDispatchChar: Char): String = { + val pattern = this.pattern // local copy + val startOfRepeater = pIndex + + pIndex += 1 + + if (repeaterDispatchChar == '{') { + val len = pattern.length() + + if (pIndex == len || !isDigit(pattern.charAt(pIndex))) + parseError("Illegal repetition") + while (pIndex != len && isDigit(pattern.charAt(pIndex))) + pIndex += 1 + if (pIndex == len) + parseError("Illegal repetition") + if (pattern.charAt(pIndex) == ',') { + pIndex += 1 + while (pIndex != len && isDigit(pattern.charAt(pIndex))) + pIndex += 1 + } + if (pIndex == len || pattern.charAt(pIndex) != '}') + parseError("Illegal repetition") + pIndex += 1 + } + + pattern.substring(startOfRepeater, pIndex) + } + + /** Builds a possessive quantifier, which is sugar for an atomic group over + * a greedy quantifier. + */ + private def buildPossessiveQuantifier(compiledGroupCountBeforeThisToken: Int, + compiledToken: String, baseRepeater: String): String = { + + /* This is very intricate. Not only do we need to surround a posteriori the + * previous token, we are introducing a new capturing group in between. + * This means that we need to renumber all backreferences contained in the + * compiled token. + */ + + // Remap group numbers + for (i <- 0 until groupNumberMap.length) { + val mapped = groupNumberMap(i) + if (mapped > compiledGroupCountBeforeThisToken) + groupNumberMap(i) = mapped + 1 + } + + // Renumber all backreferences contained in the compiled token + import js.JSStringOps._ + val amendedToken = compiledToken.jsReplace(renumberingRegExp, { + (str, backslashes, groupString) => + if (backslashes.length() % 2 == 0) { // poor man's negative look-behind + str + } else { + val groupNumber = parseInt(groupString, 10) + if (groupNumber > compiledGroupCountBeforeThisToken) + backslashes + (groupNumber + 1) + else + str + } + }: js.Function3[String, String, String, String]) + + // Plan the future remapping + compiledGroupCount += 1 + + // Finally, the encoding of the atomic group over the greedy quantifier + val myGroupNumber = compiledGroupCountBeforeThisToken + 1 + s"(?:(?=($amendedToken$baseRepeater))\\$myGroupNumber)" + } + + @inline + private def compileCaret(): String = { + pIndex += 1 + if (multiline) { + /* `multiline` implies ES2018, so we can use look-behind assertions. + * We cannot use the 'm' flag of JavaScript RegExps because its semantics + * differ from the Java ones (either with or without `UNIX_LINES`). + */ + if (unixLines) + "(?<=^|\n)" + else + "(?<=^|\r(?!\n)|[\n\u0085\u2028\u2029])" + } else { + /* Wrap as (?:^) in case it ends up being repeated, for example `^+` + * becomes `(?:^)+`. This is necessary because `^+` is not syntactically + * valid in JS, although it is valid once wrapped in a group. + * (Not that repeating ^ has any useful purpose, but the spec does not + * prevent it.) + */ + "(?:^)" + } + } + + @inline + private def compileDollar(): String = { + pIndex += 1 + if (multiline) { + /* `multiline` implies ES2018, so we can use look-behind assertions. + * We cannot use the 'm' flag of JavaScript RegExps (see ^ above). + */ + if (unixLines) + "(?=$|\n)" + else + "(?=$|(? + val cls = parsePredefinedCharacterClass(dispatchChar) + cls.kind match { + case CompiledCharClass.PosP => + "\\p{" + cls.data + "}" + case CompiledCharClass.NegP => + "\\P{" + cls.data + "}" + case CompiledCharClass.PosClass => + "[" + cls.data + "]" + case CompiledCharClass.NegClass => + codePointNotAmong(cls.data) + } + + // Boundary matchers + + case 'b' => + if (pattern.substring(pIndex, pIndex + 4) == "b{g}") { + parseError("\\b{g} is not supported") + } else { + /* Compile as is if both `UNICODE_CASE` and `UNICODE_CHARACTER_CLASS` are false. + * This is correct because: + * - since `UNICODE_CHARACTER_CLASS` is false, word chars are + * considered to be `[a-zA-Z_0-9]` for Java semantics, and + * - since `UNICODE_CASE` is false, we do not use the 'i' flag in the + * JS RegExp, and so word chars are considered to be `[a-zA-Z_0-9]` + * for the JS semantics as well. + * + * In all other cases, we determine the compiled form of `\w` and use + * a custom look-around-based implementation. + * This requires ES2018+, hence why we go to the trouble of trying to + * reuse `\b` if we can. + */ + if (unicodeCaseOrUnicodeCharacterClass) { + requireES2018Features("\\b with UNICODE_CASE") // UNICODE_CHARACTER_CLASS would have been rejected earlier + pIndex += 1 + val w = wordCharClass.data + s"(?:(?<=[$w])(?![$w])|(? + // Same strategy as for \b above + if (unicodeCaseOrUnicodeCharacterClass) { + requireES2018Features("\\B with UNICODE_CASE") // UNICODE_CHARACTER_CLASS would have been rejected earlier + pIndex += 1 + val w = wordCharClass.data + s"(?:(?<=[$w])(?=[$w])|(? + // We can always use ^ for start-of-text because we never use the 'm' flag in the JS RegExp + pIndex += 1 + "(?:^)" // wrap in case it is quantified (see compilation of '^') + case 'G' => + parseError("\\G in the middle of a pattern is not supported") + case 'Z' => + // We can always use $ for end-of-text because we never use the 'm' flag in the JS RegExp + pIndex += 1 + val lineTerminator = + if (unixLines) "\n" + else "(?:\r\n?|[\n\u0085\u2028\u2029])" + "(?=" + lineTerminator + "?$)" + case 'z' => + // We can always use $ for end-of-text because we never use the 'm' flag in the JS RegExp + pIndex += 1 + "(?:$)" // wrap in case it is quantified (see compilation of '$') + + // Linebreak matcher + + case 'R' => + pIndex += 1 + "(?:\r\n|[\n-\r\u0085\u2028\u2029])" + + // Unicode Extended Grapheme matcher + + case 'X' => + parseError("\\X is not supported") + + // Back references + + case '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => + /* From the JavaDoc: + * + * > In this class, \1 through \9 are always interpreted as back + * > references, and a larger number is accepted as a back reference if + * > at least that many subexpressions exist at that point in the + * > regular expression, otherwise the parser will drop digits until + * > the number is smaller or equal to the existing number of groups or + * > it is one digit. + */ + val start = pIndex + var end = start + 1 + + // In most cases, one of the first two conditions is immediately false + while (end != len && isDigit(pattern.charAt(end)) && + parseInt(pattern.substring(start, end + 1), 10) <= originalGroupCount) { + end += 1 + } + + val groupString = pattern.substring(start, end) + val groupNumber = parseInt(groupString, 10) + if (groupNumber > originalGroupCount) + parseError(s"numbered capturing group <$groupNumber> does not exist") + val compiledGroupNumber = groupNumberMap(groupNumber) + pIndex = end + // Wrap in a non-capturing group in case it's followed by a (de-escaped) digit + "(?:\\" + compiledGroupNumber + ")" + + case 'k' => + pIndex += 1 + if (pIndex == len || pattern.charAt(pIndex) != '<') + parseError("\\k is not followed by '<' for named capturing group") + pIndex += 1 + val groupName = parseGroupName() + val groupNumber = namedGroups.getOrElse(groupName, { + parseError(s"named capturing group <$groupName> does not exit") + }) + val compiledGroupNumber = groupNumberMap(groupNumber) + pIndex += 1 + // Wrap in a non-capturing group in case it's followed by a (de-escaped) digit + "(?:\\" + compiledGroupNumber + ")" + + // Quotes + + case 'Q' => + val start = pIndex + 1 + val end = pattern.indexOf("\\E", start) + if (end < 0) { + pIndex = pattern.length() + literal(pattern.substring(start)) + } else { + pIndex = end + 2 + literal(pattern.substring(start, end)) + } + + // Other + + case c => + literal(parseSingleCodePointEscape()) + } + } + + private def parseSingleCodePointEscape(): Int = { + val pattern = this.pattern // local copy + + (pattern.codePointAt(pIndex): @switch) match { + case '0' => + parseOctalEscape() + case 'x' => + parseHexEscape() + case 'u' => + parseUnicodeHexEscape() + case 'N' => + parseError("\\N is not supported") + case 'a' => + pIndex += 1 + 0x0007 + case 't' => + pIndex += 1 + 0x0009 + case 'n' => + pIndex += 1 + 0x000a + case 'f' => + pIndex += 1 + 0x000c + case 'r' => + pIndex += 1 + 0x000d + case 'e' => + pIndex += 1 + 0x001b + case 'c' => + pIndex += 1 + if (pIndex == pattern.length()) + parseError("Illegal control escape sequence") + val cp = pattern.codePointAt(pIndex) + pIndex += charCount(cp) + // https://stackoverflow.com/questions/35208570/java-regular-expression-cx-control-characters + cp ^ 0x40 + + case cp => + // Other letters are forbidden / reserved for future use + if ((cp >= 'A' && cp <= 'Z') || (cp >= 'a' && cp <= 'z')) + parseError("Illegal/unsupported escape sequence") + + // But everything else is accepted and quoted as is + pIndex += charCount(cp) + cp + } + } + + private def parseOctalEscape(): Int = { + /* \0n The character with octal value 0n (0 <= n <= 7) + * \0nn The character with octal value 0nn (0 <= n <= 7) + * \0mnn The character with octal value 0mnn (0 <= m <= 3, 0 <= n <= 7) + */ + + val pattern = this.pattern // local copy + val len = pattern.length() + val start = pIndex + + val d1 = + if (start + 1 < len) pattern.charAt(start + 1) - '0' + else -1 + if (d1 < 0 || d1 > 7) + parseError("Illegal octal escape sequence") + + val d2 = + if (start + 2 < len) pattern.charAt(start + 2) - '0' + else -1 + + if (d2 < 0 || d2 > 7) { + pIndex += 2 + d1 + } else if (d1 > 3) { + pIndex += 3 + d1 * 8 + d2 + } else { + val d3 = + if (start + 3 < len) pattern.charAt(start + 3) - '0' + else -1 + + if (d3 < 0 || d3 > 7) { + pIndex += 3 + d1 * 8 + d2 + } else { + pIndex += 4 + d1 * 64 + d2 * 8 + d3 + } + } + } + + private def parseHexEscape(): Int = { + /* \xhh The character with hexadecimal value 0xhh + * \x{h...h} The character with hexadecimal value 0xh...h + * (Character.MIN_CODE_POINT <= 0xh...h <= Character.MAX_CODE_POINT) + */ + + val pattern = this.pattern // local copy + val len = pattern.length() + + val start = pIndex + 1 + + if (start != len && pattern.charAt(start) == '{') { + val innerStart = start + 1 + val innerEnd = pattern.indexOf("}", innerStart) + if (innerEnd < 0) + parseError("Unclosed hexadecimal escape sequence") + val cp = parseHexCodePoint(innerStart, innerEnd, "hexadecimal") + pIndex = innerEnd + 1 + cp + } else { + val cp = parseHexCodePoint(start, start + 2, "hexadecimal") + pIndex = start + 2 + cp + } + } + + private def parseUnicodeHexEscape(): Int = { + /* \ uhhhh The character with hexadecimal value 0xhhhh + * + * An escaped high surrogate followed by an escaped low surrogate form a + * unique escaped code point. This is important in character classes. + */ + + val pattern = this.pattern // local copy + + val start = pIndex + 1 + val end = start + 4 + val codeUnit = parseHexCodePoint(start, end, "Unicode") + + pIndex = end + + val lowStart = end + 2 + val lowEnd = lowStart + 4 + + if (isHighSurrogateCP(codeUnit) && pattern.substring(end, lowStart) == "\\u") { + val low = parseHexCodePoint(lowStart, lowEnd, "Unicode") + if (isLowSurrogateCP(low)) { + pIndex = lowEnd + toCodePointCP(codeUnit, low) + } else { + codeUnit + } + } else { + codeUnit + } + } + + private def parseHexCodePoint(start: Int, end: Int, nameForError: String): Int = { + val pattern = this.pattern // local copy + val len = pattern.length() + + if (start == end || end > len) + parseError(s"Illegal $nameForError escape sequence") + + for (i <- start until end) { + if (!isHexDigit(pattern.charAt(i))) + parseError(s"Illegal $nameForError escape sequence") + } + + val cp = + if (end - start > 6) Character.MAX_CODE_POINT + 1 + else parseInt(pattern.substring(start, end), 16) + if (cp > Character.MAX_CODE_POINT) + parseError("Hexadecimal codepoint is too big") + + cp + } + + /** Parses and returns a translated version of a pre-defined character class. */ + private def parsePredefinedCharacterClass(dispatchChar: Char): CompiledCharClass = { + import CompiledCharClass._ + + pIndex += 1 + + val positive = (dispatchChar: @switch) match { + case 'd' | 'D' => + if (unicodeCharacterClass) UnicodeDigit + else ASCIIDigit + case 'h' | 'H' => + UniversalHorizontalWhiteSpace + case 's' | 'S' => + if (unicodeCharacterClass) UnicodeWhitespace + else ASCIIWhiteSpace + case 'v' | 'V' => + UniversalVerticalWhiteSpace + case 'w' | 'W' => + wordCharClass + case 'p' | 'P' => + parsePCharacterClass() + } + + if (dispatchChar >= 'a') // cheap isLower + positive + else + positive.negated + } + + /** Parses and returns a translated version of a `\p` character class. */ + private def parsePCharacterClass(): CompiledCharClass = { + val pattern = this.pattern // local copy + val len = pattern.length() + + val start = pIndex + val property = if (start == len) { + "?" // mimics the behavior of the JVM + } else if (pattern.charAt(start) == '{') { + val innerStart = start + 1 + val innerEnd = pattern.indexOf("}", innerStart) + if (innerEnd < 0) + parseError("Unclosed character family") + pIndex = innerEnd + pattern.substring(innerStart, innerEnd) + } else { + pattern.substring(start, start + 1) + } + + val result = if (!unicodeCharacterClass && asciiPOSIXCharacterClasses.contains(property)) { + val property2 = + if (asciiCaseInsensitive && (property == "Lower" || property == "Upper")) "Alpha" + else property + asciiPOSIXCharacterClasses(property2) + } else { + // For anything else, we need built-in support for \p + requireES2018Features("Unicode character family") + + predefinedPCharacterClasses.getOrElse(property, { + val scriptPrefixLen = if (property.startsWith("Is")) { + 2 + } else if (property.startsWith("sc=")) { + 3 + } else if (property.startsWith("script=")) { + 7 + } else if (property.startsWith("In") || property.startsWith("blk=") || property.startsWith("block=")) { + parseError("Blocks are not supported in \\p Unicode character families") + } else { + // Error + parseError(s"Unknown Unicode character class '$property'") + } + CompiledCharClass.posP("sc=" + canonicalizeScriptName(property.substring(scriptPrefixLen))) + }) + } + + pIndex += 1 + + result + } + + /** Validates a script name and canonicalizes its casing. + * + * The JDK regexps compare script names while ignoring case, but JavaScript + * requires the canonical name. + * + * After canonicalizing the script name, we try to create a `js.RegExp` that + * uses it. If that fails, we report the (original) script name as unknown. + */ + private def canonicalizeScriptName(scriptName: String): String = { + import js.JSStringOps._ + + val lowercase = scriptName.toLowerCase() + + canonicalizedScriptNameCache.getOrElseUpdate(lowercase, { + val canonical = lowercase.jsReplace(scriptCanonicalizeRegExp, + ((s: String) => s.toUpperCase()): js.Function1[String, String]) + + try { + new js.RegExp(s"\\p{sc=$canonical}", "u") + } catch { + case _: js.JavaScriptException => + parseError(s"Unknown character script name {$scriptName}") + } + + canonical + }) + } + + private def compileCharacterClass(): String = { + // scalastyle:off return + // the 'return' is in the case ']' + + val pattern = PatternCompiler.this.pattern // local copy + val len = pattern.length() + + pIndex += 1 // skip '[' + + /* If there is a leading '^' right after the '[', the whole class is + * negated. In a sense, '^' is the operator with the lowest precedence. + */ + val isNegated = pIndex != len && pattern.charAt(pIndex) == '^' + if (isNegated) + pIndex += 1 + + val builder = new CharacterClassBuilder(asciiCaseInsensitive, isNegated) + + while (pIndex != len) { + def processRangeOrSingleCodePoint(startCodePoint: Int): Unit = { + if (comments) + skipComments() + + if (pIndex != len && pattern.charAt(pIndex) == '-') { + // Perhaps a range of code points, unless the '-' is followed by '[' or ']' + pIndex += 1 + if (comments) + skipComments() + + if (pIndex == len) + parseError("Unclosed character class") + + val cpEnd = pattern.codePointAt(pIndex) + + if (cpEnd == '[' || cpEnd == ']') { + // Oops, it wasn't a range after all + builder.addSingleCodePoint(startCodePoint) + builder.addSingleCodePoint('-') + } else { + // Range of code points + pIndex += charCount(cpEnd) + val endCodePoint = + if (cpEnd == '\\') parseSingleCodePointEscape() + else cpEnd + if (endCodePoint < startCodePoint) + parseError("Illegal character range") + builder.addCodePointRange(startCodePoint, endCodePoint) + } + } else { + // Single code point + builder.addSingleCodePoint(startCodePoint) + } + } + + (pattern.codePointAt(pIndex): @switch) match { + case ']' => + pIndex += 1 + return builder.finish() + + case '&' => + pIndex += 1 + if (pIndex != len && pattern.charAt(pIndex) == '&') { + pIndex += 1 + builder.startNewConjunct() + } else { + processRangeOrSingleCodePoint('&') + } + + case '[' => + builder.addCharacterClass(compileCharacterClass()) + + case '\\' => + pIndex += 1 + if (pIndex == len) + parseError("Illegal escape sequence") + val c2 = pattern.charAt(pIndex) + (c2: @switch) match { + case 'd' | 'D' | 'h' | 'H' | 's' | 'S' | 'v' | 'V' | 'w' | 'W' | 'p' | 'P' => + builder.addCharacterClass(parsePredefinedCharacterClass(c2)) + + case 'Q' => + pIndex += 1 + val end = pattern.indexOf("\\E", pIndex) + if (end < 0) + parseError("Unclosed character class") + builder.addCodePointsInString(pattern, pIndex, end) + pIndex = end + 2 // for the \E + + case _ => + processRangeOrSingleCodePoint(parseSingleCodePointEscape()) + } + + case ' ' | '\t' | '\n' | '\u000B' | '\f' | '\r' if comments => + pIndex += 1 + case '#' if comments => + skipSharpComment() + + case codePoint => + pIndex += charCount(codePoint) + processRangeOrSingleCodePoint(codePoint) + } + } + + parseError("Unclosed character class") + // scalastyle:on return + } + + private def compileGroup(): String = { + val pattern = this.pattern // local copy + val len = pattern.length() + + val start = pIndex + + if (start + 1 == len || pattern.charAt(start + 1) != '?') { + // Numbered capturing group + pIndex = start + 1 + compiledGroupCount += 1 + groupNumberMap.push(compiledGroupCount) + "(" + compileInsideGroup() + ")" + } else { + if (start + 2 == len) + parseError("Unclosed group") + + val c1 = pattern.charAt(start + 2) + + if (c1 == ':' || c1 == '=' || c1 == '!') { + // Non-capturing group or look-ahead + pIndex = start + 3 + pattern.substring(start, start + 3) + compileInsideGroup() + ")" + } else if (c1 == '<') { + if (start + 3 == len) + parseError("Unclosed group") + + val c2 = pattern.charAt(start + 3) + + if (isLetter(c2)) { + // Named capturing group + pIndex = start + 3 + val name = parseGroupName() + if (namedGroups.contains(name)) + parseError(s"named capturing group <$name> is already defined") + compiledGroupCount += 1 + groupNumberMap.push(compiledGroupCount) // this changes originalGroupCount + namedGroups(name) = originalGroupCount + pIndex += 1 + "(" + compileInsideGroup() + ")" + } else { + // Look-behind group + if (c2 != '=' && c2 != '!') + parseError("Unknown look-behind group") + requireES2018Features("Look-behind group") + pIndex = start + 4 + pattern.substring(start, start + 4) + compileInsideGroup() + ")" + } + } else if (c1 == '>') { + // Atomic group + pIndex = start + 3 + compiledGroupCount += 1 + val groupNumber = compiledGroupCount + s"(?:(?=(${compileInsideGroup()}))\\$groupNumber)" + } else { + parseError("Embedded flag expression in the middle of a pattern is not supported") + } + } + } + + /** Parses a group name. + * + * Pre: `pIndex` should point right after the opening '<'. + * + * Post: `pIndex` points right before the closing '>' (it is guaranteed to be a '>'). + */ + private def parseGroupName(): String = { + val pattern = this.pattern // local copy + val len = pattern.length() + val start = pIndex + while (pIndex != len && isLetterOrDigit(pattern.charAt(pIndex))) + pIndex += 1 + if (pIndex == len || pattern.charAt(pIndex) != '>') + parseError("named capturing group is missing trailing '>'") + pattern.substring(start, pIndex) + } +} diff --git a/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala b/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala new file mode 100644 index 0000000000..0faf78fafc --- /dev/null +++ b/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala @@ -0,0 +1,56 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util.regex + +import scala.scalajs.js +import scala.scalajs.LinkingInfo + +class PatternSyntaxException(desc: String, regex: String, index: Int) + extends IllegalArgumentException { + + def getIndex(): Int = index + + def getDescription(): String = desc + + def getPattern(): String = regex + + override def getMessage(): String = { + // local copies, for code size + val idx = index + val re = regex + + val indexHint = if (idx < 0) "" else " near index " + idx + val base = desc + indexHint + "\n" + re + + if (idx >= 0 && re != null && idx < re.length()) + base + "\n" + repeat(" ", idx) + "^" + else + base + } + + @inline + private def repeat(s: String, count: Int): String = { + // TODO Use java.lang.String.repeat() once we can (JDK 11+ method) + if (LinkingInfo.esVersion >= LinkingInfo.ESVersion.ES2015) { + s.asInstanceOf[js.Dynamic].repeat(count).asInstanceOf[String] + } else { + var result = "" + var i = 0 + while (i != count) { + result += s + i += 1 + } + result + } + } +} diff --git a/javalib/src/main/scala/java/util/regex/README.md b/javalib/src/main/scala/java/util/regex/README.md new file mode 100644 index 0000000000..e71f7364b6 --- /dev/null +++ b/javalib/src/main/scala/java/util/regex/README.md @@ -0,0 +1,324 @@ +# Design document for the implementation of `j.u.regex.*` + +Java and JavaScript have different support for regular expressions. +In addition to Java having many more features, they also *differ* in the specifics of most of the features they have in common. + +For performance and code size reasons, we still want to use the native JavaScript `RegExp` class. +Modern JavaScript engines JIT-compile `RegExp`s to native code, so it is impossible to compete with that using a user-space engine. +For example, see [V8 talking about its Irregexp library](https://blog.chromium.org/2009/02/irregexp-google-chromes-new-regexp.html) and [SpiderMonkey talking about their latest integration of Irregexp](https://hacks.mozilla.org/2020/06/a-new-regexp-engine-in-spidermonkey/). + +Therefore, our strategy for `java.util.regex` is to *compile* Java regexes down to JavaScript regexes that behave in the same way. +The compiler is in the file `PatternCompiler.scala`, and is invoked at the time of `Pattern.compile()`. + +We can deal with most features in a compliant way using that strategy, while retaining performance, and without sacrificing code size too much compared to directly passing regexes through without caring about the discrepancies. +There are however a few features that are either never supported, or only supported when targeting a recent enough version of ECMAScript. + +## Support + +The set of supported features depends on the target ECMAScript version, specified in `ESFeatures.esVersion`. + +The following features are never supported: + +* the `CANON_EQ` flag, +* the `\X`, `\b{g}` and `\N{...}` expressions, +* `\p{In𝘯𝘢𝘮𝘦}` character classes representing Unicode *blocks*, +* the `\G` boundary matcher, *except* if it appears at the very beginning of the regex (e.g., `\Gfoo`), +* embedded flag expressions with inner groups, i.e., constructs of the form `(?idmsuxU-idmsuxU:𝑋)`, +* embedded flag expressions without inner groups, i.e., constructs of the form `(?idmsuxU-idmsuxU)`, *except* if they appear at the very beginning of the regex (e.g., `(?i)abc` is accepted, but `ab(?i)c` is not), and +* numeric "back" references to groups that are defined later in the pattern (note that even Java does not support *named* back references like that). + +The following features require `esVersion >= ESVersion.ES2015`: + +* the `UNICODE_CASE` flag. + +The following features require `esVersion >= ESVersion.ES2018`: + +* the `MULTILINE` and `UNICODE_CHARACTER_CLASS` flags, +* look-behind assertions `(?<=𝑋)` and `(?𝑋)`, +* possessive quantifiers `𝑋*+`, `𝑋++` and `𝑋?+`, +* the `\A`, `\Z` and `\z` boundary matchers, +* the `\R` expression, +* embedded quotations with `\Q` and `\E`, both outside and inside character classes. + +All the supported features have the correct semantics from Java. +This is even true for features that exist in JavaScript but with different semantics, among which: + +* the `^` and `$` boundary matchers with the `MULTILINE` flag (when the latter is supported), +* the predefined character classes `\h`, `\s`, `\v`, `\w` and their negated variants, respecting the `UNICODE_CHARACTER_CLASS` flag, +* the `\b` and `\B` boundary matchers, respecting the `UNICODE_CHARACTER_CLASS` flag, +* the internal format of `\p{𝘯𝘢𝘮𝘦}` character classes, including the `\p{java𝘔𝘦𝘵𝘩𝘰𝘥𝘕𝘢𝘮𝘦}` classes, +* octal escapes and control escapes. + +### Guarantees + +If a feature is not supported, a `PatternSyntaxException` is thrown at the time of `Pattern.compile()`. + +If `Pattern.compile()` succeeds, the regex is guaranteed to behave exactly like on the JVM, *except* for capturing groups within repeated segments (both for their back references and subsequent calls to `group`, `start` and `end`): + +* on the JVM, a capturing group always captures whatever substring was successfully matched last by *that* group during the processing of the regex: + - even if it was in a previous iteration of a repeated segment and the last iteration did not have a match for that group, or + - if it was during a later iteration of a repeated segment that was subsequently backtracked; +* in JS, capturing groups within repeated segments always capture what was matched (or not) during the last iteration that was eventually kept. + +The behavior of JavaScript is more "functional", whereas that of the JVM is more "imperative". +This imperative nature is also reflected in the `hitEnd()` and `requireEnd()` methods of `Matcher`, which we do not support (they don't link). + +The behavior of the JVM does not appear to be specified, and is questionable. +There are several open issues that argue it is buggy: + +* https://bugs.openjdk.java.net/browse/JDK-8027747 +* https://bugs.openjdk.java.net/browse/JDK-8187083 +* https://bugs.openjdk.java.net/browse/JDK-8187080 +* https://bugs.openjdk.java.net/browse/JDK-8187082 + +Therefore, it seems wise to keep the JavaScript behavior, and not try to replicate the JVM behavior at great cost (if that is even possible within our constrains). + +## Implementation strategy + +Java regexes are compiled to JS regexes in one pass, using a recursive descent approach. +There is a state variable `pIndex` which indicates the position inside the original `pattern`. +Compilation methods parse a subexpression at `pIndex`, advance `pIndex` past what they parsed, and return the result of the compilation. + +Parsing is always done at the code point level, and not at the individual `Char` level, using the [WTF-16 encoding](https://simonsapin.github.io/wtf-8/#wtf-16). +See [Handling surrogate pairs without support for the 'u' flag](#handling-surrogate-pairs-without-support-for-the-u-flag) for details about the behavior of lone surrogates. + +We first describe the compilation with the assumption that the underlying `RegExp`s support the `u` flag. +This is always true in ES 2015+, and dynamically determined at run-time in ES 5.1. +We will cover later what happens when it is not supported. + +### JS RegExp flags and case sensitivity + +Irrespective of the Java flags, we always use the following JS flags when they are supported (including through dynamic detection): + +- 'u' for correct handling of surrogate pairs and Unicode rules for case folding (introduced in ES2015), +- 's' for the dotAll behavior, i.e., `.` matches any code point (introduced in ES2018). + +In addition, we use the 'i' JS flag when both `CASE_INSENSITIVE` and `UNICODE_CASE` are on. +Since `UNICODE_CASE` is only supported in ES 2015+, this implies that 'u' is supported, and hence that we can leave all the handling of case insensitivity to the native RegExp. + +When `CASE_INSENSITIVE` is on but `UNICODE_CASE` is off, we must apply ASCII case insensitivity. +This is not supported by JS RegExps, so we implement it ourselves during compilation. +This is represented by the property `asciiCaseInsensitive`. +When it is true: + +* any single code point that is an ASCII letter, such as 'g', is compiled to a character class with the uppercase and lowercase variants (e.g., `[Gg]`), in subexpressions or in character classes, and +* any character range in a character class that intersects with the range `A-Z` and/or `a-z` is compiled with additional range(s) to cover the uppercase and lowercase variants. + +`PatternCompiler` never uses any other JS RegExp flag. +`Pattern` adds the 'g' flag for its general-purpose instance of `RegExp` (the one used for everything except `Matcher.matches()`), as well as the 'y' flag if the regex is sticky and it is supported. + +### Capturing groups + +Usually, there is a 1-to-1 relationship between original group numbers and compiled groups numbers. +However, differences are introduced when compiling atomic groups and possessive quantifiers. +Therefore, we maintain a mapping from original group numbers to the corresponding group numbers in the compiled pattern. +We use it for the following purposes: + +* when compiling back references of the form `\𝑁`, and +* when using the `Matcher` API to retrieve the groups' contents, start and end positions. + +Named capturing groups are always compiled as numbered capturing groups, even in ES 2018+. +We record an additional map of names to the corresponding original group numbers, and use it + +* when compiling named back references of the form `\k<𝘯𝘢𝘮𝘦>` (as numbered back references), and +* when using the `Matcher` API with group names. + +### Other main "control structures" + +The following constructs are translated as is: + +* Sequences and alternatives, +* Greedy quantifiers of the form `𝑋*`, `𝑋+` and `𝑋?`, +* Lazy quantifiers of the form `𝑋*?`, `𝑋+?` and `𝑋??`, +* Non-capturing groups of the form `(?:𝑋)`, +* Look-ahead groups of the form `(?=𝑋)` and `(?!𝑋)`, +* Look-behind groups of the form `(?<=𝑋)` and `(?𝑋)`, and +* Possessive quantifiers, for example of the form `𝑋*+`. + +### Single code points + +Subexpressions that represent a single code point are parsed and normalized as the code point that they represent. +For example, both `a` and `\x65` are compiled as `a`. +Code points that are metacharacters in JS regexes (i.e., `^ $ \ . * + ? ( ) [ ] { } |`) are escaped with a `\`, for example `\$`. +This is implemented in `def literal(cp: Int)`. + +Note that a double escape of the form `\uℎℎℎℎ\uℎℎℎℎ` representing a high surrogate and a low surrogate is treated as a single escape for a single supplementary code point. +For example, `\uD834\uDD1E` is considered as a single escape for the code point `𝄞` (U+1D11E Musical Symbol G Clef). + +This behavior only applies to `\u` escapes. +A would-be double-escape `\x{D834}\x{DD1E}` constitutes two separate code points. +In practice, such a sequence can never match anything in the input; if the input contained that sequence of code units, it would be considered as a single code point `𝄞`, which is not matched by a pattern meant to match two separate code points U+D834 and U+DD1E. + +### Quotes + +A quote starts with `\Q`, and ends at the first occurrence of `\E` or the end of the string. +The full string in between is taken as a sequence of code points. + +Each code point is compiled as described in "Single code points" for `def literal(cp: Int)`, and the compiled patterns are concatenated in a sequence. +This is implemented in `def literal(s: String)`. + +### Predefined character classes + +Predefined character classes represent a set of code points that matches a single code point in the input string. +The set typically depends on the value of `UNICODE_CHARACTER_CLASS`. + +Since virtually none of them has a corresponding predefined character class in JS RegExps, they are all compiled as custom `[...]` character classes, according to their definition. + +### Atomic groups + +Atomic groups are not well documented in the JavaDoc, but they are well covered in outside documentation such as [on Regular-Expressions.info](https://www.regular-expressions.info/atomic.html). +They have the form `(?>𝑋)`. +An atomic group matches whatever `𝑋` matches, but once it has successfully matched a particular substring, it is considered as an atomic unit. +If backtracking is needed later on because the rest of the pattern failed to match, the atomic group is backtracked as a whole. + +JS does not support atomic groups. +However, there exists a trick to implement atomic groups on top of look-ahead groups and back references, including with the correct performance characterics. +It is well documented in the article [Mimicking Atomic Groups](https://blog.stevenlevithan.com/archives/mimic-atomic-groups). +In a nutshell, we compile `(?>𝑋)` to `(?:(?=(𝑋))\𝑁)` where `𝑁` is the allocated group number for the capturing group `(𝑋)`. + +This introduces a discrepancy between the original group numbers and the compiled group numbers for any subsequent capturing group. +This is why we maintain `groupNumberMap`. +Note that the discrepancy applies within `𝑋` as well, so we record it before compiling the subexpression `𝑋`. + +### Possessive quantifiers + +[Possessive quantifiers](https://www.regular-expressions.info/possessive.html) can be interpreted as sugar for atomic groups over greedy quantifiers. +For example, `𝑋*+` is equivalent to `(?>𝑋*)`. + +Since JS does not support possessive quantifiers any more than atomic groups, we compile them as that desugaring, followed by the compilation scheme of atomic groups. + +However, there is an additional problem. +For atomic groups, we know before parsing `𝑋` that we need to record a discrepancy in the group numbering. +For possessive quantifiers, we only know that *after* having parsed `𝑋`, but it should apply also *within* `𝑋`. +We do that with postprocessing. +Before compiling any token `𝑋`, we record the current `compiledGroupCount`, and when compiling a possessive quantifier, we increment the compiled group number of those greater than the recorded count. +We do this + +- in the values of `groupNumberMap`, and +- in the back references found in the compiled pattern for `𝑋`. + +The latter is pretty ugly, but is robust nevertheless. + +### Custom character classes + +Unlike JavaScript, Java regexes support intersections and unions of character classes. +We compile them away using the following equivalences: + +* Positive intersection: `[𝐴&&𝐵]` is equivalent to `(?=[𝐴])[𝐵]` +* Negative intersection: `[^𝐴&&𝐵]` is equivalent to `[^𝐴]|[^𝐵]` +* Positive union: `[𝐴𝐵]` is equivalent to `[𝐴]|[𝐵]` +* Negative union: `[^𝐴𝐵]` is equivalent to `(?![𝐴])[^𝐵]` + +For example, using the rule on positive intersection, we can compile the example from the JavaDoc `[a-z&&[^m-p]]` to `(?=[a-z])[^m-p]`. + +An alternative design would have been to resolve all the operations at compile-time to get to flat code point sets. +This would require to expand `\p{}` and `\P{}` Unicode property names into equivalent sets, which would need a significant chunk of the Unicode database to be available. +That strategy would have a huge cost in terms of code size, and likely in terms of execution time as well (for compilation and/or matching). + +### Handling surrogate pairs without support for the 'u' flag + +So far, we have assumed that the underlying RegExp supports the 'u' flag, which we test with `supportsUnicode`. +In this section, we cover how the compilation is affected when it is not supported. +This can only happen when we target ES 5.1. + +The ECMAScript specification is very precise about how patterns and strings are interpreted when the 'u' flag is enabled. +It boils down to: + +* First, the pattern and the input, which are strings of 16-bit UTF-16 code units, are decoded into a *list of code points*, using the WTF-16 encoding. + This means that surrogate pairs become single supplementary code points, while lone surrogates (and other code units) become themselves. +* Then, all the regular expressions operators work on these lists of code points, never taking individual code units into account. + +The documentation for Java regexes does not really say anything about what it considers "characters" to be. +However, experimentation and tests show that they behave exactly like ECMAScript with the 'u' flag. + +Without support for the 'u' flag, the JavaScript RegExp engine will parse the pattern and process the input with individual Chars rather than code points. +In other words, it will consider surrogate pairs as two separate (and therefore separable) code units. +If we do nothing against it, it can jeopardize the semantics of regexes in several ways: + +* a `.` will match only the high surrogate of a surrogate pair instead of the whole codepoint, +* same issue with any negative character class like `[^a]`, +* an unpaired high surrogate in the pattern may match the high surrogate of a surrogate pair in the input, although it must not, +* a surrogate pair in a character class will be interpreted as *either* the high surrogate or the low surrogate, instead of both together, +* etc. + +Even patterns that contain only ASCII characters (escaped or not) and use no flags can behave incorrectly on inputs that contain surrogate characters (paired or unpaired). +A possible design would have been to restrict the *inputs* to strings without surrogate code units when targeting ES 5.1. +However, that could lead to patterns that fail at matching-time, rather than at compile-time (during `Pattern.compile()`), unlike all the other features that are conditioned on the ES version. + +Therefore, we go to great lengths to implement the right semantics despite the lack of support for 'u'. + +#### Overall idea of the solution + +When `supportsUnicode` is false, we apply the following changes to the compilation scheme. +In general, we make sure that: + +* something that can match a high surrogate does not match one followed by a low surrogate, +* something that can match a supplementary code point or a high surrogate never selects the high surrogate if it could match the whole code point. + +We do nothing special for low surrogates, as all possible patterns go from left to right (we don't have look-behinds in this context) and we otherwise make sure that all code points from the input are either fully matched or not at all. +Therefore, the "cursor" of the engine can never stop in the middle of a code point, and so low surrogates are only visible if they were unpaired to being with. +The only exception to this is when the cursor is at the beginning of the pattern, when using `find`. +In that case we cannot a priori prevent the JS engine from trying to find a match starting in the middle of a code point. +To address that, we have special a posteriori handling in `Pattern.execFind()`. + +#### Concretely + +A single code point that is a high surrogate `𝑥` is compiled to `(?:𝑥(?![ℒ]))`, where `ℒ` is `\uDC00-\uDFFF`, the range of all low surrogates. +The negative look-ahead group prevents a match from separating the high surrogate from a following low surrogate. + +A dot-all (in `codePointNotAmong("")`) is compiled to `(?:[^ℋ]|[ℋ](?:[ℒ]|(?![ℒ])))`, where `ℋ` is `\uD800-\uDBFF`, the range of all high surrogates. +This means either + +* any code unit that is not a high surrogate, or +* a high surrogate and a following low surrogate (taking a full code point is allowed), or +* a high surrogate that is not followed by a low surrogate (separating a surrogate pair is not allowed). + +We restrict the internal contract of `codePointNotAmong(𝑠)` to only take BMP code points that are not high surrogates, and compile it to the same as the dot-all but with the characters in `𝑠` excluded like the high surrogates: `(?:[^𝑠ℋ]|[ℋ](?:[ℒ]|(?![ℒ])))`. + +Since `UNICODE_CHARACTER_CLASS` is not supported, all but one call site of `codePointNotAmong` already respect that stricter contract. +The only one that does not is the call `codePointNotAmong(thisSegment)` inside `CharacterClassBuilder.conjunctResult()`. +To make that one compliant, we make sure not to add illegal code points in `thisSegment`. +To do that, we exploit the equivalences `[𝐴𝐵] = [𝐴]|[𝐵]` and `[^𝐴𝐵] = (?![𝐴])[𝐵]` where `𝐴` is an illegal code point to isolate it in a separate alternative, that we can compile as a single code point above. +For example, the character class `[k\uD834f]`, containing a high surrogate code point, is equivalent to `[\uD834]|[kf]`, which can be compiled as `(?:\uD834(?![ℒ]))|[kf])`. +That logic is implemented in `CharacterClassBuilder.addSingleCodePoint()`. + +Code point ranges that contain illegal code points are decomposed into the union of 4 (possibly empty) ranges: + +* one with only BMP code points below high surrogates, compiled as is +* one with high surrogates `𝑥-𝑦`, compiled to `(?:[𝑥-𝑦](?![ℒ]))` +* one with BMP code points above high surrogates, compiled as is +* one with supplementary code points `𝑥-𝑦`, where `𝑥` is the surrogate pair `𝑝𝑞` and `𝑦` is the pair `𝑠𝑡`, which is further decomposed into: + * the range `𝑝𝑞-𝑝\uDFFF`, compiled as `(?:𝑝[𝑞-\uDFFF])` + * the range `𝑝′\uDC00-𝑠′\uDFFF` where 𝑝′ = 𝑝+1 and 𝑠′ = 𝑠−1, compiled to `(?:[𝑝′-𝑠′][\uDC00-\uDFFF])` + * the range `𝑠\uDC00-𝑠𝑡`, compiled to `(?:𝑠[\uDC00-𝑡])` + +That logic is implemented in `CharacterClassBuilder.addCodePointRange()`. + +## About code size + +For historical reasons, code size is critical in this class. +Before Scala.js 1.7.0, `java.util.regex.Pattern` was just a wrapper over native `RegExp`s. +The patterns were passed through with minimal preprocessing, without caring about the proper semantics. +This created an expectation of small code size for this class. +When we fixed the semantics, we had to introduce this compiler, which is non-trivial. +In order not to regress too much on code size, we went to great lengths to minimize the code size impact of this class, in particular in the default ES 2015 configuration. + +When modifying this code, make sure to preserve as small a code size as possible. diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala new file mode 100644 index 0000000000..d95543fd80 --- /dev/null +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -0,0 +1,132 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker + +import scala.concurrent._ + +import org.junit.Test +import org.junit.Assert._ + +import org.scalajs.ir.Names._ +import org.scalajs.ir.Trees._ +import org.scalajs.ir.Types._ + +import org.scalajs.junit.async._ + +import org.scalajs.linker.analyzer._ +import org.scalajs.linker.frontend.IRLoader +import org.scalajs.linker.interface._ +import org.scalajs.linker.standard._ + +import org.scalajs.linker.testutils._ +import org.scalajs.linker.testutils.TestIRBuilder._ + +import org.scalajs.logging._ + +/** Tests for the effective size of the .js files produced when using certain + * parts of the standard library. + * + * These tests are only executed for the default Scala version of the build. + * When we use any other version, the build filters out this test class. + */ +class LibrarySizeTest { + import scala.concurrent.ExecutionContext.Implicits.global + import LibrarySizeTest._ + + @Test + def juRegexSize(): AsyncResult = await { + val PatternClass = ClassName("java.util.regex.Pattern") + val MatcherClass = ClassName("java.util.regex.Matcher") + + def line(pattern: String, flags: Int, input: String): Tree = { + val compiledPattern = ApplyStatic(EAF, PatternClass, + m("compile", List(T, I), ClassRef(PatternClass)), + List(str(pattern), int(flags)))( + ClassType(PatternClass)) + + val matcher = Apply(EAF, compiledPattern, + m("matcher", List(ClassRef("java.lang.CharSequence")), ClassRef(MatcherClass)), + List(str(input)))( + ClassType(MatcherClass)) + + consoleLog(Apply(EAF, matcher, m("matches", Nil, Z), Nil)(BooleanType)) + } + + val classDefs = Seq( + mainTestClassDef(Block( + line("[c-f]", 0, "d"), + line("[c-f]", java.util.regex.Pattern.CASE_INSENSITIVE, "D") + )) + ) + + testLinkedSizes( + expectedFastLinkSize = 188076, + expectedFullLinkSizeWithoutClosure = 175219, + expectedFullLinkSizeWithClosure = 32036, + classDefs, + moduleInitializers = MainTestModuleInitializers + ) + } +} + +object LibrarySizeTest { + private val reqsFactory = SymbolRequirement.factory("unit test") + + def testLinkedSizes(expectedFastLinkSize: Int, + expectedFullLinkSizeWithoutClosure: Int, + expectedFullLinkSizeWithClosure: Int, + classDefs: Seq[ClassDef], + symbolRequirements: SymbolRequirement = reqsFactory.none(), + moduleInitializers: Seq[ModuleInitializer] = Nil, + config: StandardConfig = StandardConfig())( + implicit ec: ExecutionContext): Future[Unit] = { + + val logger = new ScalaConsoleLogger(Level.Error) + + val fullLinkConfig = config + .withSemantics(_.optimized) + .withClosureCompilerIfAvailable(true) + + val fastLinker = StandardImpl.linker(config) + val fullLinker = StandardImpl.linker(fullLinkConfig) + + val classDefsFiles = classDefs.map(MemClassDefIRFile(_)) + + val fastOutput = MemOutputDirectory() + val fullOutput = MemOutputDirectory() + + for { + fulllib <- TestIRRepo.fulllib + irFiles = fulllib ++ classDefsFiles + fastLinkReport <- fastLinker.link(irFiles, moduleInitializers, fastOutput, logger) + fullLinkReport <- fullLinker.link(irFiles, moduleInitializers, fullOutput, logger) + } yield { + val fastSize = fastOutput.content("main.js").get.length + val fullSize = fullOutput.content("main.js").get.length + + val expectedFullLinkSize = + if (fullLinkConfig.closureCompiler) expectedFullLinkSizeWithClosure + else expectedFullLinkSizeWithoutClosure + + def roughlyEquals(expected: Int, actual: Int, tolerance: Int): Boolean = + actual >= expected - tolerance && actual <= expected + tolerance + + if (!roughlyEquals(expectedFastLinkSize, fastSize, 500) || + !roughlyEquals(expectedFullLinkSize, fullSize, 100)) { + fail( + s"\nFastLink expected $expectedFastLinkSize but got $fastSize" + + s"\nFullLink expected $expectedFullLinkSize but got $fullSize") + } + } + } +} diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala index aaf51a09c6..188c4db210 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala @@ -35,6 +35,7 @@ object TestIRBuilder { val V = VoidRef val I = IntRef + val Z = BooleanRef val O = ClassRef(ObjectClass) val T = ClassRef(BoxedStringClass) diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/BlacklistedTests.txt b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/BlacklistedTests.txt index 2083261088..0884c72b2c 100644 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/BlacklistedTests.txt +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.11.12/BlacklistedTests.txt @@ -89,9 +89,6 @@ run/hashhash.scala run/is-valid-num.scala run/stringinterpolation_macro-run.scala -# Documented semantic difference on String.split(x: Array[Char]) -run/t0325.scala - # Using Threads run/t6969.scala run/inner-obj-auto.scala diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/BlacklistedTests.txt b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/BlacklistedTests.txt index fbc3b2f582..46c4cd8a46 100644 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/BlacklistedTests.txt +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.12.14/BlacklistedTests.txt @@ -77,9 +77,6 @@ run/t2250.scala run/hashhash.scala run/is-valid-num.scala -# Documented semantic difference on String.split(x: Array[Char]) -run/t0325.scala - # Using Threads run/inner-obj-auto.scala run/predef-cycle.scala diff --git a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/BlacklistedTests.txt b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/BlacklistedTests.txt index bfc3dce90f..abbfd18add 100644 --- a/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/BlacklistedTests.txt +++ b/partest-suite/src/test/resources/scala/tools/partest/scalajs/2.13.6/BlacklistedTests.txt @@ -73,9 +73,6 @@ run/t9400.scala run/hashhash.scala run/is-valid-num.scala -# Documented semantic difference on String.split(x: Array[Char]) -run/t0325.scala - # Using Threads run/inner-obj-auto.scala run/predef-cycle.scala diff --git a/project/Build.scala b/project/Build.scala index 5831c72cd5..1c37434066 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -869,6 +869,9 @@ object Build { fileSet.toSeq.filter(_.getPath().endsWith(".scala")) }.taskValue, + + // Required for the regex (?m) flag in ReportToLinkerOutputAdapter.scala + scalaJSLinkerConfig ~= { _.withESFeatures(_.withESVersion(ESVersion.ES2018)) }, ).withScalaJSCompiler.withScalaJSJUnitPlugin.dependsOn( library, irProjectJS, jUnitRuntime % "test", testBridge % "test", jUnitAsyncJS % "test", ) @@ -947,7 +950,15 @@ object Build { exportJars := true, // required so ScalaDoc linking works - testOptions += Tests.Argument(TestFrameworks.JUnit, "-a") + testOptions += Tests.Argument(TestFrameworks.JUnit, "-a"), + + // Execute LibrarySizeTest only for the default Scala version of the build + testOptions ++= { + if (scalaVersion.value == DefaultScalaVersion) + Nil + else + Seq(Tests.Filter(s => !s.endsWith("LibrarySizeTest"))) + }, ) lazy val linker: MultiScalaProject = MultiScalaProject( @@ -1035,6 +1046,9 @@ object Build { }.taskValue, }, + // Required for the regex (?m) flag in ReportToLinkerOutputAdapter.scala + scalaJSLinkerConfig ~= { _.withESFeatures(_.withESVersion(ESVersion.ES2018)) }, + scalaJSLinkerConfig in Test ~= (_.withModuleKind(ModuleKind.CommonJSModule)) ).withScalaJSCompiler.withScalaJSJUnitPlugin.dependsOn( linkerInterfaceJS, library, irProjectJS, jUnitRuntime % "test", testBridge % "test", jUnitAsyncJS % "test" diff --git a/project/NodeJSEnvForcePolyfills.scala b/project/NodeJSEnvForcePolyfills.scala index 43b3d643f8..36fc5b3252 100644 --- a/project/NodeJSEnvForcePolyfills.scala +++ b/project/NodeJSEnvForcePolyfills.scala @@ -74,6 +74,8 @@ final class NodeJSEnvForcePolyfills(esVersion: ESVersion, config: NodeJSEnv.Conf |delete global.Uint32Array; |delete global.Float32Array; |delete global.Float64Array; + | + |delete String.prototype.repeat; """.stripMargin } diff --git a/scala-test-suite/src/test/resources/2.11.12/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.11.12/BlacklistedTests.txt index a42aef96e4..016de57f31 100644 --- a/scala-test-suite/src/test/resources/2.11.12/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.11.12/BlacklistedTests.txt @@ -73,10 +73,6 @@ scala/tools/testing/AssertUtilTest.scala scala/reflect/ClassTag.scala scala/tools/testing/AssertThrowsTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.1/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.1/BlacklistedTests.txt index 130abf861b..94059d76c2 100644 --- a/scala-test-suite/src/test/resources/2.12.1/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.1/BlacklistedTests.txt @@ -67,6 +67,7 @@ scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala scala/tools/nsc/util/StackTraceTest.scala scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/PartialFunctionSerializationTest.scala @@ -101,10 +102,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.10/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.10/BlacklistedTests.txt index c1fe8ed6c3..e79fd6c13d 100644 --- a/scala-test-suite/src/test/resources/2.12.10/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.10/BlacklistedTests.txt @@ -96,6 +96,7 @@ scala/tools/nsc/util/StackTraceTest.scala scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala scala/tools/testing/VirtualCompilerTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/MatchErrorSerializationTest.scala @@ -134,10 +135,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.11/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.11/BlacklistedTests.txt index e7a905949c..fecc21b689 100644 --- a/scala-test-suite/src/test/resources/2.12.11/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.11/BlacklistedTests.txt @@ -103,6 +103,7 @@ scala/tools/testing/AllocationTest.scala scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala scala/tools/testing/VirtualCompilerTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/MatchErrorSerializationTest.scala @@ -142,10 +143,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.12/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.12/BlacklistedTests.txt index 4235a7fac9..135ed0e44c 100644 --- a/scala-test-suite/src/test/resources/2.12.12/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.12/BlacklistedTests.txt @@ -116,6 +116,7 @@ scala/tools/testing/BytecodeTesting.scala scala/tools/testing/JOL.scala scala/tools/testing/RunTesting.scala scala/tools/testing/VirtualCompilerTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/MatchErrorSerializationTest.scala @@ -155,10 +156,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.13/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.13/BlacklistedTests.txt index 45897bf6b6..4547d401b8 100644 --- a/scala-test-suite/src/test/resources/2.12.13/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.13/BlacklistedTests.txt @@ -131,6 +131,7 @@ scala/tools/testing/BytecodeTesting.scala scala/tools/testing/JOL.scala scala/tools/testing/RunTesting.scala scala/tools/testing/VirtualCompilerTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/MatchErrorSerializationTest.scala @@ -172,10 +173,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.14/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.14/BlacklistedTests.txt index 96a6beba8b..8cae77f9bd 100644 --- a/scala-test-suite/src/test/resources/2.12.14/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.14/BlacklistedTests.txt @@ -133,6 +133,7 @@ scala/tools/testing/BytecodeTesting.scala scala/tools/testing/JOL.scala scala/tools/testing/RunTesting.scala scala/tools/testing/VirtualCompilerTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/MatchErrorSerializationTest.scala @@ -174,10 +175,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.2/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.2/BlacklistedTests.txt index 8916cecd27..7ab77797a7 100644 --- a/scala-test-suite/src/test/resources/2.12.2/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.2/BlacklistedTests.txt @@ -70,6 +70,7 @@ scala/tools/nsc/typechecker/Implicits.scala scala/tools/nsc/util/StackTraceTest.scala scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/PartialFunctionSerializationTest.scala @@ -105,10 +106,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.3/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.3/BlacklistedTests.txt index daf7afa088..3f06cda42a 100644 --- a/scala-test-suite/src/test/resources/2.12.3/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.3/BlacklistedTests.txt @@ -74,6 +74,7 @@ scala/tools/nsc/typechecker/Implicits.scala scala/tools/nsc/util/StackTraceTest.scala scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/MatchErrorSerializationTest.scala @@ -110,10 +111,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.4/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.4/BlacklistedTests.txt index bd6ef4f6ca..0ecca4009f 100644 --- a/scala-test-suite/src/test/resources/2.12.4/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.4/BlacklistedTests.txt @@ -76,6 +76,7 @@ scala/tools/nsc/typechecker/Implicits.scala scala/tools/nsc/util/StackTraceTest.scala scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/MatchErrorSerializationTest.scala @@ -112,10 +113,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.5/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.5/BlacklistedTests.txt index 9a7be17af0..6cef01dfa3 100644 --- a/scala-test-suite/src/test/resources/2.12.5/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.5/BlacklistedTests.txt @@ -81,6 +81,7 @@ scala/tools/nsc/typechecker/TypedTreeTest.scala scala/tools/nsc/util/StackTraceTest.scala scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/MatchErrorSerializationTest.scala @@ -119,10 +120,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.6/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.6/BlacklistedTests.txt index 9a7be17af0..6cef01dfa3 100644 --- a/scala-test-suite/src/test/resources/2.12.6/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.6/BlacklistedTests.txt @@ -81,6 +81,7 @@ scala/tools/nsc/typechecker/TypedTreeTest.scala scala/tools/nsc/util/StackTraceTest.scala scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/MatchErrorSerializationTest.scala @@ -119,10 +120,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.7/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.7/BlacklistedTests.txt index 7260d9f769..93831b93a8 100644 --- a/scala-test-suite/src/test/resources/2.12.7/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.7/BlacklistedTests.txt @@ -84,6 +84,7 @@ scala/tools/nsc/typechecker/TypedTreeTest.scala scala/tools/nsc/util/StackTraceTest.scala scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/MatchErrorSerializationTest.scala @@ -122,10 +123,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.8/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.8/BlacklistedTests.txt index a31e962f31..c98bdbc06f 100644 --- a/scala-test-suite/src/test/resources/2.12.8/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.8/BlacklistedTests.txt @@ -90,6 +90,7 @@ scala/tools/nsc/typechecker/TypedTreeTest.scala scala/tools/nsc/util/StackTraceTest.scala scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/MatchErrorSerializationTest.scala @@ -128,10 +129,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.12.9/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.12.9/BlacklistedTests.txt index cdd8b28c43..f643404804 100644 --- a/scala-test-suite/src/test/resources/2.12.9/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.12.9/BlacklistedTests.txt @@ -95,6 +95,7 @@ scala/tools/nsc/util/StackTraceTest.scala scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala scala/tools/testing/VirtualCompilerTesting.scala +scala/util/matching/RegexTest.scala ## Do not link scala/MatchErrorSerializationTest.scala @@ -133,10 +134,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt index e742df1ee1..67798232ba 100644 --- a/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt @@ -110,6 +110,7 @@ scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala scala/tools/testing/VirtualCompilerTesting.scala scala/util/ChainingOpsTest.scala +scala/util/matching/RegexTest.scala ## Do not link scala/CollectTest.scala @@ -166,10 +167,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.1/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.1/BlacklistedTests.txt index b0032ddd05..70b0dce1a0 100644 --- a/scala-test-suite/src/test/resources/2.13.1/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.13.1/BlacklistedTests.txt @@ -112,6 +112,7 @@ scala/tools/nsc/typechecker/ParamAliasTest.scala scala/tools/nsc/typechecker/TypedTreeTest.scala scala/tools/nsc/util/StackTraceTest.scala scala/util/ChainingOpsTest.scala +scala/util/matching/RegexTest.scala ## Do not link scala/CollectTest.scala @@ -167,10 +168,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.2/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.2/BlacklistedTests.txt index a746185596..7ad0c9c4c5 100644 --- a/scala-test-suite/src/test/resources/2.13.2/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.13.2/BlacklistedTests.txt @@ -119,6 +119,7 @@ scala/tools/nsc/typechecker/TypedTreeTest.scala scala/tools/nsc/util/StackTraceTest.scala scala/util/ChainingOpsTest.scala scala/util/TryTest.scala +scala/util/matching/RegexTest.scala ## Do not link scala/CollectTest.scala @@ -176,10 +177,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.3/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.3/BlacklistedTests.txt index e887677f16..6544fd6112 100644 --- a/scala-test-suite/src/test/resources/2.13.3/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.13.3/BlacklistedTests.txt @@ -128,6 +128,7 @@ scala/tools/nsc/typechecker/ParamAliasTest.scala scala/tools/nsc/typechecker/TypedTreeTest.scala scala/tools/nsc/util/StackTraceTest.scala scala/util/ChainingOpsTest.scala +scala/util/matching/RegexTest.scala ## Do not link scala/CollectTest.scala @@ -198,10 +199,6 @@ scala/util/control/ControlThrowableTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.4/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.4/BlacklistedTests.txt index 260a8277cc..7235c97e41 100644 --- a/scala-test-suite/src/test/resources/2.13.4/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.13.4/BlacklistedTests.txt @@ -136,6 +136,7 @@ scala/tools/nsc/typechecker/ParamAliasTest.scala scala/tools/nsc/typechecker/TypedTreeTest.scala scala/tools/nsc/util/StackTraceTest.scala scala/util/ChainingOpsTest.scala +scala/util/matching/RegexTest.scala ## Do not link scala/CollectTest.scala @@ -195,10 +196,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.5/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.5/BlacklistedTests.txt index 9efcf2ac91..8337da0f30 100644 --- a/scala-test-suite/src/test/resources/2.13.5/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.13.5/BlacklistedTests.txt @@ -204,10 +204,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.6/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.6/BlacklistedTests.txt index 2fdcb65859..31fdc02aa6 100644 --- a/scala-test-suite/src/test/resources/2.13.6/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.13.6/BlacklistedTests.txt @@ -205,10 +205,6 @@ scala/util/SystemPropertiesTest.scala # Reflection scala/reflect/ClassTagTest.scala -# Regex -scala/util/matching/CharRegexTest.scala -scala/util/matching/RegexTest.scala - # Require strict-floats scala/math/BigDecimalTest.scala diff --git a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index ae4d474971..426873ba01 100644 --- a/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/js/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -26,6 +26,8 @@ object Platform { final val executingInJVMOnJDK8OrLower = false + final val executingInJVMOnLowerThanJDK15 = false + def executingInNodeJS: Boolean = { js.typeOf(js.Dynamic.global.process) != "undefined" && !js.isUndefined(js.Dynamic.global.process.release) && @@ -102,6 +104,15 @@ object Platform { def hasAccurateFloats: Boolean = hasStrictFloats || js.typeOf(js.Dynamic.global.Math.fround) != "undefined" + def regexSupportsUnicodeCase: Boolean = + assumedESVersion >= ESVersion.ES2015 + + def regexSupportsUnicodeCharacterClasses: Boolean = + assumedESVersion >= ESVersion.ES2018 + + def regexSupportsLookBehinds: Boolean = + assumedESVersion >= ESVersion.ES2018 + def isNoModule: Boolean = BuildInfo.isNoModule def isESModule: Boolean = BuildInfo.isESModule def isCommonJSModule: Boolean = BuildInfo.isCommonJSModule diff --git a/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala b/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala index 264ec524c1..3431e0eb01 100644 --- a/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala +++ b/test-suite/jvm/src/main/scala/org/scalajs/testsuite/utils/Platform.scala @@ -24,6 +24,8 @@ object Platform { def executingInJVMOnJDK8OrLower: Boolean = jdkVersion <= 8 + def executingInJVMOnLowerThanJDK15: Boolean = jdkVersion < 15 + private lazy val jdkVersion = { val v = System.getProperty("java.version") if (v.startsWith("1.")) Integer.parseInt(v.drop(2).takeWhile(_.isDigit)) @@ -37,4 +39,8 @@ object Platform { def hasCompliantModule: Boolean = true def hasStrictFloats: Boolean = true def hasAccurateFloats: Boolean = true + + def regexSupportsUnicodeCase: Boolean = true + def regexSupportsUnicodeCharacterClasses: Boolean = true + def regexSupportsLookBehinds: Boolean = true } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex/PatternSyntaxExceptionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex/PatternSyntaxExceptionTest.scala new file mode 100644 index 0000000000..14fbb25cb3 --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex/PatternSyntaxExceptionTest.scala @@ -0,0 +1,114 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.testsuite.javalib.util.regex + +import java.util.regex._ + +import org.junit.Test +import org.junit.Assert._ + +class PatternSyntaxExceptionTest { + def pse(desc: String, regex: String, index: Int): PatternSyntaxException = + new PatternSyntaxException(desc, regex, index) + + @Test def getIndex(): Unit = { + assertEquals(2, pse("", "", 2).getIndex()) + assertEquals(-1, pse("", "", -1).getIndex()) + } + + @Test def getDescription(): Unit = { + assertEquals("foo", pse("foo", "", 0).getDescription()) + assertNull(pse(null, "re", 0).getDescription()) + } + + @Test def getPattern(): Unit = { + assertEquals("re", pse("desc", "re", 0).getPattern()) + assertNull(pse("desc", null, 0).getPattern()) + } + + @Test def getMessage(): Unit = { + assertEquals( + """ + |my description + |regexp + """.stripMargin.trim(), + pse("my description", "regexp", -1).getMessage()) + + assertEquals( + """ + |my description near index 0 + |regexp + |^ + """.stripMargin.trim(), + pse("my description", "regexp", 0).getMessage()) + + assertEquals( + """ + |my description near index 3 + |regexp + | ^ + """.stripMargin.trim(), + pse("my description", "regexp", 3).getMessage()) + + assertEquals( + """ + |my description near index 5 + |regexp + | ^ + """.stripMargin.trim(), + pse("my description", "regexp", 5).getMessage()) + + assertEquals( + """ + |my description near index 6 + |regexp + """.stripMargin.trim(), + pse("my description", "regexp", 6).getMessage()) + + assertEquals( + """ + |null near index 2 + |regexp + | ^ + """.stripMargin.trim(), + pse(null, "regexp", 2).getMessage()) + + assertEquals( + """ + |null + |regexp + """.stripMargin.trim(), + pse(null, "regexp", -1).getMessage()) + + assertEquals( + """ + |my description near index 2 + |null + """.stripMargin.trim(), + pse("my description", null, 2).getMessage()) + + assertEquals( + """ + |my description near index 0 + |null + """.stripMargin.trim(), + pse("my description", null, 0).getMessage()) + + assertEquals( + """ + |my description + |null + """.stripMargin.trim(), + pse("my description", null, -1).getMessage()) + } +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex/RegexEngineTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex/RegexEngineTest.scala new file mode 100644 index 0000000000..ba50551fe0 --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/regex/RegexEngineTest.scala @@ -0,0 +1,2526 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.testsuite.javalib.util.regex + +import java.util.regex._ +import java.util.regex.Pattern.compile + +import org.junit.Test +import org.junit.Assert._ +import org.junit.Assume._ + +import org.scalajs.testsuite.utils.Platform._ +import org.scalajs.testsuite.utils.AssertThrows.assertThrows + +class RegexEngineTest { + + // Scala-friendly names for flags + private final val UnixLines = Pattern.UNIX_LINES + private final val CaseInsensitive = Pattern.CASE_INSENSITIVE + private final val Comments = Pattern.COMMENTS + private final val Multiline = Pattern.MULTILINE + private final val Literal = Pattern.LITERAL + private final val DotAll = Pattern.DOTALL + private final val UnicodeCase = Pattern.UNICODE_CASE + private final val CanonEq = Pattern.CANON_EQ + private final val UnicodeCharacterClass = Pattern.UNICODE_CHARACTER_CLASS + + // Assertion utils + + private val flagStrings = List( + UnixLines -> "d", + CaseInsensitive -> "i", + Comments -> "x", + Multiline -> "m", + Literal -> "L", + DotAll -> "s", + UnicodeCase -> "u", + CanonEq -> "C", + UnicodeCharacterClass -> "U" + ) + + private def flagsToString(flags: Int): String = + flagStrings.filter(p => (flags & p._1) != 0).map(_._2).mkString + + private def debugEscape(pattern: String): String = { + pattern.flatMap { + case '\t' => "`t" + case '\n' => "`n" + case '\r' => "`r" + case c if c < ' ' => "`x%02X".format(c.toInt) + case c => c.toString() + } + } + + private def patternToString(pattern: Pattern): String = + s"/${debugEscape(pattern.pattern())}/${flagsToString(pattern.flags())}" + + @noinline + private def assertMatches(pattern: Pattern, input: String): Matcher = { + val matcher = pattern.matcher(input) + if (!matcher.matches()) + fail(s"expected ${patternToString(pattern)} to match '${debugEscape(input)}'") + matcher + } + + @noinline + private def assertMatches(pattern: String, flags: Int, input: String): Matcher = + assertMatches(Pattern.compile(pattern, flags), input) + + @noinline + private def assertMatches(pattern: String, input: String): Matcher = + assertMatches(Pattern.compile(pattern), input) + + @noinline + private def assertMatchesAndGroupsEquals(pattern: Pattern, input: String, + expectedGroups: String*): Matcher = { + val m = assertMatches(pattern, input) + assertGroupsEquals(m, expectedGroups: _*) + } + + @noinline + private def assertGroupsEquals(m: Matcher, expectedGroups: String*): Matcher = { + val groupCount = expectedGroups.size + assertEquals(groupCount, m.groupCount()) + if (groupCount == 1) { + // nicer error messages for a single group + assertEquals(expectedGroups.head, m.group(1)) + } else { + val actualGroups = (1 to groupCount).map(m.group(_)).toArray[AnyRef] + assertArrayEquals(expectedGroups.toArray[AnyRef], actualGroups) + } + m + } + + @noinline + private def assertFind(pattern: Pattern, input: String, pos: Int): Matcher = { + val matcher = pattern.matcher(input) + if (!matcher.find()) { + fail( + s"expected ${patternToString(pattern)} to be found in " + + s"'${debugEscape(input)}' at $pos, but was not found") + } else if (matcher.start() != pos) { + fail( + s"expected ${patternToString(pattern)} to be found in " + + s"'${debugEscape(input)}' at $pos, but was found at ${matcher.start()}") + } + matcher + } + + @noinline + private def assertFind(pattern: String, flags: Int, input: String, pos: Int): Matcher = + assertFind(compile(pattern, flags), input, pos) + + @noinline + private def assertFind(pattern: String, input: String, pos: Int): Matcher = + assertFind(compile(pattern), input, pos) + + @noinline + private def assertFind(pattern: Pattern, input: String, start: Int, end: Int): Matcher = { + val matcher = pattern.matcher(input) + if (!matcher.find()) { + fail( + s"expected ${patternToString(pattern)} to be found in " + + s"'${debugEscape(input)}' at $start-$end, but was not found") + } else if (matcher.start() != start || matcher.end() != end) { + fail( + s"expected ${patternToString(pattern)} to be found in " + + s"'${debugEscape(input)}' at $start-$end, but was found at " + + s"${matcher.start()}-${matcher.end()}") + } + matcher + } + + @noinline + private def assertFind(pattern: String, flags: Int, input: String, start: Int, end: Int): Matcher = + assertFind(compile(pattern, flags), input, start, end) + + @noinline + private def assertFind(pattern: String, input: String, start: Int, end: Int): Matcher = + assertFind(compile(pattern), input, start, end) + + @noinline + private def assertFindAll(pattern: Pattern, input: String, startPoses: Int*): Matcher = { + val matcher = pattern.matcher(input) + for (pos <- startPoses) { + if (!matcher.find()) { + fail( + s"expected ${patternToString(pattern)} to be found in " + + s"'${debugEscape(input)}' at $pos, but was not found") + } else if (matcher.start() != pos) { + fail( + s"expected ${patternToString(pattern)} to be found in " + + s"'${debugEscape(input)}' at $pos, but was found at ${matcher.start()}") + } + } + if (matcher.find()) { + fail( + s"expected ${patternToString(pattern)} *not* to be found anymore in " + + s"'${debugEscape(input)}', but was found at ${matcher.start()}") + } + matcher + } + + @noinline + private def assertFindAndGroupsEquals(pattern: Pattern, input: String, + pos: Int, expectedGroups: String*): Matcher = { + val m = assertFind(pattern, input, pos) + assertGroupsEquals(m, expectedGroups: _*) + } + + @noinline + private def assertNotMatches(pattern: Pattern, input: String): Unit = { + if (pattern.matcher(input).matches()) + fail(s"expected ${patternToString(pattern)} *not* to match '${debugEscape(input)}'") + } + + @noinline + private def assertNotMatches(pattern: String, flags: Int, input: String): Unit = + assertNotMatches(compile(pattern, flags), input) + + @noinline + private def assertNotMatches(pattern: String, input: String): Unit = + assertNotMatches(compile(pattern), input) + + @noinline + private def assertNotFind(pattern: Pattern, input: String): Unit = { + val matcher = pattern.matcher(input) + if (matcher.find()) { + fail( + s"expected ${patternToString(pattern)} *not* to be found in " + + s"'${debugEscape(input)}', but was found at ${matcher.start()}") + } + } + + @noinline + private def assertSyntaxError(pattern: String, flags: Int, desc: String, index: Int): Unit = { + val th = assertThrows(classOf[PatternSyntaxException], compile(pattern, flags)) + if (!executingInJVM) { + assertEquals(desc, th.getDescription()) + assertEquals(index, th.getIndex()) + } + } + + @noinline + private def assertSyntaxError(pattern: String, desc: String, index: Int): Unit = + assertSyntaxError(pattern, 0, desc, index) + + @noinline + private def assertSyntaxErrorInJS(pattern: String, flags: Int, desc: String, index: Int): Unit = { + if (executingInJVM) { + compile(pattern, flags) + } else { + val th = assertThrows(classOf[PatternSyntaxException], compile(pattern, flags)) + assertEquals(desc, th.getDescription()) + assertEquals(index, th.getIndex()) + } + } + + @noinline + private def assertSyntaxErrorInJS(pattern: String, desc: String, index: Int): Unit = + assertSyntaxErrorInJS(pattern, 0, desc, index) + + // Common code points and chars that we use + + private final val GClef = "\uD834\uDD1E" // 𝄞 U+1D11E MUSICAL SYMBOL G CLEF + private final val GClefHigh = "\uD834" + private final val GClefLow = "\uDD1E" + private final val EscapedGClefX = "\\x{1D11E}" + private final val EscapedGClefU = "\\uD834\\uDD1E" + private final val EscapedGClefHigh = "\\uD834" + private final val EscapedGClefLow = "\\uDD1E" + + private final val Domino = "\uD83C\uDC3D" // 🀽 U+1F03D DOMINO TILE HORIZONTAL-01-05 + private final val DominoHigh = "\uD83C" + private final val DominoLow = "\uDC3D" + private final val EscapedDominoX = "\\x{1F03D}" + private final val EscapedDominoU = "\\uD83C\\uDC3D" + private final val EscapedDominoHigh = "\\uD83C" + private final val EscapedDominoLow = "\\uDC3D" + + // Tests + + @Test def singleCodePoint(): Unit = { + val a = compile("a") + assertMatches(a, "a") + assertNotMatches(a, "A") + assertNotMatches(a, "b") + assertNotMatches(a, "") + assertNotMatches(a, "aa") + assertFind(a, "aa", 0) + assertFind(a, "bcadef", 2) + assertFind(a, GClef + "a", 2) // Surrogate pair counts as 2 for indices + assertNotFind(a, "bdcef") + + val highSurrogate = compile(GClefHigh) + assertMatches(highSurrogate, GClefHigh) + assertNotMatches(highSurrogate, GClef) + assertNotFind(highSurrogate, GClef) // a surrogate pair in the input cannot be decomposed + assertFind(highSurrogate, "a" + GClefHigh + "b", 1) // but can be found if not paired + assertFind(highSurrogate, GClef + GClefHigh, 2) + assertFind(highSurrogate, GClefHigh + DominoHigh, 0) + + val lowSurrogate = compile(GClefLow) + assertMatches(lowSurrogate, GClefLow) + assertNotMatches(lowSurrogate, GClef) + assertNotFind(lowSurrogate, GClef) // a surrogate pair in the input cannot be decomposed + assertFind(lowSurrogate, "a" + GClefLow + "b", 1) // but can be found if not paired + assertFind(lowSurrogate, GClef + GClefLow, 2) + assertFind(lowSurrogate, DominoLow + GClefLow, 1) + + val gClef = compile(GClef) + assertMatches(gClef, GClef) + assertNotMatches(gClef, "a") + assertNotMatches(gClef, Domino) + assertFind(gClef, "a" + GClef + "b", 1) + assertNotFind(gClef, Domino) + assertFind(gClef, DominoHigh + GClef + DominoLow, 1) + + // Characters for which we might think that they would need escaping, but they don't + val weirdCharsString = "\u0000\t\r\n\f\u001f /-0]}\u0085\u2028\u2029#" + assertMatches(weirdCharsString, weirdCharsString) + } + + @Test def singleEscapedCodePoint(): Unit = { + // Useless escape of ASCII char + val quote = compile("\\'") + assertMatches(quote, "'") + assertNotMatches(quote, "a") + + // Escapes for syntax characters + assertMatches("\\.", ".") + assertNotMatches("\\.", "a") + assertMatches("\\\\", "\\") + assertMatches("\\\\t", "\\t") + assertMatches("\\[a]", "[a]") + assertMatches("a\\?", "a?") + assertMatches("a\\*", "a*") + assertMatches("a\\+", "a+") + assertMatches("a\\{3}", "a{3}") + assertMatches("\\(5\\)", "(5)") + + // Escapes for characters that are syntax characters only when using Comments (the escapes work regardless) + assertMatches("\\ \\\t\\\n\\\u000B\\\f\\\r", " \t\n\u000B\f\r") + assertMatches("\\ \\\t\\\n\\\u000B\\\f\\\r", Comments, " \t\n\u000B\f\r") + assertMatches("\\#abc", "#abc") + assertMatches("\\#abc", Comments, "#abc") + + // Letter escapes for special chars + assertMatches("\\t", "\t") + assertMatches("\\n", "\n") + assertMatches("\\r", "\r") + assertMatches("\\f", "\f") + assertMatches("\\a", "\u0007") + assertMatches("\\e", "\u001B") + + // Control escapes (meant to be used in the range '@' to '_' and with '?') + assertMatches("\\c@", "\u0000") + assertMatches("\\cA", "\u0001") + assertNotMatches("\\cA", "A") + assertMatches("\\cR", "\u0012") + assertMatches("\\c_", "\u001F") + assertMatches("\\c?", 0x007f.toChar.toString()) + + /* More control escapes that are not really meant to be used. + * In general, '\cx' means `x ^ 0x40`, as explained at + * https://stackoverflow.com/questions/35208570/java-regular-expression-cx-control-characters + */ + assertMatches("\\cb", 0x0022.toChar.toString()) + assertMatches("\\c" + GClefHigh, "" + (0xd834 ^ 0x40).toChar) + assertMatches("\\c" + GClef, GClefHigh + (0xdd1e ^ 0x40).toChar) + + // Illegal trailing 'c' escape + assertSyntaxError("\\c", "Illegal control escape sequence", 2) + + // Latin-1 'x' escapes + assertMatches("\\x41", "A") + assertMatches("\\xe9", "é") + assertMatches("\\xE9", "é") + + assertSyntaxError("\\x", "Illegal hexadecimal escape sequence", 1) + assertSyntaxError("a\\xZ0", "Illegal hexadecimal escape sequence", 2) + assertSyntaxError("a\\x0z", "Illegal hexadecimal escape sequence", 2) + assertSyntaxError("a\\x0/", "Illegal hexadecimal escape sequence", 2) + + // Surrogates with 'u' and 'x' escapes + assertMatches(EscapedGClefHigh, GClefHigh) + assertMatches(EscapedGClefLow, GClefLow) + assertMatches(EscapedGClefU, GClef) + assertMatches(EscapedGClefX, GClef) + + // A surrogate "pair" where one is literal and the other is escaped does *not* create a pair + assertNotMatches(EscapedGClefHigh + GClefLow, GClef) + assertNotMatches(GClefHigh + EscapedGClefLow, GClef) + assertNotMatches(GClefHigh + "\\" + GClefLow, GClef) + + // A surrogate "pair" with 'x' escapes does *not* create a pair + assertNotMatches("\\x{D834}\\x{DD1E}", GClef) + + // A bare escape in front of a surrogate pair considers it as a whole + assertMatches("\\" + GClef, GClef) + + // Octal escapes + assertMatches("\\0040", " ") + assertMatches("\\004a", "\u0004a") + assertMatches("\\008", "\u00008") + assertMatches("\\0101", "A") + assertMatches("\\00101", "\u00081") // at most 3 significant chars are considered + assertMatches("\\0377", "\u00ff") + assertMatches("\\0400", " 0") // if the first char is 4-7, only 2 chars are considered + + assertSyntaxError("\\0", "Illegal octal escape sequence", 1) + assertSyntaxError("\\0a", "Illegal octal escape sequence", 1) + assertSyntaxError("abc\\08", "Illegal octal escape sequence", 4) + + // Unknown ASCII letter escapes are reserved + assertSyntaxError("\\g", "Illegal/unsupported escape sequence", 1) + assertSyntaxError("\\Y", "Illegal/unsupported escape sequence", 1) + } + + @Test def simpleAlternatives(): Unit = { + val fooOrBar = compile("foo|bar") + assertMatches(fooOrBar, "foo") + assertMatches(fooOrBar, "bar") + assertNotMatches(fooOrBar, "foobar") + assertNotMatches(fooOrBar, "foox") + assertNotMatches(fooOrBar, "xfoo") + assertNotMatches(fooOrBar, "barx") + assertNotMatches(fooOrBar, "xbar") + assertFind(fooOrBar, "hello foo bar", 6) + assertNotFind(fooOrBar, "hello") + + val aOrAB = compile("a|ab") + assertMatches(aOrAB, "a") + assertMatches(aOrAB, "ab") + assertFind(aOrAB, "ab", 0, 1) // The left hand side is preferred if both would work + + val abOrA = compile("ab|a") + assertMatches(abOrA, "a") + assertMatches(abOrA, "ab") + assertFind(abOrA, "ab", 0, 2) // The left hand side is preferred if both would work, even if it is longer + } + + @Test def greedyQuantifiers(): Unit = { + val starGreedy = compile("ba*") + assertMatches(starGreedy, "b") + assertMatches(starGreedy, "ba") + assertMatches(starGreedy, "baaaaa") + assertFind(starGreedy, "cbaaassefaa", 1, 5) + assertFind(starGreedy, "cbsssefaaaaa", 1, 2) + assertNotFind(starGreedy, "qsessqsssddff") + + val plusGreedy = compile("ba+") + assertMatches(plusGreedy, "ba") + assertMatches(plusGreedy, "baaaa") + assertNotFind(plusGreedy, "b") + assertFind(plusGreedy, "cbaaassefaa", 1, 5) + assertFind(plusGreedy, "cbsssefbaaa", 7, 11) + assertNotFind(plusGreedy, "qsebsqsbsddfb") + + val questionGreedy = compile("ba?") + assertMatches(questionGreedy, "b") + assertMatches(questionGreedy, "ba") + assertNotMatches(questionGreedy, "baa") + assertFind(questionGreedy, "cbaaassefaa", 1, 3) + assertFind(questionGreedy, "cbssefbaaa", 1, 2) + assertNotFind(questionGreedy, "qsessqsssddff") + + val repeatedSupplementaryCodePoint = compile("a\uD834\uDD1E*") + assertFind(repeatedSupplementaryCodePoint, "bca\uD834\uDD1E\uD834\uDD1Edef", 2, 7) + assertFind(repeatedSupplementaryCodePoint, "bca\uD834\uDD1E\uDD1Edef", 2, 5) + + // After quotes, a quantifier applies to the last code point (sic!) + val repeatedQuote = compile("a\\Qbc\\d\\E*") + assertFind(repeatedQuote, "aaabc\\dbc\\dbc", 2, 7) + assertFind(repeatedQuote, "aaabc\\ddc", 2, 8) + assertFind(repeatedQuote, "aaabc\\bc", 2, 6) + + val repeatedQuoteEndingWithSupplementaryCodePoint = compile("a\\Qbc\\\uD834\uDD1E\\E*") + assertFind(repeatedQuoteEndingWithSupplementaryCodePoint, "aaabc\\\uD834\uDD1E\uD834\uDD1Ebc", 2, 10) + assertFind(repeatedQuoteEndingWithSupplementaryCodePoint, "aaabc\\\uD834\uDD1Ebc\\\uD834\uDD1Ebc", 2, 8) + assertFind(repeatedQuoteEndingWithSupplementaryCodePoint, "aaabc\\\uD834\uDD1E\uDD1Ebc", 2, 8) + } + + @Test def lazyQuantifiers(): Unit = { + val starLazy = compile("a[bc]*?b") + assertMatches(starLazy, "ab") + assertMatches(starLazy, "abbbb") + assertMatches(starLazy, "abccbb") + assertFind(starLazy, "abbb", 0, 2) + assertFind(starLazy, "accbbbccb", 0, 4) + assertNotFind(starLazy, "accc") + + val starLazyAtEnd = compile("ba*?") + assertMatches(starLazyAtEnd, "b") + assertMatches(starLazyAtEnd, "ba") + assertMatches(starLazyAtEnd, "baaaaa") + assertFind(starLazyAtEnd, "cbaaassefaa", 1, 2) + assertFind(starLazyAtEnd, "cbsssefaaaaa", 1, 2) + assertNotFind(starLazyAtEnd, "qsessqsssddff") + + val plusLazy = compile("a[bc]+?b") + assertMatches(plusLazy, "abb") + assertMatches(plusLazy, "acb") + assertMatches(plusLazy, "abbbcccbb") + assertFind(plusLazy, "abbb", 0, 3) + assertFind(plusLazy, "accbbbccb", 0, 4) + assertNotFind(plusLazy, "accc") + assertNotFind(plusLazy, "ab") + + val plusLazyAtEnd = compile("ba+?") + assertMatches(plusLazyAtEnd, "ba") + assertMatches(plusLazyAtEnd, "baaaa") + assertNotFind(plusLazyAtEnd, "b") + assertFind(plusLazyAtEnd, "cbaaassefaa", 1, 3) + assertFind(plusLazyAtEnd, "cbsssefbaaa", 7, 9) + assertNotFind(plusLazyAtEnd, "qsebsqsbsddfb") + + val questionLazy = compile("a[bc]??b") + assertMatches(questionLazy, "ab") + assertMatches(questionLazy, "abb") + assertMatches(questionLazy, "acb") + assertFind(questionLazy, "abbb", 0, 2) + assertFind(questionLazy, "acbbbccb", 0, 3) + assertNotFind(questionLazy, "accbb") + + val questionLazyAtEnd = compile("ba??") + assertMatches(questionLazyAtEnd, "b") + assertMatches(questionLazyAtEnd, "ba") + assertNotMatches(questionLazyAtEnd, "baa") + assertFind(questionLazyAtEnd, "cbaaassefaa", 1, 2) + assertFind(questionLazyAtEnd, "cbssefbaaa", 1, 2) + assertNotFind(questionLazyAtEnd, "qsessqsssddff") + } + + @Test def possessiveQuantifiers(): Unit = { + val starPossessive = compile("ab*+[bd]") + assertFind(starPossessive, " a abbbb abbbdba ", 11, 16) + assertFind(starPossessive, " a abbbb adba ", 11, 13) + assertNotFind(starPossessive, " a abbbb dab ") + + val plusPossessive = compile("ab++[bd]") + assertFind(plusPossessive, " a abbbb abbbdba ", 11, 16) + assertFind(plusPossessive, " a abbbb adba abdd ", 17, 20) + assertNotFind(plusPossessive, " a ad abbbb dab adba ") + + val questionPossessive = compile("ab?+[bd]") + assertFind(questionPossessive, " a ab abb abb ", 9, 12) + assertFind(questionPossessive, " ad abb", 1, 3) + assertFind(questionPossessive, " abd abb ", 1, 4) + assertNotFind(questionPossessive, " a ab ") + + // Don't break numbered groups before, within, and after + val possessiveAndNumberedGroups = compile("(a)((bc)\\1\\3|(c))?+(c) \\1 \\2 \\3 \\5") + assertFindAndGroupsEquals(possessiveAndNumberedGroups, "abcabcc a bcabc bc c", 0, "a", "bcabc", "bc", null, "c") + + // Don't break named groups before, within, and after + val possessiveAndNamedGroups = + compile("(?a)(?

(?bc)\\k\\k|(?c))?+(?c) \\k \\k

\\k \\k") + val m = + assertFindAndGroupsEquals(possessiveAndNamedGroups, "abcabcc a bcabc bc c", 0, "a", "bcabc", "bc", null, "c") + assertEquals("a", m.group("A")) + assertEquals("bcabc", m.group("P")) + assertEquals("bc", m.group("B")) + assertEquals(null, m.group("C")) + assertEquals("c", m.group("D")) + } + + @Test def quotes(): Unit = { + val str = "D($[^e" + DominoHigh + "\\R)]" + GClef + DominoLow + val quoted = compile("a\\Q" + str + "\\Ec") + assertMatches(quoted, "a" + str + "c") + assertNotFind(quoted, "A" + str.toUpperCase() + "c") + + val caseInsensitive = compile("a\\Q" + str + "\\Ec", CaseInsensitive) + assertMatches(caseInsensitive, "A" + str.toUpperCase() + "c") + + // #1677 + assertMatches("^\\Qmember\\E.*\\Q\\E$", "member0") + } + + @Test def literal(): Unit = { + val str = "aD($[^e" + DominoHigh + "\\R)]" + GClef + DominoLow + "c" + val quoted = compile(str, Literal) + assertMatches(quoted, str) + assertNotFind(quoted, str.toUpperCase()) + + val caseInsensitive = compile(str, Literal | CaseInsensitive) + assertMatches(caseInsensitive, str.toUpperCase()) + } + + @Test def dot(): Unit = { + val dot = compile(".") + + assertMatches(dot, "a") + assertMatches(dot, "0") + assertMatches(dot, "\u0000") + assertMatches(dot, GClefHigh) + assertMatches(dot, GClefLow) + assertMatches(dot, GClef) // . matches an entire surrogate pair + + assertNotFind(dot, "\n") + assertNotFind(dot, "\r") + assertNotFind(dot, "\u0085") // Would be matched by a '.' in a JS RegExp + assertNotFind(dot, "\u2028") + assertNotFind(dot, "\u2029") + + assertNotFind(dot, "\r\n") + + val dotUnixLines = compile(".", UnixLines) + + assertMatches(dotUnixLines, "a") + assertMatches(dotUnixLines, "0") + assertMatches(dotUnixLines, "\u0000") + assertMatches(dotUnixLines, GClefHigh) + assertMatches(dotUnixLines, GClefLow) + assertMatches(dotUnixLines, GClef) // . matches an entire surrogate pair + + assertNotFind(dotUnixLines, "\n") + assertMatches(dotUnixLines, "\r") + assertMatches(dotUnixLines, "\u0085") + assertMatches(dotUnixLines, "\u2028") + assertMatches(dotUnixLines, "\u2029") + + assertNotMatches(dotUnixLines, "\r\n") + assertFind(dotUnixLines, "\r\n", 0, 1) + + val dotAll = compile(".", DotAll) + + assertMatches(dotAll, "a") + assertMatches(dotAll, "0") + assertMatches(dotAll, "\u0000") + assertMatches(dotAll, GClefHigh) + assertMatches(dotAll, GClefLow) + assertMatches(dotAll, GClef) // . matches an entire surrogate pair + + assertMatches(dotAll, "\n") + assertMatches(dotAll, "\r") + assertMatches(dotAll, "\u0085") + assertMatches(dotAll, "\u2028") + assertMatches(dotAll, "\u2029") + + assertNotMatches(dotAll, "\r\n") + assertFind(dotAll, "\r\n", 0, 1) + + val dotAllUnixLines = compile(".", DotAll | UnixLines) + + assertMatches(dotAllUnixLines, "a") + assertMatches(dotAllUnixLines, "0") + assertMatches(dotAllUnixLines, "\u0000") + assertMatches(dotAllUnixLines, GClefHigh) + assertMatches(dotAllUnixLines, GClefLow) + assertMatches(dotAllUnixLines, GClef) // . matches an entire surrogate pair + + assertMatches(dotAllUnixLines, "\n") + assertMatches(dotAllUnixLines, "\r") + assertMatches(dotAllUnixLines, "\u0085") + assertMatches(dotAllUnixLines, "\u2028") + assertMatches(dotAllUnixLines, "\u2029") + + assertNotMatches(dotAllUnixLines, "\r\n") + assertFind(dotAllUnixLines, "\r\n", 0, 1) + + // Test case for #1847, and for the (?s) leading flag + val codeMatcher = Pattern.compile("(?s).*(.*?).*") + assertMatches(codeMatcher, "

\nList(1)\n

") + } + + @Test def startAndEnd(): Unit = { + val startA = compile("^a") + assertMatches(startA, "a") + assertFind(startA, "ab", 0) + assertNotFind(startA, "ba") + assertNotFind(startA, "b\na") + + val forceStartA = compile("\\Aa") + assertMatches(forceStartA, "a") + assertFind(forceStartA, "ab", 0) + assertNotFind(forceStartA, "ba") + assertNotFind(forceStartA, "b\na") + + val endA = compile("a$") + assertMatches(endA, "a") + assertFind(endA, "ba", 1) + assertNotFind(endA, "ab") + assertNotFind(endA, "a\nb") + + val forceEndA = compile("a\\z") + assertMatches(forceEndA, "a") + assertFind(forceEndA, "ba", 1) + assertNotFind(forceEndA, "ab") + assertNotFind(forceEndA, "a\nb") + } + + @Test def startAndEndMultiline(): Unit = { + assumeTrue("requires look-behinds", regexSupportsLookBehinds) + + val startA = compile("^a", Multiline) + assertMatches(startA, "a") + assertFind(startA, "ab", 0) + assertNotFind(startA, "ba") + assertFind(startA, "b\na", 2) + assertFind(startA, "b\u0085a", 2) + assertFind(startA, "b\u2028a", 2) + assertFind(startA, "b\u2029a", 2) + assertFind(startA, "b\ra", 2) + assertFind(startA, "b\r\na", 3) + + val forceStartA = compile("\\Aa", Multiline) + assertMatches(forceStartA, "a") + assertFind(forceStartA, "ab", 0) + assertNotFind(forceStartA, "ba") + assertNotFind(forceStartA, "b\na") + + // Between \r and \n is not the start of a line + val startNL = compile("^\n", Multiline) + assertFind(startNL, "a\n\nb", 2) + assertNotFind(startNL, "a\r\nb") + + val endA = compile("a$", Multiline) + assertMatches(endA, "a") + assertFind(endA, "ba", 1) + assertNotFind(endA, "ab") + assertFind(endA, "a\nb", 0) + assertFind(endA, "a\u0085b", 0) + assertFind(endA, "a\u2028b", 0) + assertFind(endA, "a\u2029b", 0) + assertFind(endA, "a\rb", 0) + assertFind(endA, "a\r\nb", 0) + + val forceEndA = compile("a\\z", Multiline) + assertMatches(forceEndA, "a") + assertFind(forceEndA, "ba", 1) + assertNotFind(forceEndA, "ab") + assertNotFind(forceEndA, "a\nb") + + // Between \r and \n is not the end of a line + val endCR = compile("\r$", Multiline) + assertFind(endCR, "a\r\rb", 1) + assertNotFind(endCR, "a\r\nb") + } + + @Test def sticky(): Unit = { + val stickyFoo = compile("\\G(?:foo|bar)") + assertFindAll(stickyFoo, "foofoobarfoo foo bar", 0, 3, 6, 9) + assertMatches(stickyFoo, "foo") + assertMatches(stickyFoo, "bar") + } + + @Test def comments(): Unit = { + val lotsOfComments = compile( + " \ta # a comment is interrupted by \r" + + "b # or \n" + + "c # or \u0085" + + " d # or \u2028" + + " e# or \u2029" + + " f # but the latter 3 are not ignored! \n" + + " g # in addition, \n" + + "\t \u000B \f \r \n h # are also ignored \r" + + "i", + Comments) + + assertMatches(lotsOfComments, "abc\u0085d\u2028e\u2029fghi") + + val commentsInCharClass = compile( + "[\n" + + " A-Z # an uppercase letter\n" + + " _ \t # or an underscore\n" + + " f - # gosh, we can even have ranges\n" + + " j # split by comments!\n" + + "]", + Comments) + + assertMatches(commentsInCharClass, "A") + assertMatches(commentsInCharClass, "F") + assertMatches(commentsInCharClass, "R") + assertMatches(commentsInCharClass, "Z") + assertMatches(commentsInCharClass, "f") + assertMatches(commentsInCharClass, "g") + assertMatches(commentsInCharClass, "h") + assertMatches(commentsInCharClass, "i") + assertMatches(commentsInCharClass, "j") + assertMatches(commentsInCharClass, "_") + assertNotFind(commentsInCharClass, " ") + assertNotFind(commentsInCharClass, "\t") + assertNotFind(commentsInCharClass, "\n") + assertNotFind(commentsInCharClass, "#") + assertNotFind(commentsInCharClass, "-") + assertNotFind(commentsInCharClass, "!") + assertNotFind(commentsInCharClass, "a") + assertNotFind(commentsInCharClass, "e") + assertNotFind(commentsInCharClass, "k") + assertNotFind(commentsInCharClass, "l") + assertNotFind(commentsInCharClass, "z") + + val fakeRangeWithComments = compile("[A-D G # comment\n -]", Comments) + assertMatches(fakeRangeWithComments, "A") + assertMatches(fakeRangeWithComments, "C") + assertMatches(fakeRangeWithComments, "D") + assertMatches(fakeRangeWithComments, "G") + assertMatches(fakeRangeWithComments, "-") + assertNotMatches(fakeRangeWithComments, "E") + assertNotMatches(fakeRangeWithComments, "I") + assertNotMatches(fakeRangeWithComments, "e") + assertNotMatches(fakeRangeWithComments, "]") + assertNotMatches(fakeRangeWithComments, " ") + assertNotMatches(fakeRangeWithComments, "\n") + assertNotMatches(fakeRangeWithComments, "#") + + /* If there is a comment between the '-' and the ']', the JVM does not + * detect that it is a fake range, and reports a syntax error. Our + * implementation correctly detects that case, because it was easier than + * not detecting it. + */ + if (executingInJVM) { + assertSyntaxError("[A-D G - ]", Comments, "irrelevant", 0) + assertSyntaxError("[A-D G -# comment\n]", Comments, "irrelevant", 0) + } else { + val fakeRangeWithCommentsOnRHS = compile("[A-D G - # comment\n ]", Comments) + assertMatches(fakeRangeWithCommentsOnRHS, "A") + assertMatches(fakeRangeWithCommentsOnRHS, "C") + assertMatches(fakeRangeWithCommentsOnRHS, "D") + assertMatches(fakeRangeWithCommentsOnRHS, "G") + assertMatches(fakeRangeWithCommentsOnRHS, "-") + assertNotMatches(fakeRangeWithCommentsOnRHS, "E") + assertNotMatches(fakeRangeWithCommentsOnRHS, "I") + assertNotMatches(fakeRangeWithCommentsOnRHS, "e") + assertNotMatches(fakeRangeWithCommentsOnRHS, "]") + assertNotMatches(fakeRangeWithCommentsOnRHS, " ") + assertNotMatches(fakeRangeWithCommentsOnRHS, "\n") + assertNotMatches(fakeRangeWithCommentsOnRHS, "#") + } + + // We can still match against whitespace in the input + assertMatches("\ta\\ b\t", Comments, "a b") + assertMatches("\ta.b\t", Comments, "a b") + assertMatches("\ta[\\ c]b\t", Comments, "a b") + + // We can still match against '#' in the input + assertMatches("\ta\\#b\t", Comments, "a#b") + assertMatches("\ta.b\t", Comments, "a#b") + assertMatches("\ta[\\#c]b\t", Comments, "a#b") + } + + @Test def predefinedCharacterClasses(): Unit = { + val digit = compile("\\d") + assertMatches(digit, "0") + assertMatches(digit, "7") + assertMatches(digit, "9") + assertNotMatches(digit, "/") + assertNotMatches(digit, ":") + assertNotMatches(digit, "A") + assertNotMatches(digit, "१") // U+0967 DEVANAGARI DIGIT ONE + + val notDigit = compile("\\D") + assertNotMatches(notDigit, "0") + assertNotMatches(notDigit, "7") + assertNotMatches(notDigit, "9") + assertMatches(notDigit, "/") + assertMatches(notDigit, ":") + assertMatches(notDigit, "A") + assertMatches(notDigit, "१") // U+0967 DEVANAGARI DIGIT ONE + + val h = compile("\\h") + assertMatches(h, " ") + assertMatches(h, "\t") + assertMatches(h, "\u00A0") + assertMatches(h, "\u1680") + assertMatches(h, "\u180E") + assertMatches(h, "\u2000") + assertMatches(h, "\u2008") + assertMatches(h, "\u200A") + assertMatches(h, "\u202F") + assertMatches(h, "\u205F") + assertMatches(h, "\u3000") + assertNotMatches(h, "\n") + assertNotMatches(h, "\r") + assertNotMatches(h, "a") + assertNotMatches(h, "\u0085") + assertNotMatches(h, "\u1FFF") + assertNotMatches(h, "\u200B") + assertNotMatches(h, "\u2028") + assertNotMatches(h, "\u2029") + assertNotMatches(h, "\u3001") + assertNotMatches(h, GClefHigh) + assertNotMatches(h, GClefLow) + assertNotMatches(h, GClef) + + val noth = compile("\\H") + assertNotMatches(noth, " ") + assertNotMatches(noth, "\t") + assertNotMatches(noth, "\u00A0") + assertNotMatches(noth, "\u1680") + assertNotMatches(noth, "\u180E") + assertNotMatches(noth, "\u2000") + assertNotMatches(noth, "\u2008") + assertNotMatches(noth, "\u200A") + assertNotMatches(noth, "\u202F") + assertNotMatches(noth, "\u205F") + assertNotMatches(noth, "\u3000") + assertMatches(noth, "\n") + assertMatches(noth, "\r") + assertMatches(noth, "a") + assertMatches(noth, "\u0085") + assertMatches(noth, "\u1FFF") + assertMatches(noth, "\u200B") + assertMatches(noth, "\u2028") + assertMatches(noth, "\u2029") + assertMatches(noth, "\u3001") + assertMatches(noth, GClefHigh) + assertMatches(noth, GClefLow) + assertMatches(noth, GClef) + + val s = compile("\\s") + assertMatches(s, "\t") + assertMatches(s, "\n") + assertMatches(s, "\u000B") + assertMatches(s, "\u000C") + assertMatches(s, "\r") + assertMatches(s, " ") + assertNotMatches(s, "\u0008") + assertNotMatches(s, "\u000E") + assertNotMatches(s, "\u00A0") // #3959 would be matched by JS' \s + assertNotMatches(s, "\uFEFF") // would be matched by JS' \s + assertNotMatches(s, "\u2008") + assertNotMatches(s, "\u2028") + assertNotMatches(s, GClefHigh) + assertNotMatches(s, GClefLow) + assertNotMatches(s, GClef) + + val nots = compile("\\S") + assertNotMatches(nots, "\t") + assertNotMatches(nots, "\n") + assertNotMatches(nots, "\u000B") + assertNotMatches(nots, "\u000C") + assertNotMatches(nots, "\r") + assertNotMatches(nots, " ") + assertMatches(nots, "\u0008") + assertMatches(nots, "\u000E") + assertMatches(nots, "\u00A0") // #3959 would not be matched by JS' \S + assertMatches(nots, "\uFEFF") // would not be matched by JS' \S + assertMatches(nots, "\u2008") + assertMatches(nots, "\u2028") + assertMatches(nots, GClefHigh) + assertMatches(nots, GClefLow) + assertMatches(nots, GClef) + + val w = compile("\\w") + assertMatches(w, "a") + assertMatches(w, "X") + assertMatches(w, "5") + assertMatches(w, "_") + assertNotMatches(w, "?") + assertNotMatches(w, "é") + assertNotFind(w, GClef) + + val notw = compile("\\W") + assertNotMatches(notw, "a") + assertNotMatches(notw, "X") + assertNotMatches(notw, "5") + assertNotMatches(notw, "_") + assertMatches(notw, "?") + assertMatches(notw, "é") + assertMatches(notw, GClef) + + val lower = compile("\\p{Lower}") + assertMatches(lower, "a") + assertMatches(lower, "f") + assertMatches(lower, "z") + assertNotMatches(lower, "A") + assertNotMatches(lower, "G") + assertNotMatches(lower, "0") + assertNotMatches(lower, "-") + assertNotMatches(lower, "à") + assertNotMatches(lower, "É") + + // https://bugs.openjdk.java.net/browse/JDK-8214245 + if (!executingInJVMOnLowerThanJDK15) { + val lowerCI = compile("\\p{Lower}", CaseInsensitive) + assertMatches(lowerCI, "a") + assertMatches(lowerCI, "f") + assertMatches(lowerCI, "z") + assertMatches(lowerCI, "A") + assertMatches(lowerCI, "G") + assertNotMatches(lowerCI, "0") + assertNotMatches(lowerCI, "-") + assertNotMatches(lowerCI, "à") + assertNotMatches(lowerCI, "É") + } + + val punct = compile("\\p{Punct}") + assertMatches(punct, ":") + assertMatches(punct, "!") + assertMatches(punct, "*") + assertMatches(punct, "/") + assertMatches(punct, "?") + assertMatches(punct, "}") + assertNotMatches(punct, "a") + assertNotMatches(punct, "\n") + assertNotMatches(punct, "5") + assertNotFind(punct, GClef) + assertNotFind(punct, "\u0E4F") // ๏ Thai Character Fongman + assertNotFind(punct, "\uD804\uDC4D") // 𑁍 Brahmi Punctuation Lotus + + val notPunct = compile("\\P{Punct}") + assertNotMatches(notPunct, ":") + assertNotMatches(notPunct, "!") + assertNotMatches(notPunct, "*") + assertNotMatches(notPunct, "/") + assertNotMatches(notPunct, "?") + assertNotMatches(notPunct, "}") + assertMatches(notPunct, "a") + assertMatches(notPunct, "\n") + assertMatches(notPunct, "5") + assertMatches(notPunct, GClef) + assertMatches(notPunct, "\u0E4F") // ๏ Thai Character Fongman + assertMatches(notPunct, "\uD804\uDC4D") // 𑁍 Brahmi Punctuation Lotus + } + + @Test def predefinedUnicodeCharacterClasses(): Unit = { + assumeTrue("requires \\p{} support", regexSupportsUnicodeCharacterClasses) + + val digit = compile("\\d", UnicodeCharacterClass) + assertMatches(digit, "0") + assertMatches(digit, "7") + assertMatches(digit, "9") + assertMatches(digit, "१") // U+0967 DEVANAGARI DIGIT ONE + assertNotMatches(digit, "/") + assertNotMatches(digit, ":") + assertNotMatches(digit, "A") + assertNotMatches(digit, GClef) + + val notDigit = compile("\\D", UnicodeCharacterClass) + assertNotMatches(notDigit, "0") + assertNotMatches(notDigit, "7") + assertNotMatches(notDigit, "9") + assertNotMatches(notDigit, "१") // U+0967 DEVANAGARI DIGIT ONE + assertMatches(notDigit, "/") + assertMatches(notDigit, ":") + assertMatches(notDigit, "A") + assertMatches(notDigit, GClef) + + val h = compile("\\h", UnicodeCharacterClass) + assertMatches(h, " ") + assertMatches(h, "\t") + assertMatches(h, "\u00A0") + assertMatches(h, "\u1680") + assertMatches(h, "\u180E") + assertMatches(h, "\u2000") + assertMatches(h, "\u2008") + assertMatches(h, "\u200A") + assertMatches(h, "\u202F") + assertMatches(h, "\u205F") + assertMatches(h, "\u3000") + assertNotMatches(h, "\n") + assertNotMatches(h, "\r") + assertNotMatches(h, "a") + assertNotMatches(h, "\u0085") + assertNotMatches(h, "\u1FFF") + assertNotMatches(h, "\u200B") + assertNotMatches(h, "\u2028") + assertNotMatches(h, "\u2029") + assertNotMatches(h, "\u3001") + assertNotMatches(h, GClefHigh) + assertNotMatches(h, GClefLow) + assertNotMatches(h, GClef) + + val noth = compile("\\H", UnicodeCharacterClass) + assertNotMatches(noth, " ") + assertNotMatches(noth, "\t") + assertNotMatches(noth, "\u00A0") + assertNotMatches(noth, "\u1680") + assertNotMatches(noth, "\u180E") + assertNotMatches(noth, "\u2000") + assertNotMatches(noth, "\u2008") + assertNotMatches(noth, "\u200A") + assertNotMatches(noth, "\u202F") + assertNotMatches(noth, "\u205F") + assertNotMatches(noth, "\u3000") + assertMatches(noth, "\n") + assertMatches(noth, "\r") + assertMatches(noth, "a") + assertMatches(noth, "\u0085") + assertMatches(noth, "\u1FFF") + assertMatches(noth, "\u200B") + assertMatches(noth, "\u2028") + assertMatches(noth, "\u2029") + assertMatches(noth, "\u3001") + assertMatches(noth, GClefHigh) + assertMatches(noth, GClefLow) + assertMatches(noth, GClef) + + val s = compile("\\s", UnicodeCharacterClass) + assertMatches(s, "\t") + assertMatches(s, "\n") + assertMatches(s, "\u000B") + assertMatches(s, "\u000C") + assertMatches(s, "\r") + assertMatches(s, " ") + assertMatches(s, "\u00A0") + assertMatches(s, "\u2008") + assertMatches(s, "\u2028") + assertNotMatches(s, "\u0008") + assertNotMatches(s, "\u000E") + assertNotMatches(s, "\uFEFF") // would be matched by JS' \s + assertNotMatches(s, GClefHigh) + assertNotMatches(s, GClefLow) + assertNotMatches(s, GClef) + + val nots = compile("\\S", UnicodeCharacterClass) + assertNotMatches(nots, "\t") + assertNotMatches(nots, "\n") + assertNotMatches(nots, "\u000B") + assertNotMatches(nots, "\u000C") + assertNotMatches(nots, "\r") + assertNotMatches(nots, " ") + assertNotMatches(nots, "\u00A0") + assertNotMatches(nots, "\u2008") + assertNotMatches(nots, "\u2028") + assertMatches(nots, "\u0008") + assertMatches(nots, "\u000E") + assertMatches(nots, "\uFEFF") // would not be matched by JS' \S + assertMatches(nots, GClefHigh) + assertMatches(nots, GClefLow) + assertMatches(nots, GClef) + + val w = compile("\\w", UnicodeCharacterClass) + assertMatches(w, "a") + assertMatches(w, "X") + assertMatches(w, "5") + assertMatches(w, "_") + assertMatches(w, "é") + assertMatches(w, "é") + assertMatches(w, "\u03B1") // α U+03B1 Greek Small Letter Alpha + assertMatches(w, "१") // U+0967 DEVANAGARI DIGIT ONE + assertMatches(w, "\uD835\uDC1D") // 𝐝 U+1D41D Mathematical Bold Small D + assertNotMatches(w, "?") + assertNotFind(w, GClef) + + val notw = compile("\\W", UnicodeCharacterClass) + assertNotFind(notw, "a") + assertNotFind(notw, "X") + assertNotFind(notw, "5") + assertNotFind(notw, "_") + assertNotFind(notw, "é") + assertNotFind(notw, "é") + assertNotFind(notw, "\u03B1") // α U+03B1 Greek Small Letter Alpha + assertNotFind(notw, "१") // U+0967 DEVANAGARI DIGIT ONE + assertNotMatches(notw, "\uD835\uDC1D") // 𝐝 U+1D41D Mathematical Bold Small D + if (!executingInJVM) // on JDK 8, the JVM finds the low surrogate at pos 1 (not reproducible on JDK 17 ea) + assertNotFind(notw, "\uD835\uDC1D") // 𝐝 U+1D41D Mathematical Bold Small D + assertMatches(notw, "?") + assertMatches(notw, GClef) + + val lower = compile("\\p{Lower}", UnicodeCharacterClass) + assertMatches(lower, "a") + assertMatches(lower, "f") + assertMatches(lower, "z") + assertMatches(lower, "à") + assertNotMatches(lower, "A") + assertNotMatches(lower, "G") + assertNotMatches(lower, "0") + assertNotMatches(lower, "-") + assertNotMatches(lower, "É") + + // https://bugs.openjdk.java.net/browse/JDK-8214245 + if (!executingInJVMOnLowerThanJDK15) { + val lowerCI = compile("\\p{Lower}", CaseInsensitive | UnicodeCharacterClass) + assertMatches(lowerCI, "a") + assertMatches(lowerCI, "f") + assertMatches(lowerCI, "z") + assertMatches(lowerCI, "à") + assertMatches(lowerCI, "A") + assertMatches(lowerCI, "G") + assertMatches(lowerCI, "É") + assertNotMatches(lowerCI, "0") + assertNotMatches(lowerCI, "-") + } + + val punct = compile("\\p{Punct}", UnicodeCharacterClass) + assertMatches(punct, ":") + assertMatches(punct, "!") + assertMatches(punct, "*") + assertMatches(punct, "/") + assertMatches(punct, "?") + assertMatches(punct, "}") + assertMatches(punct, "\u0E4F") // ๏ Thai Character Fongman + assertMatches(punct, "\uD804\uDC4D") // 𑁍 Brahmi Punctuation Lotus + assertNotMatches(punct, "a") + assertNotMatches(punct, "\n") + assertNotMatches(punct, "5") + assertNotFind(punct, GClef) + + val notPunct = compile("\\P{Punct}", UnicodeCharacterClass) + assertNotMatches(notPunct, ":") + assertNotMatches(notPunct, "!") + assertNotMatches(notPunct, "*") + assertNotMatches(notPunct, "/") + assertNotMatches(notPunct, "?") + assertNotMatches(notPunct, "}") + assertNotMatches(notPunct, "\u0E4F") // ๏ Thai Character Fongman + assertNotMatches(notPunct, "\uD804\uDC4D") // 𑁍 Brahmi Punctuation Lotus + assertMatches(notPunct, "a") + assertMatches(notPunct, "\n") + assertMatches(notPunct, "5") + assertMatches(notPunct, GClef) + } + + @Test def javaCharacterClasses(): Unit = { + assumeTrue("requires \\p{} support", regexSupportsUnicodeCharacterClasses) + + val javaMirrored = compile("\\p{javaMirrored}") + assertMatches(javaMirrored, "(") + assertMatches(javaMirrored, "]") + assertMatches(javaMirrored, "\u2993") // ⦓ LEFT ARC LESS-THAN BRACKET + assertNotMatches(javaMirrored, "+") + assertNotMatches(javaMirrored, "À") + assertNotMatches(javaMirrored, "_") + + val javaUnicodeIdentifierStart = compile("\\p{javaUnicodeIdentifierStart}") + assertMatches(javaUnicodeIdentifierStart, "A") + assertMatches(javaUnicodeIdentifierStart, "É") + assertMatches(javaUnicodeIdentifierStart, "オ") + assertNotMatches(javaUnicodeIdentifierStart, "0") + assertNotMatches(javaUnicodeIdentifierStart, "_") + assertNotMatches(javaUnicodeIdentifierStart, "+") + + val javaUnicodeIdentifierPart = compile("\\p{javaUnicodeIdentifierPart}") + assertMatches(javaUnicodeIdentifierPart, "A") + assertMatches(javaUnicodeIdentifierPart, "É") + assertMatches(javaUnicodeIdentifierPart, "オ") + assertMatches(javaUnicodeIdentifierPart, "0") + assertMatches(javaUnicodeIdentifierPart, "_") + assertNotMatches(javaUnicodeIdentifierPart, "+") + + /* Other javaX character classes are exhaustively tested in + * unicodeCharClassesAreConsistentWithTheirDefinitions(). + */ + } + + @Test def asciiCharClassesAreConsistentWithTheirDefinitions(): Unit = { + @noinline + def checkConsistency(name: String, definition: String): Unit = { + val namePattern = Pattern.compile(s"\\p{$name}") + val definitionPattern = Pattern.compile(definition) + + for (cp <- 0 to 0xff) { + val cpString = cp.toChar.toString() + val nameMatches = namePattern.matcher(cpString).matches() + val definitionMatches = definitionPattern.matcher(cpString).matches() + if (nameMatches != definitionMatches) { + fail( + s"for U+${Integer.toHexString(cp)}, " + + s"\\p{$name} was $nameMatches but its definition was $definitionMatches") + } + } + } + + checkConsistency("Lower", "[a-z]") + checkConsistency("Upper", "[A-Z]") + checkConsistency("ASCII", "[\\x00-\\x7F]") + checkConsistency("Alpha", "[\\p{Lower}\\p{Upper}]") + checkConsistency("Digit", "[0-9]") + checkConsistency("Alnum", "[\\p{Alpha}\\p{Digit}]") + checkConsistency("Punct", "[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~]") + checkConsistency("Graph", "[\\p{Alnum}\\p{Punct}]") + checkConsistency("Print", "[\\p{Graph}\\x20]") + checkConsistency("Blank", "[ \\t]") + checkConsistency("Cntrl", "[\\x00-\\x1F\\x7F]") + checkConsistency("XDigit", "[0-9a-fA-F]") + checkConsistency("Space", "[ \\t\\n\\x0B\\f\\r]") + } + + /** Tests that the pre-computed desugarings of the Unicode character classes + * actually match the original definitions. + * + * This is particularly important for `Print`, `Blank` and the + * `java.lang.Character` classes, which have complex definitions that we + * heavily simplified by hand to remove the intersections, even using inside + * knowledge of the Unicode data. + * + * This test is expensive. It takes 15 seconds on my (sjrd) machine. + * However, since it only runs in ES 2018+ anyway, it is unlikely to slow + * down a typical workflow, and I believe it is worth it. + */ + @Test def unicodeCharClassesAreConsistentWithTheirDefinitions(): Unit = { + assumeTrue("requires \\p{} support", regexSupportsUnicodeCharacterClasses) + + @noinline + def checkConsistency(name: String, definition: String): Unit = { + val namePattern = Pattern.compile(s"\\p{$name}", UnicodeCharacterClass) + val definitionPattern = Pattern.compile(definition, UnicodeCharacterClass) + + // Allocate this once and for all, which shaves off 20% running time for this test + val buffer = new Array[Int](1) + + // Explicitly use a var and while loop so that even without optimizer, this test remains viable + var cp = 0 + while (cp <= Character.MAX_CODE_POINT) { + buffer(0) = cp + val cpString = new String(buffer, 0, 1) // TODO Use Character.toString(Int) when we can (JDK11+) + val nameMatches = namePattern.matcher(cpString).matches() + val definitionMatches = definitionPattern.matcher(cpString).matches() + if (nameMatches != definitionMatches) { + fail( + s"for U+${Integer.toHexString(cp)}, " + + s"\\p{$name} was $nameMatches but its definition was $definitionMatches") + } + cp += 1 + } + } + + checkConsistency("Lower", "\\p{IsLowercase}") + checkConsistency("Upper", "\\p{IsUppercase}") + checkConsistency("ASCII", "[\\u0000-\\u007f]") + checkConsistency("Alpha", "\\p{IsAlphabetic}") + checkConsistency("Digit", "\\p{IsDigit}") + checkConsistency("Alnum", "[\\p{IsAlphabetic}\\p{IsDigit}]") + checkConsistency("Punct", "\\p{IsPunctuation}") + checkConsistency("Graph", "[^\\p{IsWhite_Space}\\p{gc=Cc}\\p{gc=Cs}\\p{gc=Cn}]") + checkConsistency("Print", "[\\p{Graph}\\p{Blank}&&[^\\p{Cntrl}]]") + checkConsistency("Blank", "[\\p{IsWhite_Space}&&[^\\p{gc=Zl}\\p{gc=Zp}\\x0a\\x0b\\x0c\\x0d\\x85]]") + checkConsistency("Cntrl", "\\p{gc=Cc}") + checkConsistency("XDigit", "[\\p{gc=Nd}\\p{IsHex_Digit}]") + checkConsistency("Space", "\\p{IsWhite_Space}") + + checkConsistency("javaAlphabetic", "\\p{IsAlphabetic}") + checkConsistency("javaDefined", "\\P{Cn}") + checkConsistency("javaDigit", "\\p{Nd}") + checkConsistency("javaIdentifierIgnorable", "[\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}]") + checkConsistency("javaIdeographic", "\\p{IsIdeographic}") + checkConsistency("javaISOControl", "[\u0000-\u001F\u007F-\u009F]") + if (!executingInJVMOnJDK8OrLower) { + checkConsistency("javaJavaIdentifierPart", + "[\\p{L}\\p{Sc}\\p{Pc}\\p{Nd}\\p{Nl}\\p{Mn}\\p{Mc}\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}]") + checkConsistency("javaJavaIdentifierStart", "[\\p{L}\\p{Sc}\\p{Pc}\\p{Nl}]") + } + checkConsistency("javaLetterOrDigit", "[\\p{L}\\p{Nd}]") + checkConsistency("javaLowerCase", "\\p{IsLowercase}") + checkConsistency("javaSpaceChar", "\\p{Z}") + checkConsistency("javaTitleCase", "\\p{Lt}") + checkConsistency("javaUpperCase", "\\p{IsUppercase}") + checkConsistency("javaWhitespace", "[\t-\r\u001C-\u001F\\p{Z}&&[^\u00A0\u2007\u202F]]") + + /* The last 3 are not testable using this approach, because there is no + * support in `j.u.r.Pattern` for `Bidi_Mirrored`, `ID_Start` and + * `ID_Continue`. + checkConsistency("javaMirrored", "\\p{IsBidi_Mirrored}") + checkConsistency("javaUnicodeIdentifierPart", + "[\\p{IsID_Continue}\u2E2F\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}]") + checkConsistency("javaUnicodeIdentifierStart", "[\\p{IsID_Start}\u2E2F]") + */ + } + + @Test def unicodeProperties(): Unit = { + assumeTrue("requires \\p{} support", regexSupportsUnicodeCharacterClasses) + + for (prop <- List("L", "gc=L", "IsL", "general_category=L")) { // #2028 + val letter = compile(s"\\p{$prop}") + assertMatches(letter, "A") + assertMatches(letter, "à") + assertMatches(letter, "ç") + assertMatches(letter, "か") + assertMatches(letter, "\u03B1") // α U+03B1 Greek Small Letter Alpha + assertMatches(letter, "\uD801\uDC93") // 𐒓 U+10493 Osmanya Letter Waw + assertNotMatches(letter, ".") + assertNotMatches(letter, "-") + assertNotMatches(letter, "3") + assertNotMatches(letter, "\u2030") // ‰ U+2030 Per Mille Sign + assertNotMatches(letter, GClef) + assertNotMatches(letter, GClefHigh) + assertNotMatches(letter, GClefLow) + } + + val hiragana = compile("\\p{IsHiragana}") + assertMatches(hiragana, "ぁ") + assertMatches(hiragana, "う") + assertMatches(hiragana, "か") + assertMatches(hiragana, "が") + assertNotMatches(hiragana, "a") + assertNotMatches(hiragana, "0") + assertNotMatches(hiragana, "オ") + assertNotMatches(hiragana, "今") + + val notHiragana = compile("\\P{sc=hiRAgana}") + assertNotMatches(notHiragana, "ぁ") + assertNotMatches(notHiragana, "う") + assertNotMatches(notHiragana, "か") + assertNotMatches(notHiragana, "が") + assertMatches(notHiragana, "a") + assertMatches(notHiragana, "0") + assertMatches(notHiragana, "オ") + assertMatches(notHiragana, "今") + + // Test the normalization of script names + val olChiki = compile("\\p{script=ol_cHIki}") + assertMatches(olChiki, "\u1C5A") // ᱚ U+1C5A OL CHIKI LETTER LA + assertNotMatches(olChiki, "A") + + /* SignWriting is special because of its canonical name, which is not Sign_Writing. + * It's from Unicode 8.0.0, so it requires JDK 9+. + */ + if (!executingInJVMOnJDK8OrLower) { + val signWriting = compile("\\p{script=signwrItIng}") + assertMatches(signWriting, "\uD836\uDC36") // U+1D836 SIGNWRITING HAND-FIST MIDDLE THUMB CUPPED INDEX UP + assertNotMatches(signWriting, "A") + } + + // Non existing script names are rejected + assertSyntaxError("\\p{sc=FooBar}", "Unknown character script name {FooBar}", 12) + assertSyntaxError("\\p{script=ba_bar}", "Unknown character script name {ba_bar}", 16) + assertSyntaxError("\\p{IsFrobber}", "Unknown character script name {Frobber}", 12) + } + + @Test def simpleCharacterClasses(): Unit = { + val adf = compile("[adf]") + assertMatches(adf, "a") + assertMatches(adf, "d") + assertMatches(adf, "f") + assertNotMatches(adf, "b") + assertNotMatches(adf, "A") + assertNotMatches(adf, GClefHigh) + assertNotMatches(adf, GClefLow) + assertNotMatches(adf, GClef) + + val not_adf = compile("[^adf]") + assertNotMatches(not_adf, "a") + assertNotMatches(not_adf, "d") + assertNotMatches(not_adf, "f") + assertMatches(not_adf, "b") + assertMatches(not_adf, "A") + assertMatches(not_adf, GClefHigh) + assertMatches(not_adf, GClefLow) + assertMatches(not_adf, GClef) + + val letter = compile("[a-zA-Z]") + assertMatches(letter, "a") + assertMatches(letter, "z") + assertMatches(letter, "A") + assertMatches(letter, "Z") + assertMatches(letter, "d") + assertMatches(letter, "G") + assertNotMatches(letter, "0") + assertNotMatches(letter, "@") + assertNotMatches(letter, "_") + assertNotMatches(letter, GClefHigh) + assertNotMatches(letter, GClefLow) + assertNotMatches(letter, GClef) + + val notLetter = compile("[^a-zA-Z]") + assertNotMatches(notLetter, "a") + assertNotMatches(notLetter, "z") + assertNotMatches(notLetter, "A") + assertNotMatches(notLetter, "Z") + assertNotMatches(notLetter, "d") + assertNotMatches(notLetter, "G") + assertMatches(notLetter, "0") + assertMatches(notLetter, "@") + assertMatches(notLetter, "_") + assertMatches(notLetter, GClefHigh) + assertMatches(notLetter, GClefLow) + assertMatches(notLetter, GClef) + + // Supplementary code points count as a single unit + val letterOrGClef = compile("[a-z" + GClef + "A-Z]") + assertMatches(letterOrGClef, "a") + assertMatches(letterOrGClef, "z") + assertMatches(letterOrGClef, "A") + assertMatches(letterOrGClef, "Z") + assertMatches(letterOrGClef, "d") + assertMatches(letterOrGClef, "G") + assertMatches(letterOrGClef, GClef) + assertNotMatches(letterOrGClef, "0") + assertNotMatches(letterOrGClef, "@") + assertNotMatches(letterOrGClef, "_") + assertNotMatches(letterOrGClef, GClefHigh) + assertNotMatches(letterOrGClef, GClefLow) + + // ^ Supplementary code points count as a single unit + val notLetterNorGClef = compile("[^a-z" + GClef + "A-Z]") + assertNotMatches(notLetterNorGClef, "a") + assertNotMatches(notLetterNorGClef, "z") + assertNotMatches(notLetterNorGClef, "A") + assertNotMatches(notLetterNorGClef, "Z") + assertNotMatches(notLetterNorGClef, "d") + assertNotMatches(notLetterNorGClef, "G") + assertNotMatches(notLetterNorGClef, GClef) + assertMatches(notLetterNorGClef, "0") + assertMatches(notLetterNorGClef, "@") + assertMatches(notLetterNorGClef, "_") + assertMatches(notLetterNorGClef, GClefHigh) + assertMatches(notLetterNorGClef, GClefLow) + + // A surrogate "pair" where one is literal and the other is escaped does *not* create a pair + val gClefHighOrLow1 = compile("[" + EscapedGClefHigh + GClefLow + "]") + assertNotMatches(gClefHighOrLow1, GClef) + assertMatches(gClefHighOrLow1, GClefHigh) + assertMatches(gClefHighOrLow1, GClefLow) + val gClefHighOrLow2 = compile("[" + GClefHigh + EscapedGClefLow + "]") + assertNotMatches(gClefHighOrLow2, GClef) + assertMatches(gClefHighOrLow2, GClefHigh) + assertMatches(gClefHighOrLow2, GClefLow) + val gClefHighOrSomeLows = compile("[" + EscapedGClefHigh + "\uDD1E-\uDD24" + "]") + assertNotMatches(gClefHighOrSomeLows, GClef) + assertMatches(gClefHighOrSomeLows, GClefHigh) + assertMatches(gClefHighOrSomeLows, GClefLow) + assertMatches(gClefHighOrSomeLows, "\uDD22") + assertMatches(gClefHighOrSomeLows, "\uDD24") + assertNotMatches(gClefHighOrSomeLows, "\uDD25") + + // A surrogate "pair" with 'x' escapes does *not* create a pair + val gClefHighOrLow3 = compile("[\\x{D834}\\x{DD1E}]") + assertNotMatches(gClefHighOrLow3, GClef) + assertMatches(gClefHighOrLow3, GClefHigh) + assertMatches(gClefHighOrLow3, GClefLow) + + // Range of supplementary code points with the same high surrogate + val clefs = compile("[" + GClef + "-\\x{1D124}]") + assertMatches(clefs, GClef) + assertMatches(clefs, "\uD834\uDD24") // 𝄤 U+1D124 Musical Symbol F Clef Ottava Bassa + assertMatches(clefs, "\uD834\uDD22") // 𝄢 Musical Symbol F Clef + assertNotMatches(clefs, "a") + assertNotMatches(clefs, GClefHigh) + assertNotMatches(clefs, GClefHigh + "a") + assertNotMatches(clefs, "\uD834\uDD10") // 𝄐 MUSICAL SYMBOL FERMATA + assertNotMatches(clefs, GClefLow) + assertNotMatches(clefs, "a" + GClefLow) + + // ^ Range of supplementary code points with the same high surrogate + val notClefs = compile("[^" + GClef + "-\\x{1D124}]") + assertNotMatches(notClefs, GClef) + assertNotMatches(notClefs, "\uD834\uDD24") // 𝄤 U+1D124 Musical Symbol F Clef Ottava Bassa + assertNotMatches(notClefs, "\uD834\uDD22") // 𝄢 Musical Symbol F Clef + assertMatches(notClefs, "a") + assertMatches(notClefs, GClefHigh) + assertFind(notClefs, GClefHigh + "a", 0) + assertMatches(notClefs, "\uD834\uDD10") // 𝄐 MUSICAL SYMBOL FERMATA + assertMatches(notClefs, GClefLow) + assertFind(notClefs, "a" + GClefLow, 0) + + // Range of supplementary code points spanning several high surrogates + val supplementaryRange = compile("[" + GClef + "-\\x{1F914}]") // 🤔 U+1F914 THINKING FACE + assertMatches(supplementaryRange, GClef) + assertMatches(supplementaryRange, "\uD834\uDD22") // 𝄢 Musical Symbol F Clef + assertMatches(supplementaryRange, "\uD83C\uDC3D") // 🀽 U+1F03D DOMINO TILE HORIZONTAL-01-05 + assertMatches(supplementaryRange, "\uD83E\uDD0F") // 🤏 U+1F90F PINCHING HAND + assertMatches(supplementaryRange, "\uD83E\uDD14") // 🤔 U+1F914 THINKING FACE + assertNotMatches(supplementaryRange, "a") + assertNotMatches(supplementaryRange, GClefHigh) + assertNotMatches(supplementaryRange, GClefHigh + "a") + assertNotMatches(supplementaryRange, "\uD834\uDD10") // 𝄐 MUSICAL SYMBOL FERMATA + assertNotMatches(supplementaryRange, "\uD83E\uDD26") // 🤦 U+1F926 FACE PALM + assertNotMatches(supplementaryRange, GClefLow) + assertNotMatches(supplementaryRange, "a" + GClefLow) + + // ^ Range of supplementary code points spanning several high surrogates + val notSupplementaryRange = compile("[^" + GClef + "-\\x{1F914}]") // 🤔 U+1F914 THINKING FACE + assertNotMatches(notSupplementaryRange, GClef) + assertNotMatches(notSupplementaryRange, "\uD834\uDD22") // 𝄢 Musical Symbol F Clef + assertNotMatches(notSupplementaryRange, "\uD83C\uDC3D") // 🀽 U+1F03D DOMINO TILE HORIZONTAL-01-05 + assertNotMatches(notSupplementaryRange, "\uD83E\uDD0F") // 🤏 U+1F90F PINCHING HAND + assertNotMatches(notSupplementaryRange, "\uD83E\uDD14") // 🤔 U+1F914 THINKING FACE + assertMatches(notSupplementaryRange, "a") + assertMatches(notSupplementaryRange, GClefHigh) + assertFind(notSupplementaryRange, GClefHigh + "a", 0) + assertMatches(notSupplementaryRange, "\uD834\uDD10") // 𝄐 MUSICAL SYMBOL FERMATA + assertMatches(notSupplementaryRange, "\uD83E\uDD26") // 🤦 U+1F926 FACE PALM + assertMatches(notSupplementaryRange, GClefLow) + assertFind(notSupplementaryRange, "a" + GClefLow, 0) + + // Pseudo-surrogate pairs do not collapse as a single code point + val letterOrGClefComponents = compile("[a-z" + GClefHigh + EscapedGClefLow + "A-Z]") + assertMatches(letterOrGClefComponents, "a") + assertMatches(letterOrGClefComponents, "z") + assertMatches(letterOrGClefComponents, "A") + assertMatches(letterOrGClefComponents, "Z") + assertMatches(letterOrGClefComponents, "d") + assertMatches(letterOrGClefComponents, "G") + assertNotMatches(letterOrGClefComponents, "0") + assertNotMatches(letterOrGClefComponents, "@") + assertNotMatches(letterOrGClefComponents, "_") + assertNotMatches(letterOrGClefComponents, GClef) + assertMatches(letterOrGClefComponents, GClefHigh) + assertMatches(letterOrGClefComponents, GClefLow) + + // ^ Pseudo-surrogate pairs do not collapse as a single code point + val notLetterNorGClefComponents = compile("[^a-z" + GClefHigh + EscapedGClefLow + "A-Z]") + assertNotMatches(notLetterNorGClefComponents, "a") + assertNotMatches(notLetterNorGClefComponents, "z") + assertNotMatches(notLetterNorGClefComponents, "A") + assertNotMatches(notLetterNorGClefComponents, "Z") + assertNotMatches(notLetterNorGClefComponents, "d") + assertNotMatches(notLetterNorGClefComponents, "G") + assertMatches(notLetterNorGClefComponents, "0") + assertMatches(notLetterNorGClefComponents, "@") + assertMatches(notLetterNorGClefComponents, "_") + assertMatches(notLetterNorGClefComponents, GClef) + assertNotMatches(notLetterNorGClefComponents, GClefHigh) + assertNotMatches(notLetterNorGClefComponents, GClefLow) + } + + @Test def characterClassesWithPredefinedCharacterClasses(): Unit = { + val digitOrF = compile("[\\dF]") + assertMatches(digitOrF, "0") + assertMatches(digitOrF, "7") + assertMatches(digitOrF, "F") + assertNotMatches(digitOrF, "E") + assertNotMatches(digitOrF, "@") + assertNotMatches(digitOrF, GClefHigh) + assertNotMatches(digitOrF, GClefLow) + assertNotMatches(digitOrF, GClef) + + val notDigitNorF = compile("[^\\dF]") + assertNotMatches(notDigitNorF, "0") + assertNotMatches(notDigitNorF, "7") + assertNotMatches(notDigitNorF, "F") + assertMatches(notDigitNorF, "E") + assertMatches(notDigitNorF, "@") + assertMatches(notDigitNorF, GClefHigh) + assertMatches(notDigitNorF, GClefLow) + assertMatches(notDigitNorF, GClef) + + val notDigitOrElse5 = compile("[\\D5]") + assertNotMatches(notDigitOrElse5, "0") + assertNotMatches(notDigitOrElse5, "7") + assertMatches(notDigitOrElse5, "5") + assertMatches(notDigitOrElse5, "F") + assertMatches(notDigitOrElse5, "@") + assertMatches(notDigitOrElse5, GClefHigh) + assertMatches(notDigitOrElse5, GClefLow) + assertMatches(notDigitOrElse5, GClef) + + val notNotDigitOrElse5 = compile("[^\\D5]") + assertMatches(notNotDigitOrElse5, "0") + assertMatches(notNotDigitOrElse5, "7") + assertNotMatches(notNotDigitOrElse5, "5") + assertNotMatches(notNotDigitOrElse5, "F") + assertNotMatches(notNotDigitOrElse5, "@") + assertNotMatches(notNotDigitOrElse5, GClefHigh) + assertNotMatches(notNotDigitOrElse5, GClefLow) + assertNotMatches(notNotDigitOrElse5, GClef) + + val whitespaceOrF = compile("[\\sF]") + assertMatches(whitespaceOrF, "\t") + assertMatches(whitespaceOrF, "\r") + assertMatches(whitespaceOrF, "\n") + assertMatches(whitespaceOrF, " ") + assertMatches(whitespaceOrF, "F") + assertNotMatches(whitespaceOrF, "E") + assertNotMatches(whitespaceOrF, "\u0008") + assertNotMatches(whitespaceOrF, "\u0011") + assertNotMatches(whitespaceOrF, GClefHigh) + assertNotMatches(whitespaceOrF, GClefLow) + assertNotMatches(whitespaceOrF, GClef) + + val notWhitespaceNorF = compile("[^\\sF]") + assertNotMatches(notWhitespaceNorF, "\t") + assertNotMatches(notWhitespaceNorF, "\r") + assertNotMatches(notWhitespaceNorF, "\n") + assertNotMatches(notWhitespaceNorF, " ") + assertNotMatches(notWhitespaceNorF, "F") + assertMatches(notWhitespaceNorF, "E") + assertMatches(notWhitespaceNorF, "\u0008") + assertMatches(notWhitespaceNorF, "\u0011") + assertMatches(notWhitespaceNorF, GClefHigh) + assertMatches(notWhitespaceNorF, GClefLow) + assertMatches(notWhitespaceNorF, GClef) + + val notWhitespaceOrElseNL = compile("[\\S\\n]") + assertNotMatches(notWhitespaceOrElseNL, "\t") + assertNotMatches(notWhitespaceOrElseNL, "\r") + assertNotMatches(notWhitespaceOrElseNL, " ") + assertMatches(notWhitespaceOrElseNL, "\n") + assertMatches(notWhitespaceOrElseNL, "F") + assertMatches(notWhitespaceOrElseNL, "\u0008") + assertMatches(notWhitespaceOrElseNL, "\u0011") + assertMatches(notWhitespaceOrElseNL, GClefHigh) + assertMatches(notWhitespaceOrElseNL, GClefLow) + assertMatches(notWhitespaceOrElseNL, GClef) + + val notNotWhitespaceOrElseNL = compile("[^\\S\\n]") + assertMatches(notNotWhitespaceOrElseNL, "\t") + assertMatches(notNotWhitespaceOrElseNL, "\r") + assertMatches(notNotWhitespaceOrElseNL, " ") + assertNotMatches(notNotWhitespaceOrElseNL, "\n") + assertNotMatches(notNotWhitespaceOrElseNL, "F") + assertNotMatches(notNotWhitespaceOrElseNL, "\u0008") + assertNotMatches(notNotWhitespaceOrElseNL, "\u0011") + assertNotMatches(notNotWhitespaceOrElseNL, GClefHigh) + assertNotMatches(notNotWhitespaceOrElseNL, GClefLow) + assertNotMatches(notNotWhitespaceOrElseNL, GClef) + } + + @Test def complexCharacterClasses(): Unit = { + val ad_or_mp = compile("[a-d[m-p]]") + assertMatches(ad_or_mp, "a") + assertMatches(ad_or_mp, "c") + assertMatches(ad_or_mp, "d") + assertMatches(ad_or_mp, "m") + assertMatches(ad_or_mp, "n") + assertMatches(ad_or_mp, "p") + assertNotMatches(ad_or_mp, "e") + assertNotMatches(ad_or_mp, "A") + assertNotMatches(ad_or_mp, "N") + + val an_and_ks = compile("[a-n&&k-s]") + assertMatches(an_and_ks, "k") + assertMatches(an_and_ks, "m") + assertMatches(an_and_ks, "n") + assertNotMatches(an_and_ks, "0") + assertNotMatches(an_and_ks, "e") + assertNotMatches(an_and_ks, "j") + assertNotMatches(an_and_ks, "o") + assertNotMatches(an_and_ks, "z") + assertNotMatches(an_and_ks, "A") + assertNotMatches(an_and_ks, "N") + + val az_butNot_dfh = compile("[a-z&&[^dfh]]") + assertMatches(az_butNot_dfh, "a") + assertMatches(az_butNot_dfh, "c") + assertMatches(az_butNot_dfh, "e") + assertMatches(az_butNot_dfh, "i") + assertMatches(az_butNot_dfh, "r") + assertNotMatches(az_butNot_dfh, "d") + assertNotMatches(az_butNot_dfh, "f") + assertNotMatches(az_butNot_dfh, "h") + assertNotMatches(az_butNot_dfh, "A") + assertNotMatches(az_butNot_dfh, "0") + assertNotMatches(az_butNot_dfh, "\n") + + val az_butNot_mp = compile("[a-z&&[^m-p]]") + assertMatches(az_butNot_mp, "a") + assertMatches(az_butNot_mp, "e") + assertMatches(az_butNot_mp, "l") + assertMatches(az_butNot_mp, "q") + assertMatches(az_butNot_mp, "t") + assertNotMatches(az_butNot_mp, "m") + assertNotMatches(az_butNot_mp, "n") + assertNotMatches(az_butNot_mp, "o") + assertNotMatches(az_butNot_mp, "p") + assertNotMatches(az_butNot_mp, "E") + assertNotMatches(az_butNot_mp, "0") + assertNotMatches(az_butNot_mp, "\n") + + val id = compile("([\\w&&[\\D]][\\w]*)") + assertMatches(id, "foo") + assertMatches(id, "foo56") + assertMatches(id, "foo56bar") + assertMatches(id, "_1") + assertMatches(id, "foo_bar") + assertMatches(id, "HELLO") + assertNotMatches(id, "0") + assertNotMatches(id, "0a") + assertNotMatches(id, "01") + assertNotMatches(id, "foo-bar") + assertNotMatches(id, "foo bar") + assertNotMatches(id, "!Foo") + assertNotMatches(id, " Foo") + assertNotMatches(id, "Foo ") + + val complexUnionsAndIntersections = compile("[d-l[o-t].-?&&f[k-q] -Z&&1-3\\D]") + assertMatches(complexUnionsAndIntersections, ".") + assertMatches(complexUnionsAndIntersections, "/") + assertMatches(complexUnionsAndIntersections, "1") + assertMatches(complexUnionsAndIntersections, "3") + assertMatches(complexUnionsAndIntersections, "=") + assertMatches(complexUnionsAndIntersections, "?") + assertMatches(complexUnionsAndIntersections, "f") + assertMatches(complexUnionsAndIntersections, "k") + assertMatches(complexUnionsAndIntersections, "l") + assertMatches(complexUnionsAndIntersections, "o") + assertMatches(complexUnionsAndIntersections, "q") + assertNotMatches(complexUnionsAndIntersections, "!") + assertNotMatches(complexUnionsAndIntersections, "0") + assertNotMatches(complexUnionsAndIntersections, "5") + assertNotMatches(complexUnionsAndIntersections, "@") + assertNotMatches(complexUnionsAndIntersections, "F") + assertNotMatches(complexUnionsAndIntersections, "a") + assertNotMatches(complexUnionsAndIntersections, "e") + assertNotMatches(complexUnionsAndIntersections, "g") + assertNotMatches(complexUnionsAndIntersections, "j") + assertNotMatches(complexUnionsAndIntersections, "m") + assertNotMatches(complexUnionsAndIntersections, "n") + assertNotMatches(complexUnionsAndIntersections, "r") + assertNotMatches(complexUnionsAndIntersections, "t") + assertNotMatches(complexUnionsAndIntersections, "u") + assertNotMatches(complexUnionsAndIntersections, "z") + + // https://bugs.openjdk.java.net/browse/JDK-8216391 + if (!executingInJVMOnJDK8OrLower) { + val not_ad_or_mp = compile("[^a-d[m-p]]") + assertNotMatches(not_ad_or_mp, "a") + assertNotMatches(not_ad_or_mp, "c") + assertNotMatches(not_ad_or_mp, "d") + assertNotMatches(not_ad_or_mp, "m") + assertNotMatches(not_ad_or_mp, "n") + assertNotMatches(not_ad_or_mp, "p") + assertMatches(not_ad_or_mp, "e") + assertMatches(not_ad_or_mp, "A") + assertMatches(not_ad_or_mp, "N") + + val not_an_and_ks = compile("[^a-n&&k-s]") + assertNotMatches(not_an_and_ks, "k") + assertNotMatches(not_an_and_ks, "m") + assertNotMatches(not_an_and_ks, "n") + assertMatches(not_an_and_ks, "0") + assertMatches(not_an_and_ks, "e") + assertMatches(not_an_and_ks, "j") + assertMatches(not_an_and_ks, "o") + assertMatches(not_an_and_ks, "z") + assertMatches(not_an_and_ks, "A") + assertMatches(not_an_and_ks, "N") + + val notComplexUnionsAndIntersections = compile("[^d-l[o-t].-?&&f[k-q] -Z&&1-3\\D]") + assertNotMatches(notComplexUnionsAndIntersections, ".") + assertNotMatches(notComplexUnionsAndIntersections, "/") + assertNotMatches(notComplexUnionsAndIntersections, "1") + assertNotMatches(notComplexUnionsAndIntersections, "3") + assertNotMatches(notComplexUnionsAndIntersections, "=") + assertNotMatches(notComplexUnionsAndIntersections, "?") + assertNotMatches(notComplexUnionsAndIntersections, "f") + assertNotMatches(notComplexUnionsAndIntersections, "k") + assertNotMatches(notComplexUnionsAndIntersections, "l") + assertNotMatches(notComplexUnionsAndIntersections, "o") + assertNotMatches(notComplexUnionsAndIntersections, "q") + assertMatches(notComplexUnionsAndIntersections, "!") + assertMatches(notComplexUnionsAndIntersections, "0") + assertMatches(notComplexUnionsAndIntersections, "5") + assertMatches(notComplexUnionsAndIntersections, "@") + assertMatches(notComplexUnionsAndIntersections, "F") + assertMatches(notComplexUnionsAndIntersections, "a") + assertMatches(notComplexUnionsAndIntersections, "e") + assertMatches(notComplexUnionsAndIntersections, "g") + assertMatches(notComplexUnionsAndIntersections, "j") + assertMatches(notComplexUnionsAndIntersections, "m") + assertMatches(notComplexUnionsAndIntersections, "n") + assertMatches(notComplexUnionsAndIntersections, "r") + assertMatches(notComplexUnionsAndIntersections, "t") + assertMatches(notComplexUnionsAndIntersections, "u") + assertMatches(notComplexUnionsAndIntersections, "z") + } + } + + @Test def complexUnicodeCharacterClasses(): Unit = { + assumeTrue("requires \\p{} support", regexSupportsUnicodeCharacterClasses) + + val letterNotUpperNorLower = compile("[\\p{L}&&[^\\p{Lu}\\p{Ll}]]") + assertMatches(letterNotUpperNorLower, "か") + assertMatches(letterNotUpperNorLower, "\u01F2") // U+01F2 Dz Latin Capital Letter D with Small Letter Z + assertMatches(letterNotUpperNorLower, "今") + assertNotMatches(letterNotUpperNorLower, "e") + assertNotMatches(letterNotUpperNorLower, "A") + assertNotMatches(letterNotUpperNorLower, "N") + assertNotMatches(letterNotUpperNorLower, "À") + assertNotMatches(letterNotUpperNorLower, "0") + } + + @Test def characterClassWithQuote(): Unit = { + val cc1 = compile("[a\\Q[]\\(t-z" + GClef + "\\Ed]") + assertMatches(cc1, "a") + assertMatches(cc1, "[") + assertMatches(cc1, "]") + assertMatches(cc1, "\\") + assertMatches(cc1, "(") + assertMatches(cc1, "t") + assertMatches(cc1, "-") + assertMatches(cc1, "z") + assertMatches(cc1, GClef) + assertMatches(cc1, "d") + assertNotMatches(cc1, "A") + assertNotMatches(cc1, "b") + assertNotMatches(cc1, "Q") + assertNotMatches(cc1, "E") + assertNotMatches(cc1, "T") + assertNotMatches(cc1, "u") // between 't' and 'z' + assertNotMatches(cc1, GClefHigh) + assertNotMatches(cc1, GClefLow) + + val cc1CaseInsensitive = compile("[a\\Q[]\\(t-z" + GClef + "\\Ed]", CaseInsensitive) + assertMatches(cc1CaseInsensitive, "a") + assertMatches(cc1CaseInsensitive, "A") + assertMatches(cc1CaseInsensitive, "[") + assertMatches(cc1CaseInsensitive, "]") + assertMatches(cc1CaseInsensitive, "\\") + assertMatches(cc1CaseInsensitive, "(") + assertMatches(cc1CaseInsensitive, "t") + assertMatches(cc1CaseInsensitive, "T") + assertMatches(cc1CaseInsensitive, "-") + assertMatches(cc1CaseInsensitive, "z") + assertMatches(cc1CaseInsensitive, GClef) + assertMatches(cc1CaseInsensitive, "d") + assertNotMatches(cc1CaseInsensitive, "b") + assertNotMatches(cc1CaseInsensitive, "Q") + assertNotMatches(cc1CaseInsensitive, "E") + assertNotMatches(cc1CaseInsensitive, "u") // between 't' and 'z' + assertNotMatches(cc1CaseInsensitive, GClefHigh) + assertNotMatches(cc1CaseInsensitive, GClefLow) + } + + @Test def positiveLookAheadDoesNotBacktrack(): Unit = { + val m = assertFind("(?=(a+))a*b\\1", "baaabac", 3) + assertEquals("aba", m.group()) + assertEquals("a", m.group(1)) + } + + @Test def asciiCaseInsensitive(): Unit = { + val s = compile("s", CaseInsensitive) + assertMatches(s, "s") + assertMatches(s, "S") + assertNotMatches(s, "\u017F") // ſ LATIN SMALL LETTER LONG S + assertNotMatches(s, "t") + } + + @Test def unicodeCaseInsensitive(): Unit = { + assumeTrue("requires 'u' flag support", regexSupportsUnicodeCase) + + val s = compile("s", CaseInsensitive | UnicodeCase) + assertMatches(s, "s") + assertMatches(s, "S") + assertMatches(s, "\u017F") // ſ LATIN SMALL LETTER LONG S + assertNotMatches(s, "t") + } + + @Test def wordBoundary(): Unit = { + val fooWordBoundary = compile("foo\\b") + assertMatches(fooWordBoundary, "foo") + assertFind(fooWordBoundary, "foo bar", 0) + assertFind(fooWordBoundary, "foobar foo+bar", 7) + assertNotFind(fooWordBoundary, "foobar") + + // https://bugs.openjdk.java.net/browse/JDK-8264160 + if (!executingInJVM) + assertFind(fooWordBoundary, "fooÀbar", 0) + + val fooNonWordBoundary = compile("foo\\B") + assertNotFind(fooNonWordBoundary, "foo") + assertNotFind(fooNonWordBoundary, "foo bar") + assertFind(fooNonWordBoundary, "foo+barfoobar", 7) + assertFind(fooNonWordBoundary, "foobar", 0) + + // https://bugs.openjdk.java.net/browse/JDK-8264160 + if (!executingInJVM) + assertNotFind(fooNonWordBoundary, "fooÀbar") + } + + @Test def wordBoundaryUnicode(): Unit = { + assumeTrue("requires look-behinds", regexSupportsLookBehinds) + + val fooWordBoundary = compile("な\\b", UnicodeCharacterClass) + assertMatches(fooWordBoundary, "な") + assertFind(fooWordBoundary, "ひらがな bar", 3) + assertFind(fooWordBoundary, "なつ ひらがな+bar", 6) + assertNotFind(fooWordBoundary, "なつ") + + val fooNonWordBoundary = compile("な\\B", UnicodeCharacterClass) + assertNotFind(fooNonWordBoundary, "な") + assertNotFind(fooNonWordBoundary, "ひらがな bar") + assertFind(fooNonWordBoundary, "ひらがな+barひらがなbar", 11) + assertFind(fooNonWordBoundary, "なつ", 0) + } + + @Test def endOfInputPossiblyBeforeLineTerminator(): Unit = { + val aZ = compile("a\\Z") + assertMatches(aZ, "a") + assertFind(aZ, "a\n", 0, 1) + assertFind(aZ, "a\r\n", 0, 1) + assertFind(aZ, "a\u2028", 0, 1) + assertFind(aZ, "baa", 2, 3) + assertFind(aZ, "baa\n", 2, 3) + assertNotFind(aZ, "ab") + assertNotFind(aZ, "a\nb") + + val aZUnixLines = compile("a\\Z", UnixLines) + assertMatches(aZUnixLines, "a") + assertFind(aZUnixLines, "a\n", 0, 1) + assertNotFind(aZUnixLines, "a\r\n") + assertNotFind(aZUnixLines, "a\u2028") + assertFind(aZUnixLines, "baa", 2, 3) + assertFind(aZUnixLines, "baa\n", 2, 3) + assertNotFind(aZUnixLines, "ab") + assertNotFind(aZUnixLines, "a\nb") + + val nlZ = compile("\\n\\Z") + assertFind(nlZ, "\n", 0, 1) + assertFind(nlZ, "\n\n", 0, 1) + } + + @Test def unicodeLineBreak(): Unit = { + val lineBreak = compile("\\R") + assertMatches(lineBreak, "\n") + assertMatches(lineBreak, "\u000B") + assertMatches(lineBreak, "\u000C") + assertMatches(lineBreak, "\r") + assertMatches(lineBreak, "\u0085") + assertMatches(lineBreak, "\u2028") + assertMatches(lineBreak, "\u2029") + assertMatches(lineBreak, "\r\n") + assertNotMatches(lineBreak, "\t") + assertNotMatches(lineBreak, " ") + assertNotMatches(lineBreak, "a") + + assertFind(lineBreak, "ab\r\ncd", 2, 4) + assertFind(lineBreak, "ab\n\ncd", 2, 3) + + // \R is not affected by UNIX_LINES + + val lineBreakUnixLines = compile("\\R", UnixLines) + assertMatches(lineBreakUnixLines, "\n") + assertMatches(lineBreakUnixLines, "\u000B") + assertMatches(lineBreakUnixLines, "\u000C") + assertMatches(lineBreakUnixLines, "\r") + assertMatches(lineBreakUnixLines, "\u0085") + assertMatches(lineBreakUnixLines, "\u2028") + assertMatches(lineBreakUnixLines, "\u2029") + assertMatches(lineBreakUnixLines, "\r\n") + assertNotMatches(lineBreakUnixLines, "\t") + assertNotMatches(lineBreakUnixLines, " ") + assertNotMatches(lineBreakUnixLines, "a") + + assertFind(lineBreakUnixLines, "ab\r\ncd", 2, 4) + assertFind(lineBreakUnixLines, "ab\n\ncd", 2, 3) + } + + @Test def namedCaptureGroups(): Unit = { + val named = compile(".*((?Pizza).*?)+") + val m = assertMatchesAndGroupsEquals(named, "PizzaWithPizza", "Pizza", "Pizza") + assertEquals("Pizza", m.group("pizza")) + + val ref = compile("(?Pizza)\\k*?") + assertMatches(ref, "Pizza") + assertMatches(ref, "PizzaPizza") + assertMatches(ref, "PizzaPizzaPizza") + assertNotMatches(ref, "PizzaPizzicatoPizza") + + assertSyntaxError("(?a?)\\k?", "named capturing group does not exit", 12) + + assertSyntaxError("(?a?)(?dupe)", "named capturing group is already defined", 12) + } + + @Test def recursiveCapturingGroups(): Unit = { + val rec = compile("""(a?\1?)\1""") + assertMatches(rec, "aa") + assertMatches(rec, "") + assertNotMatches(rec, "ab") + assertNotMatches(rec, "a") + assertNotMatches(rec, "aaa") + + // The JVM kind of supports "back references" to later groups, but we don't + assertSyntaxErrorInJS("(a?\\2?)(b?\\1?)", "numbered capturing group <2> does not exist", 4) + + // The JVM tolerates "back references" to non-existing groups, but we don't + assertSyntaxErrorInJS("(a?\\3?)(b?\\1?)", "numbered capturing group <3> does not exist", 4) + + val namedRec = compile("(?a?\\k?)\\k") + assertMatches(namedRec, "aa") + assertMatches(namedRec, "") + assertNotMatches(namedRec, "ab") + assertNotMatches(namedRec, "a") + + assertSyntaxError("(?a?\\k?)(?b?\\k?)", "named capturing group does not exit", 11) + } + + @Test def backReferenceLimit(): Unit = { + val backRef12 = compile("""(a?)(b?)(c?)(d?)(e?)(f?)(g?)(h?)(i?)(k?)(l?)(m?)\12""") + assertMatches(backRef12, "acfg") + assertMatches(backRef12, "acfgmm") + assertNotMatches(backRef12, "acfgm") + assertNotMatches(backRef12, "acfgma2") + assertNotMatches(backRef12, "acfgma") + + val backRefLimited = compile("""(a?)\12""") + assertMatches(backRefLimited, "2") + assertMatches(backRefLimited, "aa2") + assertNotMatches(backRefLimited, "a2") + assertNotMatches(backRefLimited, "a") + assertNotMatches(backRefLimited, "aa") + assertNotMatches(backRefLimited, "aaa") + } + + @Test def repeatedNestedCapture(): Unit = { + val pattern = compile("(Foo(Bar)?)+") + val matcher = pattern.matcher("FooBarFoo") + assert(matcher.matches()) + assertEquals(matcher.group(), "FooBarFoo") + assertEquals(matcher.groupCount(), 2) + assertEquals(matcher.group(1), "Foo") + + // This is not good, but I (sjrd) don't think there's anything we can do about it + if (executingInJVM) + assertEquals(matcher.group(2), "Bar") + else + assertEquals(matcher.group(2), null) + } + + @Test def atomicGroups(): Unit = { + val abccNonAtomic = compile("a(bc|b)c") + assertFind(abccNonAtomic, "abcc", 0, 4) + assertFind(abccNonAtomic, "abc", 0, 3) + + val abccAtomic = compile("a(?>bc|b)c") + assertFind(abccAtomic, "abcc", 0, 4) + assertNotFind(abccAtomic, "abc") + + // Don't break numbered groups before, within, and after + val atomicGroupsAndNumberedGroups = compile("(a)(?>(bc)|(c))(c) \\1 \\2 \\4") + assertFindAndGroupsEquals(atomicGroupsAndNumberedGroups, "abcc a bc c", 0, "a", "bc", null, "c") + + // Don't break named groups before, within, and after + val atomicGroupsAndNamedGroups = compile("(?a)(?>(?bc)|(?c))(?c) \\k \\k \\k") + val m = assertFindAndGroupsEquals(atomicGroupsAndNamedGroups, "abcc a bc c", 0, "a", "bc", null, "c") + assertEquals("a", m.group("A")) + assertEquals("bc", m.group("B")) + assertEquals(null, m.group("C")) + assertEquals("c", m.group("D")) + } + + @Test def atomicGroupsAndPossessiveQuantifiersAvoidCatastrophicBacktracking(): Unit = { + // See https://www.regular-expressions.info/catastrophic.html + + val input = "x" * 50 + + /* Enable this if you want to confirm that the catastrophic version is + * indeed catastrophic: it is going to loop "forever". + */ + //val catastrophicPattern = compile("(x+x+)+y") + //assertNotMatches(catastrophicPattern, input) + + val solutionWithPossessiveQuantifier = compile("(x+x+)++y") + assertNotMatches(solutionWithPossessiveQuantifier, input) + + val solutionWithAtomicGroup = compile("(?>(x+x+)+)y") + assertNotMatches(solutionWithAtomicGroup, input) + } + + /** Tests for regexes that we found in the public Scala.js libraries at the + * time of switching from the JS `RegExp` behavior to the JVM `Pattern` + * behavior. + * + * When the groups are fetched in the original code, we check the groups + * here. Otherwise, we don't, even if there are capturing groups in the + * regex. + * + * These tests only really test that the regexes still work, but not that + * they work *in the same way* as before. In fact, they don't for some + * corner cases. By inspection, all the regexes below use features in 4 + * categories: + * + * - Features whose semantics are equivalent in `js.RegExp` and `Pattern`, + * notably ASCII characters, repeaters, classes of ASCII characters, the + * '\d' character class, the '^' and '$' boundary matchers (without + * multiline). + * - The '.', which *is* different: it matches '\x85' in `js.RegExp` but not + * in `Pattern`; this was judged acceptable as unlikely to cause a real + * difference in practice. + * - One regex uses the `CASE_INSENSITIVE` with a pattern that contains only + * ASCII letters: it now really only matches other ASCII letters; this was + * judged acceptable as probably the intended meaning anyway. + * - One regex uses '\s' and '\S', for which we obtained confirmation from + * the maintainer that the change in semantics was not an issue. + */ + @Test def regexesFoundInLibraries(): Unit = { + // scalastyle:off line.size.limit + + // https://github.com/Bathtor/api-framework/blob/d85d454b787393c539fcc4d8a09fe383041cfc2b/src/main/scala/com/lkroll/roll20/api/Attribute.scala#L144 + locally { + val rowIdPattern = compile(raw"repeating_[a-zA-Z]+_([-a-zA-Z0-9]+)_.*") + assertMatchesAndGroupsEquals(rowIdPattern, + "repeating_testsec_-KkWmmPeaGP87vaZLpkt_testsec_testf", + "-KkWmmPeaGP87vaZLpkt") + } + + // https://github.com/gemini-hlsw/lucuma-ui/blob/57dbc3c9ccf2cc108a13c18a878a9e48099ac7f4/modules/ui/src/main/scala/lucuma/ui/optics/ChangeAuditor.scala#L363 + locally { + // note: IMO the '+' is a mistake in the original regex, but we're testing it as is anyway + val n = 3 + val stripZerosPastNPlaces = compile(s"(.*\\.\\d{0,$n}[1-9]*)+0*") + assertMatchesAndGroupsEquals(stripZerosPastNPlaces, "foo.23034500000", "foo.230345") + assertMatchesAndGroupsEquals(stripZerosPastNPlaces, "foo.034500000", "foo.0345") + assertMatchesAndGroupsEquals(stripZerosPastNPlaces, "foo.34500000", "foo.345") + assertMatchesAndGroupsEquals(stripZerosPastNPlaces, "foo.00000000", "foo.000") + assertMatchesAndGroupsEquals(stripZerosPastNPlaces, "foo.000345bar.0100000", "foo.000345bar.010") + assertNotMatches(stripZerosPastNPlaces, "foo123") + assertNotMatches(stripZerosPastNPlaces, "foo.03450012000") + } + + // https://github.com/gemini-hlsw/lucuma-ui/blob/57dbc3c9ccf2cc108a13c18a878a9e48099ac7f4/modules/ui/src/main/scala/lucuma/ui/optics/ChangeAuditor.scala#L375 + locally { + val newPos = 3 + val stripZerosBeforeN = compile(s"0{0,$newPos}(\\d+)") + assertMatchesAndGroupsEquals(stripZerosBeforeN, "000001230", "001230") + assertMatchesAndGroupsEquals(stripZerosBeforeN, "001230001230", "1230001230") + assertMatchesAndGroupsEquals(stripZerosBeforeN, "1230001230", "1230001230") + assertNotMatches(stripZerosBeforeN, "00123a0001230") + } + + // https://github.com/exoego/scala-js-nodejs/blob/4bfa4a96d646665b0e0c3e7c47eb1c206e31c01d/core/src/main/scala/io/scalajs/nodejs/internal/CompilerSwitches.scala#L6 + locally { + val nodejsVersionPattern = compile("^nodeJs([0-9]{1,2})\\.([0-9]{1,2})\\.([0-9]{1,2})$") + assertMatchesAndGroupsEquals(nodejsVersionPattern, "nodeJs14.15.1", "14", "15", "1") + assertMatchesAndGroupsEquals(nodejsVersionPattern, "nodeJs1.0.2", "1", "0", "2") + assertNotMatches(nodejsVersionPattern, "nodeJs123.4.5") + assertNotMatches(nodejsVersionPattern, "node12.4.5") + } + + // https://github.com/japgolly/scalajs-benchmark/blob/7c6061c221e0deb09f8dde9a762f5001bbe3c247/benchmark/src/main/scala/japgolly/scalajs/benchmark/gui/GuiUtil.scala#L25 + locally { + val numberFmt = compile("""^-?(\d[,.]?)+(?:[,.]\d+)?$""") + assertMatches(numberFmt, "123.456644,112") + assertMatches(numberFmt, "-123.456644,112") + assertMatches(numberFmt, "1") + assertNotMatches(numberFmt, "123..456644,112") + assertNotMatches(numberFmt, "123,,456644,112") + assertNotMatches(numberFmt, "123.,456644,112") + assertNotMatches(numberFmt, "123.45a644,112") + assertNotMatches(numberFmt, ".1") + } + + // https://github.com/japgolly/scalajs-benchmark/blob/7c6061c221e0deb09f8dde9a762f5001bbe3c247/benchmark/src/main/scala/japgolly/scalajs/benchmark/gui/IntEditor.scala#L46 + locally { + val illegalChars = compile("[^0-9-]+") + assertMatches(illegalChars, "abd_\n^foo") + assertNotMatches(illegalChars, "ab5foo") + assertNotMatches(illegalChars, "ab-foo") + assertNotMatches(illegalChars, "") + } + + // https://github.com/japgolly/scalajs-benchmark/blob/7c6061c221e0deb09f8dde9a762f5001bbe3c247/benchmark/src/main/scala/japgolly/scalajs/benchmark/gui/package.scala#L73 + locally { + val r = compile("[ ,]") + assertMatches(r, " ") + assertMatches(r, ",") + assertNotMatches(r, "_") + assertNotMatches(r, " ") + assertNotMatches(r, " ,") + assertNotMatches(r, "[") + assertNotMatches(r, "]") + } + + // https://github.com/japgolly/scalajs-react/blob/3639a2a7bafabac8a9ad048e9a942cba3ca34054/core/src/main/scala/japgolly/scalajs/react/ScalaJsReactConfig.scala#L47 + locally { + val regex = compile("\\.?comp(?:onent)?$", CaseInsensitive) + assertMatches(regex, ".comp") + assertMatches(regex, ".component") + assertMatches(regex, ".coMpOneNT") + assertMatches(regex, ".COMP") + assertMatches(regex, "comp") + assertMatches(regex, ".coMPOnent") + assertNotMatches(regex, ".compon") + assertNotMatches(regex, ".cimponent") + assertNotMatches(regex, "+comp") + } + + // https://github.com/japgolly/scalajs-react/blob/4db165c363efa379d146f97401f6bcf97bc8a698/extra/src/main/scala/japgolly/scalajs/react/extra/router/Dsl.scala#L19 + locally { + val regexEscape1 = compile("""([-()\[\]{}+?*.$\^|,:#""") + assertMatches(reactStuffToIgnore, """ data-react-foo = "arg"""") + assertMatches(reactStuffToIgnore, s"""""") + assertFind(reactStuffToIgnore, """begin more -->""", 6, 23) + assertNotMatches(reactStuffToIgnore, """ data-react-foo = arg""") + assertNotMatches(reactStuffToIgnore, """