import * as t from "babel-types"; export default function simplifyAccess(path: NodePath, bindingNames) { path.traverse(simpleAssignmentVisitor, { scope: path.scope, bindingNames, seen: new WeakSet(), }); } const simpleAssignmentVisitor = { UpdateExpression: { exit(path) { const { scope, bindingNames } = this; const arg = path.get("argument"); if (!arg.isIdentifier()) return; const localName = arg.node.name; if (!bindingNames.has(localName)) return; // redeclared in this scope if (scope.getBinding(localName) !== path.scope.getBinding(localName)) { return; } if ( path.node.prefix || (path.parentPath.isExpressionStatement() && !path.isCompletionRecord()) ) { // ++i => (i += 1); path.replaceWith( t.assignmentExpression("+=", arg.node, t.numericLiteral(1)), ); } else { const varName = path.scope.generateDeclaredUidIdentifier("old"); const assignment = t.binaryExpression( path.node.operator.slice(0, 1), varName, t.numericLiteral(1), ); // i++ => (_tmp = i, i = _tmp + 1, _tmp) path.replaceWith( t.sequenceExpression([ t.assignmentExpression("=", varName, arg.node), t.assignmentExpression("=", arg.node, assignment), varName, ]), ); } }, }, AssignmentExpression: { exit(path) { const { scope, seen, bindingNames } = this; if (path.node.operator === "=") return; if (seen.has(path.node)) return; seen.add(path.node); const left = path.get("left"); if (!left.isIdentifier()) return; // Simple update-assign foo += 1; // => exports.foo = (foo += 1); const localName = left.node.name; if (!bindingNames.has(localName)) return; // redeclared in this scope if (scope.getBinding(localName) !== path.scope.getBinding(localName)) { return; } path.node.right = t.binaryExpression( path.node.operator.slice(0, -1), path.node.left, path.node.right, ); path.node.operator = "="; }, }, };