Skip to content

Commit 8198dbc

Browse files
lanceMylesBorins
authored andcommitted
repl: Enable tab completion for global properties
When `useGlobal` is false, tab completion in the repl does not enumerate global properties. Instead of just setting these properties blindly on the global context, e.g. context[prop] = global[prop] Use `Object.defineProperty` and the property descriptor found on `global` for the new property in `context`. Also addresses a previously unnoticed issue where `console` is writable when `useGlobal` is false. If the binary has been built with `./configure --without-intl` then the `Intl` builtin type will not be available in a repl runtime. Check for this in the test. Fixes: #7353 PR-URL: #7369 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 2e29b76 commit 8198dbc

File tree

4 files changed

+69
-10
lines changed

4 files changed

+69
-10
lines changed

lib/repl.js

+24-10
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ const debug = util.debuglog('repl');
3838
const parentModule = module;
3939
const replMap = new WeakMap();
4040

41+
const GLOBAL_OBJECT_PROPERTIES = ['NaN', 'Infinity', 'undefined',
42+
'eval', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'decodeURI',
43+
'decodeURIComponent', 'encodeURI', 'encodeURIComponent',
44+
'Object', 'Function', 'Array', 'String', 'Boolean', 'Number',
45+
'Date', 'RegExp', 'Error', 'EvalError', 'RangeError',
46+
'ReferenceError', 'SyntaxError', 'TypeError', 'URIError',
47+
'Math', 'JSON'];
48+
const GLOBAL_OBJECT_PROPERTY_MAP = {};
49+
GLOBAL_OBJECT_PROPERTIES.forEach((p) => GLOBAL_OBJECT_PROPERTY_MAP[p] = p);
50+
4151
try {
4252
// hack for require.resolve("./relative") to work properly.
4353
module.filename = path.resolve('repl');
@@ -520,10 +530,20 @@ REPLServer.prototype.createContext = function() {
520530
context = global;
521531
} else {
522532
context = vm.createContext();
523-
for (var i in global) context[i] = global[i];
524-
context.console = new Console(this.outputStream);
525533
context.global = context;
526-
context.global.global = context;
534+
const _console = new Console(this.outputStream);
535+
Object.defineProperty(context, 'console', {
536+
configurable: true,
537+
enumerable: true,
538+
get: () => _console
539+
});
540+
Object.getOwnPropertyNames(global).filter((name) => {
541+
if (name === 'console' || name === 'global') return false;
542+
return GLOBAL_OBJECT_PROPERTY_MAP[name] === undefined;
543+
}).forEach((name) => {
544+
Object.defineProperty(context, name,
545+
Object.getOwnPropertyDescriptor(global, name));
546+
});
527547
}
528548

529549
const module = new Module('<repl>');
@@ -989,13 +1009,7 @@ REPLServer.prototype.memory = function memory(cmd) {
9891009
function addStandardGlobals(completionGroups, filter) {
9901010
// Global object properties
9911011
// (http://www.ecma-international.org/publications/standards/Ecma-262.htm)
992-
completionGroups.push(['NaN', 'Infinity', 'undefined',
993-
'eval', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'decodeURI',
994-
'decodeURIComponent', 'encodeURI', 'encodeURIComponent',
995-
'Object', 'Function', 'Array', 'String', 'Boolean', 'Number',
996-
'Date', 'RegExp', 'Error', 'EvalError', 'RangeError',
997-
'ReferenceError', 'SyntaxError', 'TypeError', 'URIError',
998-
'Math', 'JSON']);
1012+
completionGroups.push(GLOBAL_OBJECT_PROPERTIES);
9991013
// Common keywords. Exclude for completion on the empty string, b/c
10001014
// they just get in the way.
10011015
if (filter) {

test/parallel/test-repl-console.js

+3
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ assert(r.context.console);
1818

1919
// ensure that the repl console instance is not the global one
2020
assert.notStrictEqual(r.context.console, console);
21+
22+
// ensure that the repl console instance does not have a setter
23+
assert.throws(() => r.context.console = 'foo', TypeError);

test/parallel/test-repl-context.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const repl = require('repl');
5+
6+
// Create a dummy stream that does nothing
7+
const stream = new common.ArrayStream();
8+
9+
// Test when useGlobal is false
10+
testContext(repl.start({
11+
input: stream,
12+
output: stream,
13+
useGlobal: false
14+
}));
15+
16+
function testContext(repl) {
17+
const context = repl.createContext();
18+
// ensure that the repl context gets its own "console" instance
19+
assert(context.console instanceof require('console').Console);
20+
21+
// ensure that the repl's global property is the context
22+
assert(context.global === context);
23+
24+
// ensure that the repl console instance does not have a setter
25+
assert.throws(() => context.console = 'foo');
26+
}

test/parallel/test-repl-tab-complete.js

+16
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,19 @@ putIn.run(['.clear']);
313313
testMe.complete('.b', common.mustCall((error, data) => {
314314
assert.deepStrictEqual(data, [['break'], 'b']);
315315
}));
316+
317+
const testNonGlobal = repl.start({
318+
input: putIn,
319+
output: putIn,
320+
useGlobal: false
321+
});
322+
323+
const builtins = [['Infinity', '', 'Int16Array', 'Int32Array',
324+
'Int8Array'], 'I'];
325+
326+
if (typeof Intl === 'object') {
327+
builtins[0].push('Intl');
328+
}
329+
testNonGlobal.complete('I', common.mustCall((error, data) => {
330+
assert.deepStrictEqual(data, builtins);
331+
}));

0 commit comments

Comments
 (0)