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