Ctrl K
ComunidadGuías…

packages/codemode/test/var-hoisting-test262.test.ts

Rama dev · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
 * - test/language/statements/variable/S12.2_A1.js
 * - test/language/statements/variable/S12.2_A3.js
 * - test/language/statements/variable/S12.2_A6_T1.js
 * - test/language/statements/variable/S12.2_A7.js
 * - test/language/statements/variable/S12.2_A10.js
 * - test/language/statements/variable/S12.2_A12.js
 * - test/language/block-scope/shadowing/hoisting-var-declarations-out-of-blocks.js
 * - test/language/block-scope/shadowing/catch-parameter-shadowing-var-variable.js
 * - test/language/statements/for/head-var-bound-names-in-stmt.js
 * - test/language/statements/function/scope-paramsbody-var-open.js
 * - test/language/statements/function/scope-paramsbody-var-close.js
 *
 * Copyright 2009 the Sputnik authors. All rights reserved.
 * Copyright (C) 2011, 2016 the V8 project authors. All rights reserved.
 * Test262 portions are governed by the BSD license in LICENSE.test262.
 *
 * Files that observe `var` through `eval`, `this`, `delete`, or the global object (S12.2_A2, A5, A9,
 * A11, `scope-*-none.js`, `scope-param-elem-*.js`) have no analogue here and are not ported.
 */
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "./lib/index.js"

const value = async (code: string) => {
  const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
  if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
  return result.value
}

describe("var hoisting Test262 parity", () => {
  test("test/language/statements/variable/S12.2_A1.js: use before declaration reads undefined", async () => {
    expect(
      await value(`
        __x = __x
        __y = __x ? "good fellow" : "liar"
        __z = __z === __x ? 1 : 0
        let unknown
        try { __something__undefined = __something__undefined } catch (error) { unknown = error.name }
        const before = [__y, __z, unknown]
        var __x, __y = true, __z = __y ? "smeagol" : "golum"
        return [...before, __y, __z]
      `),
    ).toEqual(["liar", 1, "ReferenceError", true, "smeagol"])
  })

  test("test/language/statements/variable/S12.2_A3.js: nested functions redeclare or assign", async () => {
    expect(
      await value(`
        var __var = "OUT"
        const inner = (function () {
          var __var = "IN"
          ;(function () { __var = "INNER_SPACE" })()
          ;(function () { var __var = "INNER_SUN" })()
          return __var
        })()
        const after = __var
        const assigned = (function () {
          __var = "IN"
          ;(function () { __var = "INNERED" })()
          ;(function () { var __var = "INNAGER" })()
          return __var
        })()
        return [inner, after, assigned, __var]
      `),
    ).toEqual(["INNER_SPACE", "OUT", "INNERED", "INNERED"])
  })

  test("test/language/statements/variable/S12.2_A6_T1.js: var inside try and catch is hoisted", async () => {
    expect(
      await value(`
        intry__var = intry__var
        incatch__var = incatch__var
        try { var intry__var } catch (e) { var incatch__var }
        return [typeof intry__var, typeof incatch__var]
      `),
    ).toEqual(["undefined", "undefined"])
  })

  test("test/language/statements/variable/S12.2_A7.js: var after break inside for is hoisted", async () => {
    expect(
      await value(`
        infor_var = infor_var
        for (;;) { break; var infor_var }
        return typeof infor_var
      `),
    ).toBe("undefined")
  })

  test("test/language/statements/variable/S12.2_A10.js: var in for head is hoisted", async () => {
    expect(
      await value(`
        __ind = __ind
        for (var __ind; ; ) { break }
        return typeof __ind
      `),
    ).toBe("undefined")
  })

  test("test/language/statements/variable/S12.2_A12.js: var in do-while body is hoisted", async () => {
    expect(
      await value(`
        x = x
        do var x; while (false)
        return typeof x
      `),
    ).toBe("undefined")
  })

  test("test/language/block-scope/shadowing/hoisting-var-declarations-out-of-blocks.js", async () => {
    expect(
      await value(`
        function fn() {
          { var x = 1; var y }
          return [x, typeof y]
        }
        return fn()
      `),
    ).toEqual([1, "undefined"])
  })

  test("test/language/block-scope/shadowing/catch-parameter-shadowing-var-variable.js", async () => {
    expect(
      await value(`
        function fn() {
          var a = 1
          let caught
          try { throw "stuff3" } catch (a) { caught = a }
          return [caught, a]
        }
        return fn()
      `),
    ).toEqual(["stuff3", 1])
  })

  test("test/language/statements/for/head-var-bound-names-in-stmt.js: redeclaring the head var in the body", async () => {
    expect(
      await value(`
        var iterCount = 0
        var first = true
        for (var x; first; first = false) {
          var x
          iterCount += 1
        }
        return iterCount
      `),
    ).toBe(1)
  })

  test("test/language/statements/function/scope-paramsbody-var-open.js: parameter defaults see the outer var", async () => {
    expect(
      await value(`
        var x = "outside"
        var probeParams, probeBody
        function f(_ = probeParams = function () { return x }) {
          var x = "inside"
          probeBody = function () { return x }
        }
        f()
        return [probeParams(), probeBody()]
      `),
    ).toEqual(["outside", "inside"])
  })

  test("test/language/statements/function/scope-paramsbody-var-close.js: body var does not leak out", async () => {
    expect(
      await value(`
        var probe
        function f(_ = null) {
          var x = "inside"
          probe = function () { return x }
        }
        f()
        var x = "outside"
        return [probe(), x]
      `),
    ).toEqual(["inside", "outside"])
  })
})

describe("var semantics beyond Test262", () => {
  test("redeclaration and block-level var assign the one function-scoped binding", async () => {
    expect(await value(`var a = 1; var a = 2; { var a = 3 } return a`)).toBe(3)
    expect(await value(`var q = 1; { let q = 2 } return q`)).toBe(1)
  })

  test("var loop counters are shared by closures, let counters are not", async () => {
    expect(
      await value(`
        const byVar = []
        for (var i = 0; i < 3; i++) byVar.push(() => i)
        const byLet = []
        for (let j = 0; j < 3; j++) byLet.push(() => j)
        return [byVar.map((f) => f()), byLet.map((f) => f())]
      `),
    ).toEqual([
      [3, 3, 3],
      [0, 1, 2],
    ])
  })

  test("for...in and for...of var heads survive the loop", async () => {
    expect(await value(`for (var k in { a: 1 }) {} for (var [p, q] of [[1, 2]]) {} return [k, p, q]`)).toEqual([
      "a",
      1,
      2,
    ])
  })

  test("var and function declarations of the same name share a binding", async () => {
    expect(await value(`var fn = 1; function fn() {} return typeof fn`)).toBe("number")
    expect(await value(`function fn() {} var fn; return typeof fn`)).toBe("function")
    expect(await value(`function h() { var fn = 1; function fn() {} return typeof fn } return h()`)).toBe("number")
  })

  test("a var named after a parameter keeps the argument until assigned", async () => {
    expect(await value(`function f(a) { var a; return a } return f(7)`)).toBe(7)
    expect(await value(`function f(a) { var a = 2; return a } return f(7)`)).toBe(2)
  })

  test("var does not hoist across function boundaries", async () => {
    expect(await value(`return [typeof b, (() => { var b = 1; return b })()]; var b`)).toEqual(["undefined", 1])
    expect(
      await value(
        `function outer() { var o = 1; function inner() { var o = 2; return o } return [inner(), o] } return outer()`,
      ),
    ).toEqual([2, 1])
  })

  test("switch cases, labels, and generators hoist var", async () => {
    expect(await value(`switch (1) { case 1: var s = 9 } label: { var lb = 1 } return [s, lb]`)).toEqual([9, 1])
    expect(await value(`function* gen() { var t = 1; yield t; var t = 2; yield t } return [...gen()]`)).toEqual([1, 2])
  })
})

describe("function declarations and expressions", () => {
  test("the last of repeated function declarations wins, and a var may share the name", async () => {
    expect(
      await value(`
        function f() { return 1 }
        const first = f()
        function f() { return 2 }
        var g = 1
        function g() { return 3 }
        return [first, f(), typeof g]
      `),
    ).toEqual([2, 2, "number"])
  })

  test("a named function expression sees its own name inside its body, read-only", async () => {
    expect(
      await value(`
        const fact = function inner(n) { return n <= 1 ? 1 : n * inner(n - 1) }
        const reassign = function inner() { inner = 5 }
        let failure
        try { reassign() } catch (error) { failure = error.constructor.name }
        return [fact(4), typeof inner, failure]
      `),
    ).toEqual([24, "undefined", "TypeError"])
  })

  test("generator functions have their own prototype", async () => {
    expect(
      await value(`
        function* g() {}
        async function* ag() {}
        function f() {}
        return [g() instanceof g, ag() instanceof ag, g.prototype === ag.prototype, typeof g.prototype, typeof f.prototype]
      `),
    ).toEqual([true, true, false, "object", "undefined"])
  })
})

describe("switch case function hoisting", () => {
  test("function declarations are visible across all cases before their statement runs", async () => {
    expect(await value(`switch (1) { case 1: return foo(); function foo() { return "hoisted" } }`)).toBe("hoisted")
    expect(await value(`switch (2) { case 1: function foo() { return "a" } break; case 2: return foo() }`)).toBe("a")
  })
})