<commit_before>/* Scala.js compiler
 * Copyright 2013 LAMP/EPFL
 * @author <NAME>
 */

package scala.scalajs.compiler

import scala.tools.nsc
import nsc._

import scala.collection.immutable.ListMap
import scala.collection.mutable

/** Prepares classes extending js.Any for JavaScript interop
 *
 * This phase does:
 * - Sanity checks for js.Any hierarchy
 * - Annotate subclasses of js.Any to be treated specially
 * - Rewrite calls to scala.Enumeration.Value (include name string)
 * - Create JSExport methods: Dummy methods that are propagated
 *   through the whole compiler chain to mark exports. This allows
 *   exports to have the same semantics than methods.
 *
 * @author <NAME>
 */
abstract class PrepJSInterop extends plugins.PluginComponent
                                with PrepJSExports
                                with transform.Transform {
  val jsAddons: JSGlobalAddons {
    val global: PrepJSInterop.this.global.type
  }

  val scalaJSOpts: ScalaJSOptions

  import global._
  import jsAddons._
  import definitions._
  import rootMirror._
  import jsDefinitions._

  val phaseName = "jsinterop"

  override def newPhase(p: nsc.Phase) = new JSInteropPhase(p)
  class JSInteropPhase(prev: nsc.Phase) extends Phase(prev) {
    override def name = phaseName
    override def description = "Prepare ASTs for JavaScript interop"
  }

  override protected def newTransformer(unit: CompilationUnit) =
    new JSInteropTransformer(unit)

  class JSInteropTransformer(unit: CompilationUnit) extends Transformer {

    // Force evaluation of JSDynamicLiteral: Strangely, we are unable to find
    // nested objects in the JSCode phase (probably after flatten).
    // Therefore we force the symbol of js.Dynamic.literal here in order to
    // have access to it in JSCode.
    JSDynamicLiteral

    var inJSAnyMod = false
    var inJSAnyCls = false
    var inScalaCls = false
    /** are we inside a subclass of scala.Enumeration */
    var inScalaEnum = false
    /** are we inside the implementation of scala.Enumeration? */
    var inEnumImpl = false

    def jsAnyClassOnly = !inJSAnyCls && allowJSAny
    def allowImplDef   = !inJSAnyCls && !inJSAnyMod
    def allowJSAny     = !inScalaCls
    def inJSAny        = inJSAnyMod || inJSAnyCls

    /** DefDefs in class templates that export methods to JavaScript */
    val exporters = mutable.Map.empty[Symbol, mutable.ListBuffer[Tree]]

    override def transform(tree: Tree): Tree = postTransform { tree match {
      // Catch special case of ClassDef in ModuleDef
      case cldef: ClassDef if jsAnyClassOnly && isJSAny(cldef) =>
        transformJSAny(cldef)

      // Catch forbidden implDefs
      case idef: ImplDef if !allowImplDef =>
        unit.error(idef.pos, "Traits, classes and objects extending js.Any " +
            "may not have inner traits, classes or objects")
        super.transform(tree)

      // Handle js.Anys
      case idef: ImplDef if isJSAny(idef) =>
        transformJSAny(idef)

      // Catch the definition of scala.Enumeration itself
      case cldef: ClassDef if cldef.symbol == ScalaEnumClass =>
        enterEnumImpl { super.transform(cldef) }

      // Catch Scala Enumerations to transform calls to scala.Enumeration.Value
      case cldef: ClassDef if isScalaEnum(cldef) =>
        enterScalaCls {
          enterScalaEnum {
            super.transform(cldef)
          }
        }
      case idef: ImplDef if isScalaEnum(idef) =>
        enterScalaEnum { super.transform(idef) }

      // Catch (Scala) ClassDefs to forbid js.Anys
      case cldef: ClassDef =>
        enterScalaCls { super.transform(cldef) }

      // Catch DefDefs in JSAny to forbid setters with non-unit return type
      case ddef: DefDef if inJSAny && isNonJSScalaSetter(ddef.symbol) =>
        unit.error(tree.pos, "Setters that do not return Unit are " +
            "not allowed in types extending js.Any")
        super.transform(ddef)

      // Catch ValDefs in enumerations with simple calls to Value
      case ValDef(mods, name, tpt, ScalaEnumValNoName(optPar)) if inScalaEnum =>
        val nrhs = ScalaEnumValName(tree.symbol.owner, tree.symbol, optPar)
        treeCopy.ValDef(tree, mods, name, transform(tpt), nrhs)

      // Catch Select on Enumeration.Value we couldn't transform but need to
      // we ignore the implementation of scala.Enumeration itself
      case ScalaEnumValNoName(_) if !inEnumImpl =>
        unit.warning(tree.pos,
                     """Couldn't transform call to Enumeration.Value.
                       |The resulting program is unlikely to function properly as this
                       |operation requires reflection.""".stripMargin)
        super.transform(tree)

      // Catch calls to Predef.classOf[T]. These should NEVER reach this phase
      // but unfortunately do. In normal cases, the typer phase replaces these
      // calls by a literal constant of the given type. However, when we compile
      // the scala library itself and Predef.scala is in the sources, this does
      // not happen.
      //
      // The trees reach this phase under the form:
      //
      //   scala.this.Predef.classOf[T]
      //
      // If we encounter such a tree, depending on the plugin options, we fail
      // here or silently fix those calls.
      case TypeApply(
          classOfTree @ Select(Select(This(scala_?), predef_?), classOf_?),
          List(tpeArg))
        if (scala_?.decoded   == "scala"  &&
            predef_?.decoded  == "Predef" &&
            classOf_?.decoded == "classOf") =>

        if (scalaJSOpts.fixClassOf) {
          // Replace call by literal constant containing type
          if (typer.checkClassType(tpeArg)) {
            typer.typed { Literal(Constant(tpeArg.tpe.dealias.widen)) }
          } else {
            unit.error(tpeArg.pos, s"Type ${tpeArg} is not a class type")
            EmptyTree
          }
        } else {
          unit.error(classOfTree.pos,
              """This classOf resulted in an unresolved classOf in the jscode
                |phase. This is most likely a bug in the Scala compiler. ScalaJS
                |is probably able to work around this bug. Enable the workaround
                |by passing the fixClassOf option to the plugin.""".stripMargin)
          EmptyTree
        }

      case ddef: DefDef =>
        // Generate exporters for this ddef if required
        exporters.getOrElseUpdate(ddef.symbol.owner,
            mutable.ListBuffer.empty) ++= genExportMember(ddef)

        super.transform(tree)

      case _ => super.transform(tree)
    } }

    private def postTransform(tree: Tree) = tree match {
      case Template(parents, self, body) =>
        val clsSym = tree.symbol.owner
        val exports = exporters.get(clsSym).toIterable.flatten

        // Add exports to the template
        treeCopy.Template(tree, parents, self, body ++ exports)
      case _ => tree
    }

    /**
     * Performs checks and rewrites specific to classes / objects extending
     * js.Any
     */
    private def transformJSAny(implDef: ImplDef) = {
      val sym = implDef.symbol

      lazy val badParent = sym.info.parents.find(t => !(t <:< JSAnyClass.tpe))
      def inScalaJSJSPackage = sym.enclosingPackage == ScalaJSJSPackage

      implDef match {
        // Check that we do not extends a trait that does not extends js.Any
        case _ if !inScalaJSJSPackage && !badParent.isEmpty &&<commit_msg>Fix #361: Forbid exporting local classes and objects<commit_after>/* Scala.js compiler
 * Copyright 2013 LAMP/EPFL
 * @author <NAME>
 */

package scala.scalajs.compiler

import scala.tools.nsc
import nsc._

import scala.collection.immutable.ListMap
import scala.collection.mutable

/** Prepares classes extending js.Any for JavaScript interop
 *
 * This phase does:
 * - Sanity checks for js.Any hierarchy
 * - Annotate subclasses of js.Any to be treated specially
 * - Rewrite calls to scala.Enumeration.Value (include name string)
 * - Create JSExport methods: Dummy methods that are propagated
 *   through the whole compiler chain to mark exports. This allows
 *   exports to have the same semantics than methods.
 *
 * @author <NAME>
 */
abstract class PrepJSInterop extends plugins.PluginComponent
                                with PrepJSExports
                                with transform.Transform {
  val jsAddons: JSGlobalAddons {
    val global: PrepJSInterop.this.global.type
  }

  val scalaJSOpts: ScalaJSOptions

  import global._
  import jsAddons._
  import definitions._
  import rootMirror._
  import jsDefinitions._

  val phaseName = "jsinterop"

  override def newPhase(p: nsc.Phase) = new JSInteropPhase(p)
  class JSInteropPhase(prev: nsc.Phase) extends Phase(prev) {
    override def name = phaseName
    override def description = "Prepare ASTs for JavaScript interop"
  }

  override protected def newTransformer(unit: CompilationUnit) =
    new JSInteropTransformer(unit)

  class JSInteropTransformer(unit: CompilationUnit) extends Transformer {

    // Force evaluation of JSDynamicLiteral: Strangely, we are unable to find
    // nested objects in the JSCode phase (probably after flatten).
    // Therefore we force the symbol of js.Dynamic.literal here in order to
    // have access to it in JSCode.
    JSDynamicLiteral

    var inJSAnyMod = false
    var inJSAnyCls = false
    var inScalaCls = false
    /** are we inside a subclass of scala.Enumeration */
    var inScalaEnum = false
    /** are we inside the implementation of scala.Enumeration? */
    var inEnumImpl = false

    def jsAnyClassOnly = !inJSAnyCls && allowJSAny
    def allowImplDef   = !inJSAnyCls && !inJSAnyMod
    def allowJSAny     = !inScalaCls
    def inJSAny        = inJSAnyMod || inJSAnyCls

    /** DefDefs in class templates that export methods to JavaScript */
    val exporters = mutable.Map.empty[Symbol, mutable.ListBuffer[Tree]]

    override def transform(tree: Tree): Tree = postTransform { tree match {
      // Catch special case of ClassDef in ModuleDef
      case cldef: ClassDef if jsAnyClassOnly && isJSAny(cldef) =>
        transformJSAny(cldef)

      // Catch forbidden implDefs
      case idef: ImplDef if !allowImplDef =>
        unit.error(idef.pos, "Traits, classes and objects extending js.Any " +
            "may not have inner traits, classes or objects")
        super.transform(tree)

      // Handle js.Anys
      case idef: ImplDef if isJSAny(idef) =>
        transformJSAny(idef)

      // Catch the definition of scala.Enumeration itself
      case cldef: ClassDef if cldef.symbol == ScalaEnumClass =>
        enterEnumImpl { super.transform(cldef) }

      // Catch Scala Enumerations to transform calls to scala.Enumeration.Value
      case cldef: ClassDef if isScalaEnum(cldef) =>
        enterScalaCls {
          enterScalaEnum {
            super.transform(cldef)
          }
        }
      case idef: ImplDef if isScalaEnum(idef) =>
        enterScalaEnum { super.transform(idef) }

      // Catch (Scala) ClassDefs to forbid js.Anys
      case cldef: ClassDef =>
        enterScalaCls { super.transform(cldef) }

      // Catch DefDefs in JSAny to forbid setters with non-unit return type
      case ddef: DefDef if inJSAny && isNonJSScalaSetter(ddef.symbol) =>
        unit.error(tree.pos, "Setters that do not return Unit are " +
            "not allowed in types extending js.Any")
        super.transform(ddef)

      // Catch ValDefs in enumerations with simple calls to Value
      case ValDef(mods, name, tpt, ScalaEnumValNoName(optPar)) if inScalaEnum =>
        val nrhs = ScalaEnumValName(tree.symbol.owner, tree.symbol, optPar)
        treeCopy.ValDef(tree, mods, name, transform(tpt), nrhs)

      // Catch Select on Enumeration.Value we couldn't transform but need to
      // we ignore the implementation of scala.Enumeration itself
      case ScalaEnumValNoName(_) if !inEnumImpl =>
        unit.warning(tree.pos,
                     """Couldn't transform call to Enumeration.Value.
                       |The resulting program is unlikely to function properly as this
                       |operation requires reflection.""".stripMargin)
        super.transform(tree)

      // Catch calls to Predef.classOf[T]. These should NEVER reach this phase
      // but unfortunately do. In normal cases, the typer phase replaces these
      // calls by a literal constant of the given type. However, when we compile
      // the scala library itself and Predef.scala is in the sources, this does
      // not happen.
      //
      // The trees reach this phase under the form:
      //
      //   scala.this.Predef.classOf[T]
      //
      // If we encounter such a tree, depending on the plugin options, we fail
      // here or silently fix those calls.
      case TypeApply(
          classOfTree @ Select(Select(This(scala_?), predef_?), classOf_?),
          List(tpeArg))
        if (scala_?.decoded   == "scala"  &&
            predef_?.decoded  == "Predef" &&
            classOf_?.decoded == "classOf") =>

        if (scalaJSOpts.fixClassOf) {
          // Replace call by literal constant containing type
          if (typer.checkClassType(tpeArg)) {
            typer.typed { Literal(Constant(tpeArg.tpe.dealias.widen)) }
          } else {
            unit.error(tpeArg.pos, s"Type ${tpeArg} is not a class type")
            EmptyTree
          }
        } else {
          unit.error(classOfTree.pos,
              """This classOf resulted in an unresolved classOf in the jscode
                |phase. This is most likely a bug in the Scala compiler. ScalaJS
                |is probably able to work around this bug. Enable the workaround
                |by passing the fixClassOf option to the plugin.""".stripMargin)
          EmptyTree
        }

      case ddef: DefDef =>
        // Generate exporters for this ddef if required
        exporters.getOrElseUpdate(ddef.symbol.owner,
            mutable.ListBuffer.empty) ++= genExportMember(ddef)

        super.transform(tree)

      case _ => super.transform(tree)
    } }

    private def postTransform(tree: Tree) = tree match {
      case Template(parents, self, body) =>
        val clsSym = tree.symbol.owner
        val exports = exporters.get(clsSym).toIterable.flatten

        // Add exports to the template
        treeCopy.Template(tree, parents, self, body ++ exports)

      case implDef: ImplDef =>
        val sym = implDef.symbol
        if (sym.isLocal) {
          for ((_, pos) <- jsInterop.exportsOf(sym)) {
            currentUnit.error(pos, "You may not export a local class or object")
          }
        }

        implDef
      case _ => tree
    }

    /**
     * Performs checks and rewrites specific to classes / objects extending
     * js.Any
     */
    private def transformJSAny(implDef: ImplDef) = {
      val sym = implDef.symbol

      lazy val badParent = sym.info.parents.find(t => !(t <:< JSAnyClass.tpe))
      def inScalaJSJSPackage = sym.enclosingPackage == ScalaJSJSPackage

      implDef match {
        // Check that we do not extends a trait that does not extends js.Any
        case _ if !inScalaJSJSPackage && !badParent.isEmpty &&
{'fraction_non_alphanumeric': 0.08211221122112211, 'fraction_numerical': 0.0007260726072607261, 'mean_word_length': 2.450466864040082, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 40, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '138606', 'n_tokens_mistral': 4486, 'n_tokens_neox': 4298, 'n_words': 1397}