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'use strict'; 23 24const common = require('../common'); 25const ArrayStream = require('../common/arraystream'); 26const { 27 hijackStderr, 28 restoreStderr 29} = require('../common/hijackstdio'); 30const assert = require('assert'); 31const path = require('path'); 32const fixtures = require('../common/fixtures'); 33const hasInspector = process.features.inspector; 34 35if (!common.isMainThread) 36 common.skip('process.chdir is not available in Workers'); 37 38// We have to change the directory to ../fixtures before requiring repl 39// in order to make the tests for completion of node_modules work properly 40// since repl modifies module.paths. 41process.chdir(fixtures.fixturesDir); 42 43const repl = require('repl'); 44 45function getNoResultsFunction() { 46 return common.mustCall((err, data) => { 47 assert.ifError(err); 48 assert.deepStrictEqual(data[0], []); 49 }); 50} 51 52const works = [['inner.one'], 'inner.o']; 53const putIn = new ArrayStream(); 54const testMe = repl.start('', putIn); 55 56// Some errors are passed to the domain, but do not callback 57testMe._domain.on('error', assert.ifError); 58 59// Tab Complete will not break in an object literal 60putIn.run([ 61 'var inner = {', 62 'one:1' 63]); 64testMe.complete('inner.o', getNoResultsFunction()); 65 66testMe.complete('console.lo', common.mustCall(function(error, data) { 67 assert.deepStrictEqual(data, [['console.log'], 'console.lo']); 68})); 69 70// Tab Complete will return globally scoped variables 71putIn.run(['};']); 72testMe.complete('inner.o', common.mustCall(function(error, data) { 73 assert.deepStrictEqual(data, works); 74})); 75 76putIn.run(['.clear']); 77 78// Tab Complete will not break in an ternary operator with () 79putIn.run([ 80 'var inner = ( true ', 81 '?', 82 '{one: 1} : ' 83]); 84testMe.complete('inner.o', getNoResultsFunction()); 85 86putIn.run(['.clear']); 87 88// Tab Complete will return a simple local variable 89putIn.run([ 90 'var top = function() {', 91 'var inner = {one:1};' 92]); 93testMe.complete('inner.o', getNoResultsFunction()); 94 95// When you close the function scope tab complete will not return the 96// locally scoped variable 97putIn.run(['};']); 98testMe.complete('inner.o', getNoResultsFunction()); 99 100putIn.run(['.clear']); 101 102// Tab Complete will return a complex local variable 103putIn.run([ 104 'var top = function() {', 105 'var inner = {', 106 ' one:1', 107 '};' 108]); 109testMe.complete('inner.o', getNoResultsFunction()); 110 111putIn.run(['.clear']); 112 113// Tab Complete will return a complex local variable even if the function 114// has parameters 115putIn.run([ 116 'var top = function(one, two) {', 117 'var inner = {', 118 ' one:1', 119 '};' 120]); 121testMe.complete('inner.o', getNoResultsFunction()); 122 123putIn.run(['.clear']); 124 125// Tab Complete will return a complex local variable even if the 126// scope is nested inside an immediately executed function 127putIn.run([ 128 'var top = function() {', 129 '(function test () {', 130 'var inner = {', 131 ' one:1', 132 '};' 133]); 134testMe.complete('inner.o', getNoResultsFunction()); 135 136putIn.run(['.clear']); 137 138// The definition has the params and { on a separate line. 139putIn.run([ 140 'var top = function() {', 141 'r = function test (', 142 ' one, two) {', 143 'var inner = {', 144 ' one:1', 145 '};' 146]); 147testMe.complete('inner.o', getNoResultsFunction()); 148 149putIn.run(['.clear']); 150 151// Currently does not work, but should not break, not the { 152putIn.run([ 153 'var top = function() {', 154 'r = function test ()', 155 '{', 156 'var inner = {', 157 ' one:1', 158 '};' 159]); 160testMe.complete('inner.o', getNoResultsFunction()); 161 162putIn.run(['.clear']); 163 164// Currently does not work, but should not break 165putIn.run([ 166 'var top = function() {', 167 'r = function test (', 168 ')', 169 '{', 170 'var inner = {', 171 ' one:1', 172 '};' 173]); 174testMe.complete('inner.o', getNoResultsFunction()); 175 176putIn.run(['.clear']); 177 178// Make sure tab completion works on non-Objects 179putIn.run([ 180 'var str = "test";' 181]); 182testMe.complete('str.len', common.mustCall(function(error, data) { 183 assert.deepStrictEqual(data, [['str.length'], 'str.len']); 184})); 185 186putIn.run(['.clear']); 187 188// Tab completion should not break on spaces 189const spaceTimeout = setTimeout(function() { 190 throw new Error('timeout'); 191}, 1000); 192 193testMe.complete(' ', common.mustCall(function(error, data) { 194 assert.ifError(error); 195 assert.strictEqual(data[1], ''); 196 assert.ok(data[0].includes('globalThis')); 197 clearTimeout(spaceTimeout); 198})); 199 200// Tab completion should pick up the global "toString" object, and 201// any other properties up the "global" object's prototype chain 202testMe.complete('toSt', common.mustCall(function(error, data) { 203 assert.deepStrictEqual(data, [['toString'], 'toSt']); 204})); 205 206// Own properties should shadow properties on the prototype 207putIn.run(['.clear']); 208putIn.run([ 209 'var x = Object.create(null);', 210 'x.a = 1;', 211 'x.b = 2;', 212 'var y = Object.create(x);', 213 'y.a = 3;', 214 'y.c = 4;' 215]); 216testMe.complete('y.', common.mustCall(function(error, data) { 217 assert.deepStrictEqual(data, [['y.b', '', 'y.a', 'y.c'], 'y.']); 218})); 219 220// Tab complete provides built in libs for require() 221putIn.run(['.clear']); 222 223testMe.complete('require(\'', common.mustCall(function(error, data) { 224 assert.strictEqual(error, null); 225 repl._builtinLibs.forEach(function(lib) { 226 assert(data[0].includes(lib), `${lib} not found`); 227 }); 228})); 229 230testMe.complete('require(\'n', common.mustCall(function(error, data) { 231 assert.strictEqual(error, null); 232 assert.strictEqual(data.length, 2); 233 assert.strictEqual(data[1], 'n'); 234 assert(data[0].includes('net')); 235 // It's possible to pick up non-core modules too 236 data[0].forEach(function(completion) { 237 if (completion) 238 assert(/^n/.test(completion)); 239 }); 240})); 241 242{ 243 const expected = ['@nodejsscope', '@nodejsscope/']; 244 putIn.run(['.clear']); 245 testMe.complete('require(\'@nodejs', common.mustCall((err, data) => { 246 assert.strictEqual(err, null); 247 assert.deepStrictEqual(data, [expected, '@nodejs']); 248 })); 249} 250 251// Test tab completion for require() relative to the current directory 252{ 253 putIn.run(['.clear']); 254 255 const cwd = process.cwd(); 256 process.chdir(__dirname); 257 258 ['require(\'.', 'require(".'].forEach((input) => { 259 testMe.complete(input, common.mustCall((err, data) => { 260 assert.strictEqual(err, null); 261 assert.strictEqual(data.length, 2); 262 assert.strictEqual(data[1], '.'); 263 assert.strictEqual(data[0].length, 2); 264 assert.ok(data[0].includes('./')); 265 assert.ok(data[0].includes('../')); 266 })); 267 }); 268 269 ['require(\'..', 'require("..'].forEach((input) => { 270 testMe.complete(input, common.mustCall((err, data) => { 271 assert.strictEqual(err, null); 272 assert.deepStrictEqual(data, [['../'], '..']); 273 })); 274 }); 275 276 ['./', './test-'].forEach((path) => { 277 [`require('${path}`, `require("${path}`].forEach((input) => { 278 testMe.complete(input, common.mustCall((err, data) => { 279 assert.strictEqual(err, null); 280 assert.strictEqual(data.length, 2); 281 assert.strictEqual(data[1], path); 282 assert.ok(data[0].includes('./test-repl-tab-complete')); 283 })); 284 }); 285 }); 286 287 ['../parallel/', '../parallel/test-'].forEach((path) => { 288 [`require('${path}`, `require("${path}`].forEach((input) => { 289 testMe.complete(input, common.mustCall((err, data) => { 290 assert.strictEqual(err, null); 291 assert.strictEqual(data.length, 2); 292 assert.strictEqual(data[1], path); 293 assert.ok(data[0].includes('../parallel/test-repl-tab-complete')); 294 })); 295 }); 296 }); 297 298 { 299 const path = '../fixtures/repl-folder-extensions/f'; 300 testMe.complete(`require('${path}`, common.mustCall((err, data) => { 301 assert.ifError(err); 302 assert.strictEqual(data.length, 2); 303 assert.strictEqual(data[1], path); 304 assert.ok(data[0].includes('../fixtures/repl-folder-extensions/foo.js')); 305 })); 306 } 307 308 process.chdir(cwd); 309} 310 311// Make sure tab completion works on context properties 312putIn.run(['.clear']); 313 314putIn.run([ 315 'var custom = "test";' 316]); 317testMe.complete('cus', common.mustCall(function(error, data) { 318 assert.deepStrictEqual(data, [['custom'], 'cus']); 319})); 320 321// Make sure tab completion doesn't crash REPL with half-baked proxy objects. 322// See: https://github.com/nodejs/node/issues/2119 323putIn.run(['.clear']); 324 325putIn.run([ 326 'var proxy = new Proxy({}, {ownKeys: () => { throw new Error(); }});' 327]); 328 329testMe.complete('proxy.', common.mustCall(function(error, data) { 330 assert.strictEqual(error, null); 331 assert(Array.isArray(data)); 332})); 333 334// Make sure tab completion does not include integer members of an Array 335putIn.run(['.clear']); 336 337putIn.run(['var ary = [1,2,3];']); 338testMe.complete('ary.', common.mustCall(function(error, data) { 339 assert.strictEqual(data[0].includes('ary.0'), false); 340 assert.strictEqual(data[0].includes('ary.1'), false); 341 assert.strictEqual(data[0].includes('ary.2'), false); 342})); 343 344// Make sure tab completion does not include integer keys in an object 345putIn.run(['.clear']); 346putIn.run(['var obj = {1:"a","1a":"b",a:"b"};']); 347 348testMe.complete('obj.', common.mustCall(function(error, data) { 349 assert.strictEqual(data[0].includes('obj.1'), false); 350 assert.strictEqual(data[0].includes('obj.1a'), false); 351 assert(data[0].includes('obj.a')); 352})); 353 354// Don't try to complete results of non-simple expressions 355putIn.run(['.clear']); 356putIn.run(['function a() {}']); 357 358testMe.complete('a().b.', getNoResultsFunction()); 359 360// Works when prefixed with spaces 361putIn.run(['.clear']); 362putIn.run(['var obj = {1:"a","1a":"b",a:"b"};']); 363 364testMe.complete(' obj.', common.mustCall((error, data) => { 365 assert.strictEqual(data[0].includes('obj.1'), false); 366 assert.strictEqual(data[0].includes('obj.1a'), false); 367 assert(data[0].includes('obj.a')); 368})); 369 370// Works inside assignments 371putIn.run(['.clear']); 372 373testMe.complete('var log = console.lo', common.mustCall((error, data) => { 374 assert.deepStrictEqual(data, [['console.log'], 'console.lo']); 375})); 376 377// Tab completion for defined commands 378putIn.run(['.clear']); 379 380testMe.complete('.b', common.mustCall((error, data) => { 381 assert.deepStrictEqual(data, [['break'], 'b']); 382})); 383putIn.run(['.clear']); 384putIn.run(['var obj = {"hello, world!": "some string", "key": 123}']); 385testMe.complete('obj.', common.mustCall((error, data) => { 386 assert.strictEqual(data[0].includes('obj.hello, world!'), false); 387 assert(data[0].includes('obj.key')); 388})); 389 390// Tab completion for files/directories 391{ 392 putIn.run(['.clear']); 393 process.chdir(__dirname); 394 395 const readFileSyncs = ['fs.readFileSync("', 'fs.promises.readFileSync("']; 396 if (!common.isWindows) { 397 readFileSyncs.forEach((readFileSync) => { 398 const fixturePath = `${readFileSync}../fixtures/test-repl-tab-completion`; 399 testMe.complete(fixturePath, common.mustCall((err, data) => { 400 assert.strictEqual(err, null); 401 assert.ok(data[0][0].includes('.hiddenfiles')); 402 assert.ok(data[0][1].includes('hellorandom.txt')); 403 assert.ok(data[0][2].includes('helloworld.js')); 404 })); 405 406 testMe.complete(`${fixturePath}/hello`, 407 common.mustCall((err, data) => { 408 assert.strictEqual(err, null); 409 assert.ok(data[0][0].includes('hellorandom.txt')); 410 assert.ok(data[0][1].includes('helloworld.js')); 411 }) 412 ); 413 414 testMe.complete(`${fixturePath}/.h`, 415 common.mustCall((err, data) => { 416 assert.strictEqual(err, null); 417 assert.ok(data[0][0].includes('.hiddenfiles')); 418 }) 419 ); 420 421 testMe.complete(`${readFileSync}./xxxRandom/random`, 422 common.mustCall((err, data) => { 423 assert.strictEqual(err, null); 424 assert.strictEqual(data[0].length, 0); 425 }) 426 ); 427 428 const testPath = fixturePath.slice(0, -1); 429 testMe.complete(testPath, common.mustCall((err, data) => { 430 assert.strictEqual(err, null); 431 assert.ok(data[0][0].includes('test-repl-tab-completion')); 432 assert.strictEqual( 433 data[1], 434 path.basename(testPath) 435 ); 436 })); 437 }); 438 } 439} 440 441[ 442 Array, 443 Buffer, 444 445 Uint8Array, 446 Uint16Array, 447 Uint32Array, 448 449 Uint8ClampedArray, 450 Int8Array, 451 Int16Array, 452 Int32Array, 453 Float32Array, 454 Float64Array, 455].forEach((type) => { 456 putIn.run(['.clear']); 457 458 if (type === Array) { 459 putIn.run([ 460 'var ele = [];', 461 'for (let i = 0; i < 1e6 + 1; i++) ele[i] = 0;', 462 'ele.biu = 1;' 463 ]); 464 } else if (type === Buffer) { 465 putIn.run(['var ele = Buffer.alloc(1e6 + 1); ele.biu = 1;']); 466 } else { 467 putIn.run([`var ele = new ${type.name}(1e6 + 1); ele.biu = 1;`]); 468 } 469 470 hijackStderr(common.mustNotCall()); 471 testMe.complete('ele.', common.mustCall((err, data) => { 472 restoreStderr(); 473 assert.ifError(err); 474 475 const ele = (type === Array) ? 476 [] : 477 (type === Buffer ? 478 Buffer.alloc(0) : 479 new type(0)); 480 481 assert.strictEqual(data[0].includes('ele.biu'), true); 482 483 data[0].forEach((key) => { 484 if (!key || key === 'ele.biu') return; 485 assert.notStrictEqual(ele[key.substr(4)], undefined); 486 }); 487 })); 488}); 489 490// check Buffer.prototype.length not crashing. 491// Refs: https://github.com/nodejs/node/pull/11961 492putIn.run['.clear']; 493testMe.complete('Buffer.prototype.', common.mustCall()); 494 495const testNonGlobal = repl.start({ 496 input: putIn, 497 output: putIn, 498 useGlobal: false 499}); 500 501const builtins = [['Infinity', 'Int16Array', 'Int32Array', 502 'Int8Array'], 'I']; 503 504if (common.hasIntl) { 505 builtins[0].push('Intl'); 506} 507testNonGlobal.complete('I', common.mustCall((error, data) => { 508 assert.deepStrictEqual(data, builtins); 509})); 510 511// To test custom completer function. 512// Sync mode. 513const customCompletions = 'aaa aa1 aa2 bbb bb1 bb2 bb3 ccc ddd eee'.split(' '); 514const testCustomCompleterSyncMode = repl.start({ 515 prompt: '', 516 input: putIn, 517 output: putIn, 518 completer: function completer(line) { 519 const hits = customCompletions.filter((c) => c.startsWith(line)); 520 // Show all completions if none found. 521 return [hits.length ? hits : customCompletions, line]; 522 } 523}); 524 525// On empty line should output all the custom completions 526// without complete anything. 527testCustomCompleterSyncMode.complete('', common.mustCall((error, data) => { 528 assert.deepStrictEqual(data, [ 529 customCompletions, 530 '' 531 ]); 532})); 533 534// On `a` should output `aaa aa1 aa2` and complete until `aa`. 535testCustomCompleterSyncMode.complete('a', common.mustCall((error, data) => { 536 assert.deepStrictEqual(data, [ 537 'aaa aa1 aa2'.split(' '), 538 'a' 539 ]); 540})); 541 542// To test custom completer function. 543// Async mode. 544const testCustomCompleterAsyncMode = repl.start({ 545 prompt: '', 546 input: putIn, 547 output: putIn, 548 completer: function completer(line, callback) { 549 const hits = customCompletions.filter((c) => c.startsWith(line)); 550 // Show all completions if none found. 551 callback(null, [hits.length ? hits : customCompletions, line]); 552 } 553}); 554 555// On empty line should output all the custom completions 556// without complete anything. 557testCustomCompleterAsyncMode.complete('', common.mustCall((error, data) => { 558 assert.deepStrictEqual(data, [ 559 customCompletions, 560 '' 561 ]); 562})); 563 564// On `a` should output `aaa aa1 aa2` and complete until `aa`. 565testCustomCompleterAsyncMode.complete('a', common.mustCall((error, data) => { 566 assert.deepStrictEqual(data, [ 567 'aaa aa1 aa2'.split(' '), 568 'a' 569 ]); 570})); 571 572// Tab completion in editor mode 573const editorStream = new ArrayStream(); 574const editor = repl.start({ 575 stream: editorStream, 576 terminal: true, 577 useColors: false 578}); 579 580editorStream.run(['.clear']); 581editorStream.run(['.editor']); 582 583editor.completer('Uin', common.mustCall((error, data) => { 584 assert.deepStrictEqual(data, [['Uint'], 'Uin']); 585})); 586 587editorStream.run(['.clear']); 588editorStream.run(['.editor']); 589 590editor.completer('var log = console.l', common.mustCall((error, data) => { 591 assert.deepStrictEqual(data, [['console.log'], 'console.l']); 592})); 593 594{ 595 // Tab completion of lexically scoped variables 596 const stream = new ArrayStream(); 597 const testRepl = repl.start({ stream }); 598 599 stream.run([` 600 let lexicalLet = true; 601 const lexicalConst = true; 602 class lexicalKlass {} 603 `]); 604 605 ['Let', 'Const', 'Klass'].forEach((type) => { 606 const query = `lexical${type[0]}`; 607 const expected = hasInspector ? [[`lexical${type}`], query] : 608 [[], `lexical${type[0]}`]; 609 testRepl.complete(query, common.mustCall((error, data) => { 610 assert.deepStrictEqual(data, expected); 611 })); 612 }); 613} 614