Skip to content

Commit eacb7f6

Browse files
committed
repl: handle errors from getters during completion
1 parent 049664b commit eacb7f6

File tree

2 files changed

+92
-3
lines changed

2 files changed

+92
-3
lines changed

lib/repl.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,7 +1836,7 @@ function includesProxiesOrGetters(expr, exprStr, evalFn, ctx, callback) {
18361836
if (astProp.type === 'Literal') {
18371837
// We have something like `obj['foo'].x` where `x` is the literal
18381838

1839-
if (isProxy(obj[astProp.value])) {
1839+
if (safeIsProxyAccess(obj, astProp.value)) {
18401840
return cb(true);
18411841
}
18421842

@@ -1854,7 +1854,7 @@ function includesProxiesOrGetters(expr, exprStr, evalFn, ctx, callback) {
18541854
) {
18551855
// We have something like `obj.foo.x` where `foo` is the identifier
18561856

1857-
if (isProxy(obj[astProp.name])) {
1857+
if (safeIsProxyAccess(obj, astProp.name)) {
18581858
return cb(true);
18591859
}
18601860

@@ -1881,7 +1881,7 @@ function includesProxiesOrGetters(expr, exprStr, evalFn, ctx, callback) {
18811881
}
18821882

18831883
if (typeof evaledProp === 'string') {
1884-
if (isProxy(obj[evaledProp])) {
1884+
if (safeIsProxyAccess(obj, evaledProp)) {
18851885
return cb(true);
18861886
}
18871887

@@ -1898,6 +1898,15 @@ function includesProxiesOrGetters(expr, exprStr, evalFn, ctx, callback) {
18981898
);
18991899
}
19001900

1901+
function safeIsProxyAccess(obj, prop) {
1902+
// Accessing `prop` may trigger a getter that throws, so we use try-catch to guard against it
1903+
try {
1904+
return isProxy(obj[prop]);
1905+
} catch {
1906+
return false;
1907+
}
1908+
}
1909+
19011910
return callback(false);
19021911
}
19031912

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const repl = require('repl');
5+
const ArrayStream = require('../common/arraystream');
6+
const assert = require('assert');
7+
8+
(async function() {
9+
await runTest();
10+
})().then(common.mustCall());
11+
12+
async function runTest() {
13+
const input = new ArrayStream();
14+
const output = new ArrayStream();
15+
16+
const replServer = repl.start({
17+
prompt: '',
18+
input,
19+
output: output,
20+
allowBlockingCompletions: true,
21+
terminal: true
22+
});
23+
24+
replServer._domain.on('error', (e) => {
25+
assert.fail(`Error in REPL domain: ${e}`);
26+
});
27+
await new Promise((resolve, reject) => {
28+
replServer.eval(`
29+
class Person1 {
30+
constructor(name) { this.name = name; }
31+
get name() { throw new Error(); } set name(value) { this._name = value; }
32+
};
33+
const foo = new Person1("Alice")
34+
`, replServer.context, '', (err) => {
35+
if (err) {
36+
reject(err);
37+
} else {
38+
resolve();
39+
}
40+
});
41+
});
42+
replServer.complete(
43+
'foo.name.',
44+
common.mustCall((error, data) => {
45+
assert.strictEqual(error, null);
46+
assert.strictEqual(data.length, 2);
47+
assert.strictEqual(data[1], 'foo.name.');
48+
})
49+
);
50+
51+
replServer.complete(
52+
'foo["name"].',
53+
common.mustCall((error, data) => {
54+
assert.strictEqual(error, null);
55+
assert.strictEqual(data.length, 2);
56+
assert.strictEqual(data[1], 'foo["name"].');
57+
})
58+
);
59+
await new Promise((resolve, reject) => {
60+
replServer.eval(`
61+
function getNameText() {
62+
return "name";
63+
}
64+
`, replServer.context, '', (err) => {
65+
if (err) {
66+
reject(err);
67+
} else {
68+
resolve();
69+
}
70+
});
71+
});
72+
replServer.complete(
73+
'foo[getNameText()].',
74+
common.mustCall((error, data) => {
75+
assert.strictEqual(error, null);
76+
assert.strictEqual(data.length, 2);
77+
assert.strictEqual(data[1], 'foo[getNameText()].');
78+
})
79+
);
80+
}

0 commit comments

Comments
 (0)