Skip to content

Commit

Permalink
Fix subtle bug where nested super expressions could result in bad tra…
Browse files Browse the repository at this point in the history
…nsformed output.

This had to do with ensuring edits occur leafs first, root last in the AST. Super assignment expressions were being edited out of turn by invoking the edits on visiting the left side rather than the assignment expression itself, which could cause the parent assignment expression to be edited before the child right side expression is edited - causing overwriting of the output source.
  • Loading branch information
leebyron committed Oct 7, 2016
1 parent d8e0914 commit b1976f2
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 31 deletions.
82 changes: 51 additions & 31 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ var asyncToGenVisitor = {
MemberExpression: {
leave: leaveMemberExpression
},
AssignmentExpression: {
leave: leaveAssignmentExpression
},
ForAwaitStatement: {
leave: leaveForAwait
}
Expand Down Expand Up @@ -360,16 +363,60 @@ function leaveMemberExpression(editor, node, ast, stack) {
// Only transform super member expressions.
if (node.object.type !== 'Super') return;

var contextNode = stack.parent;

// Do not transform delete unary or left-hand-side expressions.
if (
contextNode.operator === 'delete' ||
contextNode.type === 'AssignmentExpression' && contextNode.left === node
) return;

// Only within transformed async function scopes.
var envRecord = currentScope(ast);
if (!envRecord.async) return;

var contextNode = stack.parent;
envRecord.referencesSuper = true;

convertSuperMember(editor, node, ast);

editor.overwrite(node.object.start, node.object.end, '$uper(');
editor.insertLeft(node.end, ')');

// Ensure super.prop() use the current this binding.
if (contextNode.type === 'CallExpression') {
envRecord.referencesThis = true;
var idx = findTokenIndex(ast.tokens, node.end);
while (ast.tokens[idx].type.label !== '(') {
idx++;
}
editor.overwrite(ast.tokens[idx].start, ast.tokens[idx].end, contextNode.arguments.length ? '.call(this,' : '.call(this');
}
}

function leaveAssignmentExpression(editor, node, ast, stack) {
// Only transform super assignment expressions.
var left = node.left;
if (left.type !== 'MemberExpression' || left.object.type !== 'Super') return;

// Only within transformed async function scopes.
var envRecord = currentScope(ast);
if (!envRecord.async) return;

envRecord.referencesSuperEq = true;

convertSuperMember(editor, left, ast);

// Do not transform delete unary expressions.
if (contextNode.operator === 'delete') return;
editor.overwrite(left.object.start, left.object.end, '$uperEq(');
editor.insertLeft(node.end, ')')

// Convert member property to function argument
var idx = findTokenIndex(ast.tokens, left.end);
while (ast.tokens[idx].type.label !== '=') {
idx++;
}
editor.overwrite(ast.tokens[idx].start, ast.tokens[idx].end, ',');
}

function convertSuperMember(editor, node, ast) {
var idx = findTokenIndex(ast.tokens, node.object.end);
while (ast.tokens[idx].type.label !== (node.computed ? '[' : '.')) {
idx++;
Expand All @@ -381,33 +428,6 @@ function leaveMemberExpression(editor, node, ast, stack) {
editor.insertRight(node.property.start, '"');
editor.insertLeft(node.property.end, '"');
}

// super.prop = value
if (contextNode.type === 'AssignmentExpression' && contextNode.left === node) {
envRecord.referencesSuperEq = true;
editor.overwrite(node.object.start, node.object.end, '$uperEq(');
editor.insertRight(contextNode.end, ')')

var idx = findTokenIndex(ast.tokens, node.end);
while (ast.tokens[idx].type.label !== '=') {
idx++;
}
editor.overwrite(ast.tokens[idx].start, ast.tokens[idx].end, ',');
} else {
envRecord.referencesSuper = true;
editor.overwrite(node.object.start, node.object.end, '$uper(');
editor.insertLeft(node.end, ')');

// Ensure super.prop() use the current this binding.
if (contextNode.type === 'CallExpression') {
envRecord.referencesThis = true;
var idx = findTokenIndex(ast.tokens, node.end);
while (ast.tokens[idx].type.label !== '(') {
idx++;
}
editor.overwrite(ast.tokens[idx].start, ast.tokens[idx].end, contextNode.arguments.length ? '.call(this,' : '.call(this');
}
}
}

function leaveForAwait(editor, node, ast) {
Expand Down
1 change: 1 addition & 0 deletions test/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class SuperDuper extends BaseClass {
barAsync() {return __async(function*($uper,$uperEq){
const arg = $uper("arg").call(this)
$uperEq("arg" , $uper("arg").call(this))
$uperEq("arg" , $uper($uperEq("arg" , $uper("arg").call(this))))

$uperEq( /*a*/ /*b*/ "arg" /*c*/ , /*d*/ $uper( /*e*/ /*f*/ "arg")) /*g*/
$uperEq( /*a*/ /*b*/ arg /*c*/ /*d*/ , /*e*/ $uper( /*f*/ /*g*/ arg /*h*/ )) /*i*/
Expand Down
1 change: 1 addition & 0 deletions test/source.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class SuperDuper extends BaseClass {
async barAsync() {
const arg = super.arg()
super.arg = super.arg()
super.arg = super[super.arg = super.arg()]

super /*a*/ . /*b*/ arg /*c*/ = /*d*/ super /*e*/ . /*f*/ arg /*g*/
super /*a*/ [ /*b*/ arg /*c*/ ] /*d*/ = /*e*/ super /*f*/ [ /*g*/ arg /*h*/ ] /*i*/
Expand Down

0 comments on commit b1976f2

Please sign in to comment.