From 39e7bb87e28f5c3a878025fec9db23161054ba83 Mon Sep 17 00:00:00 2001 From: Ryan Petrich Date: Sat, 20 Apr 2019 16:39:54 -0400 Subject: [PATCH] Support default parameters internally so that exceptions are converted to a rejected Promise and so that it doesn't interfere with babel's rewriting passes (closes #28) --- async-to-promises.ts | 33 ++++++++++++++++++- .../hoisted.js | 1 + .../inlined.js | 1 + .../class methods with default this/input.js | 13 ++++++++ .../class methods with default this/output.js | 1 + 5 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/class methods with default this/hoisted.js create mode 100644 tests/class methods with default this/inlined.js create mode 100644 tests/class methods with default this/input.js create mode 100644 tests/class methods with default this/output.js diff --git a/async-to-promises.ts b/async-to-promises.ts index bf159b3..e50b6e1 100644 --- a/async-to-promises.ts +++ b/async-to-promises.ts @@ -1,4 +1,4 @@ -import { ArrowFunctionExpression, AwaitExpression, BlockStatement, CallExpression, LabeledStatement, Node, Expression, FunctionDeclaration, Statement, Identifier, ForStatement, ForInStatement, SpreadElement, ReturnStatement, ForOfStatement, Function, FunctionExpression, MemberExpression, NumericLiteral, ThisExpression, SwitchCase, Program, VariableDeclaration, VariableDeclarator, StringLiteral, BooleanLiteral, Pattern, LVal, YieldExpression } from "babel-types"; +import { ArrowFunctionExpression, AwaitExpression, BlockStatement, CallExpression, ClassMethod, LabeledStatement, Node, Expression, FunctionDeclaration, Statement, Identifier, ForStatement, ForInStatement, SpreadElement, ReturnStatement, ForOfStatement, Function, FunctionExpression, MemberExpression, NumericLiteral, ThisExpression, SwitchCase, Program, VariableDeclaration, VariableDeclarator, StringLiteral, BooleanLiteral, Pattern, LVal, YieldExpression } from "babel-types"; import { NodePath, Scope, Visitor } from "babel-traverse"; import { readFileSync } from "fs"; import { join } from "path"; @@ -1659,6 +1659,9 @@ export default function({ types, template, traverse, transformFromAst, version } if (path.isArrayExpression()) { return path.get("elements").every(path => path === null || path.node === null ? true : isExpressionOfLiterals(path as NodePath, literalNames)); } + if (path.isNullLiteral()) { + return true; + } if (path.isObjectExpression()) { return path.get("properties").every(path => { if (!path.isObjectProperty()) { @@ -3263,6 +3266,32 @@ export default function({ types, template, traverse, transformFromAst, version } } } + // Rewrite function arguments with default values to be check statements inserted into the body + function rewriteDefaultArguments(targetPath: NodePath | NodePath, pluginState: PluginState) { + const statements: Statement[] = []; + const params = targetPath.get("params"); + const literals: string[] = []; + for (let i = 0; i < params.length; i++) { + const param = params[i]; + if (param.isAssignmentPattern()) { + const init = param.get("right"); + if (!isExpressionOfLiterals(init, literals)) { + const id = param.get("left").node; + const initNode = init.node; + param.replaceWith(id); + const isMissing = types.binaryExpression("===", id, types.identifier("undefined")); + const assignment = types.expressionStatement(types.assignmentExpression("=", id, initNode)); + statements.push(types.ifStatement(isMissing, assignment)); + } + } else if (param.isIdentifier()) { + literals.push(param.node.name); + } + } + if (statements.length) { + targetPath.node.body.body = statements.concat(targetPath.node.body.body); + } + } + // Main babel plugin implementation and top level visitor return { manipulateOptions(options: any, parserOptions: { plugins: string[] }) { @@ -3315,6 +3344,7 @@ export default function({ types, template, traverse, transformFromAst, version } reorderPathBeforeSiblingStatements(targetPath); return; } + rewriteDefaultArguments(path, this); rewriteThisArgumentsAndHoistFunctions(path, path, false); const bodyPath = path.get("body"); if (path.node.generator) { @@ -3389,6 +3419,7 @@ export default function({ types, template, traverse, transformFromAst, version } const body = path.get("body"); let newBody: NodePath; if (path.node.kind === "method") { + rewriteDefaultArguments(path, this); body.replaceWith(types.blockStatement([ body.node ])); diff --git a/tests/class methods with default this/hoisted.js b/tests/class methods with default this/hoisted.js new file mode 100644 index 0000000..b976af1 --- /dev/null +++ b/tests/class methods with default this/hoisted.js @@ -0,0 +1 @@ +function(){const foo=_async(function(result){if(result===undefined)result=Promise.resolve(true);return result;});return class{foo(baz){const _this=this;return _call(function(){if(baz===undefined)baz=_this.bar();return _await(baz);});}bar(result=foo()){return result;}};} \ No newline at end of file diff --git a/tests/class methods with default this/inlined.js b/tests/class methods with default this/inlined.js new file mode 100644 index 0000000..007593d --- /dev/null +++ b/tests/class methods with default this/inlined.js @@ -0,0 +1 @@ +function(){const foo=function(result){try{if(result===undefined)result=Promise.resolve(true);return Promise.resolve(result);}catch(e){return Promise.reject(e);}};return class{foo(baz){try{const _this=this;if(baz===undefined)baz=_this.bar();return Promise.resolve(baz);}catch(e){return Promise.reject(e);}}bar(result=foo()){return result;}};} \ No newline at end of file diff --git a/tests/class methods with default this/input.js b/tests/class methods with default this/input.js new file mode 100644 index 0000000..7dc5d22 --- /dev/null +++ b/tests/class methods with default this/input.js @@ -0,0 +1,13 @@ +function() { + async function foo(result = Promise.resolve(true)) { + return result; + } + return class { + async foo(baz = this.bar()) { + return await baz; + } + bar(result = foo()) { + return result; + } + }; +} diff --git a/tests/class methods with default this/output.js b/tests/class methods with default this/output.js new file mode 100644 index 0000000..caed68a --- /dev/null +++ b/tests/class methods with default this/output.js @@ -0,0 +1 @@ +function(){const foo=_async((result)=>{if(result===undefined)result=Promise.resolve(true);return result;});return class{foo(baz){const _this=this;return _call(()=>{if(baz===undefined)baz=_this.bar();return _await(baz);});}bar(result=foo()){return result;}};} \ No newline at end of file