From c8b2767360d06f309b56cc8525e481a918c32926 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Sun, 15 Jun 2025 00:45:34 +0100 Subject: [PATCH 1/2] repl: fix tab completion not working with computer string properties --- lib/repl.js | 2 +- .../test-repl-tab-complete-computed-props.js | 95 +++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-repl-tab-complete-computed-props.js diff --git a/lib/repl.js b/lib/repl.js index 547b3e29adc07d..1d408fde88679b 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1226,7 +1226,7 @@ const importRE = /\bimport\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`]) const requireRE = /\brequire\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/; const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/; const simpleExpressionRE = - /(?:[\w$'"`[{(](?:\w|\$|['"`\]})])*\??\.)*[a-zA-Z_$](?:\w|\$)*\??\.?$/; + /(?:[\w$'"`[{(](?:\w|['"`](\w| |\t)*['"`]|\$|['"`\]})])*\??(?:\.|])?)*(?:[a-zA-Z_$])?(?:\w|\$)*\??\.?$/m; const versionedFileNamesRe = /-\d+\.\d+/; function isIdentifier(str) { diff --git a/test/parallel/test-repl-tab-complete-computed-props.js b/test/parallel/test-repl-tab-complete-computed-props.js new file mode 100644 index 00000000000000..e87078dd8733ae --- /dev/null +++ b/test/parallel/test-repl-tab-complete-computed-props.js @@ -0,0 +1,95 @@ +'use strict'; + +const common = require('../common'); +const ArrayStream = require('../common/arraystream'); +const { describe, it, before, after } = require('node:test'); +const assert = require('assert'); + +const repl = require('repl'); + +function prepareREPL() { + const input = new ArrayStream(); + const replServer = repl.start({ + prompt: '', + input, + output: process.stdout, + allowBlockingCompletions: true, + }); + + // Some errors are passed to the domain, but do not callback + replServer._domain.on('error', assert.ifError); + + return { replServer, input }; +} + +function testCompletion(replServer, { input, expectedCompletions }) { + replServer.complete( + input, + common.mustCall((_error, data) => { + assert.deepStrictEqual(data, [expectedCompletions, input]); + }), + ); +}; + +describe('REPL tab object completion on computed properties', () => { + describe('simple string cases', () => { + let replServer; + + before(() => { + const { replServer: server, input } = prepareREPL(); + replServer = server; + + input.run([ + ` + const obj = { + one: 1, + innerObj: { two: 2 }, + 'inner object': { three: 3 }, + }; + + const oneStr = 'one'; + `, + ]); + }); + + after(() => { + replServer.close(); + }); + + it('works with double quoted strings', () => testCompletion(replServer, { + input: 'obj["one"].toFi', + expectedCompletions: ['obj["one"].toFixed'], + })); + + it('works with single quoted strings', () => testCompletion(replServer, { + input: "obj['one'].toFi", + expectedCompletions: ["obj['one'].toFixed"], + })); + + it('works with template strings', () => testCompletion(replServer, { + input: 'obj[`one`].toFi', + expectedCompletions: ['obj[`one`].toFixed'], + })); + + it('works with nested objects', () => { + testCompletion(replServer, { + input: 'obj["innerObj"].tw', + expectedCompletions: ['obj["innerObj"].two'], + }); + testCompletion(replServer, { + input: 'obj["innerObj"].two.tofi', + expectedCompletions: ['obj["innerObj"].two.toFixed'], + }); + }); + + it('works with nested objects combining different type of strings', () => testCompletion(replServer, { + input: 'obj["innerObj"][`two`].tofi', + expectedCompletions: ['obj["innerObj"][`two`].toFixed'], + })); + + it('works with strings with spaces', () => testCompletion(replServer, { + input: 'obj["inner object"].th', + expectedCompletions: ['obj["inner object"].three'], + })); + }); +}); From da0d780c157872c2455fa954d5c5cd4aa395fbfd Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Sun, 15 Jun 2025 00:46:14 +0100 Subject: [PATCH 2/2] test: add tests for repl tab completion on computer variable properties --- lib/repl.js | 2 +- .../test-repl-tab-complete-computed-props.js | 43 +++++++++++++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index 1d408fde88679b..d598de3f51926f 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1226,7 +1226,7 @@ const importRE = /\bimport\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`]) const requireRE = /\brequire\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/; const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/; const simpleExpressionRE = - /(?:[\w$'"`[{(](?:\w|['"`](\w| |\t)*['"`]|\$|['"`\]})])*\??(?:\.|])?)*(?:[a-zA-Z_$])?(?:\w|\$)*\??\.?$/m; + /(?:[\w$'"`[{(](?:(\w| |\t)*?['"`]|\$|['"`\]})])*\??(?:\.|])?)*?(?:[a-zA-Z_$])?(?:\w|\$)*\??\.?$/; const versionedFileNamesRe = /-\d+\.\d+/; function isIdentifier(str) { diff --git a/test/parallel/test-repl-tab-complete-computed-props.js b/test/parallel/test-repl-tab-complete-computed-props.js index e87078dd8733ae..753d01fdab017a 100644 --- a/test/parallel/test-repl-tab-complete-computed-props.js +++ b/test/parallel/test-repl-tab-complete-computed-props.js @@ -24,10 +24,10 @@ function prepareREPL() { function testCompletion(replServer, { input, expectedCompletions }) { replServer.complete( - input, - common.mustCall((_error, data) => { - assert.deepStrictEqual(data, [expectedCompletions, input]); - }), + input, + common.mustCall((_error, data) => { + assert.deepStrictEqual(data, [expectedCompletions, input]); + }), ); }; @@ -92,4 +92,39 @@ describe('REPL tab object completion on computed properties', () => { expectedCompletions: ['obj["inner object"].three'], })); }); + + describe('variables as indexes', () => { + let replServer; + + before(() => { + const { replServer: server, input } = prepareREPL(); + replServer = server; + + input.run([ + ` + const oneStr = 'One'; + const helloWorldStr = 'Hello' + ' ' + 'World'; + + const obj = { + [oneStr]: 1, + ['Hello World']: 'hello world!', + }; + `, + ]); + }); + + after(() => { + replServer.close(); + }); + + it('works with a simple variable', () => testCompletion(replServer, { + input: 'obj[oneStr].toFi', + expectedCompletions: ['obj[oneStr].toFixed'], + })); + + it('works with a computed variable', () => testCompletion(replServer, { + input: 'obj[helloWorldStr].tolocaleup', + expectedCompletions: ['obj[helloWorldStr].toLocaleUpperCase'], + })); + }); });