diff options
Diffstat (limited to 'compiler/src/test/scala')
11 files changed, 1785 insertions, 0 deletions
diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/DiverseErrorsTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/DiverseErrorsTest.scala new file mode 100644 index 0000000..0fe10f8 --- /dev/null +++ b/compiler/src/test/scala/scala/scalajs/compiler/test/DiverseErrorsTest.scala @@ -0,0 +1,31 @@ +package scala.scalajs.compiler.test + +import scala.scalajs.compiler.test.util._ +import org.junit.Test + +class DiverseErrorsTest extends DirectTest with TestHelpers { + + override def preamble = + """import scala.scalajs.js + """ + + @Test + def noIsInstanceOnJSRaw = { + + """ + trait JSRaw extends js.Object + + class A { + val a: AnyRef = "asdf" + def x = a.isInstanceOf[JSRaw] + } + """ hasErrors + """ + |newSource1.scala:7: error: isInstanceOf[JSRaw] not supported because it is a raw JS trait + | def x = a.isInstanceOf[JSRaw] + | ^ + """ + + } + +} diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/EnumerationInteropTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/EnumerationInteropTest.scala new file mode 100644 index 0000000..e186cf4 --- /dev/null +++ b/compiler/src/test/scala/scala/scalajs/compiler/test/EnumerationInteropTest.scala @@ -0,0 +1,135 @@ +package scala.scalajs.compiler.test + +import scala.scalajs.compiler.test.util._ + +import org.junit.Test + +class EnumerationInteropTest extends DirectTest with TestHelpers { + + @Test + def warnIfUnableToTransformValue = { + + """ + class A extends Enumeration { + val a = { + println("oh, oh!") + Value + } + val b = { + println("oh, oh!") + Value(4) + } + } + """ hasWarns + """ + |newSource1.scala:5: warning: Couldn't transform call to Enumeration.Value. + |The resulting program is unlikely to function properly as this + |operation requires reflection. + | Value + | ^ + |newSource1.scala:9: warning: Couldn't transform call to Enumeration.Value. + |The resulting program is unlikely to function properly as this + |operation requires reflection. + | Value(4) + | ^ + """ + + } + + @Test + def warnIfNoNameVal = { + + """ + class A extends Enumeration { + val a = new Val + val b = new Val(10) + } + """ hasWarns + """ + |newSource1.scala:3: warning: Calls to the non-string constructors of Enumeration.Val + |require reflection at runtime. The resulting + |program is unlikely to function properly. + | val a = new Val + | ^ + |newSource1.scala:4: warning: Calls to the non-string constructors of Enumeration.Val + |require reflection at runtime. The resulting + |program is unlikely to function properly. + | val b = new Val(10) + | ^ + """ + + } + + @Test + def warnIfNullValue = { + + """ + class A extends Enumeration { + val a = Value(null) + val b = Value(10, null) + } + """ hasWarns + """ + |newSource1.scala:3: warning: Passing null as name to Enumeration.Value + |requires reflection at runtime. The resulting + |program is unlikely to function properly. + | val a = Value(null) + | ^ + |newSource1.scala:4: warning: Passing null as name to Enumeration.Value + |requires reflection at runtime. The resulting + |program is unlikely to function properly. + | val b = Value(10, null) + | ^ + """ + + } + + @Test + def warnIfNullNewVal = { + + """ + class A extends Enumeration { + val a = new Val(null) + val b = new Val(10, null) + } + """ hasWarns + """ + |newSource1.scala:3: warning: Passing null as name to a constructor of Enumeration.Val + |requires reflection at runtime. The resulting + |program is unlikely to function properly. + | val a = new Val(null) + | ^ + |newSource1.scala:4: warning: Passing null as name to a constructor of Enumeration.Val + |requires reflection at runtime. The resulting + |program is unlikely to function properly. + | val b = new Val(10, null) + | ^ + """ + + } + + @Test + def warnIfExtNoNameVal = { + + """ + class A extends Enumeration { + protected class Val1 extends Val + protected class Val2 extends Val(1) + } + """ warns() // no message checking: position differs in 2.10 and 2.11 + + } + + @Test + def warnIfExtNullNameVal = { + + """ + class A extends Enumeration { + protected class Val1 extends Val(null) + protected class Val2 extends Val(1,null) + } + """ warns() // no message checking: position differs in 2.10 and 2.11 + + } + +} diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/JSDynamicLiteralTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/JSDynamicLiteralTest.scala new file mode 100644 index 0000000..bc1a1b4 --- /dev/null +++ b/compiler/src/test/scala/scala/scalajs/compiler/test/JSDynamicLiteralTest.scala @@ -0,0 +1,102 @@ +package scala.scalajs.compiler.test + +import scala.scalajs.compiler.test.util._ +import org.junit.Test + +class JSDynamicLiteralTest extends DirectTest with TestHelpers { + + override def preamble = + """import scala.scalajs.js.Dynamic.{ literal => lit } + """ + + @Test + def callApplyOnly = { + + // selectDynamic (with any name) + expr""" + lit.helloWorld + """.fails() // Scala error, no string checking due to versions + + // applyDynamicNamed with wrong method name + expr""" + lit.helloWorld(a = "a") + """ hasErrors + """ + |newSource1.scala:3: error: js.Dynamic.literal does not have a method named helloWorld + | lit.helloWorld(a = "a") + | ^ + """ + + // applyDynamic with wrong method name + expr""" + lit.helloWorld("a" -> "a") + """ hasErrors + """ + |newSource1.scala:3: error: js.Dynamic.literal does not have a method named helloWorld + | lit.helloWorld("a" -> "a") + | ^ + """ + + } + + @Test + def goodTypesOnly = { + + // Bad value type (applyDynamic) + """ + class A { + val x = new Object() + def foo = lit("a" -> x) + } + """.fails() + + // Bad key type (applyDynamic) + """ + class A { + val x = Seq() + def foo = lit(x -> "a") + } + """.fails() + + // Bad value type (applyDynamicNamed) + """ + class A { + val x = new Object() + def foo = lit(a = x) + } + """.fails() + + } + + @Test + def noNonLiteralMethodName = { + + // applyDynamicNamed + """ + class A { + val x = "string" + def foo = lit.applyDynamicNamed(x)() + } + """ hasErrors + """ + |newSource1.scala:5: error: js.Dynamic.literal.applyDynamicNamed may not be called directly + | def foo = lit.applyDynamicNamed(x)() + | ^ + """ + + // applyDynamic + """ + class A { + val x = "string" + def foo = lit.applyDynamic(x)() + } + """ hasErrors + """ + |newSource1.scala:5: error: js.Dynamic.literal.applyDynamic may not be called directly + | def foo = lit.applyDynamic(x)() + | ^ + """ + + } + +} diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportASTTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportASTTest.scala new file mode 100644 index 0000000..4a2b1af --- /dev/null +++ b/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportASTTest.scala @@ -0,0 +1,38 @@ +package scala.scalajs.compiler.test + +import util._ + +import org.junit.Test +import org.junit.Assert._ + +import scala.scalajs.ir.{Trees => js} + +class JSExportASTTest extends JSASTTest { + + @Test + def inheritExportMethods: Unit = { + + var props = 0 + + """ + import scala.scalajs.js.annotation.JSExport + + class A { + @JSExport + def foo = 1 + } + + class B extends A { + @JSExport + override def foo = 2 + } + """.traverse { + case js.PropertyDef(js.StringLiteral("foo"), _, _, _) => + props += 1 + } + + assertEquals("Only define the property `foo` once", props, 1) + + } + +} diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportTest.scala new file mode 100644 index 0000000..c675420 --- /dev/null +++ b/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportTest.scala @@ -0,0 +1,745 @@ +package scala.scalajs.compiler.test + +import scala.scalajs.compiler.test.util._ +import org.junit.Test +import org.junit.Ignore + +class JSExportTest extends DirectTest with TestHelpers { + + override def preamble = + """import scala.scalajs.js.annotation._ + """ + + @Test + def noDoubleUnderscoreExport = { + // Normal exports + """ + class A { + @JSExport(name = "__") + def foo = 1 + + @JSExport + def bar__(x: Int) = x + } + + @JSExport + class B__ + """ hasErrors + """ + |newSource1.scala:4: error: An exported name may not contain a double underscore (`__`) + | @JSExport(name = "__") + | ^ + |newSource1.scala:8: error: An exported name may not contain a double underscore (`__`) + | def bar__(x: Int) = x + | ^ + |newSource1.scala:12: error: An exported name may not contain a double underscore (`__`) + | class B__ + | ^ + """ + + // Inherited exports (objects) + """ + @JSExportDescendentObjects + trait A + + package fo__o { + object B extends A + } + """ hasErrors + """ + |newSource1.scala:7: error: B may not have a double underscore (`__`) in its fully qualified + |name, since it is forced to be exported by a @JSExportDescendentObjects on trait A + | object B extends A + | ^ + """ + + // Inherited exports (classes) + """ + @JSExportDescendentClasses + trait A + + package fo__o { + class B(x: Int) extends A { + def this() = this(1) + private def this(s: String) = this(1) + } + } + """ hasErrors + """ + |newSource1.scala:7: error: B may not have a double underscore (`__`) in its fully qualified + |name, since it is forced to be exported by a @JSExportDescendentClasses on trait A + | class B(x: Int) extends A { + | ^ + |newSource1.scala:8: error: B may not have a double underscore (`__`) in its fully qualified + |name, since it is forced to be exported by a @JSExportDescendentClasses on trait A + | def this() = this(1) + | ^ + """ + } + + @Test + def noConflictingExport = { + """ + class Confl { + @JSExport("value") + def hello = "foo" + + @JSExport("value") + def world = "bar" + } + """ fails() // No error test, Scala version dependent error messages + + """ + class Confl { + class Box[T](val x: T) + + @JSExport + def ub(x: Box[String]): String = x.x + @JSExport + def ub(x: Box[Int]): Int = x.x + } + """ fails() // No error test, Scala version dependent error messages + + """ + class Confl { + @JSExport + def rtType(x: scala.scalajs.js.prim.Number) = x + + @JSExport + def rtType(x: Double) = x + } + """ fails() // Error message depends on Scala version + + """ + class Confl { + @JSExport + def foo(x: Int)(ys: Int*) = x + + @JSExport + def foo(x: Int*) = x + } + """ hasErrors + """ + |newSource1.scala:7: error: Cannot disambiguate overloads for exported method $js$exported$meth$foo with types + | (x: Seq)Object + | (x: Int, ys: Seq)Object + | @JSExport + | ^ + """ + + """ + class Confl { + @JSExport + def foo(x: Int = 1) = x + @JSExport + def foo(x: String*) = x + } + """ hasErrors + """ + |newSource1.scala:4: error: Cannot disambiguate overloads for exported method $js$exported$meth$foo with types + | (x: Int)Object + | (x: Seq)Object + | @JSExport + | ^ + """ + + """ + class Confl { + @JSExport + def foo(x: scala.scalajs.js.prim.Number, y: String)(z: Int = 1) = x + @JSExport + def foo(x: Double, y: String)(z: String*) = x + } + """ fails() // Error message depends on Scala version + + """ + class A { + @JSExport + def a(x: scala.scalajs.js.Any) = 1 + + @JSExport + def a(x: Any) = 2 + } + """ fails() // Error message depends on Scala version + + } + + @Test + def noExportLocal = { + // Local class + """ + class A { + def method = { + @JSExport + class A + } + } + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a local class + | @JSExport + | ^ + """ + + // Local object + """ + class A { + def method = { + @JSExport + object A + } + } + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a local object + | @JSExport + | ^ + """ + + // Local method + """ + class A { + def method = { + @JSExport + def foo = 1 + } + } + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a local definition + | @JSExport + | ^ + """ + + // Local val + """ + class A { + def method = { + @JSExport + val x = 1 + } + } + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a local definition + | @JSExport + | ^ + """ + + // Local var + """ + class A { + def method = { + @JSExport + var x = 1 + } + } + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a local definition + | @JSExport + | ^ + """ + + } + + @Test + def infoExportLocal = { + + """ + class A(@JSExport val x: Int) + """ hasErrors + """ + |newSource1.scala:3: error: You may not export a local definition. To export a (case) class field, use the meta-annotation scala.annotation.meta.field like this: @(JSExport @field). + | class A(@JSExport val x: Int) + | ^ + """ + + } + + @Test + def noMiddleVarArg = { + + """ + class A { + @JSExport + def method(xs: Int*)(ys: String) = 1 + } + """ hasErrors + """ + |newSource1.scala:4: error: In an exported method, a *-parameter must come last (through all parameter lists) + | @JSExport + | ^ + """ + + } + + @Test + def noMiddleDefaultParam = { + + """ + class A { + @JSExport + def method(x: Int = 1)(y: String) = 1 + } + """ hasErrors + """ + |newSource1.scala:4: error: In an exported method, all parameters with defaults must be at the end + | @JSExport + | ^ + """ + + } + + @Test + def noExportTrait = { + + """ + @JSExport + trait Test + """ hasErrors + """ + |newSource1.scala:3: error: You may not export a trait + | @JSExport + | ^ + """ + + } + + @Test + def noExportNonPublicClassOrObject = { + + """ + @JSExport + private class A + + @JSExport + protected[this] class B + """ hasErrors + """ + |newSource1.scala:3: error: You may only export public and protected classes + | @JSExport + | ^ + |newSource1.scala:6: error: You may only export public and protected classes + | @JSExport + | ^ + """ + + """ + @JSExport + private object A + + @JSExport + protected[this] object B + """ hasErrors + """ + |newSource1.scala:3: error: You may only export public and protected objects + | @JSExport + | ^ + |newSource1.scala:6: error: You may only export public and protected objects + | @JSExport + | ^ + """ + + } + + @Test + def noExportNonPublicMember = { + + """ + class A { + @JSExport + private def foo = 1 + + @JSExport + protected[this] def bar = 2 + } + """ hasErrors + """ + |newSource1.scala:4: error: You may only export public and protected methods + | @JSExport + | ^ + |newSource1.scala:7: error: You may only export public and protected methods + | @JSExport + | ^ + """ + + } + + @Test + def noExportNestedClass = { + + """ + class A { + @JSExport + class Nested + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a nested class. Create an exported factory method in the outer class to work around this limitation. + | @JSExport + | ^ + """ + + """ + object A { + @JSExport + class Nested + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a nested class. Create an exported factory method in the outer class to work around this limitation. + | @JSExport + | ^ + """ + + } + + @Test + def noExportNestedObject = { + + """ + class A { + @JSExport + object Nested + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a nested object + | @JSExport + | ^ + """ + + """ + object A { + @JSExport + object Nested + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a nested object + | @JSExport + | ^ + """ + + } + + @Test + def noExportJSRaw = { + + """ + import scala.scalajs.js + + @JSExport + object A extends js.Object + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a class extending js.Any + | @JSExport + | ^ + """ + + """ + import scala.scalajs.js + + @JSExport + class A extends js.Object + """ hasErrors + """ + |newSource1.scala:5: error: You may not export a constructor of a subclass of js.Any + | @JSExport + | ^ + """ + + } + + @Test + def noExportJSRawMember = { + + """ + import scala.scalajs.js + + class A extends js.Object { + @JSExport + def foo: Int = js.native + } + """ hasErrors + """ + |newSource1.scala:6: error: You may not export a method of a subclass of js.Any + | @JSExport + | ^ + """ + + } + + @Test + def noBadSetterType = { + + // Bad param list + """ + class A { + @JSExport + def foo_=(x: Int, y: Int) = () + } + """ hasErrors + """ + |newSource1.scala:4: error: A method ending in _= will be exported as setter. But foo_= does not have the right signature to do so (single argument, unit return type). + | @JSExport + | ^ + """ + + // Bad return type + """ + class A { + @JSExport + def foo_=(x: Int) = "string" + } + """ hasErrors + """ + |newSource1.scala:4: error: A method ending in _= will be exported as setter. But foo_= does not have the right signature to do so (single argument, unit return type). + | @JSExport + | ^ + """ + + } + + @Test + def noBadToStringExport = { + + """ + class A { + @JSExport("toString") + def a(): Int = 5 + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a zero-argument method named other than 'toString' under the name 'toString' + | @JSExport("toString") + | ^ + """ + + } + + @Test + def noBadNameExportAll = { + + """ + @JSExportAll + class A { + val __f = 1 + def a_= = 2 + } + """ hasErrors + """ + |newSource1.scala:5: error: An exported name may not contain a double underscore (`__`) + | val __f = 1 + | ^ + |newSource1.scala:3: error: A method ending in _= will be exported as setter. But a_= does not have the right signature to do so (single argument, unit return type). + | @JSExportAll + | ^ + """ + + } + + @Test + def noConflictingMethodAndProperty = { + + // Basic case + """ + class A { + @JSExport("a") + def bar() = 2 + + @JSExport("a") + val foo = 1 + } + """ hasErrors + """ + |newSource1.scala:7: error: Exported method a conflicts with A.$js$exported$prop$a + | @JSExport("a") + | ^ + |newSource1.scala:4: error: Exported property a conflicts with A.$js$exported$meth$a + | @JSExport("a") + | ^ + """ + + // Inherited case + """ + class A { + @JSExport("a") + def bar() = 2 + } + + class B extends A { + @JSExport("a") + def foo_=(x: Int): Unit = () + + @JSExport("a") + val foo = 1 + } + """ hasErrors + """ + |newSource1.scala:4: error: Exported property a conflicts with A.$js$exported$meth$a + | @JSExport("a") + | ^ + """ + + } + + @Test + def noOverrideNamedExport = { + + """ + class A { + @JSExportNamed + def foo(x: Int, y: Int) = 1 + } + + class B extends A { + @JSExportNamed + override def foo(x: Int, y: Int) = 2 + } + """ hasErrors + """ + |newSource1.scala:9: error: overriding method $js$exported$meth$foo in class A of type (namedArgs: Any)Any; + | method $js$exported$meth$foo cannot override final member + | @JSExportNamed + | ^ + """ + + } + + @Test + def noConflictNamedExport = { + + // Normal method + """ + class A { + @JSExportNamed + def foo(x: Int, y: Int) = 1 + + @JSExport + def foo(x: scala.scalajs.js.Any) = 2 + } + """ fails() // No error test, Scala version dependent error messages + + // Ctors + """ + class A { + @JSExportNamed + def this(x: Int) = this() + + @JSExport + def this(x: scala.scalajs.js.Any) = this + + @JSExportNamed + def this(x: Long) = this() + } + """ fails() // No error test, Scala version dependent error messages + + } + + @Test + def noNamedExportObject = { + + """ + @JSExportNamed + object A + """ hasErrors + """ + |newSource1.scala:3: error: You may not use @JSNamedExport on an object + | @JSExportNamed + | ^ + """ + + } + + @Test + def noNamedExportVarArg = { + + """ + class A { + @JSExportNamed + def foo(a: Int*) = 1 + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not name-export a method with a *-parameter + | @JSExportNamed + | ^ + """ + + } + + @Test + def noNamedExportProperty = { + + // Getter + """ + class A { + @JSExportNamed + def a = 1 + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a getter or a setter as a named export + | @JSExportNamed + | ^ + """ + + + // Setter + """ + class A { + @JSExportNamed + def a_=(x: Int) = () + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a getter or a setter as a named export + | @JSExportNamed + | ^ + """ + + } + + @Test + def gracefulDoubleDefaultFail = { + // This used to blow up (i.e. not just fail), because PrepJSExports asked + // for the symbol of the default parameter getter of [[y]], and asserted its + // not overloaded. Since the Scala compiler only fails later on this, the + // assert got triggered and made the compiler crash + """ + class A { + @JSExport + def foo(x: String, y: String = "hello") = x + def foo(x: Int, y: String = "bar") = x + } + """ fails() + } + + @Test + def noNonLiteralExportNames = { + + """ + object A { + val a = "Hello" + final val b = "World" + } + + class B { + @JSExport(A.a) + def foo = 1 + @JSExport(A.b) + def bar = 1 + } + """ hasErrors + """ + |newSource1.scala:9: error: The argument to JSExport must be a literal string + | @JSExport(A.a) + | ^ + """ + + } + +} diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/JSInteropTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/JSInteropTest.scala new file mode 100644 index 0000000..99c274f --- /dev/null +++ b/compiler/src/test/scala/scala/scalajs/compiler/test/JSInteropTest.scala @@ -0,0 +1,350 @@ +package scala.scalajs.compiler.test + +import scala.scalajs.compiler.test.util._ + +import org.junit.Test +import org.junit.Ignore + +class JSInteropTest extends DirectTest with TestHelpers { + + override def preamble = + """import scala.scalajs.js + """ + + @Test + def noInnerClassTraitObject: Unit = { + + val objs = List("class", "trait", "object") + + for { + outer <- objs + inner <- objs + } yield { + s""" + $outer A extends js.Object { + $inner A + } + """ hasErrors + s""" + |newSource1.scala:4: error: Traits, classes and objects extending js.Any may not have inner traits, classes or objects + | $inner A + | ${" " * inner.length}^ + """ + } + + } + + @Test + def noBadSetters = { + + """ + class A extends js.Object { + def foo_=(x: Int): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:4: error: Setters that do not return Unit are not allowed in types extending js.Any + | def foo_=(x: Int): Int = js.native + | ^ + """ + + } + + @Test + def onlyJSRawTraits = { + + """ + trait A + class B extends js.Object with A + """ hasErrors + """ + |newSource1.scala:4: error: B extends A which does not extend js.Any. + | class B extends js.Object with A + | ^ + """ + + """ + trait A + class B extends js.Object with Serializable + """ hasErrors + """ + |newSource1.scala:4: error: B extends scala.Serializable which does not extend js.Any. + | class B extends js.Object with Serializable + | ^ + """ + + } + + @Test + def noAnonymousClass = { + + """ + class A { + val x = new js.Object { + def a: Int = js.native + } + } + """ hasErrors + """ + |newSource1.scala:4: error: Anonymous classes may not extend js.Any + | val x = new js.Object { + | ^ + """ + + } + + @Test + def noCaseClassObject = { + + """ + case class A(x: Int) extends js.Object + """ hasErrors + """ + |newSource1.scala:3: error: Classes and objects extending js.Any may not have a case modifier + | case class A(x: Int) extends js.Object + | ^ + """ + + """ + case object B extends js.Object + """ hasErrors + """ + |newSource1.scala:3: error: Classes and objects extending js.Any may not have a case modifier + | case object B extends js.Object + | ^ + """ + + } + + @Test + def notNested: Unit = { + + val outers = List("class", "trait") + val inners = List("trait", "class", "object") + + for { + outer <- outers + inner <- inners + } yield { + + val errTrg = if (inner == "object") "Objects" else "Classes" + + s""" + $outer A { + $inner Inner extends js.Object + } + """ hasErrors + s""" + |newSource1.scala:4: error: $errTrg extending js.Any may not be defined inside a class or trait + | $inner Inner extends js.Object + | ${" " * inner.length}^ + """ + } + + } + + @Test + def noGlobalScopeClass = { + + """ + class A extends js.GlobalScope + """ hasErrors + """ + |newSource1.scala:3: error: Only objects may extend js.GlobalScope + | class A extends js.GlobalScope + | ^ + """ + + """ + trait A extends js.GlobalScope + """ hasErrors + """ + |newSource1.scala:3: error: Only objects may extend js.GlobalScope + | trait A extends js.GlobalScope + | ^ + """ + + } + + @Test + def noLocalClass = { + + """ + object A { + def a = { + class B extends js.Object + } + } + """ hasErrors + """ + |newSource1.scala:5: error: Local classes and objects may not extend js.Any + | class B extends js.Object + | ^ + """ + + } + + @Test + def noLocalObject = { + + """ + object A { + def a = { + object B extends js.Object + } + } + """ hasErrors + """ + |newSource1.scala:5: error: Local classes and objects may not extend js.Any + | object B extends js.Object + | ^ + """ + + } + + @Test + def noExtendJSAny = { + + """ + class A extends js.Any + """ hasErrors + """ + |newSource1.scala:3: error: illegal inheritance from sealed trait Any + | class A extends js.Any + | ^ + """ + + } + + @Test + def noNativeInJSAny = { + + """ + class A extends js.Object { + @native + def value: Int = js.native + } + """ hasErrors + """ + |newSource1.scala:5: error: Methods in a js.Any may not be @native + | def value: Int = js.native + | ^ + """ + + } + + @Test + def warnJSAnyBody = { + + """ + class A extends js.Object { + def value: Int = ??? + val x: Int = ??? + } + """ hasWarns + """ + |newSource1.scala:4: warning: Members of traits, classes and objects extending js.Any may only contain members that call js.native. This will be enforced in 1.0. + | def value: Int = ??? + | ^ + |newSource1.scala:5: warning: Members of traits, classes and objects extending js.Any may only contain members that call js.native. This will be enforced in 1.0. + | val x: Int = ??? + | ^ + """ + + """ + trait A extends js.Object { + def value: Int + val x: Int + } + """ hasWarns + """ + |newSource1.scala:4: warning: Members of traits, classes and objects extending js.Any may only contain members that call js.native. This will be enforced in 1.0. + | def value: Int + | ^ + |newSource1.scala:5: warning: Members of traits, classes and objects extending js.Any may only contain members that call js.native. This will be enforced in 1.0. + | val x: Int + | ^ + """ + + } + + @Test + def noCallSecondaryCtor = { + + """ + class A(x: Int, y: Int) extends js.Object { + def this(x: Int) = this(x, 5) + def this() = this(7) + } + """ hasErrors + """ + |newSource1.scala:5: error: A secondary constructor of a class extending js.Any may only call the primary constructor + | def this() = this(7) + | ^ + """ + + } + + @Test + def noUseJsNative = { + + """ + class A { + def foo = js.native + } + """ hasErrors + """ + |newSource1.scala:4: error: js.native may only be used as stub implementation in facade types + | def foo = js.native + | ^ + """ + + } + + @Test + def warnNothingRaw = { + + """ + class A extends js.Object { + def foo = js.native + val bar = js.native + } + """ hasWarns + """ + |newSource1.scala:4: warning: The type of foo got inferred as Nothing. To suppress this warning, explicitly ascribe the type. + | def foo = js.native + | ^ + |newSource1.scala:5: warning: The type of bar got inferred as Nothing. To suppress this warning, explicitly ascribe the type. + | val bar = js.native + | ^ + """ + + } + + @Test + def noNonLiteralJSName = { + + """ + import js.annotation.JSName + + object A { + val a = "Hello" + final val b = "World" + } + + class B extends js.Object { + @JSName(A.a) + def foo: Int = js.native + @JSName(A.b) + def bar: Int = js.native + } + """ hasErrors + """ + |newSource1.scala:11: error: The argument to JSName must be a literal string + | @JSName(A.a) + | ^ + """ + + } + +} diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/OptimizationTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/OptimizationTest.scala new file mode 100644 index 0000000..7f15c7a --- /dev/null +++ b/compiler/src/test/scala/scala/scalajs/compiler/test/OptimizationTest.scala @@ -0,0 +1,109 @@ +package scala.scalajs.compiler.test + +import util._ + +import org.junit.Test + +import scala.scalajs.ir.{Trees => js, Types => jstpe} + +class OptimizationTest extends JSASTTest { + + @Test + def unwrapScalaFunWrapper: Unit = { + + // Make sure we do not wrap and unwrap right away + """ + import scala.scalajs.js + + class A { + val jsFun: js.Function = (x: Int) => x * 2 + } + """. + hasNot("runtime.AnonFunction ctor") { + case js.New(jstpe.ClassType("sjsr_AnonFunction1"), _, _) => + } + + // Make sure our wrapper matcher has the right name + """ + import scala.scalajs.js + + class A { + val scalaFun = (x: Int) => x * 2 + val jsFun: js.Function = scalaFun + } + """. + has("runtime.AnonFunction ctor") { + case js.New(jstpe.ClassType("sjsr_AnonFunction1"), _, _) => + } + + /* Make sure js.Array(...) is optimized away completely for several kinds + * of data types. + */ + """ + import scala.scalajs.js + + class VC(val x: Int) extends AnyVal + + class A { + val a = js.Array(5, 7, 9, -3) + val b = js.Array("hello", "world") + val c = js.Array('a', 'b') + val d = js.Array(Nil) + val e = js.Array(new VC(151189)) + } + """. + hasNot("any of the wrapArray methods") { + case js.Apply(_, js.Ident(name, _), _) + if name.startsWith("wrap") && name.endsWith("__scm_WrappedArray") => + } + + /* Make sure varargs are optimized to use js.WrappedArray instead of + * scm.WrappedArray, for various data types. + */ + """ + import scala.scalajs.js + + class VC(val x: Int) extends AnyVal + + class A { + val a = List(5, 7, 9, -3) + val b = List("hello", "world") + val c = List('a', 'b') + val d = List(Nil) + val e = List(new VC(151189)) + } + """. + hasNot("any of the wrapArray methods") { + case js.Apply(_, js.Ident(name, _), _) + if name.startsWith("wrap") && name.endsWith("__scm_WrappedArray") => + } + + // Make sure our wrapper matcher has the right name + """ + import scala.scalajs.js + + class A { + val a: Seq[Int] = new Array[Int](5) + } + """. + has("one of the wrapArray methods") { + case js.Apply(_, js.Ident(name, _), _) + if name.startsWith("wrap") && name.endsWith("__scm_WrappedArray") => + } + + // Verify the optimized emitted code for 'new js.Object' and 'new js.Array' + """ + import scala.scalajs.js + + class A { + val o = new js.Object + val a = new js.Array + } + """. + hasNot("any reference to the global scope") { + case js.JSEnvInfo() => + } + + } + +} diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/PositionTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/PositionTest.scala new file mode 100644 index 0000000..e25399b --- /dev/null +++ b/compiler/src/test/scala/scala/scalajs/compiler/test/PositionTest.scala @@ -0,0 +1,37 @@ +package scala.scalajs.compiler.test + +import util.JSASTTest + +import org.junit.Test +import org.junit.Assert._ + +import scala.reflect.internal.util.BatchSourceFile + +import scala.scalajs.ir.{Trees => js} + +class PositionTest extends JSASTTest { + + @Test + def virtualFilePosition = { + + val name = "<foo with illegal URI chars: %%>" + val source = new BatchSourceFile(name, + """class A { def x = 1 }""") + + var found = false + sourceAST(source) traverse { + case lit: js.IntLiteral => + found = true + assertEquals( + "Scheme of virtual file URI should be `virtualfile'", + "virtualfile", lit.pos.source.getScheme) + assertEquals( + "Scheme specific part of virtual file URI should be its path", + name, lit.pos.source.getSchemeSpecificPart) + } + + assertTrue("Should have IntLiteral tree", found) + + } + +} diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/util/DirectTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/util/DirectTest.scala new file mode 100644 index 0000000..8289129 --- /dev/null +++ b/compiler/src/test/scala/scala/scalajs/compiler/test/util/DirectTest.scala @@ -0,0 +1,70 @@ +package scala.scalajs.compiler.test.util + +import scala.tools.nsc._ +import reporters.{Reporter, ConsoleReporter} +import scala.reflect.internal.util.{ SourceFile, BatchSourceFile } + +import scala.scalajs.compiler.ScalaJSPlugin + +import scala.collection.mutable + +/** This is heavily inspired by scala's partest suite's DirectTest */ +abstract class DirectTest { + + /** these arguments are always added to the args passed to newSettings */ + def extraArgs: List[String] = Nil + + /** create settings objects for test from arg string */ + def newSettings(args: List[String]) = { + val s = new Settings + s processArguments (args, true) + s + } + + def newScalaJSCompiler(args: String*): Global = { + val settings = newSettings( + List( + "-d", testOutputPath, + "-bootclasspath", scalaLibPath, + "-classpath", scalaJSLibPath) ++ + extraArgs ++ args.toList) + + lazy val global: Global = new Global(settings, newReporter(settings)) { + override lazy val plugins = newScalaJSPlugin(global) :: Nil + } + + global + } + + def newScalaJSPlugin(global: Global): ScalaJSPlugin = + new ScalaJSPlugin(global) + + def newReporter(settings: Settings) = new ConsoleReporter(settings) + + def newSources(codes: String*) = codes.toList.zipWithIndex map { + case (src, idx) => new BatchSourceFile(s"newSource${idx + 1}.scala", src) + } + + def withRun[T](global: Global)(f: global.Run => T): T = { + global.reporter.reset() + f(new global.Run) + } + + def compileSources(global: Global)(sources: SourceFile*): Boolean = { + withRun(global)(_ compileSources sources.toList) + !global.reporter.hasErrors + } + + def compileString(global: Global)(sourceCode: String): Boolean = + compileSources(global)(newSources(sourceCode): _*) + + def compileString(sourceCode: String): Boolean = + compileString(defaultGlobal)(sourceCode) + + lazy val defaultGlobal = newScalaJSCompiler() + + def testOutputPath = sys.props("scala.scalajs.compiler.test.output") + def scalaJSLibPath = sys.props("scala.scalajs.compiler.test.scalajslib") + def scalaLibPath = sys.props("scala.scalajs.compiler.test.scalalib") + +} diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/util/JSASTTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/util/JSASTTest.scala new file mode 100644 index 0000000..d3dfd75 --- /dev/null +++ b/compiler/src/test/scala/scala/scalajs/compiler/test/util/JSASTTest.scala @@ -0,0 +1,100 @@ +package scala.scalajs.compiler.test.util + +import language.implicitConversions + +import scala.tools.nsc._ +import scala.reflect.internal.util.SourceFile + +import scala.util.control.ControlThrowable + +import org.junit.Assert._ + +import scala.scalajs.compiler.{ScalaJSPlugin, JSTreeExtractors} +import JSTreeExtractors.jse +import scala.scalajs.ir +import ir.{Trees => js} + +abstract class JSASTTest extends DirectTest { + + private var lastAST: JSAST = _ + + class JSAST(val clDefs: List[js.Tree]) { + type Pat = PartialFunction[js.Tree, Unit] + + class PFTraverser(pf: Pat) extends ir.Traversers.Traverser { + private case object Found extends ControlThrowable + + private[this] var finding = false + + def find: Boolean = { + finding = true + try { + clDefs.map(traverse) + false + } catch { + case Found => true + } + } + + def traverse(): Unit = { + finding = false + clDefs.map(traverse) + } + + override def traverse(tree: js.Tree): Unit = { + if (finding && pf.isDefinedAt(tree)) + throw Found + + if (!finding) + pf.lift(tree) + + super.traverse(tree) + } + } + + def has(trgName: String)(pf: Pat): this.type = { + val tr = new PFTraverser(pf) + assertTrue(s"AST should have $trgName", tr.find) + this + } + + def hasNot(trgName: String)(pf: Pat): this.type = { + val tr = new PFTraverser(pf) + assertFalse(s"AST should not have $trgName", tr.find) + this + } + + def traverse(pf: Pat): this.type = { + val tr = new PFTraverser(pf) + tr.traverse() + this + } + + def show: this.type = { + clDefs foreach println _ + this + } + + } + + implicit def string2ast(str: String): JSAST = stringAST(str) + + override def newScalaJSPlugin(global: Global) = new ScalaJSPlugin(global) { + override def generatedJSAST(cld: List[js.Tree]): Unit = { + lastAST = new JSAST(cld) + } + } + + def stringAST(code: String): JSAST = stringAST(defaultGlobal)(code) + def stringAST(global: Global)(code: String): JSAST = { + compileString(global)(code) + lastAST + } + + def sourceAST(source: SourceFile): JSAST = sourceAST(defaultGlobal)(source) + def sourceAST(global: Global)(source: SourceFile): JSAST = { + compileSources(global)(source) + lastAST + } + +} diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/util/TestHelpers.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/util/TestHelpers.scala new file mode 100644 index 0000000..adad89c --- /dev/null +++ b/compiler/src/test/scala/scala/scalajs/compiler/test/util/TestHelpers.scala @@ -0,0 +1,68 @@ +package scala.scalajs.compiler.test.util + +import java.io._ +import scala.tools.nsc._ + +import reporters.ConsoleReporter + +import org.junit.Assert._ + +import scala.util.matching.Regex + +trait TestHelpers extends DirectTest { + + private[this] val errBuffer = new CharArrayWriter + + override def newReporter(settings: Settings) = { + val in = new BufferedReader(new StringReader("")) + val out = new PrintWriter(errBuffer) + new ConsoleReporter(settings, in, out) + } + + /** will be prefixed to every code that is compiled. use for imports */ + def preamble = "" + + /** pimps a string to compile it and apply the specified test */ + implicit class CompileTests(val code: String) { + + def hasErrors(expected: String) = { + val reps = repResult { + assertFalse("snippet shouldn't compile", compileString(preamble + code)) + } + assertEquals("should have right errors", + expected.stripMargin.trim, reps.trim) + } + + def hasWarns(expected: String) = { + val reps = repResult { + assertTrue("snippet should compile", compileString(preamble + code)) + } + assertEquals("should have right warnings", + expected.stripMargin.trim, reps.trim) + } + + def fails() = + assertFalse("snippet shouldn't compile", compileString(preamble + code)) + + def warns() = { + val reps = repResult { + assertTrue("snippet should compile", compileString(preamble + code)) + } + assertFalse("should have warnings", reps.isEmpty) + } + + def succeeds() = + assertTrue("snippet should compile", compileString(preamble + code)) + + private def repResult(body: => Unit) = { + errBuffer.reset() + body + errBuffer.toString + } + } + + implicit class CodeWrappers(sc: StringContext) { + def expr() = new CompileTests(s"class A { ${sc.parts.mkString} }") + } + +} |