From e36168fc9606c52b07b57eba441f6a46bc2b2c68 Mon Sep 17 00:00:00 2001 From: Sangmin Yoon Date: Wed, 27 May 2015 18:10:45 +0900 Subject: [PATCH 1/3] repl: fix tab completion for a non-global context Use vm.isContext() to properly identify contexts. PR-URL: https://github.com/joyent/node/pull/25382 PR-URL: io.js PR-URL here Reviewed-By: Colin Ihrig --- lib/repl.js | 4 +--- test/parallel/test-repl-tab-complete.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index 420fde45eb6a6b..4e2356135708ea 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -611,9 +611,7 @@ REPLServer.prototype.complete = function(line, callback) { if (!expr) { // If context is instance of vm.ScriptContext // Get global vars synchronously - if (this.useGlobal || - this.context.constructor && - this.context.constructor.name === 'Context') { + if (this.useGlobal || vm.isContext(this.context)) { var contextProto = this.context; while (contextProto = Object.getPrototypeOf(contextProto)) { completionGroups.push(Object.getOwnPropertyNames(contextProto)); diff --git a/test/parallel/test-repl-tab-complete.js b/test/parallel/test-repl-tab-complete.js index 659c8046a98173..6fe62f39fc91b6 100644 --- a/test/parallel/test-repl-tab-complete.js +++ b/test/parallel/test-repl-tab-complete.js @@ -206,3 +206,13 @@ testMe.complete('require(\'n', function(error, data) { assert.strictEqual(error, null); assert.deepEqual(data, [['net'], 'n']); }); + +// Make sure tab completion works on context properties +putIn.run(['.clear']); + +putIn.run([ + 'var custom = "test";' +]); +testMe.complete('cus', function(error, data) { + assert.deepEqual(data, [['custom'], 'cus']); +}); From 81699d2f64ccac0656524d59c25280d794b5eb68 Mon Sep 17 00:00:00 2001 From: Julien Gilli Date: Mon, 15 Dec 2014 12:21:54 -0800 Subject: [PATCH 2/3] repl: make 'Unexpected token' errors recoverable Fix the regexp used to detect 'Unexpected token' errors so that they can be considered as recoverable. This fixes the following use case: > var foo = 'bar \ ... baz'; undefined > foo 'bar baz' > Fixes: https://github.com/joyent/node/issues/8874 PR-URL: https://github.com/joyent/node/pull/8875 PR-URL: insert io.js PR-URL Reviewed-By: Colin Ihrig --- lib/repl.js | 2 +- .../test-repl-unexpected-token-recoverable.js | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-repl-unexpected-token-recoverable.js diff --git a/lib/repl.js b/lib/repl.js index 4e2356135708ea..38f66f0b2b10aa 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -994,7 +994,7 @@ function isRecoverableError(e, self) { self._inTemplateLiteral = true; return true; } - return /^(Unexpected end of input|Unexpected token :)/.test(message); + return /^(Unexpected end of input|Unexpected token)/.test(message); } return false; } diff --git a/test/parallel/test-repl-unexpected-token-recoverable.js b/test/parallel/test-repl-unexpected-token-recoverable.js new file mode 100644 index 00000000000000..4cfaa5136b6a55 --- /dev/null +++ b/test/parallel/test-repl-unexpected-token-recoverable.js @@ -0,0 +1,33 @@ +'use strict'; +/* + * This is a regression test for https://github.com/joyent/node/issues/8874. + */ +var common = require('../common'); +var assert = require('assert'); + +var spawn = require('child_process').spawn; +// use -i to force node into interactive mode, despite stdout not being a TTY +var args = [ '-i' ]; +var child = spawn(process.execPath, args); + +var input = 'var foo = "bar\\\nbaz"'; +// Match '...' as well since it marks a multi-line statement +var expectOut = /^> ... undefined\n/; + +child.stderr.setEncoding('utf8'); +child.stderr.on('data', function(c) { + throw new Error('child.stderr be silent'); +}); + +child.stdout.setEncoding('utf8'); +var out = ''; +child.stdout.on('data', function(c) { + out += c; +}); + +child.stdout.on('end', function() { + assert(expectOut.test(out)); + console.log('ok'); +}); + +child.stdin.end(input); From fd178f83bc562ef484221d21a519a8ba10114ea4 Mon Sep 17 00:00:00 2001 From: cjihrig Date: Wed, 24 Jun 2015 16:11:43 -0400 Subject: [PATCH 3/3] test: fix test-repl-tab-complete.js test-repl-tab-complete.js contains numerous assertions that are never run. Anything that results in a ReferenceError bails out, and never calls the functions containing the assertions. This commit adds checking for successful tab completions, as well as ReferenceErrors. PR-URL: insert here --- test/parallel/test-repl-tab-complete.js | 72 +++++++++++++++++-------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/test/parallel/test-repl-tab-complete.js b/test/parallel/test-repl-tab-complete.js index 6fe62f39fc91b6..1bf0c700eb2e54 100644 --- a/test/parallel/test-repl-tab-complete.js +++ b/test/parallel/test-repl-tab-complete.js @@ -2,8 +2,18 @@ var common = require('../common'); var assert = require('assert'); var util = require('util'); - var repl = require('repl'); +var referenceErrors = 0; +var completionCount = 0; + +function doNotCall() { + assert(false); +} + +process.on('exit', function() { + assert.strictEqual(referenceErrors, 6); + assert.strictEqual(completionCount, 12); +}); // A stream to push an array into a REPL function ArrayStream() { @@ -21,27 +31,37 @@ ArrayStream.prototype.resume = function() {}; ArrayStream.prototype.write = function() {}; var works = [['inner.one'], 'inner.o']; -var doesNotBreak = [[], 'inner.o']; - var putIn = new ArrayStream(); var testMe = repl.start('', putIn); +// Some errors are passed to the domain, but do not callback +testMe._domain.on('error', function(err) { + // Errors come from another context, so instanceof doesn't work + var str = err.toString(); + + if (/^ReferenceError:/.test(str)) + referenceErrors++; + else + assert(false); +}); + // Tab Complete will not break in an object literal putIn.run(['.clear']); putIn.run([ 'var inner = {', 'one:1' ]); -testMe.complete('inner.o', function(error, data) { - assert.deepEqual(data, doesNotBreak); -}); +testMe.complete('inner.o', doNotCall); + testMe.complete('console.lo', function(error, data) { + completionCount++; assert.deepEqual(data, [['console.log'], 'console.lo']); }); // Tab Complete will return globaly scoped variables putIn.run(['};']); testMe.complete('inner.o', function(error, data) { + completionCount++; assert.deepEqual(data, works); }); @@ -53,9 +73,7 @@ putIn.run([ '?', '{one: 1} : ' ]); -testMe.complete('inner.o', function(error, data) { - assert.deepEqual(data, doesNotBreak); -}); +testMe.complete('inner.o', doNotCall); putIn.run(['.clear']); @@ -65,15 +83,14 @@ putIn.run([ 'var inner = {one:1};' ]); testMe.complete('inner.o', function(error, data) { + completionCount++; assert.deepEqual(data, works); }); // When you close the function scope tab complete will not return the // locally scoped variable putIn.run(['};']); -testMe.complete('inner.o', function(error, data) { - assert.deepEqual(data, doesNotBreak); -}); +testMe.complete('inner.o', doNotCall); putIn.run(['.clear']); @@ -85,6 +102,7 @@ putIn.run([ '};' ]); testMe.complete('inner.o', function(error, data) { + completionCount++; assert.deepEqual(data, works); }); @@ -99,6 +117,7 @@ putIn.run([ '};' ]); testMe.complete('inner.o', function(error, data) { + completionCount++; assert.deepEqual(data, works); }); @@ -114,12 +133,12 @@ putIn.run([ '};' ]); testMe.complete('inner.o', function(error, data) { + completionCount++; assert.deepEqual(data, works); }); putIn.run(['.clear']); -// currently does not work, but should not break note the inner function // def has the params and { on a separate line putIn.run([ 'var top = function() {', @@ -129,9 +148,7 @@ putIn.run([ ' one:1', '};' ]); -testMe.complete('inner.o', function(error, data) { - assert.deepEqual(data, doesNotBreak); -}); +testMe.complete('inner.o', doNotCall); putIn.run(['.clear']); @@ -144,9 +161,7 @@ putIn.run([ ' one:1', '};' ]); -testMe.complete('inner.o', function(error, data) { - assert.deepEqual(data, doesNotBreak); -}); +testMe.complete('inner.o', doNotCall); putIn.run(['.clear']); @@ -160,9 +175,7 @@ putIn.run([ ' one:1', '};' ]); -testMe.complete('inner.o', function(error, data) { - assert.deepEqual(data, doesNotBreak); -}); +testMe.complete('inner.o', doNotCall); putIn.run(['.clear']); @@ -171,6 +184,7 @@ putIn.run([ 'var str = "test";' ]); testMe.complete('str.len', function(error, data) { + completionCount++; assert.deepEqual(data, [['str.length'], 'str.len']); }); @@ -182,6 +196,7 @@ var spaceTimeout = setTimeout(function() { }, 1000); testMe.complete(' ', function(error, data) { + completionCount++; assert.deepEqual(data, [[], undefined]); clearTimeout(spaceTimeout); }); @@ -189,6 +204,7 @@ testMe.complete(' ', function(error, data) { // tab completion should pick up the global "toString" object, and // any other properties up the "global" object's prototype chain testMe.complete('toSt', function(error, data) { + completionCount++; assert.deepEqual(data, [['toString'], 'toSt']); }); @@ -196,6 +212,7 @@ testMe.complete('toSt', function(error, data) { putIn.run(['.clear']); testMe.complete('require(\'', function(error, data) { + completionCount++; assert.strictEqual(error, null); repl._builtinLibs.forEach(function(lib) { assert.notStrictEqual(data[0].indexOf(lib), -1, lib + ' not found'); @@ -203,8 +220,16 @@ testMe.complete('require(\'', function(error, data) { }); testMe.complete('require(\'n', function(error, data) { + completionCount++; assert.strictEqual(error, null); - assert.deepEqual(data, [['net'], 'n']); + assert.strictEqual(data.length, 2); + assert.strictEqual(data[1], 'n'); + assert.notStrictEqual(data[0].indexOf('net'), -1); + // It's possible to pick up non-core modules too + data[0].forEach(function(completion) { + if (completion) + assert(/^n/.test(completion)); + }); }); // Make sure tab completion works on context properties @@ -214,5 +239,6 @@ putIn.run([ 'var custom = "test";' ]); testMe.complete('cus', function(error, data) { + completionCount++; assert.deepEqual(data, [['custom'], 'cus']); });