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) | ^ """ } }