Skip to content

Commit

Permalink
Don't use a separate context for the repl.
Browse files Browse the repository at this point in the history
Fix nodejs#1484
Fix nodejs#1834
Fix nodejs#1482
Fix nodejs#771

It's been a while now, and we've seen how this separate context thing
works.  It constantly confuses people, and no one actually uses '.clear'
anyway, so the benefit of that feature does not justify the constant
WTFery.

This makes repl.context actually be a getter that returns the global
object, and prints a deprecation warning.  The '.clear' command is gone,
and will report that it's an invalid repl keyword.  Tests updated to
allow the require, module, and exports globals, which are still
available in the repl just like they were before, by making them global.
  • Loading branch information
isaacs committed Oct 18, 2011
1 parent d2698d1 commit 73dcc62
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 103 deletions.
18 changes: 1 addition & 17 deletions doc/api/repl.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -83,28 +83,12 @@ The special variable `_` (underscore) contains the result of the last expression
> _ += 1
4

The REPL provides access to any variables in the global scope. You can expose
a variable to the REPL explicitly by assigning it to the `context` object
associated with each `REPLServer`. For example:

// repl_test.js
var repl = require("repl"),
msg = "message";

repl.start().context.m = msg;

Things in the `context` object appear as local within the REPL:

mjr:~$ node repl_test.js
> m
'message'
The REPL provides access to any variables in the global scope.

There are a few special REPL commands:

- `.break` - While inputting a multi-line expression, sometimes you get lost
or just don't care about completing it. `.break` will start over.
- `.clear` - Resets the `context` object to an empty object and clears any
multi-line expression.
- `.exit` - Close the I/O stream, which will cause the REPL to exit.
- `.help` - Show this list of special commands.

Expand Down
114 changes: 28 additions & 86 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,17 @@ var path = require('path');
var fs = require('fs');
var rl = require('readline');

global.module = module;
global.exports = exports;
global.require = require;

// If obj.hasOwnProperty has been overridden, then calling
// obj.hasOwnProperty(prop) will break.
// See: https://github.com/joyent/node/issues/1707
function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}


var context;

exports.disableColors = process.env.NODE_DISABLE_COLORS ? true : false;

// hack for require.resolve("./relative") to work properly.
Expand All @@ -71,16 +72,27 @@ exports.writer = util.inspect;
function REPLServer(prompt, stream, eval) {
var self = this;

self.eval = eval || function(code, context, file, cb) {
var contextWarning;
Object.defineProperty(this, 'context', {
get: function() {
if (!contextWarning) {
contextWarning = 'repl.context is deprecated.';
console.error(contextWarning);
}
return global;
}
});


self.eval = eval || function(code, file, cb) {
try {
var err, result = vm.runInContext(code, context, file);
var err, result = vm.runInThisContext(code, file);
} catch (e) {
err = e;
}
cb(err, result);
};

self.resetContext();
self.bufferedCommand = '';

if (stream) {
Expand Down Expand Up @@ -173,14 +185,13 @@ function REPLServer(prompt, stream, eval) {
// First we attempt to eval as expression with parens.
// This catches '{a : 1}' properly.
self.eval('(' + evalCmd + ')',
self.context,
'repl',
function(e, ret) {
if (e && !isSyntaxError(e)) return finish(e);

if (typeof ret === 'function' || e) {
// Now as statement without parens.
self.eval(evalCmd, self.context, 'repl', finish);
self.eval(evalCmd, 'repl', finish);
} else {
finish(null, ret);
}
Expand Down Expand Up @@ -218,7 +229,7 @@ function REPLServer(prompt, stream, eval) {

// If we got any output - print it (if no error)
if (!e && ret !== undefined) {
self.context._ = ret;
global._ = ret;
self.outputStream.write(exports.writer(ret) + '\n');
}

Expand All @@ -245,25 +256,12 @@ exports.start = function(prompt, source, eval) {
};


REPLServer.prototype.createContext = function() {
var context = vm.createContext();

for (var i in global) context[i] = global[i];
context.module = module;
context.require = require;
context.global = context;
context.global.global = context;

return context;
};

var resetWarning;
REPLServer.prototype.resetContext = function(force) {
if (!context || force) {
context = this.createContext();
for (var i in require.cache) delete require.cache[i];
if (!resetWarning) {
resetWarning = 'REPLServer.resetContext is deprecated.';
console.error(resetWarning);
}

this.context = context;
};

REPLServer.prototype.displayPrompt = function() {
Expand Down Expand Up @@ -413,26 +411,9 @@ REPLServer.prototype.complete = function(line, callback) {
if (!expr) {
// If context is instance of vm.ScriptContext
// Get global vars synchronously
if (this.context.constructor.name === 'Context') {
completionGroups.push(Object.getOwnPropertyNames(this.context));
addStandardGlobals();
completionGroupsLoaded();
} else {
this.eval('.scope', this.context, 'repl', function(err, globals) {
if (err || !globals) {
addStandardGlobals();
} else if (Array.isArray(globals[0])) {
// Add grouped globals
globals.forEach(function(group) {
completionGroups.push(group);
});
} else {
completionGroups.push(globals);
addStandardGlobals();
}
completionGroupsLoaded();
});
}
completionGroups.push(Object.getOwnPropertyNames(global));
addStandardGlobals();
completionGroupsLoaded();

function addStandardGlobals() {
// Global object properties
Expand All @@ -457,7 +438,7 @@ REPLServer.prototype.complete = function(line, callback) {
}

} else {
this.eval(expr, this.context, 'repl', function(e, obj) {
this.eval(expr, 'repl', function(e, obj) {
// if (e) console.log(e);

if (obj != null) {
Expand Down Expand Up @@ -584,16 +565,6 @@ function defineDefaultCommands(repl) {
}
});

repl.defineCommand('clear', {
help: 'Break, and also clear the local context',
action: function() {
this.outputStream.write('Clearing context...\n');
this.bufferedCommand = '';
this.resetContext(true);
this.displayPrompt();
}
});

repl.defineCommand('exit', {
help: 'Exit the repl',
action: function() {
Expand Down Expand Up @@ -628,32 +599,3 @@ function trimWhitespace(cmd) {
function regexpEscape(s) {
return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}


/**
* Converts commands that use var and function <name>() to use the
* local exports.context when evaled. This provides a local context
* on the REPL.
*
* @param {String} cmd The cmd to convert.
* @return {String} The converted command.
*/
REPLServer.prototype.convertToContext = function(cmd) {
var self = this, matches,
scopeVar = /^\s*var\s*([_\w\$]+)(.*)$/m,
scopeFunc = /^\s*function\s*([_\w\$]+)/;

// Replaces: var foo = "bar"; with: self.context.foo = bar;
matches = scopeVar.exec(cmd);
if (matches && matches.length === 3) {
return 'self.context.' + matches[1] + matches[2];
}

// Replaces: function foo() {}; with: foo = function foo() {};
matches = scopeFunc.exec(self.bufferedCommand);
if (matches && matches.length === 2) {
return matches[1] + ' = ' + self.bufferedCommand;
}

return cmd;
};
11 changes: 11 additions & 0 deletions test/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,17 @@ process.on('exit', function() {
knownGlobals.push(DataView);
}

// repl pollution
if (global.hasOwnProperty('module')) {
knownGlobals.push(global.module);
}
if (global.hasOwnProperty('require')) {
knownGlobals.push(global.require);
}
if (global.hasOwnProperty('exports')) {
knownGlobals.push(global.exports);
}

for (var x in global) {
var found = false;

Expand Down

0 comments on commit 73dcc62

Please sign in to comment.