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

281 lines
9.7 KiB
TypeScript

/**
* 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 "../src/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")
})
})