From 76c5dc995e7853dc871df118beaea31ced3528aa Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Sun, 17 May 2020 05:20:08 +0200 Subject: [PATCH] repl: support optional chaining during autocompletion This makes sure the autocompletion is able to handle optional chaining notations. Signed-off-by: Ruben Bridgewater PR-URL: https://github.com/nodejs/node/pull/33450 Reviewed-By: James M Snell Reviewed-By: Benjamin Gruenbaum --- lib/repl.js | 20 ++++++++++++-------- test/parallel/test-repl-tab-complete.js | 13 +++++++++++++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index 41aaf126e39b94..369c12852b42b2 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1066,7 +1066,7 @@ REPLServer.prototype.turnOffEditorMode = deprecate( const requireRE = /\brequire\s*\(\s*['"`](([\w@./-]+\/)?(?:[\w@./-]*))(?![^'"`])$/; const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/; const simpleExpressionRE = - /(?:[a-zA-Z_$](?:\w|\$)*\.)*[a-zA-Z_$](?:\w|\$)*\.?$/; + /(?:[a-zA-Z_$](?:\w|\$)*\??\.)*[a-zA-Z_$](?:\w|\$)*\??\.?$/; function isIdentifier(str) { if (str === '') { @@ -1138,7 +1138,7 @@ function complete(line, callback) { line = line.trimLeft(); // REPL commands (e.g. ".break"). - let filter; + let filter = ''; if (/^\s*\.(\w*)$/.test(line)) { completionGroups.push(ObjectKeys(this.commands)); completeOn = line.match(/^\s*\.(\w*)$/)[1]; @@ -1253,10 +1253,8 @@ function complete(line, callback) { let expr; completeOn = (match ? match[0] : ''); if (line.length === 0) { - filter = ''; expr = ''; } else if (line[line.length - 1] === '.') { - filter = ''; expr = match[0].slice(0, match[0].length - 1); } else { const bits = match[0].split('.'); @@ -1285,6 +1283,12 @@ function complete(line, callback) { return; } + let chaining = '.'; + if (expr[expr.length - 1] === '?') { + expr = expr.slice(0, -1); + chaining = '?.'; + } + const evalExpr = `try { ${expr} } catch {}`; this.eval(evalExpr, this.context, 'repl', (e, obj) => { if (obj != null) { @@ -1316,12 +1320,12 @@ function complete(line, callback) { } if (memberGroups.length) { - for (let i = 0; i < memberGroups.length; i++) { - completionGroups.push( - memberGroups[i].map((member) => `${expr}.${member}`)); + expr += chaining; + for (const group of memberGroups) { + completionGroups.push(group.map((member) => `${expr}${member}`)); } if (filter) { - filter = `${expr}.${filter}`; + filter = `${expr}${filter}`; } } diff --git a/test/parallel/test-repl-tab-complete.js b/test/parallel/test-repl-tab-complete.js index c40485ce3f5eac..0511e32d0a9719 100644 --- a/test/parallel/test-repl-tab-complete.js +++ b/test/parallel/test-repl-tab-complete.js @@ -68,6 +68,19 @@ testMe.complete('console.lo', common.mustCall(function(error, data) { assert.deepStrictEqual(data, [['console.log'], 'console.lo']); })); +testMe.complete('console?.lo', common.mustCall((error, data) => { + assert.deepStrictEqual(data, [['console?.log'], 'console?.lo']); +})); + +testMe.complete('console?.zzz', common.mustCall((error, data) => { + assert.deepStrictEqual(data, [[], 'console?.zzz']); +})); + +testMe.complete('console?.', common.mustCall((error, data) => { + assert(data[0].includes('console?.log')); + assert.strictEqual(data[1], 'console?.'); +})); + // Tab Complete will return globally scoped variables putIn.run(['};']); testMe.complete('inner.o', common.mustCall(function(error, data) {