diff --git a/async-to-promises.ts b/async-to-promises.ts index cf5ece8..96b1560 100644 --- a/async-to-promises.ts +++ b/async-to-promises.ts @@ -20,6 +20,7 @@ import { ForOfStatement, Function, FunctionExpression, + LogicalExpression, MemberExpression, NumericLiteral, ThisExpression, @@ -1117,6 +1118,19 @@ export default function({ declarators, expression: discardingIntrinsics(args[0]), }; + case "_continueIgnored": + const firstArgument = args[0]; + if ( + types.isCallExpression(firstArgument) && + (types.isIdentifier(firstArgument.callee) || types.isMemberExpression(firstArgument.callee)) + ) { + if (helperNameMap.get(firstArgument.callee) === "_continueIgnored") { + return { + declarators, + expression: firstArgument, + }; + } + } } } // Emit the call to the helper with the arguments @@ -2123,12 +2137,12 @@ export default function({ } // Emit a truthy literal, using 1/0 if minified - function booleanLiteral(value: boolean, minify?: boolean) { + function booleanLiteral(value: boolean, minify?: boolean): NumericLiteral | BooleanLiteral { return minify ? types.numericLiteral(value ? 1 : 0) : types.booleanLiteral(value); } // Emit an optimized conditional expression, simplifying if possible - function conditionalExpression(test: Expression, consequent: Expression, alternate: Expression) { + function conditionalExpression(test: Expression, consequent: Expression, alternate: Expression): Expression { const looseValue = extractLooseBooleanValue(test); if (typeof looseValue !== "undefined") { return looseValue ? consequent : alternate; @@ -2190,7 +2204,7 @@ export default function({ } // Emit a logical or, optimizing based on the truthiness of both sides - function logicalOr(left: Expression, right: Expression): Expression { + function logicalOr(left: L, right: R): LogicalExpression | L | R { if (extractLooseBooleanValue(left) === true) { return left; } else if (extractBooleanValue(left) === false) { @@ -2201,7 +2215,11 @@ export default function({ } // Emit a logical or, optimizing based on the loose truthiness of both sides assuming that the consumer of the expression only cares about truthiness - function logicalOrLoose(left: Expression, right: Expression, minify?: boolean): Expression { + function logicalOrLoose( + left: L, + right: R, + minify?: boolean + ): LogicalExpression | BooleanLiteral | NumericLiteral | L | R { switch (extractLooseBooleanValue(left)) { case false: return extractLooseBooleanValue(right) === false ? booleanLiteral(false, minify) : right; @@ -2676,16 +2694,18 @@ export default function({ // Build an expression that checks if a loop should be exited function buildBreakExitCheck( state: PluginState, - exitIdentifier: Identifier | undefined, - breakIdentifiers: { identifier: Identifier }[] + exitIdentifier?: Identifier | undefined, + breakIdentifiers?: { identifier: Identifier }[] ): Expression | undefined { - let expressions: Expression[] = (breakIdentifiers.map((identifier) => identifier.identifier) || []).concat( - exitIdentifier ? [exitIdentifier] : [] - ); - if (expressions.length) { - return expressions.reduce((accumulator, identifier) => - logicalOrLoose(accumulator, identifier, readConfigKey(state.opts, "minify")) - ); + if (breakIdentifiers !== undefined && breakIdentifiers.length > 0) { + const minify = readConfigKey(state.opts, "minify"); + const first = breakIdentifiers[0].identifier as Expression; + const partial = breakIdentifiers + .slice(1) + .reduce((accumulator, { identifier }) => logicalOrLoose(accumulator, identifier, minify), first); + return exitIdentifier ? logicalOrLoose(partial, exitIdentifier, minify) : partial; + } else { + return exitIdentifier; } } @@ -2784,27 +2804,43 @@ export default function({ function replaceReturnsAndBreaks( pluginState: PluginState, path: NodePath, - exitIdentifier?: Identifier + exitIdentifier: Identifier | undefined, + existingUsedIdentifiers?: BreakContinueItem[] ): BreakContinueItem[] { + const usedIdentifiers: BreakContinueItem[] = []; + // Import existing break identifiers that still exist in scope + if (existingUsedIdentifiers !== undefined) { + for (const item of existingUsedIdentifiers) { + if ( + path.parentPath.scope.getBinding(item.identifier.name) === + path.scope.getBinding(item.identifier.name) + ) { + usedIdentifiers.push(item); + } + } + } + // Search for new breaks const state = { pluginState, exitIdentifier, breakIdentifiers: breakContinueStackForPath(path), - usedIdentifiers: [] as BreakContinueItem[], + usedIdentifiers, }; path.traverse(replaceReturnsAndBreaksVisitor, state); - for (const identifier of state.usedIdentifiers) { - if (!identifier.path.parentPath.scope.getBinding(identifier.identifier.name)) { - identifier.path.parentPath.scope.push({ + // Add declarations for any new identifiers + for (const { identifier, path: identifierPath } of usedIdentifiers) { + const parentScope = identifierPath.parentPath.scope; + if (!parentScope.getBinding(identifier.name)) { + parentScope.push({ kind: "let", - id: identifier.identifier, + id: identifier, init: readConfigKey(pluginState.opts, "minify") ? undefined : booleanLiteral(false, readConfigKey(pluginState.opts, "minify")), }); } } - return state.usedIdentifiers; + return usedIdentifiers; } // Finds the break identifier associated with a path @@ -3083,6 +3119,7 @@ export default function({ }); } } + let breakIdentifiers: BreakContinueItem[] = []; for (const item of paths) { const parent = item.parent; if ( @@ -3094,10 +3131,11 @@ export default function({ isForAwaitStatement(parent) || parent.isLabeledStatement() ) { - item.breakIdentifiers = replaceReturnsAndBreaks( + breakIdentifiers = item.breakIdentifiers = replaceReturnsAndBreaks( pluginState, parent.get("body") as NodePath, - item.exitIdentifier + item.exitIdentifier, + breakIdentifiers ); if (parent.isForStatement()) { if ((item.forToIdentifiers = identifiersInForToLengthStatement(parent))) { @@ -3105,17 +3143,38 @@ export default function({ } } } else if (item.parent.isSwitchStatement()) { + breakIdentifiers = breakIdentifiers.slice(); item.cases = item.parent.get("cases").map((casePath) => { + const caseExits = pathsReturnOrThrow(casePath); + const caseBreaks = pathsBreak(casePath); + const caseBreakIdentifiers = (item.breakIdentifiers = replaceReturnsAndBreaks( + pluginState, + casePath, + item.exitIdentifier, + breakIdentifiers + )); + for (const breakItem of caseBreakIdentifiers) { + if ( + !breakIdentifiers.find((existing) => existing.identifier.name === breakItem.identifier.name) + ) { + breakIdentifiers.push(breakItem); + } + } return { casePath, - caseExits: pathsReturnOrThrow(casePath), - caseBreaks: pathsBreak(casePath), - breakIdentifiers: replaceReturnsAndBreaks(pluginState, casePath, item.exitIdentifier), + caseExits, + caseBreaks, + breakIdentifiers: caseBreakIdentifiers, test: casePath.node.test, }; }); - } else if (item.exitIdentifier) { - replaceReturnsAndBreaks(pluginState, parent, item.exitIdentifier); + } else { + breakIdentifiers = item.breakIdentifiers = replaceReturnsAndBreaks( + pluginState, + parent, + item.exitIdentifier, + breakIdentifiers + ); } } for (const { @@ -3184,7 +3243,7 @@ export default function({ const exitCheck = buildBreakExitCheck( pluginState, explicitExits.any && !explicitExits.all ? exitIdentifier : undefined, - [] + breakIdentifiers ); let expression: Expression | Statement = rewriteAsyncNode( state.generatorState, @@ -3321,7 +3380,7 @@ export default function({ exitIdentifier ), ]; - const exitCheck = buildBreakExitCheck(pluginState, exitIdentifier, breakIdentifiers || []); + const exitCheck = buildBreakExitCheck(pluginState, exitIdentifier, breakIdentifiers); if (exitCheck) { params.push( functionize( @@ -3373,7 +3432,7 @@ export default function({ } } else { let testExpression = parent.node.test; - const breakExitCheck = buildBreakExitCheck(pluginState, exitIdentifier, breakIdentifiers || []); + const breakExitCheck = buildBreakExitCheck(pluginState, exitIdentifier, breakIdentifiers); if (breakExitCheck) { const inverted = logicalNot(breakExitCheck, readConfigKey(pluginState.opts, "minify")); testExpression = diff --git a/tests/for break with identifier/hoisted.js b/tests/for break with identifier/hoisted.js index 48430dc..a2978a8 100644 --- a/tests/for break with identifier/hoisted.js +++ b/tests/for break with identifier/hoisted.js @@ -1 +1 @@ -_async(function(foo){let _loopInterrupt;function _temp(){_loopInterrupt=1;}loop:return _continueIgnored(_for(function(){return!_loopInterrupt;},void 0,function(){return _call(foo,_temp);}));}) \ No newline at end of file +_async(function(foo){let _loopInterrupt;function _temp(){_loopInterrupt=1;}return _continueIgnored(_for(function(){return!_loopInterrupt;},void 0,function(){return _call(foo,_temp);}));}) \ No newline at end of file diff --git a/tests/for break with identifier/inlined.js b/tests/for break with identifier/inlined.js index 9b4ebe6..e6036b9 100644 --- a/tests/for break with identifier/inlined.js +++ b/tests/for break with identifier/inlined.js @@ -1 +1 @@ -function(foo){try{let _loopInterrupt=false;const _temp=_for(function(){return!_loopInterrupt;},void 0,function(){return Promise.resolve(foo()).then(function(){_loopInterrupt=true;});});loop:return Promise.resolve(_temp&&_temp.then?_temp.then(function(){}):void 0);}catch(e){return Promise.reject(e);}} \ No newline at end of file +function(foo){try{let _loopInterrupt=false;const _temp=_for(function(){return!_loopInterrupt;},void 0,function(){return Promise.resolve(foo()).then(function(){_loopInterrupt=true;});});const _temp2=function(){if(_temp&&_temp.then)return _temp.then(function(){});}();return Promise.resolve(_temp2&&_temp2.then?_temp2.then(function(){}):void 0);}catch(e){return Promise.reject(e);}} \ No newline at end of file diff --git a/tests/for break with identifier/output.js b/tests/for break with identifier/output.js index be70b63..7cc2a85 100644 --- a/tests/for break with identifier/output.js +++ b/tests/for break with identifier/output.js @@ -1 +1 @@ -_async(foo=>{let _loopInterrupt=false;loop:return _continueIgnored(_for(()=>!_loopInterrupt,void 0,()=>_call(foo,()=>{_loopInterrupt=true;})));}) \ No newline at end of file +_async(foo=>{let _loopInterrupt=false;return _continueIgnored(_for(()=>!_loopInterrupt,void 0,()=>_call(foo,()=>{_loopInterrupt=true;})));}) \ No newline at end of file diff --git a/tests/for of await double with break and two labels/hoisted.js b/tests/for of await double with break and two labels/hoisted.js index eba376b..4194ede 100644 --- a/tests/for of await double with break and two labels/hoisted.js +++ b/tests/for of await double with break and two labels/hoisted.js @@ -1 +1 @@ -_async(function(matrix){let _outerInterrupt;var result=0;return _continue(_forOf(matrix,function(row){let _innerInterrupt;function _temp(_value){result+=_value;if(result>10){_outerInterrupt=_innerInterrupt=1;return;}if(result<0){_innerInterrupt=1;}}return _continueIgnored(_forOf(row,function(value){return _await(value,_temp);},function(){return _innerInterrupt||_outerInterrupt;}));}),function(){return result;});}) \ No newline at end of file +_async(function(matrix){let _outerInterrupt;var result=0;return _continue(_forOf(matrix,function(row){let _innerInterrupt;function _temp(_value){result+=_value;if(result>10){_outerInterrupt=_innerInterrupt=1;return;}if(result<0){_innerInterrupt=1;}}return _continueIgnored(_forOf(row,function(value){return _await(value,_temp);},function(){return _innerInterrupt||_outerInterrupt;}));},function(){return _outerInterrupt;}),function(){return result;});}) \ No newline at end of file diff --git a/tests/for of await double with break and two labels/inlined.js b/tests/for of await double with break and two labels/inlined.js index 9bad1c5..21e7672 100644 --- a/tests/for of await double with break and two labels/inlined.js +++ b/tests/for of await double with break and two labels/inlined.js @@ -1 +1 @@ -function(matrix){try{let _outerInterrupt=false;var result=0;const _temp2=_forOf(matrix,function(row){let _innerInterrupt=false;const _temp=_forOf(row,function(value){return Promise.resolve(value).then(function(_value){result+=_value;if(result>10){_outerInterrupt=_innerInterrupt=true;return;}if(result<0){_innerInterrupt=true;}});},function(){return _innerInterrupt||_outerInterrupt;});if(_temp&&_temp.then)return _temp.then(function(){});});return Promise.resolve(_temp2&&_temp2.then?_temp2.then(function(){return result;}):result);}catch(e){return Promise.reject(e);}} \ No newline at end of file +function(matrix){try{let _outerInterrupt=false;var result=0;const _temp2=_forOf(matrix,function(row){let _innerInterrupt=false;const _temp=_forOf(row,function(value){return Promise.resolve(value).then(function(_value){result+=_value;if(result>10){_outerInterrupt=_innerInterrupt=true;return;}if(result<0){_innerInterrupt=true;}});},function(){return _innerInterrupt||_outerInterrupt;});if(_temp&&_temp.then)return _temp.then(function(){});},function(){return _outerInterrupt;});return Promise.resolve(_temp2&&_temp2.then?_temp2.then(function(){return result;}):result);}catch(e){return Promise.reject(e);}} \ No newline at end of file diff --git a/tests/for of await double with break and two labels/output.js b/tests/for of await double with break and two labels/output.js index 38016b5..b620019 100644 --- a/tests/for of await double with break and two labels/output.js +++ b/tests/for of await double with break and two labels/output.js @@ -1 +1 @@ -_async(matrix=>{let _outerInterrupt=false;var result=0;return _continue(_forOf(matrix,row=>{let _innerInterrupt=false;return _continueIgnored(_forOf(row,value=>{return _await(value,(_value)=>{result+=_value;if(result>10){_outerInterrupt=_innerInterrupt=true;return;}if(result<0){_innerInterrupt=true;}});},()=>_innerInterrupt||_outerInterrupt));}),()=>result);}) \ No newline at end of file +_async(matrix=>{let _outerInterrupt=false;var result=0;return _continue(_forOf(matrix,row=>{let _innerInterrupt=false;return _continueIgnored(_forOf(row,value=>{return _await(value,(_value)=>{result+=_value;if(result>10){_outerInterrupt=_innerInterrupt=true;return;}if(result<0){_innerInterrupt=true;}});},()=>_innerInterrupt||_outerInterrupt));},()=>_outerInterrupt),()=>result);}) \ No newline at end of file diff --git a/tests/for of await double with break/hoisted.js b/tests/for of await double with break/hoisted.js index 3da0286..517fa63 100644 --- a/tests/for of await double with break/hoisted.js +++ b/tests/for of await double with break/hoisted.js @@ -1 +1 @@ -_async(function(matrix){let _outerInterrupt;function _outerInterrupt2(){return _outerInterrupt;}function _value2(value){return _await(value,_temp);}function _temp(_value){result+=_value;if(result>10){_outerInterrupt=1;}}var result=0;return _continue(_forOf(matrix,function(row){return _continueIgnored(_forOf(row,_value2,_outerInterrupt2));}),function(){return result;});}) \ No newline at end of file +_async(function(matrix){let _outerInterrupt;function _outerInterrupt2(){return _outerInterrupt;}function _value2(value){return _await(value,_temp);}function _temp(_value){result+=_value;if(result>10){_outerInterrupt=1;}}var result=0;return _continue(_forOf(matrix,function(row){return _continueIgnored(_forOf(row,_value2,_outerInterrupt2));},function(){return _outerInterrupt;}),function(){return result;});}) \ No newline at end of file diff --git a/tests/for of await double with break/inlined.js b/tests/for of await double with break/inlined.js index 16ec8b6..d04f73d 100644 --- a/tests/for of await double with break/inlined.js +++ b/tests/for of await double with break/inlined.js @@ -1 +1 @@ -function(matrix){try{let _outerInterrupt=false;var result=0;const _temp2=_forOf(matrix,function(row){const _temp=_forOf(row,function(value){return Promise.resolve(value).then(function(_value){result+=_value;if(result>10){_outerInterrupt=true;}});},function(){return _outerInterrupt;});if(_temp&&_temp.then)return _temp.then(function(){});});return Promise.resolve(_temp2&&_temp2.then?_temp2.then(function(){return result;}):result);}catch(e){return Promise.reject(e);}} \ No newline at end of file +function(matrix){try{let _outerInterrupt=false;var result=0;const _temp2=_forOf(matrix,function(row){const _temp=_forOf(row,function(value){return Promise.resolve(value).then(function(_value){result+=_value;if(result>10){_outerInterrupt=true;}});},function(){return _outerInterrupt;});if(_temp&&_temp.then)return _temp.then(function(){});},function(){return _outerInterrupt;});return Promise.resolve(_temp2&&_temp2.then?_temp2.then(function(){return result;}):result);}catch(e){return Promise.reject(e);}} \ No newline at end of file diff --git a/tests/for of await double with break/output.js b/tests/for of await double with break/output.js index 3a039c6..b9bee33 100644 --- a/tests/for of await double with break/output.js +++ b/tests/for of await double with break/output.js @@ -1 +1 @@ -_async(matrix=>{let _outerInterrupt=false;var result=0;return _continue(_forOf(matrix,row=>_continueIgnored(_forOf(row,value=>{return _await(value,(_value)=>{result+=_value;if(result>10){_outerInterrupt=true;}});},()=>_outerInterrupt))),()=>result);}) \ No newline at end of file +_async(matrix=>{let _outerInterrupt=false;var result=0;return _continue(_forOf(matrix,row=>_continueIgnored(_forOf(row,value=>{return _await(value,(_value)=>{result+=_value;if(result>10){_outerInterrupt=true;}});},()=>_outerInterrupt)),()=>_outerInterrupt),()=>result);}) \ No newline at end of file diff --git a/tests/while with inner break/hoisted.js b/tests/while with inner break/hoisted.js new file mode 100644 index 0000000..6404565 --- /dev/null +++ b/tests/while with inner break/hoisted.js @@ -0,0 +1 @@ +_async(function(){let _interrupt;function _temp3(){if(_interrupt)return;result=2;}function _temp2(){return _await(null,_temp);}function _temp(){result=1;_interrupt=1;}let result=0;return _continue(_for(function(){return!_interrupt;},void 0,function(){return _continue(_catch(_temp2,_empty),_temp3);}),function(){return result;});}) \ No newline at end of file diff --git a/tests/while with inner break/inlined.js b/tests/while with inner break/inlined.js new file mode 100644 index 0000000..b691fd3 --- /dev/null +++ b/tests/while with inner break/inlined.js @@ -0,0 +1 @@ +function(){try{let _interrupt=false;let result=0;const _temp3=_for(function(){return!_interrupt;},void 0,function(){function _temp2(){if(_interrupt)return;result=2;}const _temp=_catch(function(){return Promise.resolve(null).then(function(){result=1;_interrupt=true;});},function(){});return _temp&&_temp.then?_temp.then(_temp2):_temp2(_temp);});return Promise.resolve(_temp3&&_temp3.then?_temp3.then(function(){return result;}):result);}catch(e){return Promise.reject(e);}} \ No newline at end of file diff --git a/tests/while with inner break/input.js b/tests/while with inner break/input.js index d3bc56f..449d33c 100644 --- a/tests/while with inner break/input.js +++ b/tests/while with inner break/input.js @@ -5,8 +5,8 @@ async function() { await null; result = 1; break; + } catch (e) { } - catch {} result = 2; } return result; diff --git a/tests/while with inner break/output.js b/tests/while with inner break/output.js new file mode 100644 index 0000000..38d75ed --- /dev/null +++ b/tests/while with inner break/output.js @@ -0,0 +1 @@ +_async(()=>{let _interrupt=false;let result=0;return _continue(_for(()=>!_interrupt,void 0,()=>_continue(_catch(()=>_await(null,()=>{result=1;_interrupt=true;}),_empty),()=>{if(_interrupt)return;result=2;})),()=>result);}) \ No newline at end of file