1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22/* A REPL library that you can include in your own code to get a runtime 23 * interface to your program. 24 * 25 * const repl = require("repl"); 26 * // start repl on stdin 27 * repl.start("prompt> "); 28 * 29 * // listen for unix socket connections and start repl on them 30 * net.createServer(function(socket) { 31 * repl.start("node via Unix socket> ", socket); 32 * }).listen("/tmp/node-repl-sock"); 33 * 34 * // listen for TCP socket connections and start repl on them 35 * net.createServer(function(socket) { 36 * repl.start("node via TCP socket> ", socket); 37 * }).listen(5001); 38 * 39 * // expose foo to repl context 40 * repl.start("node > ").context.foo = "stdin is fun"; 41 */ 42 43'use strict'; 44 45const { 46 ArrayPrototypeFilter, 47 ArrayPrototypeFindIndex, 48 ArrayPrototypeForEach, 49 ArrayPrototypeIncludes, 50 ArrayPrototypeJoin, 51 ArrayPrototypeMap, 52 ArrayPrototypePop, 53 ArrayPrototypePush, 54 ArrayPrototypePushApply, 55 ArrayPrototypeReverse, 56 ArrayPrototypeShift, 57 ArrayPrototypeSlice, 58 ArrayPrototypeSome, 59 ArrayPrototypeSort, 60 ArrayPrototypeSplice, 61 ArrayPrototypeUnshift, 62 Boolean, 63 Error, 64 FunctionPrototypeBind, 65 JSONStringify, 66 MathMaxApply, 67 NumberIsNaN, 68 NumberParseFloat, 69 ObjectAssign, 70 ObjectCreate, 71 ObjectDefineProperty, 72 ObjectGetOwnPropertyDescriptor, 73 ObjectGetOwnPropertyNames, 74 ObjectGetPrototypeOf, 75 ObjectKeys, 76 ObjectSetPrototypeOf, 77 Promise, 78 ReflectApply, 79 RegExp, 80 RegExpPrototypeExec, 81 SafePromiseRace, 82 SafeSet, 83 SafeWeakSet, 84 StringPrototypeCharAt, 85 StringPrototypeCodePointAt, 86 StringPrototypeEndsWith, 87 StringPrototypeIncludes, 88 StringPrototypeRepeat, 89 StringPrototypeSlice, 90 StringPrototypeSplit, 91 StringPrototypeStartsWith, 92 StringPrototypeTrim, 93 StringPrototypeTrimLeft, 94 StringPrototypeToLocaleLowerCase, 95 Symbol, 96 SyntaxError, 97 SyntaxErrorPrototype, 98 globalThis, 99} = primordials; 100 101const { BuiltinModule } = require('internal/bootstrap/realm'); 102const { 103 makeRequireFunction, 104 addBuiltinLibsToObject, 105} = require('internal/modules/helpers'); 106const { 107 isIdentifierStart, 108 isIdentifierChar, 109 parse: acornParse, 110} = require('internal/deps/acorn/acorn/dist/acorn'); 111const acornWalk = require('internal/deps/acorn/acorn-walk/dist/walk'); 112const { 113 decorateErrorStack, 114 isError, 115 deprecate, 116 SideEffectFreeRegExpPrototypeSymbolReplace, 117 SideEffectFreeRegExpPrototypeSymbolSplit, 118} = require('internal/util'); 119const { inspect } = require('internal/util/inspect'); 120const vm = require('vm'); 121 122const { runInThisContext, runInContext } = vm.Script.prototype; 123 124const path = require('path'); 125const fs = require('fs'); 126const { Interface } = require('readline'); 127const { 128 commonPrefix, 129} = require('internal/readline/utils'); 130const { Console } = require('console'); 131const { shouldColorize } = require('internal/util/colors'); 132const CJSModule = require('internal/modules/cjs/loader').Module; 133let _builtinLibs = ArrayPrototypeFilter( 134 CJSModule.builtinModules, 135 (e) => !StringPrototypeStartsWith(e, '_'), 136); 137const nodeSchemeBuiltinLibs = ArrayPrototypeMap( 138 _builtinLibs, (lib) => `node:${lib}`); 139ArrayPrototypeForEach( 140 BuiltinModule.getSchemeOnlyModuleNames(), 141 (lib) => ArrayPrototypePush(nodeSchemeBuiltinLibs, `node:${lib}`), 142); 143const domain = require('domain'); 144let debug = require('internal/util/debuglog').debuglog('repl', (fn) => { 145 debug = fn; 146}); 147const { 148 codes: { 149 ERR_CANNOT_WATCH_SIGINT, 150 ERR_INVALID_REPL_EVAL_CONFIG, 151 ERR_INVALID_REPL_INPUT, 152 ERR_SCRIPT_EXECUTION_INTERRUPTED, 153 }, 154 isErrorStackTraceLimitWritable, 155 overrideStackTrace, 156} = require('internal/errors'); 157const { sendInspectorCommand } = require('internal/util/inspector'); 158const { getOptionValue } = require('internal/options'); 159const { 160 validateFunction, 161 validateObject, 162} = require('internal/validators'); 163const experimentalREPLAwait = getOptionValue( 164 '--experimental-repl-await', 165); 166const pendingDeprecation = getOptionValue('--pending-deprecation'); 167const { 168 REPL_MODE_SLOPPY, 169 REPL_MODE_STRICT, 170 isRecoverableError, 171 kStandaloneREPL, 172 setupPreview, 173 setupReverseSearch, 174} = require('internal/repl/utils'); 175const { 176 constants: { 177 ALL_PROPERTIES, 178 SKIP_SYMBOLS, 179 }, 180 getOwnNonIndexProperties, 181} = internalBinding('util'); 182const { 183 startSigintWatchdog, 184 stopSigintWatchdog, 185} = internalBinding('contextify'); 186 187const history = require('internal/repl/history'); 188const { 189 extensionFormatMap, 190 legacyExtensionFormatMap, 191} = require('internal/modules/esm/formats'); 192const { 193 makeContextifyScript, 194} = require('internal/vm'); 195let nextREPLResourceNumber = 1; 196// This prevents v8 code cache from getting confused and using a different 197// cache from a resource of the same name 198function getREPLResourceName() { 199 return `REPL${nextREPLResourceNumber++}`; 200} 201 202// Lazy-loaded. 203let processTopLevelAwait; 204 205const globalBuiltins = 206 new SafeSet(vm.runInNewContext('Object.getOwnPropertyNames(globalThis)')); 207 208const parentModule = module; 209const domainSet = new SafeWeakSet(); 210 211const kBufferedCommandSymbol = Symbol('bufferedCommand'); 212const kContextId = Symbol('contextId'); 213 214let addedNewListener = false; 215 216try { 217 // Hack for require.resolve("./relative") to work properly. 218 module.filename = path.resolve('repl'); 219} catch { 220 // path.resolve('repl') fails when the current working directory has been 221 // deleted. Fall back to the directory name of the (absolute) executable 222 // path. It's not really correct but what are the alternatives? 223 const dirname = path.dirname(process.execPath); 224 module.filename = path.resolve(dirname, 'repl'); 225} 226 227// Hack for repl require to work properly with node_modules folders 228module.paths = CJSModule._nodeModulePaths(module.filename); 229 230// This is the default "writer" value, if none is passed in the REPL options, 231// and it can be overridden by custom print functions, such as `probe` or 232// `eyes.js`. 233const writer = (obj) => inspect(obj, writer.options); 234writer.options = { ...inspect.defaultOptions, showProxy: true }; 235 236// Converts static import statement to dynamic import statement 237const toDynamicImport = (codeLine) => { 238 let dynamicImportStatement = ''; 239 const ast = acornParse(codeLine, { __proto__: null, sourceType: 'module', ecmaVersion: 'latest' }); 240 acornWalk.ancestor(ast, { 241 ImportDeclaration(node) { 242 const awaitDynamicImport = `await import(${JSONStringify(node.source.value)});`; 243 if (node.specifiers.length === 0) { 244 dynamicImportStatement += awaitDynamicImport; 245 } else if (node.specifiers.length === 1 && node.specifiers[0].type === 'ImportNamespaceSpecifier') { 246 dynamicImportStatement += `const ${node.specifiers[0].local.name} = ${awaitDynamicImport}`; 247 } else { 248 const importNames = ArrayPrototypeJoin(ArrayPrototypeMap(node.specifiers, ({ local, imported }) => 249 (local.name === imported?.name ? local.name : `${imported?.name ?? 'default'}: ${local.name}`), 250 ), ', '); 251 dynamicImportStatement += `const { ${importNames} } = ${awaitDynamicImport}`; 252 } 253 }, 254 }); 255 return dynamicImportStatement; 256}; 257 258function REPLServer(prompt, 259 stream, 260 eval_, 261 useGlobal, 262 ignoreUndefined, 263 replMode) { 264 if (!(this instanceof REPLServer)) { 265 return new REPLServer(prompt, 266 stream, 267 eval_, 268 useGlobal, 269 ignoreUndefined, 270 replMode); 271 } 272 273 let options; 274 if (prompt !== null && typeof prompt === 'object') { 275 // An options object was given. 276 options = { ...prompt }; 277 stream = options.stream || options.socket; 278 eval_ = options.eval; 279 useGlobal = options.useGlobal; 280 ignoreUndefined = options.ignoreUndefined; 281 prompt = options.prompt; 282 replMode = options.replMode; 283 } else { 284 options = {}; 285 } 286 287 if (!options.input && !options.output) { 288 // Legacy API, passing a 'stream'/'socket' option. 289 if (!stream) { 290 // Use stdin and stdout as the default streams if none were given. 291 stream = process; 292 } 293 // We're given a duplex readable/writable Stream, like a `net.Socket` 294 // or a custom object with 2 streams, or the `process` object. 295 options.input = stream.stdin || stream; 296 options.output = stream.stdout || stream; 297 } 298 299 if (options.terminal === undefined) { 300 options.terminal = options.output.isTTY; 301 } 302 options.terminal = !!options.terminal; 303 304 if (options.terminal && options.useColors === undefined) { 305 // If possible, check if stdout supports colors or not. 306 options.useColors = shouldColorize(options.output) || process.env.NODE_DISABLE_COLORS === undefined; 307 } 308 309 // TODO(devsnek): Add a test case for custom eval functions. 310 const preview = options.terminal && 311 (options.preview !== undefined ? !!options.preview : !eval_); 312 313 ObjectDefineProperty(this, 'inputStream', { 314 __proto__: null, 315 get: pendingDeprecation ? 316 deprecate(() => this.input, 317 'repl.inputStream and repl.outputStream are deprecated. ' + 318 'Use repl.input and repl.output instead', 319 'DEP0141') : 320 () => this.input, 321 set: pendingDeprecation ? 322 deprecate((val) => this.input = val, 323 'repl.inputStream and repl.outputStream are deprecated. ' + 324 'Use repl.input and repl.output instead', 325 'DEP0141') : 326 (val) => this.input = val, 327 enumerable: false, 328 configurable: true, 329 }); 330 ObjectDefineProperty(this, 'outputStream', { 331 __proto__: null, 332 get: pendingDeprecation ? 333 deprecate(() => this.output, 334 'repl.inputStream and repl.outputStream are deprecated. ' + 335 'Use repl.input and repl.output instead', 336 'DEP0141') : 337 () => this.output, 338 set: pendingDeprecation ? 339 deprecate((val) => this.output = val, 340 'repl.inputStream and repl.outputStream are deprecated. ' + 341 'Use repl.input and repl.output instead', 342 'DEP0141') : 343 (val) => this.output = val, 344 enumerable: false, 345 configurable: true, 346 }); 347 348 this.allowBlockingCompletions = !!options.allowBlockingCompletions; 349 this.useColors = !!options.useColors; 350 this._domain = options.domain || domain.create(); 351 this.useGlobal = !!useGlobal; 352 this.ignoreUndefined = !!ignoreUndefined; 353 this.replMode = replMode || module.exports.REPL_MODE_SLOPPY; 354 this.underscoreAssigned = false; 355 this.last = undefined; 356 this.underscoreErrAssigned = false; 357 this.lastError = undefined; 358 this.breakEvalOnSigint = !!options.breakEvalOnSigint; 359 this.editorMode = false; 360 // Context id for use with the inspector protocol. 361 this[kContextId] = undefined; 362 363 if (this.breakEvalOnSigint && eval_) { 364 // Allowing this would not reflect user expectations. 365 // breakEvalOnSigint affects only the behavior of the default eval(). 366 throw new ERR_INVALID_REPL_EVAL_CONFIG(); 367 } 368 369 if (options[kStandaloneREPL]) { 370 // It is possible to introspect the running REPL accessing this variable 371 // from inside the REPL. This is useful for anyone working on the REPL. 372 module.exports.repl = this; 373 } else if (!addedNewListener) { 374 // Add this listener only once and use a WeakSet that contains the REPLs 375 // domains. Otherwise we'd have to add a single listener to each REPL 376 // instance and that could trigger the `MaxListenersExceededWarning`. 377 process.prependListener('newListener', (event, listener) => { 378 if (event === 'uncaughtException' && 379 process.domain && 380 listener.name !== 'domainUncaughtExceptionClear' && 381 domainSet.has(process.domain)) { 382 // Throw an error so that the event will not be added and the current 383 // domain takes over. That way the user is notified about the error 384 // and the current code evaluation is stopped, just as any other code 385 // that contains an error. 386 throw new ERR_INVALID_REPL_INPUT( 387 'Listeners for `uncaughtException` cannot be used in the REPL'); 388 } 389 }); 390 addedNewListener = true; 391 } 392 393 domainSet.add(this._domain); 394 395 const savedRegExMatches = ['', '', '', '', '', '', '', '', '', '']; 396 const sep = '\u0000\u0000\u0000'; 397 const regExMatcher = new RegExp(`^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` + 398 `${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` + 399 `${sep}(.*)$`); 400 401 eval_ = eval_ || defaultEval; 402 403 const self = this; 404 405 // Pause taking in new input, and store the keys in a buffer. 406 const pausedBuffer = []; 407 let paused = false; 408 function pause() { 409 paused = true; 410 } 411 412 function unpause() { 413 if (!paused) return; 414 paused = false; 415 let entry; 416 const tmpCompletionEnabled = self.isCompletionEnabled; 417 while ((entry = ArrayPrototypeShift(pausedBuffer)) !== undefined) { 418 const { 0: type, 1: payload, 2: isCompletionEnabled } = entry; 419 switch (type) { 420 case 'key': { 421 const { 0: d, 1: key } = payload; 422 self.isCompletionEnabled = isCompletionEnabled; 423 self._ttyWrite(d, key); 424 break; 425 } 426 case 'close': 427 self.emit('exit'); 428 break; 429 } 430 if (paused) { 431 break; 432 } 433 } 434 self.isCompletionEnabled = tmpCompletionEnabled; 435 } 436 437 function defaultEval(code, context, file, cb) { 438 let result, script, wrappedErr; 439 let err = null; 440 let wrappedCmd = false; 441 let awaitPromise = false; 442 const input = code; 443 444 // It's confusing for `{ a : 1 }` to be interpreted as a block 445 // statement rather than an object literal. So, we first try 446 // to wrap it in parentheses, so that it will be interpreted as 447 // an expression. Note that if the above condition changes, 448 // lib/internal/repl/utils.js needs to be changed to match. 449 if (RegExpPrototypeExec(/^\s*{/, code) !== null && 450 RegExpPrototypeExec(/;\s*$/, code) === null) { 451 code = `(${StringPrototypeTrim(code)})\n`; 452 wrappedCmd = true; 453 } 454 455 const hostDefinedOptionId = Symbol(`eval:${file}`); 456 let parentURL; 457 try { 458 const { pathToFileURL } = require('internal/url'); 459 // Adding `/repl` prevents dynamic imports from loading relative 460 // to the parent of `process.cwd()`. 461 parentURL = pathToFileURL(path.join(process.cwd(), 'repl')).href; 462 } catch { 463 // Continue regardless of error. 464 } 465 async function importModuleDynamically(specifier, _, importAttributes) { 466 const asyncESM = require('internal/process/esm_loader'); 467 return asyncESM.esmLoader.import(specifier, parentURL, 468 importAttributes); 469 } 470 // `experimentalREPLAwait` is set to true by default. 471 // Shall be false in case `--no-experimental-repl-await` flag is used. 472 if (experimentalREPLAwait && StringPrototypeIncludes(code, 'await')) { 473 if (processTopLevelAwait === undefined) { 474 ({ processTopLevelAwait } = require('internal/repl/await')); 475 } 476 477 try { 478 const potentialWrappedCode = processTopLevelAwait(code); 479 if (potentialWrappedCode !== null) { 480 code = potentialWrappedCode; 481 wrappedCmd = true; 482 awaitPromise = true; 483 } 484 } catch (e) { 485 let recoverableError = false; 486 if (e.name === 'SyntaxError') { 487 // Remove all "await"s and attempt running the script 488 // in order to detect if error is truly non recoverable 489 const fallbackCode = SideEffectFreeRegExpPrototypeSymbolReplace(/\bawait\b/g, code, ''); 490 try { 491 makeContextifyScript( 492 fallbackCode, // code 493 file, // filename, 494 0, // lineOffset 495 0, // columnOffset, 496 undefined, // cachedData 497 false, // produceCachedData 498 undefined, // parsingContext 499 hostDefinedOptionId, // hostDefinedOptionId 500 importModuleDynamically, // importModuleDynamically 501 ); 502 } catch (fallbackError) { 503 if (isRecoverableError(fallbackError, fallbackCode)) { 504 recoverableError = true; 505 err = new Recoverable(e); 506 } 507 } 508 } 509 if (!recoverableError) { 510 decorateErrorStack(e); 511 err = e; 512 } 513 } 514 } 515 516 // First, create the Script object to check the syntax 517 if (code === '\n') 518 return cb(null); 519 520 if (err === null) { 521 while (true) { 522 try { 523 if (self.replMode === module.exports.REPL_MODE_STRICT && 524 RegExpPrototypeExec(/^\s*$/, code) === null) { 525 // "void 0" keeps the repl from returning "use strict" as the result 526 // value for statements and declarations that don't return a value. 527 code = `'use strict'; void 0;\n${code}`; 528 } 529 script = makeContextifyScript( 530 code, // code 531 file, // filename, 532 0, // lineOffset 533 0, // columnOffset, 534 undefined, // cachedData 535 false, // produceCachedData 536 undefined, // parsingContext 537 hostDefinedOptionId, // hostDefinedOptionId 538 importModuleDynamically, // importModuleDynamically 539 ); 540 } catch (e) { 541 debug('parse error %j', code, e); 542 if (wrappedCmd) { 543 // Unwrap and try again 544 wrappedCmd = false; 545 awaitPromise = false; 546 code = input; 547 wrappedErr = e; 548 continue; 549 } 550 // Preserve original error for wrapped command 551 const error = wrappedErr || e; 552 if (isRecoverableError(error, code)) 553 err = new Recoverable(error); 554 else 555 err = error; 556 } 557 break; 558 } 559 } 560 561 // This will set the values from `savedRegExMatches` to corresponding 562 // predefined RegExp properties `RegExp.$1`, `RegExp.$2` ... `RegExp.$9` 563 RegExpPrototypeExec(regExMatcher, 564 ArrayPrototypeJoin(savedRegExMatches, sep)); 565 566 let finished = false; 567 function finishExecution(err, result) { 568 if (finished) return; 569 finished = true; 570 571 // After executing the current expression, store the values of RegExp 572 // predefined properties back in `savedRegExMatches` 573 for (let idx = 1; idx < savedRegExMatches.length; idx += 1) { 574 savedRegExMatches[idx] = RegExp[`$${idx}`]; 575 } 576 577 cb(err, result); 578 } 579 580 if (!err) { 581 // Unset raw mode during evaluation so that Ctrl+C raises a signal. 582 let previouslyInRawMode; 583 if (self.breakEvalOnSigint) { 584 // Start the SIGINT watchdog before entering raw mode so that a very 585 // quick Ctrl+C doesn't lead to aborting the process completely. 586 if (!startSigintWatchdog()) 587 throw new ERR_CANNOT_WATCH_SIGINT(); 588 previouslyInRawMode = self._setRawMode(false); 589 } 590 591 try { 592 try { 593 const scriptOptions = { 594 displayErrors: false, 595 breakOnSigint: self.breakEvalOnSigint, 596 }; 597 598 if (self.useGlobal) { 599 result = ReflectApply(runInThisContext, script, [scriptOptions]); 600 } else { 601 result = ReflectApply(runInContext, script, [context, scriptOptions]); 602 } 603 } finally { 604 if (self.breakEvalOnSigint) { 605 // Reset terminal mode to its previous value. 606 self._setRawMode(previouslyInRawMode); 607 608 // Returns true if there were pending SIGINTs *after* the script 609 // has terminated without being interrupted itself. 610 if (stopSigintWatchdog()) { 611 self.emit('SIGINT'); 612 } 613 } 614 } 615 } catch (e) { 616 err = e; 617 618 if (process.domain) { 619 debug('not recoverable, send to domain'); 620 process.domain.emit('error', err); 621 process.domain.exit(); 622 return; 623 } 624 } 625 626 if (awaitPromise && !err) { 627 let sigintListener; 628 pause(); 629 let promise = result; 630 if (self.breakEvalOnSigint) { 631 const interrupt = new Promise((resolve, reject) => { 632 sigintListener = () => { 633 const tmp = Error.stackTraceLimit; 634 if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; 635 const err = new ERR_SCRIPT_EXECUTION_INTERRUPTED(); 636 if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmp; 637 reject(err); 638 }; 639 prioritizedSigintQueue.add(sigintListener); 640 }); 641 promise = SafePromiseRace([promise, interrupt]); 642 } 643 644 (async () => { 645 try { 646 const result = (await promise)?.value; 647 finishExecution(null, result); 648 } catch (err) { 649 if (err && process.domain) { 650 debug('not recoverable, send to domain'); 651 process.domain.emit('error', err); 652 process.domain.exit(); 653 return; 654 } 655 finishExecution(err); 656 } finally { 657 // Remove prioritized SIGINT listener if it was not called. 658 prioritizedSigintQueue.delete(sigintListener); 659 unpause(); 660 } 661 })(); 662 } 663 } 664 665 if (!awaitPromise || err) { 666 finishExecution(err, result); 667 } 668 } 669 670 self.eval = self._domain.bind(eval_); 671 672 self._domain.on('error', function debugDomainError(e) { 673 debug('domain error'); 674 let errStack = ''; 675 676 if (typeof e === 'object' && e !== null) { 677 overrideStackTrace.set(e, (error, stackFrames) => { 678 let frames; 679 if (typeof stackFrames === 'object') { 680 // Search from the bottom of the call stack to 681 // find the first frame with a null function name 682 const idx = ArrayPrototypeFindIndex( 683 ArrayPrototypeReverse(stackFrames), 684 (frame) => frame.getFunctionName() === null, 685 ); 686 // If found, get rid of it and everything below it 687 frames = ArrayPrototypeSplice(stackFrames, idx + 1); 688 } else { 689 frames = stackFrames; 690 } 691 // FIXME(devsnek): this is inconsistent with the checks 692 // that the real prepareStackTrace dispatch uses in 693 // lib/internal/errors.js. 694 if (typeof Error.prepareStackTrace === 'function') { 695 return Error.prepareStackTrace(error, frames); 696 } 697 ArrayPrototypePush(frames, error); 698 return ArrayPrototypeJoin(ArrayPrototypeReverse(frames), '\n at '); 699 }); 700 decorateErrorStack(e); 701 702 if (e.domainThrown) { 703 delete e.domain; 704 delete e.domainThrown; 705 } 706 707 if (isError(e)) { 708 if (e.stack) { 709 if (e.name === 'SyntaxError') { 710 // Remove stack trace. 711 e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( 712 /^\s+at\s.*\n?/gm, 713 SideEffectFreeRegExpPrototypeSymbolReplace(/^REPL\d+:\d+\r?\n/, e.stack, ''), 714 ''); 715 const importErrorStr = 'Cannot use import statement outside a ' + 716 'module'; 717 if (StringPrototypeIncludes(e.message, importErrorStr)) { 718 e.message = 'Cannot use import statement inside the Node.js ' + 719 'REPL, alternatively use dynamic import: ' + toDynamicImport(self.lines.at(-1)); 720 e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( 721 /SyntaxError:.*\n/, 722 e.stack, 723 `SyntaxError: ${e.message}\n`); 724 } 725 } else if (self.replMode === module.exports.REPL_MODE_STRICT) { 726 e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( 727 /(\s+at\s+REPL\d+:)(\d+)/, 728 e.stack, 729 (_, pre, line) => pre + (line - 1), 730 ); 731 } 732 } 733 errStack = self.writer(e); 734 735 // Remove one line error braces to keep the old style in place. 736 if (errStack[0] === '[' && errStack[errStack.length - 1] === ']') { 737 errStack = StringPrototypeSlice(errStack, 1, -1); 738 } 739 } 740 } 741 742 if (!self.underscoreErrAssigned) { 743 self.lastError = e; 744 } 745 746 if (options[kStandaloneREPL] && 747 process.listenerCount('uncaughtException') !== 0) { 748 process.nextTick(() => { 749 process.emit('uncaughtException', e); 750 self.clearBufferedCommand(); 751 self.lines.level = []; 752 self.displayPrompt(); 753 }); 754 } else { 755 if (errStack === '') { 756 errStack = self.writer(e); 757 } 758 const lines = SideEffectFreeRegExpPrototypeSymbolSplit(/(?<=\n)/, errStack); 759 let matched = false; 760 761 errStack = ''; 762 ArrayPrototypeForEach(lines, (line) => { 763 if (!matched && 764 RegExpPrototypeExec(/^\[?([A-Z][a-z0-9_]*)*Error/, line) !== null) { 765 errStack += writer.options.breakLength >= line.length ? 766 `Uncaught ${line}` : 767 `Uncaught:\n${line}`; 768 matched = true; 769 } else { 770 errStack += line; 771 } 772 }); 773 if (!matched) { 774 const ln = lines.length === 1 ? ' ' : ':\n'; 775 errStack = `Uncaught${ln}${errStack}`; 776 } 777 // Normalize line endings. 778 errStack += StringPrototypeEndsWith(errStack, '\n') ? '' : '\n'; 779 self.output.write(errStack); 780 self.clearBufferedCommand(); 781 self.lines.level = []; 782 self.displayPrompt(); 783 } 784 }); 785 786 self.clearBufferedCommand(); 787 788 function completer(text, cb) { 789 ReflectApply(complete, self, 790 [text, self.editorMode ? self.completeOnEditorMode(cb) : cb]); 791 } 792 793 ReflectApply(Interface, this, [{ 794 input: options.input, 795 output: options.output, 796 completer: options.completer || completer, 797 terminal: options.terminal, 798 historySize: options.historySize, 799 prompt, 800 }]); 801 802 self.resetContext(); 803 804 this.commands = ObjectCreate(null); 805 defineDefaultCommands(this); 806 807 // Figure out which "writer" function to use 808 self.writer = options.writer || module.exports.writer; 809 810 if (self.writer === writer) { 811 // Conditionally turn on ANSI coloring. 812 writer.options.colors = self.useColors; 813 814 if (options[kStandaloneREPL]) { 815 ObjectDefineProperty(inspect, 'replDefaults', { 816 __proto__: null, 817 get() { 818 return writer.options; 819 }, 820 set(options) { 821 validateObject(options, 'options'); 822 return ObjectAssign(writer.options, options); 823 }, 824 enumerable: true, 825 configurable: true, 826 }); 827 } 828 } 829 830 function _parseREPLKeyword(keyword, rest) { 831 const cmd = this.commands[keyword]; 832 if (cmd) { 833 ReflectApply(cmd.action, this, [rest]); 834 return true; 835 } 836 return false; 837 } 838 839 self.on('close', function emitExit() { 840 if (paused) { 841 ArrayPrototypePush(pausedBuffer, ['close']); 842 return; 843 } 844 self.emit('exit'); 845 }); 846 847 let sawSIGINT = false; 848 let sawCtrlD = false; 849 const prioritizedSigintQueue = new SafeSet(); 850 self.on('SIGINT', function onSigInt() { 851 if (prioritizedSigintQueue.size > 0) { 852 for (const task of prioritizedSigintQueue) { 853 task(); 854 } 855 return; 856 } 857 858 const empty = self.line.length === 0; 859 self.clearLine(); 860 _turnOffEditorMode(self); 861 862 const cmd = self[kBufferedCommandSymbol]; 863 if (!(cmd && cmd.length > 0) && empty) { 864 if (sawSIGINT) { 865 self.close(); 866 sawSIGINT = false; 867 return; 868 } 869 self.output.write( 870 '(To exit, press Ctrl+C again or Ctrl+D or type .exit)\n', 871 ); 872 sawSIGINT = true; 873 } else { 874 sawSIGINT = false; 875 } 876 877 self.clearBufferedCommand(); 878 self.lines.level = []; 879 self.displayPrompt(); 880 }); 881 882 self.on('line', function onLine(cmd) { 883 debug('line %j', cmd); 884 cmd = cmd || ''; 885 sawSIGINT = false; 886 887 if (self.editorMode) { 888 self[kBufferedCommandSymbol] += cmd + '\n'; 889 890 // code alignment 891 const matches = self._sawKeyPress ? 892 RegExpPrototypeExec(/^\s+/, cmd) : null; 893 if (matches) { 894 const prefix = matches[0]; 895 self.write(prefix); 896 self.line = prefix; 897 self.cursor = prefix.length; 898 } 899 ReflectApply(_memory, self, [cmd]); 900 return; 901 } 902 903 // Check REPL keywords and empty lines against a trimmed line input. 904 const trimmedCmd = StringPrototypeTrim(cmd); 905 906 // Check to see if a REPL keyword was used. If it returns true, 907 // display next prompt and return. 908 if (trimmedCmd) { 909 if (StringPrototypeCharAt(trimmedCmd, 0) === '.' && 910 StringPrototypeCharAt(trimmedCmd, 1) !== '.' && 911 NumberIsNaN(NumberParseFloat(trimmedCmd))) { 912 const matches = RegExpPrototypeExec(/^\.([^\s]+)\s*(.*)$/, trimmedCmd); 913 const keyword = matches && matches[1]; 914 const rest = matches && matches[2]; 915 if (ReflectApply(_parseREPLKeyword, self, [keyword, rest]) === true) { 916 return; 917 } 918 if (!self[kBufferedCommandSymbol]) { 919 self.output.write('Invalid REPL keyword\n'); 920 finish(null); 921 return; 922 } 923 } 924 } 925 926 const evalCmd = self[kBufferedCommandSymbol] + cmd + '\n'; 927 928 debug('eval %j', evalCmd); 929 self.eval(evalCmd, self.context, getREPLResourceName(), finish); 930 931 function finish(e, ret) { 932 debug('finish', e, ret); 933 ReflectApply(_memory, self, [cmd]); 934 935 if (e && !self[kBufferedCommandSymbol] && 936 StringPrototypeStartsWith(StringPrototypeTrim(cmd), 'npm ')) { 937 self.output.write('npm should be run outside of the ' + 938 'Node.js REPL, in your normal shell.\n' + 939 '(Press Ctrl+D to exit.)\n'); 940 self.displayPrompt(); 941 return; 942 } 943 944 // If error was SyntaxError and not JSON.parse error 945 if (e) { 946 if (e instanceof Recoverable && !sawCtrlD) { 947 // Start buffering data like that: 948 // { 949 // ... x: 1 950 // ... } 951 self[kBufferedCommandSymbol] += cmd + '\n'; 952 self.displayPrompt(); 953 return; 954 } 955 self._domain.emit('error', e.err || e); 956 } 957 958 // Clear buffer if no SyntaxErrors 959 self.clearBufferedCommand(); 960 sawCtrlD = false; 961 962 // If we got any output - print it (if no error) 963 if (!e && 964 // When an invalid REPL command is used, error message is printed 965 // immediately. We don't have to print anything else. So, only when 966 // the second argument to this function is there, print it. 967 arguments.length === 2 && 968 (!self.ignoreUndefined || ret !== undefined)) { 969 if (!self.underscoreAssigned) { 970 self.last = ret; 971 } 972 self.output.write(self.writer(ret) + '\n'); 973 } 974 975 // Display prompt again (unless we already did by emitting the 'error' 976 // event on the domain instance). 977 if (!e) { 978 self.displayPrompt(); 979 } 980 } 981 }); 982 983 self.on('SIGCONT', function onSigCont() { 984 if (self.editorMode) { 985 self.output.write(`${self._initialPrompt}.editor\n`); 986 self.output.write( 987 '// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)\n'); 988 self.output.write(`${self[kBufferedCommandSymbol]}\n`); 989 self.prompt(true); 990 } else { 991 self.displayPrompt(true); 992 } 993 }); 994 995 const { reverseSearch } = setupReverseSearch(this); 996 997 const { 998 clearPreview, 999 showPreview, 1000 } = setupPreview( 1001 this, 1002 kContextId, 1003 kBufferedCommandSymbol, 1004 preview, 1005 ); 1006 1007 // Wrap readline tty to enable editor mode and pausing. 1008 const ttyWrite = FunctionPrototypeBind(self._ttyWrite, self); 1009 self._ttyWrite = (d, key) => { 1010 key = key || {}; 1011 if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === 'c')) { 1012 ArrayPrototypePush(pausedBuffer, 1013 ['key', [d, key], self.isCompletionEnabled]); 1014 return; 1015 } 1016 if (!self.editorMode || !self.terminal) { 1017 // Before exiting, make sure to clear the line. 1018 if (key.ctrl && key.name === 'd' && 1019 self.cursor === 0 && self.line.length === 0) { 1020 self.clearLine(); 1021 } 1022 clearPreview(key); 1023 if (!reverseSearch(d, key)) { 1024 ttyWrite(d, key); 1025 const showCompletionPreview = key.name !== 'escape'; 1026 showPreview(showCompletionPreview); 1027 } 1028 return; 1029 } 1030 1031 // Editor mode 1032 if (key.ctrl && !key.shift) { 1033 switch (key.name) { 1034 // TODO(BridgeAR): There should not be a special mode necessary for full 1035 // multiline support. 1036 case 'd': // End editor mode 1037 _turnOffEditorMode(self); 1038 sawCtrlD = true; 1039 ttyWrite(d, { name: 'return' }); 1040 break; 1041 case 'n': // Override next history item 1042 case 'p': // Override previous history item 1043 break; 1044 default: 1045 ttyWrite(d, key); 1046 } 1047 } else { 1048 switch (key.name) { 1049 case 'up': // Override previous history item 1050 case 'down': // Override next history item 1051 break; 1052 case 'tab': 1053 // Prevent double tab behavior 1054 self._previousKey = null; 1055 ttyWrite(d, key); 1056 break; 1057 default: 1058 ttyWrite(d, key); 1059 } 1060 } 1061 }; 1062 1063 self.displayPrompt(); 1064} 1065ObjectSetPrototypeOf(REPLServer.prototype, Interface.prototype); 1066ObjectSetPrototypeOf(REPLServer, Interface); 1067 1068// Prompt is a string to print on each line for the prompt, 1069// source is a stream to use for I/O, defaulting to stdin/stdout. 1070function start(prompt, source, eval_, useGlobal, ignoreUndefined, replMode) { 1071 return new REPLServer( 1072 prompt, source, eval_, useGlobal, ignoreUndefined, replMode); 1073} 1074 1075REPLServer.prototype.setupHistory = function setupHistory(historyFile, cb) { 1076 history(this, historyFile, cb); 1077}; 1078 1079REPLServer.prototype.clearBufferedCommand = function clearBufferedCommand() { 1080 this[kBufferedCommandSymbol] = ''; 1081}; 1082 1083REPLServer.prototype.close = function close() { 1084 if (this.terminal && this._flushing && !this._closingOnFlush) { 1085 this._closingOnFlush = true; 1086 this.once('flushHistory', () => 1087 ReflectApply(Interface.prototype.close, this, []), 1088 ); 1089 1090 return; 1091 } 1092 process.nextTick(() => 1093 ReflectApply(Interface.prototype.close, this, []), 1094 ); 1095}; 1096 1097REPLServer.prototype.createContext = function() { 1098 let context; 1099 if (this.useGlobal) { 1100 context = globalThis; 1101 } else { 1102 sendInspectorCommand((session) => { 1103 session.post('Runtime.enable'); 1104 session.once('Runtime.executionContextCreated', ({ params }) => { 1105 this[kContextId] = params.context.id; 1106 }); 1107 context = vm.createContext(); 1108 session.post('Runtime.disable'); 1109 }, () => { 1110 context = vm.createContext(); 1111 }); 1112 ArrayPrototypeForEach(ObjectGetOwnPropertyNames(globalThis), (name) => { 1113 // Only set properties that do not already exist as a global builtin. 1114 if (!globalBuiltins.has(name)) { 1115 ObjectDefineProperty(context, name, 1116 { 1117 __proto__: null, 1118 ...ObjectGetOwnPropertyDescriptor(globalThis, name), 1119 }); 1120 } 1121 }); 1122 context.global = context; 1123 const _console = new Console(this.output); 1124 ObjectDefineProperty(context, 'console', { 1125 __proto__: null, 1126 configurable: true, 1127 writable: true, 1128 value: _console, 1129 }); 1130 } 1131 1132 const replModule = new CJSModule('<repl>'); 1133 replModule.paths = CJSModule._resolveLookupPaths('<repl>', parentModule); 1134 1135 ObjectDefineProperty(context, 'module', { 1136 __proto__: null, 1137 configurable: true, 1138 writable: true, 1139 value: replModule, 1140 }); 1141 ObjectDefineProperty(context, 'require', { 1142 __proto__: null, 1143 configurable: true, 1144 writable: true, 1145 value: makeRequireFunction(replModule), 1146 }); 1147 1148 addBuiltinLibsToObject(context, '<REPL>'); 1149 1150 return context; 1151}; 1152 1153REPLServer.prototype.resetContext = function() { 1154 this.context = this.createContext(); 1155 this.underscoreAssigned = false; 1156 this.underscoreErrAssigned = false; 1157 // TODO(BridgeAR): Deprecate the lines. 1158 this.lines = []; 1159 this.lines.level = []; 1160 1161 ObjectDefineProperty(this.context, '_', { 1162 __proto__: null, 1163 configurable: true, 1164 get: () => this.last, 1165 set: (value) => { 1166 this.last = value; 1167 if (!this.underscoreAssigned) { 1168 this.underscoreAssigned = true; 1169 this.output.write('Expression assignment to _ now disabled.\n'); 1170 } 1171 }, 1172 }); 1173 1174 ObjectDefineProperty(this.context, '_error', { 1175 __proto__: null, 1176 configurable: true, 1177 get: () => this.lastError, 1178 set: (value) => { 1179 this.lastError = value; 1180 if (!this.underscoreErrAssigned) { 1181 this.underscoreErrAssigned = true; 1182 this.output.write( 1183 'Expression assignment to _error now disabled.\n'); 1184 } 1185 }, 1186 }); 1187 1188 // Allow REPL extensions to extend the new context 1189 this.emit('reset', this.context); 1190}; 1191 1192REPLServer.prototype.displayPrompt = function(preserveCursor) { 1193 let prompt = this._initialPrompt; 1194 if (this[kBufferedCommandSymbol].length) { 1195 prompt = '...'; 1196 const len = this.lines.level.length ? this.lines.level.length - 1 : 0; 1197 const levelInd = StringPrototypeRepeat('..', len); 1198 prompt += levelInd + ' '; 1199 } 1200 1201 // Do not overwrite `_initialPrompt` here 1202 ReflectApply(Interface.prototype.setPrompt, this, [prompt]); 1203 this.prompt(preserveCursor); 1204}; 1205 1206// When invoked as an API method, overwrite _initialPrompt 1207REPLServer.prototype.setPrompt = function setPrompt(prompt) { 1208 this._initialPrompt = prompt; 1209 ReflectApply(Interface.prototype.setPrompt, this, [prompt]); 1210}; 1211 1212const importRE = /\bimport\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/; 1213const requireRE = /\brequire\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/; 1214const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/; 1215const simpleExpressionRE = 1216 /(?:[\w$'"`[{(](?:\w|\$|['"`\]})])*\??\.)*[a-zA-Z_$](?:\w|\$)*\??\.?$/; 1217const versionedFileNamesRe = /-\d+\.\d+/; 1218 1219function isIdentifier(str) { 1220 if (str === '') { 1221 return false; 1222 } 1223 const first = StringPrototypeCodePointAt(str, 0); 1224 if (!isIdentifierStart(first)) { 1225 return false; 1226 } 1227 const firstLen = first > 0xffff ? 2 : 1; 1228 for (let i = firstLen; i < str.length; i += 1) { 1229 const cp = StringPrototypeCodePointAt(str, i); 1230 if (!isIdentifierChar(cp)) { 1231 return false; 1232 } 1233 if (cp > 0xffff) { 1234 i += 1; 1235 } 1236 } 1237 return true; 1238} 1239 1240function isNotLegacyObjectPrototypeMethod(str) { 1241 return isIdentifier(str) && 1242 str !== '__defineGetter__' && 1243 str !== '__defineSetter__' && 1244 str !== '__lookupGetter__' && 1245 str !== '__lookupSetter__'; 1246} 1247 1248function filteredOwnPropertyNames(obj) { 1249 if (!obj) return []; 1250 // `Object.prototype` is the only non-contrived object that fulfills 1251 // `Object.getPrototypeOf(X) === null && 1252 // Object.getPrototypeOf(Object.getPrototypeOf(X.constructor)) === X`. 1253 let isObjectPrototype = false; 1254 if (ObjectGetPrototypeOf(obj) === null) { 1255 const ctorDescriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor'); 1256 if (ctorDescriptor && ctorDescriptor.value) { 1257 const ctorProto = ObjectGetPrototypeOf(ctorDescriptor.value); 1258 isObjectPrototype = ctorProto && ObjectGetPrototypeOf(ctorProto) === obj; 1259 } 1260 } 1261 const filter = ALL_PROPERTIES | SKIP_SYMBOLS; 1262 return ArrayPrototypeFilter( 1263 getOwnNonIndexProperties(obj, filter), 1264 isObjectPrototype ? isNotLegacyObjectPrototypeMethod : isIdentifier); 1265} 1266 1267function getGlobalLexicalScopeNames(contextId) { 1268 return sendInspectorCommand((session) => { 1269 let names = []; 1270 session.post('Runtime.globalLexicalScopeNames', { 1271 executionContextId: contextId, 1272 }, (error, result) => { 1273 if (!error) names = result.names; 1274 }); 1275 return names; 1276 }, () => []); 1277} 1278 1279REPLServer.prototype.complete = function() { 1280 ReflectApply(this.completer, this, arguments); 1281}; 1282 1283function gracefulReaddir(...args) { 1284 try { 1285 return ReflectApply(fs.readdirSync, null, args); 1286 } catch { 1287 // Continue regardless of error. 1288 } 1289} 1290 1291function completeFSFunctions(match) { 1292 let baseName = ''; 1293 let filePath = match[1]; 1294 let fileList = gracefulReaddir(filePath, { withFileTypes: true }); 1295 1296 if (!fileList) { 1297 baseName = path.basename(filePath); 1298 filePath = path.dirname(filePath); 1299 fileList = gracefulReaddir(filePath, { withFileTypes: true }) || []; 1300 } 1301 1302 const completions = ArrayPrototypeMap( 1303 ArrayPrototypeFilter( 1304 fileList, 1305 (dirent) => StringPrototypeStartsWith(dirent.name, baseName), 1306 ), 1307 (d) => d.name, 1308 ); 1309 1310 return [[completions], baseName]; 1311} 1312 1313// Provide a list of completions for the given leading text. This is 1314// given to the readline interface for handling tab completion. 1315// 1316// Example: 1317// complete('let foo = util.') 1318// -> [['util.print', 'util.debug', 'util.log', 'util.inspect'], 1319// 'util.' ] 1320// 1321// Warning: This eval's code like "foo.bar.baz", so it will run property 1322// getter code. 1323function complete(line, callback) { 1324 // List of completion lists, one for each inheritance "level" 1325 let completionGroups = []; 1326 let completeOn, group; 1327 1328 // Ignore right whitespace. It could change the outcome. 1329 line = StringPrototypeTrimLeft(line); 1330 1331 let filter = ''; 1332 1333 let match; 1334 // REPL commands (e.g. ".break"). 1335 if ((match = RegExpPrototypeExec(/^\s*\.(\w*)$/, line)) !== null) { 1336 ArrayPrototypePush(completionGroups, ObjectKeys(this.commands)); 1337 completeOn = match[1]; 1338 if (completeOn.length) { 1339 filter = completeOn; 1340 } 1341 } else if ((match = RegExpPrototypeExec(requireRE, line)) !== null) { 1342 // require('...<Tab>') 1343 completeOn = match[1]; 1344 filter = completeOn; 1345 if (this.allowBlockingCompletions) { 1346 const subdir = match[2] || ''; 1347 const extensions = ObjectKeys(this.context.require.extensions); 1348 const indexes = ArrayPrototypeMap(extensions, 1349 (extension) => `index${extension}`); 1350 ArrayPrototypePush(indexes, 'package.json', 'index'); 1351 1352 group = []; 1353 let paths = []; 1354 1355 if (completeOn === '.') { 1356 group = ['./', '../']; 1357 } else if (completeOn === '..') { 1358 group = ['../']; 1359 } else if (RegExpPrototypeExec(/^\.\.?\//, completeOn) !== null) { 1360 paths = [process.cwd()]; 1361 } else { 1362 paths = []; 1363 ArrayPrototypePushApply(paths, module.paths); 1364 ArrayPrototypePushApply(paths, CJSModule.globalPaths); 1365 } 1366 1367 ArrayPrototypeForEach(paths, (dir) => { 1368 dir = path.resolve(dir, subdir); 1369 const dirents = gracefulReaddir(dir, { withFileTypes: true }) || []; 1370 ArrayPrototypeForEach(dirents, (dirent) => { 1371 if (RegExpPrototypeExec(versionedFileNamesRe, dirent.name) !== null || 1372 dirent.name === '.npm') { 1373 // Exclude versioned names that 'npm' installs. 1374 return; 1375 } 1376 const extension = path.extname(dirent.name); 1377 const base = StringPrototypeSlice(dirent.name, 0, -extension.length); 1378 if (!dirent.isDirectory()) { 1379 if (StringPrototypeIncludes(extensions, extension) && 1380 (!subdir || base !== 'index')) { 1381 ArrayPrototypePush(group, `${subdir}${base}`); 1382 } 1383 return; 1384 } 1385 ArrayPrototypePush(group, `${subdir}${dirent.name}/`); 1386 const absolute = path.resolve(dir, dirent.name); 1387 if (ArrayPrototypeSome( 1388 gracefulReaddir(absolute) || [], 1389 (subfile) => ArrayPrototypeIncludes(indexes, subfile), 1390 )) { 1391 ArrayPrototypePush(group, `${subdir}${dirent.name}`); 1392 } 1393 }); 1394 }); 1395 if (group.length) { 1396 ArrayPrototypePush(completionGroups, group); 1397 } 1398 } 1399 1400 ArrayPrototypePush(completionGroups, _builtinLibs, nodeSchemeBuiltinLibs); 1401 } else if ((match = RegExpPrototypeExec(importRE, line)) !== null) { 1402 // import('...<Tab>') 1403 completeOn = match[1]; 1404 filter = completeOn; 1405 if (this.allowBlockingCompletions) { 1406 const subdir = match[2] || ''; 1407 // File extensions that can be imported: 1408 const extensions = ObjectKeys( 1409 getOptionValue('--experimental-specifier-resolution') === 'node' ? 1410 legacyExtensionFormatMap : 1411 extensionFormatMap); 1412 1413 // Only used when loading bare module specifiers from `node_modules`: 1414 const indexes = ArrayPrototypeMap(extensions, (ext) => `index${ext}`); 1415 ArrayPrototypePush(indexes, 'package.json'); 1416 1417 group = []; 1418 let paths = []; 1419 if (completeOn === '.') { 1420 group = ['./', '../']; 1421 } else if (completeOn === '..') { 1422 group = ['../']; 1423 } else if (RegExpPrototypeExec(/^\.\.?\//, completeOn) !== null) { 1424 paths = [process.cwd()]; 1425 } else { 1426 paths = ArrayPrototypeSlice(module.paths); 1427 } 1428 1429 ArrayPrototypeForEach(paths, (dir) => { 1430 dir = path.resolve(dir, subdir); 1431 const isInNodeModules = path.basename(dir) === 'node_modules'; 1432 const dirents = gracefulReaddir(dir, { withFileTypes: true }) || []; 1433 ArrayPrototypeForEach(dirents, (dirent) => { 1434 const { name } = dirent; 1435 if (RegExpPrototypeExec(versionedFileNamesRe, name) !== null || 1436 name === '.npm') { 1437 // Exclude versioned names that 'npm' installs. 1438 return; 1439 } 1440 1441 if (!dirent.isDirectory()) { 1442 const extension = path.extname(name); 1443 if (StringPrototypeIncludes(extensions, extension)) { 1444 ArrayPrototypePush(group, `${subdir}${name}`); 1445 } 1446 return; 1447 } 1448 1449 ArrayPrototypePush(group, `${subdir}${name}/`); 1450 if (!subdir && isInNodeModules) { 1451 const absolute = path.resolve(dir, name); 1452 const subfiles = gracefulReaddir(absolute) || []; 1453 if (ArrayPrototypeSome(subfiles, (subfile) => { 1454 return ArrayPrototypeIncludes(indexes, subfile); 1455 })) { 1456 ArrayPrototypePush(group, `${subdir}${name}`); 1457 } 1458 } 1459 }); 1460 }); 1461 1462 if (group.length) { 1463 ArrayPrototypePush(completionGroups, group); 1464 } 1465 } 1466 1467 ArrayPrototypePush(completionGroups, _builtinLibs, nodeSchemeBuiltinLibs); 1468 } else if ((match = RegExpPrototypeExec(fsAutoCompleteRE, line)) !== null && 1469 this.allowBlockingCompletions) { 1470 ({ 0: completionGroups, 1: completeOn } = completeFSFunctions(match)); 1471 // Handle variable member lookup. 1472 // We support simple chained expressions like the following (no function 1473 // calls, etc.). That is for simplicity and also because we *eval* that 1474 // leading expression so for safety (see WARNING above) don't want to 1475 // eval function calls. 1476 // 1477 // foo.bar<|> # completions for 'foo' with filter 'bar' 1478 // spam.eggs.<|> # completions for 'spam.eggs' with filter '' 1479 // foo<|> # all scope vars with filter 'foo' 1480 // foo.<|> # completions for 'foo' with filter '' 1481 } else if (line.length === 0 || 1482 RegExpPrototypeExec(/\w|\.|\$/, line[line.length - 1]) !== null) { 1483 const { 0: match } = RegExpPrototypeExec(simpleExpressionRE, line) || ['']; 1484 if (line.length !== 0 && !match) { 1485 completionGroupsLoaded(); 1486 return; 1487 } 1488 let expr = ''; 1489 completeOn = match; 1490 if (StringPrototypeEndsWith(line, '.')) { 1491 expr = StringPrototypeSlice(match, 0, -1); 1492 } else if (line.length !== 0) { 1493 const bits = StringPrototypeSplit(match, '.'); 1494 filter = ArrayPrototypePop(bits); 1495 expr = ArrayPrototypeJoin(bits, '.'); 1496 } 1497 1498 // Resolve expr and get its completions. 1499 if (!expr) { 1500 // Get global vars synchronously 1501 ArrayPrototypePush(completionGroups, 1502 getGlobalLexicalScopeNames(this[kContextId])); 1503 let contextProto = this.context; 1504 while ((contextProto = ObjectGetPrototypeOf(contextProto)) !== null) { 1505 ArrayPrototypePush(completionGroups, 1506 filteredOwnPropertyNames(contextProto)); 1507 } 1508 const contextOwnNames = filteredOwnPropertyNames(this.context); 1509 if (!this.useGlobal) { 1510 // When the context is not `global`, builtins are not own 1511 // properties of it. 1512 // `globalBuiltins` is a `SafeSet`, not an Array-like. 1513 ArrayPrototypePush(contextOwnNames, ...globalBuiltins); 1514 } 1515 ArrayPrototypePush(completionGroups, contextOwnNames); 1516 if (filter !== '') addCommonWords(completionGroups); 1517 completionGroupsLoaded(); 1518 return; 1519 } 1520 1521 let chaining = '.'; 1522 if (StringPrototypeEndsWith(expr, '?')) { 1523 expr = StringPrototypeSlice(expr, 0, -1); 1524 chaining = '?.'; 1525 } 1526 1527 const memberGroups = []; 1528 const evalExpr = `try { ${expr} } catch {}`; 1529 this.eval(evalExpr, this.context, getREPLResourceName(), (e, obj) => { 1530 try { 1531 let p; 1532 if ((typeof obj === 'object' && obj !== null) || 1533 typeof obj === 'function') { 1534 memberGroups.push(filteredOwnPropertyNames(obj)); 1535 p = ObjectGetPrototypeOf(obj); 1536 } else { 1537 p = obj.constructor ? obj.constructor.prototype : null; 1538 } 1539 // Circular refs possible? Let's guard against that. 1540 let sentinel = 5; 1541 while (p !== null && sentinel-- !== 0) { 1542 memberGroups.push(filteredOwnPropertyNames(p)); 1543 p = ObjectGetPrototypeOf(p); 1544 } 1545 } catch { 1546 // Maybe a Proxy object without `getOwnPropertyNames` trap. 1547 // We simply ignore it here, as we don't want to break the 1548 // autocompletion. Fixes the bug 1549 // https://github.com/nodejs/node/issues/2119 1550 } 1551 1552 if (memberGroups.length) { 1553 expr += chaining; 1554 ArrayPrototypeForEach(memberGroups, (group) => { 1555 ArrayPrototypePush(completionGroups, 1556 ArrayPrototypeMap(group, 1557 (member) => `${expr}${member}`)); 1558 }); 1559 if (filter) { 1560 filter = `${expr}${filter}`; 1561 } 1562 } 1563 1564 completionGroupsLoaded(); 1565 }); 1566 return; 1567 } 1568 1569 return completionGroupsLoaded(); 1570 1571 // Will be called when all completionGroups are in place 1572 // Useful for async autocompletion 1573 function completionGroupsLoaded() { 1574 // Filter, sort (within each group), uniq and merge the completion groups. 1575 if (completionGroups.length && filter) { 1576 const newCompletionGroups = []; 1577 const lowerCaseFilter = StringPrototypeToLocaleLowerCase(filter); 1578 ArrayPrototypeForEach(completionGroups, (group) => { 1579 const filteredGroup = ArrayPrototypeFilter(group, (str) => { 1580 // Filter is always case-insensitive following chromium autocomplete 1581 // behavior. 1582 return StringPrototypeStartsWith( 1583 StringPrototypeToLocaleLowerCase(str), 1584 lowerCaseFilter, 1585 ); 1586 }); 1587 if (filteredGroup.length) { 1588 ArrayPrototypePush(newCompletionGroups, filteredGroup); 1589 } 1590 }); 1591 completionGroups = newCompletionGroups; 1592 } 1593 1594 const completions = []; 1595 // Unique completions across all groups. 1596 const uniqueSet = new SafeSet(); 1597 uniqueSet.add(''); 1598 // Completion group 0 is the "closest" (least far up the inheritance 1599 // chain) so we put its completions last: to be closest in the REPL. 1600 ArrayPrototypeForEach(completionGroups, (group) => { 1601 ArrayPrototypeSort(group, (a, b) => (b > a ? 1 : -1)); 1602 const setSize = uniqueSet.size; 1603 ArrayPrototypeForEach(group, (entry) => { 1604 if (!uniqueSet.has(entry)) { 1605 ArrayPrototypeUnshift(completions, entry); 1606 uniqueSet.add(entry); 1607 } 1608 }); 1609 // Add a separator between groups. 1610 if (uniqueSet.size !== setSize) { 1611 ArrayPrototypeUnshift(completions, ''); 1612 } 1613 }); 1614 1615 // Remove obsolete group entry, if present. 1616 if (completions[0] === '') { 1617 ArrayPrototypeShift(completions); 1618 } 1619 1620 callback(null, [completions, completeOn]); 1621 } 1622} 1623 1624REPLServer.prototype.completeOnEditorMode = (callback) => (err, results) => { 1625 if (err) return callback(err); 1626 1627 const { 0: completions, 1: completeOn = '' } = results; 1628 let result = ArrayPrototypeFilter(completions, Boolean); 1629 1630 if (completeOn && result.length !== 0) { 1631 result = [commonPrefix(result)]; 1632 } 1633 1634 callback(null, [result, completeOn]); 1635}; 1636 1637REPLServer.prototype.defineCommand = function(keyword, cmd) { 1638 if (typeof cmd === 'function') { 1639 cmd = { action: cmd }; 1640 } else { 1641 validateFunction(cmd.action, 'cmd.action'); 1642 } 1643 this.commands[keyword] = cmd; 1644}; 1645 1646// TODO(BridgeAR): This should be replaced with acorn to build an AST. The 1647// language became more complex and using a simple approach like this is not 1648// sufficient anymore. 1649function _memory(cmd) { 1650 const self = this; 1651 self.lines = self.lines || []; 1652 self.lines.level = self.lines.level || []; 1653 1654 // Save the line so I can do magic later 1655 if (cmd) { 1656 const len = self.lines.level.length ? self.lines.level.length - 1 : 0; 1657 ArrayPrototypePush(self.lines, StringPrototypeRepeat(' ', len) + cmd); 1658 } else { 1659 // I don't want to not change the format too much... 1660 ArrayPrototypePush(self.lines, ''); 1661 } 1662 1663 if (!cmd) { 1664 self.lines.level = []; 1665 return; 1666 } 1667 1668 // I need to know "depth." 1669 // Because I can not tell the difference between a } that 1670 // closes an object literal and a } that closes a function 1671 const countMatches = (regex, str) => { 1672 let count = 0; 1673 while (RegExpPrototypeExec(regex, str) !== null) count++; 1674 return count; 1675 }; 1676 1677 // Going down is { and ( e.g. function() { 1678 // going up is } and ) 1679 const dw = countMatches(/[{(]/g, cmd); 1680 const up = countMatches(/[})]/g, cmd); 1681 let depth = dw.length - up.length; 1682 1683 if (depth) { 1684 (function workIt() { 1685 if (depth > 0) { 1686 // Going... down. 1687 // Push the line#, depth count, and if the line is a function. 1688 // Since JS only has functional scope I only need to remove 1689 // "function() {" lines, clearly this will not work for 1690 // "function() 1691 // {" but nothing should break, only tab completion for local 1692 // scope will not work for this function. 1693 ArrayPrototypePush(self.lines.level, { 1694 line: self.lines.length - 1, 1695 depth: depth, 1696 }); 1697 } else if (depth < 0) { 1698 // Going... up. 1699 const curr = ArrayPrototypePop(self.lines.level); 1700 if (curr) { 1701 const tmp = curr.depth + depth; 1702 if (tmp < 0) { 1703 // More to go, recurse 1704 depth += curr.depth; 1705 workIt(); 1706 } else if (tmp > 0) { 1707 // Remove and push back 1708 curr.depth += depth; 1709 ArrayPrototypePush(self.lines.level, curr); 1710 } 1711 } 1712 } 1713 }()); 1714 } 1715} 1716 1717function addCommonWords(completionGroups) { 1718 // Only words which do not yet exist as global property should be added to 1719 // this list. 1720 ArrayPrototypePush(completionGroups, [ 1721 'async', 'await', 'break', 'case', 'catch', 'const', 'continue', 1722 'debugger', 'default', 'delete', 'do', 'else', 'export', 'false', 1723 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'let', 1724 'new', 'null', 'return', 'switch', 'this', 'throw', 'true', 'try', 1725 'typeof', 'var', 'void', 'while', 'with', 'yield', 1726 ]); 1727} 1728 1729function _turnOnEditorMode(repl) { 1730 repl.editorMode = true; 1731 ReflectApply(Interface.prototype.setPrompt, repl, ['']); 1732} 1733 1734function _turnOffEditorMode(repl) { 1735 repl.editorMode = false; 1736 repl.setPrompt(repl._initialPrompt); 1737} 1738 1739function defineDefaultCommands(repl) { 1740 repl.defineCommand('break', { 1741 help: 'Sometimes you get stuck, this gets you out', 1742 action: function() { 1743 this.clearBufferedCommand(); 1744 this.displayPrompt(); 1745 }, 1746 }); 1747 1748 let clearMessage; 1749 if (repl.useGlobal) { 1750 clearMessage = 'Alias for .break'; 1751 } else { 1752 clearMessage = 'Break, and also clear the local context'; 1753 } 1754 repl.defineCommand('clear', { 1755 help: clearMessage, 1756 action: function() { 1757 this.clearBufferedCommand(); 1758 if (!this.useGlobal) { 1759 this.output.write('Clearing context...\n'); 1760 this.resetContext(); 1761 } 1762 this.displayPrompt(); 1763 }, 1764 }); 1765 1766 repl.defineCommand('exit', { 1767 help: 'Exit the REPL', 1768 action: function() { 1769 this.close(); 1770 }, 1771 }); 1772 1773 repl.defineCommand('help', { 1774 help: 'Print this help message', 1775 action: function() { 1776 const names = ArrayPrototypeSort(ObjectKeys(this.commands)); 1777 const longestNameLength = MathMaxApply( 1778 ArrayPrototypeMap(names, (name) => name.length), 1779 ); 1780 ArrayPrototypeForEach(names, (name) => { 1781 const cmd = this.commands[name]; 1782 const spaces = 1783 StringPrototypeRepeat(' ', longestNameLength - name.length + 3); 1784 const line = `.${name}${cmd.help ? spaces + cmd.help : ''}\n`; 1785 this.output.write(line); 1786 }); 1787 this.output.write('\nPress Ctrl+C to abort current expression, ' + 1788 'Ctrl+D to exit the REPL\n'); 1789 this.displayPrompt(); 1790 }, 1791 }); 1792 1793 repl.defineCommand('save', { 1794 help: 'Save all evaluated commands in this REPL session to a file', 1795 action: function(file) { 1796 try { 1797 fs.writeFileSync(file, ArrayPrototypeJoin(this.lines, '\n')); 1798 this.output.write(`Session saved to: ${file}\n`); 1799 } catch { 1800 this.output.write(`Failed to save: ${file}\n`); 1801 } 1802 this.displayPrompt(); 1803 }, 1804 }); 1805 1806 repl.defineCommand('load', { 1807 help: 'Load JS from a file into the REPL session', 1808 action: function(file) { 1809 try { 1810 const stats = fs.statSync(file); 1811 if (stats && stats.isFile()) { 1812 _turnOnEditorMode(this); 1813 const data = fs.readFileSync(file, 'utf8'); 1814 this.write(data); 1815 _turnOffEditorMode(this); 1816 this.write('\n'); 1817 } else { 1818 this.output.write( 1819 `Failed to load: ${file} is not a valid file\n`, 1820 ); 1821 } 1822 } catch { 1823 this.output.write(`Failed to load: ${file}\n`); 1824 } 1825 this.displayPrompt(); 1826 }, 1827 }); 1828 if (repl.terminal) { 1829 repl.defineCommand('editor', { 1830 help: 'Enter editor mode', 1831 action() { 1832 _turnOnEditorMode(this); 1833 this.output.write( 1834 '// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)\n'); 1835 }, 1836 }); 1837 } 1838} 1839 1840function Recoverable(err) { 1841 this.err = err; 1842} 1843ObjectSetPrototypeOf(Recoverable.prototype, SyntaxErrorPrototype); 1844ObjectSetPrototypeOf(Recoverable, SyntaxError); 1845 1846module.exports = { 1847 start, 1848 writer, 1849 REPLServer, 1850 REPL_MODE_SLOPPY, 1851 REPL_MODE_STRICT, 1852 Recoverable, 1853}; 1854 1855ObjectDefineProperty(module.exports, 'builtinModules', { 1856 __proto__: null, 1857 get: () => _builtinLibs, 1858 set: (val) => _builtinLibs = val, 1859 enumerable: true, 1860 configurable: true, 1861}); 1862 1863ObjectDefineProperty(module.exports, '_builtinLibs', { 1864 __proto__: null, 1865 get: pendingDeprecation ? deprecate( 1866 () => _builtinLibs, 1867 'repl._builtinLibs is deprecated. Check module.builtinModules instead', 1868 'DEP0142', 1869 ) : () => _builtinLibs, 1870 set: pendingDeprecation ? deprecate( 1871 (val) => _builtinLibs = val, 1872 'repl._builtinLibs is deprecated. Check module.builtinModules instead', 1873 'DEP0142', 1874 ) : (val) => _builtinLibs = val, 1875 enumerable: false, 1876 configurable: true, 1877}); 1878