mirror of
https://github.com/zhigang1992/esbuild.git
synced 2026-01-12 22:46:54 +08:00
403 lines
10 KiB
JavaScript
403 lines
10 KiB
JavaScript
function generateTestCase(assign) {
|
|
let sideEffectCount = 0
|
|
let patternCount = 0
|
|
let limit = 10
|
|
let depth = 0
|
|
|
|
function choice(n) {
|
|
return Math.random() * n | 0
|
|
}
|
|
|
|
function patternAndValue() {
|
|
patternCount++
|
|
switch (choice(3)) {
|
|
case 0: return array()
|
|
case 1: return object()
|
|
case 2: return [assign(), choice(10), choice(10)]
|
|
}
|
|
}
|
|
|
|
function sideEffect(result) {
|
|
return `s(${sideEffectCount++}${result ? `, ${result}` : ''})`
|
|
}
|
|
|
|
function indent(open, items, close) {
|
|
let tab = ' '
|
|
items = items.map(i => `\n${tab.repeat(depth + 1)}${i}`).join(',')
|
|
return `${open}${items}\n${tab.repeat(depth)}${close}`
|
|
}
|
|
|
|
function id() {
|
|
return String.fromCharCode('a'.charCodeAt(0) + choice(3))
|
|
}
|
|
|
|
function array() {
|
|
let count = 1 + choice(2)
|
|
let pattern = []
|
|
let value = []
|
|
|
|
depth++
|
|
for (let i = 0; i < count; i++) {
|
|
if (patternCount > limit) break
|
|
let [pat, val, defVal] = patternAndValue()
|
|
switch (choice(3)) {
|
|
case 0:
|
|
pattern.push(pat)
|
|
value.push(val)
|
|
break
|
|
case 1:
|
|
pattern.push(`${pat} = ${sideEffect(defVal)}`)
|
|
value.push(val)
|
|
break
|
|
case 2:
|
|
pattern.push(`${pat} = ${sideEffect(defVal)}`)
|
|
value.push(defVal)
|
|
break
|
|
}
|
|
if (choice(10) < 8) value.push(val)
|
|
}
|
|
if (choice(2)) {
|
|
pattern.push(`...${assign()}`)
|
|
if (choice(10) < 8) value.push(choice(10))
|
|
}
|
|
depth--
|
|
|
|
return [
|
|
indent('[', pattern, ']'),
|
|
indent('[', value, ']'),
|
|
'[]',
|
|
]
|
|
}
|
|
|
|
function object() {
|
|
let count = 1 + choice(2)
|
|
let pattern = []
|
|
let value = []
|
|
let valKeys = new Set()
|
|
|
|
depth++
|
|
for (let i = 0; i < count; i++) {
|
|
if (patternCount > limit) break
|
|
let valKey = id()
|
|
if (valKeys.has(valKey)) continue
|
|
valKeys.add(valKey)
|
|
let patKey = choice() ? valKey : `[${sideEffect(`'${valKey}'`)}]`
|
|
let [pat, val, defVal] = patternAndValue()
|
|
switch (choice(3)) {
|
|
case 0:
|
|
pattern.push(`${patKey}: ${pat}`)
|
|
value.push(`${valKey}: ${val}`)
|
|
break
|
|
case 1:
|
|
pattern.push(`${patKey}: ${pat} = ${sideEffect(defVal)}`)
|
|
value.push(`${valKey}: ${val}`)
|
|
break
|
|
case 2:
|
|
pattern.push(`${patKey}: ${pat} = ${sideEffect(defVal)}`)
|
|
value.push(`${valKey}: ${defVal}`)
|
|
break
|
|
}
|
|
}
|
|
if (choice(2)) {
|
|
pattern.push(`...${assign()}`)
|
|
if (choice(10) < 8) value.push(`${id()}: ${choice(10)}`)
|
|
}
|
|
depth--
|
|
|
|
return [
|
|
indent('{', pattern, '}'),
|
|
indent('{', value, '}'),
|
|
'{}',
|
|
]
|
|
}
|
|
|
|
return choice(2) ? array() : object()
|
|
}
|
|
|
|
function evaluate(code) {
|
|
let effectTrace = []
|
|
let assignTarget = {}
|
|
let sideEffect = (id, value) => (effectTrace.push(id), value)
|
|
new Function('a', 's', code)(assignTarget, sideEffect)
|
|
return JSON.stringify({ assignTarget, effectTrace })
|
|
}
|
|
|
|
function generateTestCases(trials) {
|
|
let testCases = []
|
|
|
|
while (testCases.length < trials) {
|
|
let ids = []
|
|
let assignCount = 0
|
|
let [pattern, value] = generateTestCase(() => {
|
|
let id = `_${assignCount++}`
|
|
ids.push(id)
|
|
return id
|
|
})
|
|
try {
|
|
evaluate(`(${pattern.replace(/_/g, 'a._')} = ${value});`)
|
|
testCases.push([pattern, value, ids])
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
return testCases
|
|
}
|
|
|
|
function AssignmentOperator([pattern, value]) {
|
|
let ts = `(${pattern.replace(/_/g, 'a._')} = ${value});`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function NamespaceExport([pattern, value]) {
|
|
let ts = `namespace a { export const ${`${pattern} = ${value}`} }`
|
|
let js = `(${pattern.replace(/_/g, 'a._')} = ${value});`
|
|
return { js, ts }
|
|
}
|
|
|
|
function ConstDeclaration([pattern, value, ids]) {
|
|
let ts = `const ${pattern} = ${value};${ids.map(id => `\na.${id} = ${id};`).join('')}`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function LetDeclaration([pattern, value, ids]) {
|
|
let ts = `let ${pattern} = ${value};${ids.map(id => `\na.${id} = ${id};`).join('')}`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function VarDeclaration([pattern, value, ids]) {
|
|
let ts = `var ${pattern} = ${value};${ids.map(id => `\na.${id} = ${id};`).join('')}`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function TryCatchBinding([pattern, value, ids]) {
|
|
let ts = `try { throw ${value} } catch (${pattern}) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function FunctionStatementArguments([pattern, value, ids]) {
|
|
let ts = `function foo(${pattern}) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }\nfoo(${value});`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function FunctionExpressionArguments([pattern, value, ids]) {
|
|
let ts = `(function(${pattern}) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} })(${value});`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function ArrowFunctionArguments([pattern, value, ids]) {
|
|
let ts = `((${pattern}) => { ${ids.map(id => `a.${id} = ${id};`).join('\n')} })(${value});`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function ObjectMethodArguments([pattern, value, ids]) {
|
|
let ts = `({ foo(${pattern}) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} } }).foo(${value});`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function ClassStatementMethodArguments([pattern, value, ids]) {
|
|
let ts = `class Foo { foo(${pattern}) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} } }\nnew Foo().foo(${value});`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function ClassExpressionMethodArguments([pattern, value, ids]) {
|
|
let ts = `(new (class { foo(${pattern}) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} } })).foo(${value});`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function ForLoopConst([pattern, value, ids]) {
|
|
let ts = `var i; for (const ${pattern} = ${value}; i < 1; i++) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function ForLoopLet([pattern, value, ids]) {
|
|
let ts = `for (let ${pattern} = ${value}, i = 0; i < 1; i++) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function ForLoopVar([pattern, value, ids]) {
|
|
let ts = `for (var ${pattern} = ${value}, i = 0; i < 1; i++) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function ForLoop([pattern, value]) {
|
|
let ts = `for (${pattern.replace(/_/g, 'a._')} = ${value}; 0; ) ;`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function ForOfLoopConst([pattern, value, ids]) {
|
|
let ts = `for (const ${pattern} of [${value}]) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function ForOfLoopLet([pattern, value, ids]) {
|
|
let ts = `for (let ${pattern} of [${value}]) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function ForOfLoopVar([pattern, value, ids]) {
|
|
let ts = `for (var ${pattern} of [${value}]) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
function ForOfLoop([pattern, value]) {
|
|
let ts = `for (${pattern.replace(/_/g, 'a._')} of [${value}]) ;`
|
|
let js = ts
|
|
return { js, ts }
|
|
}
|
|
|
|
async function verify(test, transform, testCases) {
|
|
let verbose = process.argv.indexOf('--verbose') >= 0
|
|
let indent = t => t.replace(/\n/g, '\n ')
|
|
let newline = false
|
|
console.log(`${test.name} (${transform.name}):`)
|
|
|
|
await concurrentMap(testCases, 20, async (testCase) => {
|
|
let { js, ts } = test(testCase)
|
|
let expected
|
|
try {
|
|
expected = evaluate(js)
|
|
} catch (e) {
|
|
return
|
|
}
|
|
|
|
let transformed
|
|
try {
|
|
transformed = await transform(ts)
|
|
} catch (e) {
|
|
process.stdout.write('T')
|
|
newline = true
|
|
|
|
if (verbose) {
|
|
console.log('\n' + '='.repeat(80))
|
|
console.log(indent(`Original code:\n${ts}`))
|
|
console.log(indent(`Transform error:\n${e}`))
|
|
newline = false
|
|
}
|
|
return
|
|
}
|
|
|
|
let actual
|
|
try {
|
|
actual = evaluate(transformed)
|
|
} catch (e) {
|
|
actual = e + ''
|
|
}
|
|
|
|
if (actual !== expected) {
|
|
process.stdout.write(actual.indexOf('SyntaxError') >= 0 ? 'S' : 'X')
|
|
newline = true
|
|
|
|
if (verbose) {
|
|
console.log('\n' + '='.repeat(80))
|
|
console.log(indent(`Original code:\n${ts}`))
|
|
console.log(indent(`Transformed code:\n${transformed}`))
|
|
console.log(indent(`Expected output:\n${expected}`))
|
|
console.log(indent(`Actual output:\n${actual}`))
|
|
newline = false
|
|
}
|
|
} else {
|
|
process.stdout.write('-')
|
|
newline = true
|
|
}
|
|
})
|
|
|
|
if (newline) process.stdout.write('\n')
|
|
}
|
|
|
|
function concurrentMap(items, batch, callback) {
|
|
return new Promise((resolve, reject) => {
|
|
let index = 0
|
|
let pending = 0
|
|
let next = () => {
|
|
if (index === items.length && pending === 0) {
|
|
resolve()
|
|
} else if (index < items.length) {
|
|
let item = items[index++]
|
|
pending++
|
|
callback(item).then(() => {
|
|
pending--
|
|
next()
|
|
}, e => {
|
|
items.length = 0
|
|
reject(e)
|
|
})
|
|
}
|
|
}
|
|
for (let i = 0; i < batch; i++)next()
|
|
})
|
|
}
|
|
|
|
async function main() {
|
|
let es = require('./esbuild').installForTests()
|
|
let esbuild = async (x) => (await es.transform(x, { target: 'es6', loader: 'ts' })).code.trim()
|
|
|
|
console.log(`
|
|
Options:
|
|
--verbose = Print details for failures
|
|
|
|
Legend:
|
|
- = The test passed
|
|
X = The test failed
|
|
T = The transform function itself failed
|
|
S = The generated code has a syntax error`)
|
|
|
|
let tests = [
|
|
// Bindings
|
|
ConstDeclaration,
|
|
LetDeclaration,
|
|
VarDeclaration,
|
|
TryCatchBinding,
|
|
FunctionStatementArguments,
|
|
FunctionExpressionArguments,
|
|
ArrowFunctionArguments,
|
|
ObjectMethodArguments,
|
|
ClassStatementMethodArguments,
|
|
ClassExpressionMethodArguments,
|
|
ForLoopConst,
|
|
ForLoopLet,
|
|
ForLoopVar,
|
|
ForOfLoopConst,
|
|
ForOfLoopLet,
|
|
ForOfLoopVar,
|
|
|
|
// Destructuring
|
|
AssignmentOperator,
|
|
ForLoop,
|
|
ForOfLoop,
|
|
|
|
// TypeScript-specific
|
|
NamespaceExport,
|
|
]
|
|
let transforms = [
|
|
esbuild,
|
|
]
|
|
let testCases = generateTestCases(100)
|
|
|
|
for (let transform of transforms) {
|
|
console.log()
|
|
for (let test of tests) {
|
|
await verify(test, transform, testCases)
|
|
}
|
|
}
|
|
}
|
|
|
|
main().catch(e => setTimeout(() => { throw e }))
|