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