diff --git a/package.json b/package.json index 8549ba8..6ac62f1 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@babel/core": "^7.5.5", + "@babel/generator": "^7.5.5", "@babel/parser": "^7.5.5", "@babel/plugin-proposal-class-properties": "^7.1.0", "@babel/plugin-proposal-decorators": "^7.4.4", @@ -34,6 +35,7 @@ "@babel/plugin-syntax-import-meta": "^7.0.0", "@babel/plugin-transform-modules-commonjs": "^7.5.0", "@babel/plugin-transform-typescript": "^7.5.5", + "@babel/traverse": "^7.5.5", "bluebird": "^3.5.2", "chalk": "^2.4.1", "cheerio": "^0.22.0", @@ -44,7 +46,6 @@ "json5": "^2.1.0", "node-fetch": "^2.3.0", "prettier": "^1.14.3", - "recast": "^0.18.1", "shell-escape": "^0.2.0", "superagent": "^3.8.3" }, diff --git a/src/plugins/js-eval/jsEvalPlugin.js b/src/plugins/js-eval/jsEvalPlugin.js index c092dad..f4bf7d4 100644 --- a/src/plugins/js-eval/jsEvalPlugin.js +++ b/src/plugins/js-eval/jsEvalPlugin.js @@ -10,6 +10,36 @@ const CMD = ['node', '--no-warnings', '/run/run.js']; const CMD_SHIMS = ['node', '-r', '/run/node_modules/airbnb-js-shims/target/es2019', '--no-warnings', '/run/run.js']; const CMD_HARMONY = ['node', '--harmony', '--experimental-vm-modules', '--experimental-modules', '--no-warnings', '/run/run.js']; +const babelTransformOpts = { + parserOpts: { + allowAwaitOutsideFunction: true, + allowReturnOutsideFunction: true, + }, + plugins: [ + '@babel/plugin-transform-typescript', + '@babel/plugin-transform-modules-commonjs', // required by dynamicImport + ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: false }], // must be before class-properties https://babeljs.io/docs/en/babel-plugin-proposal-decorators#note-compatibility-with-babel-plugin-proposal-class-properties + '@babel/plugin-proposal-class-properties', + '@babel/plugin-proposal-do-expressions', + '@babel/plugin-proposal-export-default-from', + '@babel/plugin-proposal-export-namespace-from', + '@babel/plugin-proposal-function-sent', + '@babel/plugin-proposal-function-bind', + '@babel/plugin-proposal-json-strings', + '@babel/plugin-proposal-logical-assignment-operators', + '@babel/plugin-proposal-nullish-coalescing-operator', + '@babel/plugin-proposal-numeric-separator', + '@babel/plugin-proposal-optional-catch-binding', + '@babel/plugin-proposal-optional-chaining', + '@babel/plugin-proposal-partial-application', + ['@babel/plugin-proposal-pipeline-operator', { proposal: 'minimal' }], + '@babel/plugin-proposal-throw-expressions', + '@babel/plugin-proposal-dynamic-import', + '@babel/plugin-syntax-bigint', + '@babel/plugin-syntax-import-meta', + ] +}; + const jsEvalPlugin = async ({ mentionUser, respond, message, selfConfig = {} }) => { if (!/^[nhbsme?]>/.test(message)) return; const mode = message[0]; @@ -17,35 +47,7 @@ const jsEvalPlugin = async ({ mentionUser, respond, message, selfConfig = {} }) let code = message.slice(2); if (mode === 'b') { - code = (await babel.transformAsync(code, { - parserOpts: { - allowAwaitOutsideFunction: true, - allowReturnOutsideFunction: true, - }, - plugins: [ - '@babel/plugin-transform-typescript', - '@babel/plugin-transform-modules-commonjs', // required by dynamicImport - ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: false }], // must be before class-properties https://babeljs.io/docs/en/babel-plugin-proposal-decorators#note-compatibility-with-babel-plugin-proposal-class-properties - '@babel/plugin-proposal-class-properties', - '@babel/plugin-proposal-do-expressions', - '@babel/plugin-proposal-export-default-from', - '@babel/plugin-proposal-export-namespace-from', - '@babel/plugin-proposal-function-sent', - '@babel/plugin-proposal-function-bind', - '@babel/plugin-proposal-json-strings', - '@babel/plugin-proposal-logical-assignment-operators', - '@babel/plugin-proposal-nullish-coalescing-operator', - '@babel/plugin-proposal-numeric-separator', - '@babel/plugin-proposal-optional-catch-binding', - '@babel/plugin-proposal-optional-chaining', - '@babel/plugin-proposal-partial-application', - ['@babel/plugin-proposal-pipeline-operator', { proposal: 'minimal' }], - '@babel/plugin-proposal-throw-expressions', - '@babel/plugin-proposal-dynamic-import', - '@babel/plugin-syntax-bigint', - '@babel/plugin-syntax-import-meta', - ] - })).code; + code = (await babel.transformAsync(code, babelTransformOpts)).code; } code = processTopLevelAwait(code) || code; // it returns null when no TLA is found diff --git a/src/plugins/js-eval/processTopLevelAwait.js b/src/plugins/js-eval/processTopLevelAwait.js index 9b9f641..f2bc109 100644 --- a/src/plugins/js-eval/processTopLevelAwait.js +++ b/src/plugins/js-eval/processTopLevelAwait.js @@ -1,19 +1,41 @@ const babelParser = require('@babel/parser'); -const recast = require('recast'); +const babelTraverse = require('@babel/traverse').default; +const babelGenerator = require('@babel/generator').default; + +const babelParseOpts = { + allowAwaitOutsideFunction: true, + plugins: [ + 'throwExpressions', + 'bigInt', + ['decorators', { decoratorsBeforeExport: true }], + 'classProperties', + 'classPrivateProperties', + 'classPrivateMethods', + 'doExpressions', + 'dynamicImport', + 'exportDefaultFrom', + 'exportNamespaceFrom', + 'functionBind', + 'functionSent', + 'importMeta', + 'logicalAssignment', + 'nullishCoalescingOperator', + 'numericSeparator', + // 'objectRestSpread', + 'optionalCatchBinding', + 'optionalChaining', + 'partialApplication', + ['pipelineOperator', { proposal: 'minimal' }], + 'throwExpressions' + ] +}; -const b = recast.types.builders; function processTopLevelAwait(src) { let root; try { - root = recast.parse(src, { - parser: { - parse(src) { - return babelParser.parse(src, { allowAwaitOutsideFunction: true }); - } - } - }); + root = babelParser.parse(src, babelParseOpts); } catch (error) { return null; // if code is not valid, don't bother } @@ -21,33 +43,30 @@ function processTopLevelAwait(src) { let containsAwait = false; let containsReturn = false; - recast.visit(root, { - visitNode: function (path) { - const node = path.value; - - switch (node.type) { + babelTraverse(root, { + enter(path) { + switch (path.type) { + case 'FunctionDeclaration': case 'FunctionExpression': case 'ArrowFunctionExpression': case 'MethodDefinition': + case 'ClassMethod': // stop when entering a new function scope: - return false; + return path.stop(); case 'ForOfStatement': - if (node.await === true) { + if (path.node.await === true) { containsAwait = true; } - return this.traverse(path); + return; case 'AwaitExpression': containsAwait = true; - return this.traverse(path); + return; case 'ReturnStatement': containsReturn = true; - return this.traverse(path); - - default: - return this.traverse(path); + return; } } }); @@ -65,20 +84,26 @@ function processTopLevelAwait(src) { } // replace last node with a returnStatement of this node - root.program.body[root.program.body.length - 1] = b.returnStatement(last); - - const iiafe = b.callExpression( - b.arrowFunctionExpression( - [], - b.blockStatement(root.program.body), - true - ), - [] - ); - - iiafe.callee.async = true; - - return recast.print(iiafe).code; + root.program.body[root.program.body.length - 1] = { + type: 'ReturnStatement', + argument: last + }; + + const iiafe = { + type: 'CallExpression', + callee: { + type: 'ArrowFunctionExpression', + async: true, + params: [], + body: { + type: 'BlockStatement', + body: root.program.body + }, + }, + arguments: [] + }; + + return babelGenerator(iiafe).code; } module.exports = processTopLevelAwait;