github.com/evanw/esbuild@v0.21.4/scripts/test262.js (about) 1 const vm = require('vm') 2 const fs = require('fs') 3 const path = require('path') 4 const jsYaml = require('js-yaml') 5 const isatty = require('tty').isatty(process.stdout.fd) 6 const { installForTests } = require('./esbuild') 7 8 const testDir = path.join(__dirname, '..', 'demo', 'test262', 'test') 9 const harnessDir = path.join(__dirname, '..', 'demo', 'test262', 'harness') 10 11 const progressBarLength = 64 12 const eraseProgressBar = () => { 13 previousProgressBar = null 14 let text = `\r${' '.repeat(progressBarLength)}\r` 15 if (printNewlineWhenErasing) { 16 printNewlineWhenErasing = false 17 text += '\n' 18 } 19 return text 20 } 21 let previousProgressBar = null 22 let printNewlineWhenErasing = false 23 24 const resetColor = isatty ? `\x1b[0m` : '' 25 const boldColor = isatty ? `\x1b[1m` : '' 26 const dimColor = isatty ? `\x1b[37m` : '' 27 const underlineColor = isatty ? `\x1b[4m` : '' 28 29 const redColor = isatty ? `\x1b[31m` : '' 30 const greenColor = isatty ? `\x1b[32m` : '' 31 const blueColor = isatty ? `\x1b[34m` : '' 32 33 const cyanColor = isatty ? `\x1b[36m` : '' 34 const magentaColor = isatty ? `\x1b[35m` : '' 35 const yellowColor = isatty ? `\x1b[33m` : '' 36 37 const redBgRedColor = isatty ? `\x1b[41;31m` : '' 38 const redBgWhiteColor = isatty ? `\x1b[41;97m` : '' 39 const greenBgGreenColor = isatty ? `\x1b[42;32m` : '' 40 const greenBgWhiteColor = isatty ? `\x1b[42;97m` : '' 41 const blueBgBlueColor = isatty ? `\x1b[44;34m` : '' 42 const blueBgWhiteColor = isatty ? `\x1b[44;97m` : '' 43 44 const cyanBgCyanColor = isatty ? `\x1b[46;36m` : '' 45 const cyanBgBlackColor = isatty ? `\x1b[46;30m` : '' 46 const magentaBgMagentaColor = isatty ? `\x1b[45;35m` : '' 47 const magentaBgBlackColor = isatty ? `\x1b[45;30m` : '' 48 const yellowBgYellowColor = isatty ? `\x1b[43;33m` : '' 49 const yellowBgBlackColor = isatty ? `\x1b[43;30m` : '' 50 51 const whiteBgWhiteColor = isatty ? `\x1b[107;97m` : '' 52 const whiteBgBlackColor = isatty ? `\x1b[107;30m` : '' 53 54 const skipTheseFeatures = new Set([ 55 'regexp-v-flag', 56 'regexp-match-indices', 57 'regexp-named-groups', 58 'regexp-unicode-property-escapes', 59 ]) 60 61 const skipTheseTests = new Set([ 62 // Skip these tests because esbuild deliberately always supports ESM syntax 63 // in all files. Also esbuild doesn't support the script goal at all. 64 'language/expressions/import.meta/syntax/goal-script.js', 65 'language/global-code/export.js', 66 'language/global-code/import.js', 67 68 // Skip these tests because we deliberately support top-level return (input 69 // files are treated as CommonJS and/or ESM but never as global code, and 70 // top-level return is allowed in CommonJS) 71 'language/statements/return/S12.9_A1_T1.js', 72 'language/statements/return/S12.9_A1_T10.js', 73 'language/statements/return/S12.9_A1_T2.js', 74 'language/statements/return/S12.9_A1_T3.js', 75 'language/statements/return/S12.9_A1_T4.js', 76 'language/statements/return/S12.9_A1_T5.js', 77 'language/statements/return/S12.9_A1_T6.js', 78 'language/statements/return/S12.9_A1_T7.js', 79 'language/statements/return/S12.9_A1_T8.js', 80 'language/statements/return/S12.9_A1_T9.js', 81 'language/global-code/return.js', 82 83 // Skip these tests because we deliberately support parsing top-level await 84 // in all files. Files containing top-level await are always interpreted as 85 // ESM, never as CommonJS. 86 'language/expressions/assignmenttargettype/simple-basic-identifierreference-await.js', 87 'language/expressions/await/await-BindingIdentifier-in-global.js', 88 'language/expressions/await/await-in-global.js', 89 'language/expressions/await/await-in-nested-function.js', 90 'language/expressions/await/await-in-nested-generator.js', 91 'language/expressions/class/class-name-ident-await-escaped.js', 92 'language/expressions/class/class-name-ident-await.js', 93 'language/expressions/class/static-init-await-reference.js', 94 'language/expressions/dynamic-import/2nd-param-await-ident.js', 95 'language/expressions/dynamic-import/assignment-expression/await-identifier.js', 96 'language/expressions/function/static-init-await-reference.js', 97 'language/expressions/generators/static-init-await-reference.js', 98 'language/expressions/in/private-field-rhs-await-absent.js', 99 'language/expressions/object/identifier-shorthand-await-strict-mode.js', 100 'language/expressions/object/method-definition/static-init-await-reference-accessor.js', 101 'language/expressions/object/method-definition/static-init-await-reference-generator.js', 102 'language/expressions/object/method-definition/static-init-await-reference-normal.js', 103 'language/module-code/top-level-await/new-await-script-code.js', 104 'language/reserved-words/await-script.js', 105 'language/statements/class/class-name-ident-await-escaped.js', 106 'language/statements/class/class-name-ident-await.js', 107 'language/statements/labeled/value-await-non-module-escaped.js', 108 'language/statements/labeled/value-await-non-module.js', 109 110 // Skip these tests because we don't currently validate the contents of 111 // regular expressions. We could do this but it's not necessary to parse 112 // JavaScript successfully since we parse enough of it to be able to 113 // determine where the regular expression ends (just "\" and "[]" pairs). 114 'language/literals/regexp/early-err-pattern.js', 115 'language/literals/regexp/invalid-braced-quantifier-exact.js', 116 'language/literals/regexp/invalid-braced-quantifier-lower.js', 117 'language/literals/regexp/invalid-braced-quantifier-range.js', 118 'language/literals/regexp/invalid-optional-lookbehind.js', 119 'language/literals/regexp/invalid-optional-negative-lookbehind.js', 120 'language/literals/regexp/invalid-range-lookbehind.js', 121 'language/literals/regexp/invalid-range-negative-lookbehind.js', 122 'language/literals/regexp/u-invalid-class-escape.js', 123 'language/literals/regexp/u-invalid-extended-pattern-char.js', 124 'language/literals/regexp/u-invalid-identity-escape.js', 125 'language/literals/regexp/u-invalid-legacy-octal-escape.js', 126 'language/literals/regexp/u-invalid-non-empty-class-ranges-no-dash-a.js', 127 'language/literals/regexp/u-invalid-non-empty-class-ranges-no-dash-ab.js', 128 'language/literals/regexp/u-invalid-non-empty-class-ranges-no-dash-b.js', 129 'language/literals/regexp/u-invalid-non-empty-class-ranges.js', 130 'language/literals/regexp/u-invalid-oob-decimal-escape.js', 131 'language/literals/regexp/u-invalid-optional-lookahead.js', 132 'language/literals/regexp/u-invalid-optional-lookbehind.js', 133 'language/literals/regexp/u-invalid-optional-negative-lookahead.js', 134 'language/literals/regexp/u-invalid-optional-negative-lookbehind.js', 135 'language/literals/regexp/u-invalid-range-lookahead.js', 136 'language/literals/regexp/u-invalid-range-lookbehind.js', 137 'language/literals/regexp/u-invalid-range-negative-lookahead.js', 138 'language/literals/regexp/u-invalid-range-negative-lookbehind.js', 139 'language/literals/regexp/u-unicode-esc-bounds.js', 140 'language/literals/regexp/u-unicode-esc-non-hex.js', 141 'language/literals/regexp/unicode-escape-nls-err.js', 142 ]) 143 144 const skipEvaluatingTheseIncludes = new Set([ 145 'nativeFunctionMatcher.js', // We don't preserve "toString()" on functions 146 ]) 147 148 const skipEvaluatingTheseFeatures = new Set([ 149 // Node's version of V8 doesn't implement these 150 'hashbang', 151 'legacy-regexp', 152 'regexp-duplicate-named-groups', 153 'symbols-as-weakmap-keys', 154 'tail-call-optimization', 155 156 // We don't care about API-related things 157 'ArrayBuffer', 158 'change-array-by-copy', 159 'DataView', 160 'resizable-arraybuffer', 161 'ShadowRealm', 162 'SharedArrayBuffer', 163 'String.prototype.toWellFormed', 164 'Symbol.match', 165 'Symbol.replace', 166 'Symbol.unscopables', 167 'Temporal', 168 'TypedArray', 169 ]) 170 171 const skipEvaluatingTheseTests = new Set([ 172 // This test is skipped because it crashes V8: 173 // 174 // # 175 // # Fatal error in , line 0 176 // # Check failed: i < self->length(). 177 // # 178 // # 179 // # 180 // #FailureMessage Object: 0x7fffac9a1c30 181 // 1: 0xc2abf1 [node] 182 // 2: 0x1f231d4 V8_Fatal(char const*, ...) [node] 183 // 3: 0xdac209 v8::FixedArray::Get(v8::Local<v8::Context>, int) const [node] 184 // 4: 0xb74ab7 [node] 185 // 5: 0xb76a2d [node] 186 // 6: 0xf2436f v8::internal::Isolate::RunHostImportModuleDynamicallyCallback(v8::internal::MaybeHandle<v8::internal::Script>, 187 // v8::internal::Handle<v8::internal::Object>, v8::internal::MaybeHandle<v8::internal::Object>) [node] 188 // 7: 0x137e3db v8::internal::Runtime_DynamicImportCall(int, unsigned long*, v8::internal::Isolate*) [node] 189 // 8: 0x17fd4f4 [node] 190 // 191 'language/expressions/dynamic-import/import-assertions/2nd-param-assert-enumeration.js', 192 ]) 193 194 function findFiles() { 195 function visit(dir) { 196 for (const entry of fs.readdirSync(dir)) { 197 const fullEntry = path.join(dir, entry) 198 const stats = fs.statSync(fullEntry) 199 if (stats.isDirectory()) { 200 visit(fullEntry) 201 } else if (stats.isFile() && entry.endsWith('.js') && !entry.includes('_FIXTURE')) { 202 files.push(fullEntry) 203 } 204 } 205 } 206 207 const files = [] 208 for (const entry of fs.readdirSync(testDir)) { 209 if (entry === 'staging' || entry === 'intl402' || entry === 'built-ins') continue // We're not interested in these 210 visit(path.join(testDir, entry)) 211 } 212 213 // Reverse for faster iteration times because many of the more interesting tests come last 214 return files.reverse() 215 } 216 217 async function checkTransformAPI({ esbuild, file, content, yaml }) { 218 if (yaml.flags) { 219 if (yaml.flags.includes('onlyStrict')) content = '"use strict";' + content 220 if (yaml.flags.includes('module')) content += '\nexport {}' 221 } 222 223 // Step 1: Try transforming the file normally 224 const shouldParse = !yaml.negative || yaml.negative.phase !== 'parse' 225 let result 226 try { 227 result = await esbuild.transform(content, { sourcefile: file }) 228 } catch (error) { 229 if (shouldParse) { 230 error.kind = 'Transform' 231 throw error 232 } 233 return // Stop now if this test is supposed to fail 234 } 235 if (!shouldParse) { 236 const error = new Error('Unexpected successful transform') 237 error.kind = 'Transform' 238 throw error 239 } 240 241 // Step 2: Try transforming the output again (this should always succeed) 242 let result2 243 try { 244 result2 = await esbuild.transform(result.code, { sourcefile: file }) 245 } catch (error) { 246 error.kind = 'Reparse' 247 throw error 248 } 249 250 // Step 3: The output should be the same the second time 251 if (result2.code !== result.code) { 252 const lines = result.code.split('\n') 253 const lines2 = result2.code.split('\n') 254 let i = 0 255 while (i < lines.length && i < lines2.length && lines[i] === lines2[i]) i++ 256 const error = { toString: () => `${redColor}-${lines[i]}\n${greenColor}+${lines2[i]}${resetColor}` } 257 error.kind = 'Reprint' 258 throw error 259 } 260 261 // Step 4: Try minifying the output once 262 let result4 263 try { 264 result4 = await esbuild.transform(result2.code, { sourcefile: file, minify: true }) 265 } catch (error) { 266 error.kind = 'Minify' 267 throw error 268 } 269 270 // Step 5: Try minifying the output again 271 let result5 272 try { 273 result5 = await esbuild.transform(result4.code, { sourcefile: file, minify: true }) 274 } catch (error) { 275 error.kind = 'Minify' 276 throw error 277 } 278 } 279 280 async function checkBuildAPI({ esbuild, file, content, yaml }) { 281 const plugins = [] 282 if (yaml.flags) { 283 const isOnlyStrict = yaml.flags.includes('onlyStrict') 284 const isModule = yaml.flags.includes('module') 285 if (isOnlyStrict || isModule) { 286 plugins.push({ 287 name: 'modify', 288 setup(build) { 289 build.onLoad({ filter: /./ }, args => { 290 if (args.path === file) { 291 let loaded = content 292 if (isOnlyStrict) loaded = '"use strict";' + loaded 293 if (isModule) loaded += '\nexport {}' 294 return { contents: loaded } 295 } 296 }) 297 }, 298 }) 299 } 300 } 301 302 // Step 1: Try building the file normally 303 const isModule = yaml.flags && yaml.flags.includes('module') 304 const isDynamicImport = yaml.flags && yaml.flags.includes('dynamic-import') 305 const isAsync = yaml.flags && yaml.flags.includes('async') 306 const shouldParse = !yaml.negative || yaml.negative.phase === 'runtime' 307 let result 308 try { 309 const options = { 310 entryPoints: [file], 311 write: false, 312 keepNames: true, 313 logLevel: 'silent', 314 plugins, 315 target: 'node' + process.version.slice(1), 316 logOverride: { 'direct-eval': 'warning' }, 317 } 318 if (isModule || isDynamicImport || isAsync) { 319 options.bundle = true 320 options.format = isModule ? 'esm' : 'iife' 321 options.external = [ 322 '', // Some tests use this as a dummy argument to an "import('')" that is never evaluated 323 ] 324 } 325 result = await esbuild.build(options) 326 } catch (error) { 327 if (shouldParse) { 328 error.kind = 'Build' 329 throw error 330 } 331 return // Stop now if this test is supposed to fail 332 } 333 if (!shouldParse) { 334 const error = new Error('Unexpected successful build') 335 error.kind = 'Build' 336 throw error 337 } 338 339 // Don't evaluate problematic files 340 const hasDirectEval = result.warnings.some(msg => msg.id === 'direct-eval') 341 if ( 342 hasDirectEval || 343 skipEvaluatingTheseTests.has(path.relative(testDir, file)) || 344 (yaml.includes && yaml.includes.some(include => skipEvaluatingTheseIncludes.has(include))) || 345 (yaml.features && yaml.features.some(feature => skipEvaluatingTheseFeatures.has(feature))) 346 ) { 347 return 348 } 349 350 // Step 3: Try evaluating the file using node 351 const importDir = path.dirname(file) 352 const shouldEvaluate = !yaml.negative 353 try { 354 await runCodeInHarness(yaml, content, importDir) 355 } catch (error) { 356 // Ignore tests that fail when run in node 357 if (shouldEvaluate) console.log(eraseProgressBar() + dimColor + `IGNORING ${path.relative(testDir, file)}: ${error}` + resetColor) 358 return 359 } 360 if (!shouldEvaluate) { 361 // Ignore tests that incorrectly pass when run in node 362 return 363 } 364 365 // Step 3: Try evaluating the file we generated 366 const code = result.outputFiles[0].text 367 try { 368 await runCodeInHarness(yaml, code, importDir) 369 } catch (error) { 370 if (shouldEvaluate) { 371 if (typeof error === 'string') error = new Error(error) 372 error.kind = 'Evaluate' 373 throw error 374 } 375 return // Stop now if this test is supposed to fail 376 } 377 if (!shouldEvaluate) { 378 const error = new Error('Unexpected successful evaluation') 379 error.kind = 'Evaluate' 380 throw error 381 } 382 383 // Step 4: If evaluation worked and was supposed to work, check to see 384 // if it still works after running esbuild's syntax lowering pass on it 385 for (let version = 2015; version <= 2022; version++) { 386 let result 387 try { 388 result = await esbuild.transform(code, { sourcefile: file, target: `es${version}` }) 389 } catch (error) { 390 continue // This means esbuild doesn't support lowering this code to this old a version 391 } 392 try { 393 await runCodeInHarness(yaml, code, importDir) 394 } catch (error) { 395 if (typeof error === 'string') error = new Error(error) 396 error.kind = 'Lower' 397 throw error 398 } 399 break 400 } 401 } 402 403 async function main() { 404 const startTime = Date.now() 405 406 // Get this warning out of the way so it's not in the middle of our output. 407 // Note: If this constructor is missing, you need to run node with the 408 // "--experimental-vm-modules" flag (and use a version of node that has it). 409 { 410 const temp = new vm.SourceTextModule('') 411 await temp.link(() => { throw new Error }) 412 await temp.evaluate() 413 } 414 415 console.log(`\n${dimColor}Finding tests...${resetColor}`) 416 const files = findFiles() 417 console.log(`Found ${files.length} test files`) 418 419 console.log(`\n${dimColor}Installing esbuild...${resetColor}`) 420 const esbuild = installForTests() 421 422 console.log(`\n${dimColor}Running tests...${resetColor}\n`) 423 const errorCounts = {} 424 let skippedCount = 0 425 426 await forEachInParallel(files, 32, async (file) => { 427 // Don't parse files that esbuild deliberately handles differently 428 if (skipTheseTests.has(path.relative(testDir, file))) { 429 skippedCount++ 430 return 431 } 432 433 try { 434 const content = fs.readFileSync(file, 'utf8') 435 const start = content.indexOf('/*---') 436 const end = content.indexOf('---*/') 437 if (start < 0 || end < 0) throw new Error(`Missing YAML metadata`) 438 const yaml = jsYaml.safeLoad(content.slice(start + 5, end)) 439 440 // Don't parse files that test things we don't care about 441 if (yaml.features && yaml.features.some(feature => skipTheseFeatures.has(feature))) { 442 skippedCount++ 443 return 444 } 445 446 await checkTransformAPI({ esbuild, file, content, yaml }) 447 await checkBuildAPI({ esbuild, file, content, yaml }) 448 } 449 450 catch (error) { 451 errorCounts[error.kind] = (errorCounts[error.kind] || 0) + 1 452 printError(file, error) 453 } 454 }) 455 456 const table = [] 457 table.push(['Total tests', `${files.length}`]) 458 table.push(['Tests ran', `${files.length - skippedCount}`]) 459 table.push(['Tests skipped', `${skippedCount}`]) 460 for (const kind of Object.keys(errorCounts).sort()) { 461 table.push([kind + ' errors', `${errorCounts[kind]}`]) 462 } 463 const seconds = (Date.now() - startTime) / 1000 464 const minutes = Math.floor(seconds / 60) 465 table.push(['Time taken', `${minutes ? `${minutes} min ${+(seconds - minutes * 60).toFixed(1)} sec` : `${+seconds.toFixed(1)} sec`}`]) 466 const maxLength = Math.max(...table.map(x => x[0].length)) 467 printNewlineWhenErasing = true 468 process.stdout.write(eraseProgressBar()) 469 for (const [key, value] of table) { 470 console.log(`${boldColor}${(key + ':').padEnd(maxLength + 1)}${resetColor} ${value}`) 471 } 472 } 473 474 function forEachInParallel(items, batchSize, callback) { 475 return new Promise((resolve, reject) => { 476 let inFlight = 0 477 let i = 0 478 479 function next() { 480 if (i === items.length && inFlight === 0) { 481 process.stdout.write(eraseProgressBar()) 482 return resolve() 483 } 484 485 const completed = Math.floor(progressBarLength * i / items.length) 486 if (previousProgressBar !== completed) { 487 previousProgressBar = completed 488 const progressHead = '\u2501'.repeat(Math.max(0, completed - 1)) 489 const progressBoundary = completed ? '\u252B' : '' 490 const progressTail = '\u2500'.repeat(progressBarLength - completed) 491 process.stdout.write(`\r` + greenColor + progressHead + progressBoundary + dimColor + progressTail + resetColor) 492 } 493 494 while (i < items.length && inFlight < batchSize) { 495 inFlight++ 496 callback(items[i++]).then(() => { 497 inFlight-- 498 next() 499 }, reject) 500 } 501 } 502 503 next() 504 }) 505 } 506 507 const harnessFiles = new Map 508 let defaultHarness = '' 509 510 for (const entry of fs.readdirSync(harnessDir)) { 511 if (entry.startsWith('.') || !entry.endsWith('.js')) { 512 continue 513 } 514 const file = path.join(harnessDir, entry) 515 const content = fs.readFileSync(file, 'utf8') 516 if (entry === 'assert.js' || entry === 'sta.js') { 517 defaultHarness += content 518 continue 519 } 520 harnessFiles.set(entry, content) 521 } 522 523 function createHarnessForTest(yaml) { 524 let harness = defaultHarness 525 526 if (yaml.includes) { 527 for (const include of yaml.includes) { 528 const content = harnessFiles.get(include) 529 if (!content) throw new Error(`Included file is missing: ${include}`) 530 harness += content 531 } 532 } 533 534 return harness 535 } 536 537 async function runCodeInHarness(yaml, code, importDir) { 538 const context = {} 539 const isAsync = yaml.flags && yaml.flags.includes('async') 540 const isModule = yaml.flags && yaml.flags.includes('module') 541 const isRaw = yaml.flags && yaml.flags.includes('raw') 542 543 // See: https://github.com/nodejs/node/issues/36351 544 const unique = () => '//' + Math.random() 545 546 const runCode = async () => { 547 const moduleCache = new Map 548 const dynamicImportCache = new Map 549 550 const findModule = (modulePath) => { 551 let module = moduleCache.get(modulePath) 552 if (!module) { 553 const code = fs.readFileSync(modulePath, 'utf8') 554 if (modulePath.endsWith('json')) { 555 const evaluate = function () { 556 this.setExport('default', vm.runInContext('JSON.parse', context)(code)) 557 } 558 module = new vm.SyntheticModule(['default'], evaluate, { context }) 559 } else { 560 module = new vm.SourceTextModule(code + unique(), { context, importModuleDynamically }) 561 } 562 moduleCache.set(modulePath, module) 563 } 564 return module 565 } 566 567 const linker = (specifier, referencingModule) => { 568 return findModule(path.join(importDir, specifier)) 569 } 570 571 const importModuleDynamically = (specifier, script) => { 572 const where = path.join(importDir, specifier) 573 let promise = dynamicImportCache.get(where) 574 if (!promise) { 575 const module = findModule(where, context) 576 if (module.status === 'unlinked') { 577 promise = module.link(linker) 578 .then(() => module.evaluate()) 579 .then(() => module) 580 } else { 581 promise = Promise.resolve(module) 582 } 583 dynamicImportCache.set(where, promise) 584 } 585 return promise 586 } 587 588 vm.createContext(context) 589 if (!isRaw) vm.runInContext(createHarnessForTest(yaml), context) 590 591 if (isModule) { 592 const module = new vm.SourceTextModule(code + unique(), { context, importModuleDynamically }) 593 await module.link(linker) 594 await module.evaluate() 595 } else { 596 const script = new vm.Script(code, { importModuleDynamically }) 597 script.runInContext(context) 598 } 599 } 600 601 if (isAsync) { 602 await new Promise((resolve, reject) => { 603 context.$DONE = err => err ? reject(err) : resolve() 604 runCode(code, context).catch(reject) 605 }) 606 } else { 607 await runCode(code, context) 608 } 609 } 610 611 function printError(file, error) { 612 let detail 613 614 if (error.errors) { 615 const { text, location } = error.errors[0] 616 if (location) { 617 const { file, line, column, lineText, length } = location 618 detail = ' ' + dimColor + path.basename(file) + ':' + line + ':' + column + ': ' + resetColor + text + '\n' + 619 ' ' + dimColor + lineText.slice(0, column) + greenColor + lineText.slice(column, column + length) + dimColor + lineText.slice(column + length) + resetColor + '\n' + 620 ' ' + greenColor + ' '.repeat(column) + (length > 1 ? '~'.repeat(length) : '^') + resetColor 621 } else { 622 detail = dimColor + ('\n' + text).split('\n').join('\n ').slice(1) + resetColor 623 } 624 } else { 625 detail = dimColor + ('\n' + error).split('\n').join('\n ').slice(1) + resetColor 626 } 627 628 const prettyPath = path.relative(testDir, file) 629 printNewlineWhenErasing = true 630 console.log(eraseProgressBar() + tagMap[error.kind] + ' ' + prettyPath + '\n' + detail) 631 printNewlineWhenErasing = true 632 } 633 634 const tagMap = { 635 Transform: redBgRedColor + `[` + redBgWhiteColor + `TRANSFORM ERROR` + redBgRedColor + `]` + resetColor, 636 Build: magentaBgMagentaColor + `[` + magentaBgBlackColor + `BUILD ERROR` + magentaBgMagentaColor + `]` + resetColor, 637 Reparse: yellowBgYellowColor + `[` + yellowBgBlackColor + `REPARSE ERROR` + yellowBgYellowColor + `]` + resetColor, 638 Reprint: cyanBgCyanColor + `[` + cyanBgBlackColor + `REPRINT ERROR` + cyanBgCyanColor + `]` + resetColor, 639 Minify: blueBgBlueColor + `[` + blueBgWhiteColor + `MINIFY ERROR` + blueBgBlueColor + `]` + resetColor, 640 Evaluate: greenBgGreenColor + `[` + greenBgWhiteColor + `EVALUATE ERROR` + greenBgGreenColor + `]` + resetColor, 641 Lower: whiteBgWhiteColor + `[` + whiteBgBlackColor + `LOWER ERROR` + whiteBgWhiteColor + `]` + resetColor, 642 } 643 644 process.on('unhandledRejection', () => { 645 // Don't exit when a test does this 646 }) 647 648 main().catch(e => setTimeout(() => { 649 throw e 650 }))