mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-09 22:25:02 +03:00
281 lines
9.7 KiB
TypeScript
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")
|
|
})
|
|
})
|