summaryrefslogtreecommitdiff
path: root/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala
diff options
context:
space:
mode:
Diffstat (limited to 'examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala')
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala244
1 files changed, 244 insertions, 0 deletions
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala
new file mode 100644
index 0000000..3621050
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala
@@ -0,0 +1,244 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.tools.nsc._
+
+import scala.collection.mutable
+
+/** Additions to Global meaningful for the JavaScript backend
+ *
+ * @author Sébastien Doeraene
+ */
+trait JSGlobalAddons extends JSDefinitions
+ with Compat210Component {
+ val global: Global
+
+ import global._
+ import jsDefinitions._
+ import definitions._
+
+ /** JavaScript primitives, used in jscode */
+ object jsPrimitives extends JSPrimitives {
+ val global: JSGlobalAddons.this.global.type = JSGlobalAddons.this.global
+ val jsAddons: ThisJSGlobalAddons =
+ JSGlobalAddons.this.asInstanceOf[ThisJSGlobalAddons]
+ }
+
+ /** global javascript interop related helpers */
+ object jsInterop {
+ import scala.reflect.NameTransformer
+ import scala.reflect.internal.Flags
+
+ private val exportPrefix = "$js$exported$"
+ private val methodExportPrefix = exportPrefix + "meth$"
+ private val propExportPrefix = exportPrefix + "prop$"
+
+ case class ExportInfo(jsName: String, pos: Position, isNamed: Boolean)
+
+ /** retrieves the names a sym should be exported to from its annotations
+ *
+ * Note that for accessor symbols, the annotations of the accessed symbol
+ * are used, rather than the annotations of the accessor itself.
+ */
+ def exportsOf(sym: Symbol): List[ExportInfo] = {
+ val exports = directExportsOf(sym) ++ inheritedExportsOf(sym)
+
+ // Calculate the distinct exports for this symbol (eliminate double
+ // occurrences of (name, isNamed) pairs).
+ val buf = new mutable.ListBuffer[ExportInfo]
+ val seen = new mutable.HashSet[(String, Boolean)]
+ for (exp <- exports) {
+ if (!seen.contains((exp.jsName, exp.isNamed))) {
+ buf += exp
+ seen += ((exp.jsName, exp.isNamed))
+ }
+ }
+
+ buf.toList
+ }
+
+ private def directExportsOf(sym: Symbol): List[ExportInfo] = {
+ val trgSym = {
+ // For accessors, look on the val/var def
+ if (sym.isAccessor) sym.accessed
+ // For primary class constructors, look on the class itself
+ else if (sym.isPrimaryConstructor && !sym.owner.isModuleClass) sym.owner
+ else sym
+ }
+
+ // Annotations that are directly on the member
+ val directAnnots = for {
+ annot <- trgSym.annotations
+ if annot.symbol == JSExportAnnotation ||
+ annot.symbol == JSExportNamedAnnotation
+ } yield annot
+
+ // Annotations for this member on the whole unit
+ val unitAnnots = {
+ if (sym.isMethod && sym.isPublic &&
+ !sym.isConstructor && !sym.isSynthetic)
+ sym.owner.annotations.filter(_.symbol == JSExportAllAnnotation)
+ else
+ Nil
+ }
+
+ for {
+ annot <- directAnnots ++ unitAnnots
+ } yield {
+ // Is this a named export or a normal one?
+ val named = annot.symbol == JSExportNamedAnnotation
+
+ def explicitName = annot.stringArg(0).getOrElse {
+ reporter.error(annot.pos,
+ s"The argument to ${annot.symbol.name} must be a literal string")
+ "dummy"
+ }
+
+ val name =
+ if (annot.args.nonEmpty) explicitName
+ else if (sym.isConstructor) decodedFullName(sym.owner)
+ else if (sym.isModuleClass) decodedFullName(sym)
+ else sym.unexpandedName.decoded.stripSuffix("_=")
+
+ // Enforce that methods ending with _= are exported as setters
+ if (sym.isMethod && !sym.isConstructor &&
+ sym.name.decoded.endsWith("_=") && !isJSSetter(sym)) {
+ reporter.error(annot.pos, "A method ending in _= will be exported " +
+ s"as setter. But ${sym.name.decoded} does not have the right " +
+ "signature to do so (single argument, unit return type).")
+ }
+
+ // Enforce no __ in name
+ if (name.contains("__")) {
+ // Get position for error message
+ val pos = if (annot.stringArg(0).isDefined)
+ annot.args.head.pos
+ else trgSym.pos
+
+ reporter.error(pos,
+ "An exported name may not contain a double underscore (`__`)")
+ }
+
+ // Make sure we do not override the default export of toString
+ if (!sym.isConstructor && name == "toString" && !named &&
+ sym.name != nme.toString_ && sym.tpe.params.isEmpty &&
+ !isJSGetter(sym)) {
+ reporter.error(annot.pos, "You may not export a zero-argument " +
+ "method named other than 'toString' under the name 'toString'")
+ }
+
+ if (named && isJSProperty(sym)) {
+ reporter.error(annot.pos,
+ "You may not export a getter or a setter as a named export")
+ }
+
+ ExportInfo(name, annot.pos, named)
+ }
+ }
+
+ private def inheritedExportsOf(sym: Symbol): List[ExportInfo] = {
+ // The symbol from which we (potentially) inherit exports. It also
+ // gives the exports their name
+ val trgSym = {
+ if (sym.isModuleClass)
+ sym
+ else if (sym.isConstructor && sym.isPublic &&
+ sym.owner.isConcreteClass && !sym.owner.isModuleClass)
+ sym.owner
+ else NoSymbol
+ }
+
+ if (trgSym == NoSymbol) {
+ Nil
+ } else {
+ val trgAnnot =
+ if (sym.isModuleClass) JSExportDescendentObjectsAnnotation
+ else JSExportDescendentClassesAnnotation
+
+ val forcingSym =
+ trgSym.ancestors.find(_.annotations.exists(_.symbol == trgAnnot))
+
+ val name = decodedFullName(trgSym)
+
+ forcingSym.map { fs =>
+ // Enfore no __ in name
+ if (name.contains("__")) {
+ // Get all annotation positions for error message
+ reporter.error(sym.pos,
+ s"""${trgSym.name} may not have a double underscore (`__`) in its fully qualified
+ |name, since it is forced to be exported by a @${trgAnnot.name} on ${fs}""".stripMargin)
+ }
+
+ ExportInfo(name, sym.pos, false)
+ }.toList
+ }
+ }
+
+ /** Just like sym.fullName, but does not encode components */
+ private def decodedFullName(sym: Symbol): String = {
+ if (sym.isRoot || sym.isRootPackage || sym == NoSymbol) sym.name.decoded
+ else if (sym.owner.isEffectiveRoot) sym.name.decoded
+ else decodedFullName(sym.effectiveOwner.enclClass) + '.' + sym.name.decoded
+ }
+
+ /** creates a name for an export specification */
+ def scalaExportName(jsName: String, isProp: Boolean): TermName = {
+ val pref = if (isProp) propExportPrefix else methodExportPrefix
+ val encname = NameTransformer.encode(jsName)
+ newTermName(pref + encname)
+ }
+
+ /** checks if the given symbol is a JSExport */
+ def isExport(sym: Symbol): Boolean =
+ sym.unexpandedName.startsWith(exportPrefix) &&
+ !sym.hasFlag(Flags.DEFAULTPARAM)
+
+ /** retrieves the originally assigned jsName of this export and whether it
+ * is a property
+ */
+ def jsExportInfo(name: Name): (String, Boolean) = {
+ def dropPrefix(prefix: String) ={
+ if (name.startsWith(prefix)) {
+ // We can't decode right away due to $ separators
+ val enc = name.encoded.substring(prefix.length)
+ Some(NameTransformer.decode(enc))
+ } else None
+ }
+
+ dropPrefix(methodExportPrefix).map((_,false)) orElse
+ dropPrefix(propExportPrefix).map((_,true)) getOrElse
+ sys.error("non-exported name passed to jsInfoSpec")
+ }
+
+ def isJSProperty(sym: Symbol): Boolean = isJSGetter(sym) || isJSSetter(sym)
+
+ /** has this symbol to be translated into a JS getter (both directions)? */
+ def isJSGetter(sym: Symbol): Boolean = {
+ sym.tpe.params.isEmpty && enteringPhase(currentRun.uncurryPhase) {
+ sym.tpe.isInstanceOf[NullaryMethodType]
+ }
+ }
+
+ /** has this symbol to be translated into a JS setter (both directions)? */
+ def isJSSetter(sym: Symbol) = {
+ sym.unexpandedName.decoded.endsWith("_=") &&
+ sym.tpe.resultType.typeSymbol == UnitClass &&
+ enteringPhase(currentRun.uncurryPhase) {
+ sym.tpe.paramss match {
+ case List(List(arg)) => !isScalaRepeatedParamType(arg.tpe)
+ case _ => false
+ }
+ }
+ }
+
+ /** has this symbol to be translated into a JS bracket access (JS to Scala) */
+ def isJSBracketAccess(sym: Symbol) =
+ sym.hasAnnotation(JSBracketAccessAnnotation)
+
+ }
+
+}