Skip to content

Commit a414c1e

Browse files
kingsword09targos
authored andcommitted
repl: fix REPL completion under unary expressions
PR-URL: #59744 Fixes: #59735 Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 65e4e68 commit a414c1e

File tree

2 files changed

+151
-0
lines changed

2 files changed

+151
-0
lines changed

lib/repl.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1744,6 +1744,16 @@ function findExpressionCompleteTarget(code) {
17441744
return findExpressionCompleteTarget(lastDeclarationInitCode);
17451745
}
17461746

1747+
// If the last statement is an expression statement with a unary operator (delete, typeof, etc.)
1748+
// we want to extract the argument for completion (e.g. for `delete obj.prop` we want `obj.prop`)
1749+
if (lastBodyStatement.type === 'ExpressionStatement' &&
1750+
lastBodyStatement.expression.type === 'UnaryExpression' &&
1751+
lastBodyStatement.expression.argument) {
1752+
const argument = lastBodyStatement.expression.argument;
1753+
const argumentCode = code.slice(argument.start, argument.end);
1754+
return findExpressionCompleteTarget(argumentCode);
1755+
}
1756+
17471757
// If any of the above early returns haven't activated then it means that
17481758
// the potential complete target is the full code (e.g. the code represents
17491759
// a simple partial identifier, a member expression, etc...)
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const repl = require('repl');
6+
const { describe, it } = require('node:test');
7+
8+
// This test verifies that tab completion works correctly with unary expressions
9+
// like delete, typeof, void, etc. This is a regression test for the issue where
10+
// typing "delete globalThis._" and then backspacing and typing "globalThis"
11+
// would cause "globalThis is not defined" error.
12+
13+
describe('REPL tab completion with unary expressions', () => {
14+
it('should handle delete operator correctly', (t, done) => {
15+
const r = repl.start({
16+
prompt: '',
17+
input: process.stdin,
18+
output: process.stdout,
19+
terminal: false,
20+
});
21+
22+
// Test delete with member expression
23+
r.complete(
24+
'delete globalThis._',
25+
common.mustSucceed((completions) => {
26+
assert.strictEqual(completions[1], 'globalThis._');
27+
28+
// Test delete with identifier
29+
r.complete(
30+
'delete globalThis',
31+
common.mustSucceed((completions) => {
32+
assert.strictEqual(completions[1], 'globalThis');
33+
r.close();
34+
done();
35+
})
36+
);
37+
})
38+
);
39+
});
40+
41+
it('should handle typeof operator correctly', (t, done) => {
42+
const r = repl.start({
43+
prompt: '',
44+
input: process.stdin,
45+
output: process.stdout,
46+
terminal: false,
47+
});
48+
49+
r.complete(
50+
'typeof globalThis',
51+
common.mustSucceed((completions) => {
52+
assert.strictEqual(completions[1], 'globalThis');
53+
r.close();
54+
done();
55+
})
56+
);
57+
});
58+
59+
it('should handle void operator correctly', (t, done) => {
60+
const r = repl.start({
61+
prompt: '',
62+
input: process.stdin,
63+
output: process.stdout,
64+
terminal: false,
65+
});
66+
67+
r.complete(
68+
'void globalThis',
69+
common.mustSucceed((completions) => {
70+
assert.strictEqual(completions[1], 'globalThis');
71+
r.close();
72+
done();
73+
})
74+
);
75+
});
76+
77+
it('should handle other unary operators correctly', (t, done) => {
78+
const r = repl.start({
79+
prompt: '',
80+
input: process.stdin,
81+
output: process.stdout,
82+
terminal: false,
83+
});
84+
85+
const unaryOperators = [
86+
'!globalThis',
87+
'+globalThis',
88+
'-globalThis',
89+
'~globalThis',
90+
];
91+
92+
let testIndex = 0;
93+
94+
function testNext() {
95+
if (testIndex >= unaryOperators.length) {
96+
r.close();
97+
done();
98+
return;
99+
}
100+
101+
const testCase = unaryOperators[testIndex++];
102+
r.complete(
103+
testCase,
104+
common.mustSucceed((completions) => {
105+
assert.strictEqual(completions[1], 'globalThis');
106+
testNext();
107+
})
108+
);
109+
}
110+
111+
testNext();
112+
});
113+
114+
it('should still evaluate globalThis correctly after unary expression completion', (t, done) => {
115+
const r = repl.start({
116+
prompt: '',
117+
input: process.stdin,
118+
output: process.stdout,
119+
terminal: false,
120+
});
121+
122+
// First trigger completion with delete
123+
r.complete(
124+
'delete globalThis._',
125+
common.mustSucceed(() => {
126+
// Then evaluate globalThis
127+
r.eval(
128+
'globalThis',
129+
r.context,
130+
'test.js',
131+
common.mustSucceed((result) => {
132+
assert.strictEqual(typeof result, 'object');
133+
assert.ok(result !== null);
134+
r.close();
135+
done();
136+
})
137+
);
138+
})
139+
);
140+
});
141+
});

0 commit comments

Comments
 (0)