From d536124bcc44ff4adf1fc96690bda55cb1900ce1 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Fri, 11 Sep 2015 18:12:40 -0400 Subject: [PATCH] Improve `meteor shell` command evaluation. Specific improvements: - Parentheses are now stripped from commands that look like named classes so that they will be treated as class declarations rather than as named class expressions. - When the `ecmascript` package is installed, `compileForShell` errors are now exposed to the `evalCommand` callback. - Instead of using `vm.runInThisContext` to parse and evaluate commands at the same time, `evalCommand` now parses commands by creating a new `vm.Script` and later evaluates them using `script.runInThisContext()`, so that `SyntaxError`s can be reported immediately. Fixes #5131. --- tools/static-assets/server/shell-server.js | 43 +++++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/tools/static-assets/server/shell-server.js b/tools/static-assets/server/shell-server.js index c706e8968f..bce1dff101 100644 --- a/tools/static-assets/server/shell-server.js +++ b/tools/static-assets/server/shell-server.js @@ -266,21 +266,54 @@ var evalCommandPromise = Promise.resolve(); function evalCommand(command, context, filename, callback) { if (Package.ecmascript) { + var noParens = stripParens(command); + if (noParens !== command) { + var classMatch = /^\s*class\s+(\w+)/.exec(noParens); + if (classMatch && classMatch[1] !== "extends") { + // If the command looks like a named ES2015 class, we remove the + // extra layer of parentheses added by the REPL so that the + // command will be evaluated as a class declaration rather than as + // a named class expression. Note that you can still type (class A + // {}) explicitly to evaluate a named class expression. The REPL + // code that calls evalCommand handles named function expressions + // similarly (first with and then without parentheses), but that + // code doesn't know about ES2015 classes, which is why we have to + // handle them here. + command = noParens; + } + } + try { - command = command.replace(/^\(|\)$/g, ""); command = Package.ecmascript.ECMAScript.compileForShell(command); } catch (error) { - // If there was an error compiling the original command, there's a - // chance it didn't need to be compiled at all, so we might as well - // let vm.runInThisContext try to evaluate it. + callback(error); + return; } } + try { + var script = new vm.Script(command, { + filename: filename, + displayErrors: false + }); + } catch (parseError) { + callback(parseError); + return; + } + evalCommandPromise.then(function () { - callback(null, vm.runInThisContext(command, filename)); + callback(null, script.runInThisContext()); }).catch(callback); } +function stripParens(command) { + if (command.charAt(0) === "(" && + command.charAt(command.length - 1) === ")") { + return command.slice(1, command.length - 1); + } + return command; +} + // This function allows a persistent history of shell commands to be saved // to and loaded from .meteor/local/shell-history. Sp.initializeHistory = function initializeHistory() {