summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/workers/parser/mapBindings.js
blob: 5c6b40c045d596fa72d122ad302721f7ee300d31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */

// @flow

import { replaceNode } from "./utils/ast";
import { isTopLevel } from "./utils/helpers";

import generate from "@babel/generator";
import * as t from "@babel/types";

function getAssignmentTarget(node, bindings) {
  if (t.isObjectPattern(node)) {
    for (const property of node.properties) {
      if (t.isRestElement(property)) {
        property.argument = getAssignmentTarget(property.argument, bindings);
      } else {
        property.value = getAssignmentTarget(property.value, bindings);
      }
    }

    return node;
  }

  if (t.isArrayPattern(node)) {
    for (const [i, element] of node.elements.entries()) {
      node.elements[i] = getAssignmentTarget(element, bindings);
    }

    return node;
  }

  if (t.isAssignmentPattern(node)) {
    node.left = getAssignmentTarget(node.left, bindings);

    return node;
  }

  if (t.isRestElement(node)) {
    node.argument = getAssignmentTarget(node.argument, bindings);

    return node;
  }

  if (t.isIdentifier(node)) {
    return bindings.includes(node.name)
      ? node
      : t.memberExpression(t.identifier("self"), node);
  }

  return node;
}

// translates new bindings `var a = 3` into `self.a = 3`
// and existing bindings `var a = 3` into `a = 3` for re-assignments
function globalizeDeclaration(node, bindings) {
  return node.declarations.map(declaration =>
    t.expressionStatement(
      t.assignmentExpression(
        "=",
        getAssignmentTarget(declaration.id, bindings),
        declaration.init || t.unaryExpression("void", t.numericLiteral(0))
      )
    )
  );
}

// translates new bindings `a = 3` into `self.a = 3`
// and keeps assignments the same for existing bindings.
function globalizeAssignment(node, bindings) {
  return t.assignmentExpression(
    node.operator,
    getAssignmentTarget(node.left, bindings),
    node.right
  );
}

export default function mapExpressionBindings(
  expression: string,
  ast?: Object,
  bindings: string[] = []
): string {
  let isMapped = false;
  let shouldUpdate = true;

  t.traverse(ast, (node, ancestors) => {
    const parent = ancestors[ancestors.length - 1];

    if (t.isWithStatement(node)) {
      shouldUpdate = false;
      return;
    }

    if (!isTopLevel(ancestors)) {
      return;
    }

    if (t.isAssignmentExpression(node)) {
      if (t.isIdentifier(node.left) || t.isPattern(node.left)) {
        const newNode = globalizeAssignment(node, bindings);
        isMapped = true;
        return replaceNode(ancestors, newNode);
      }

      return;
    }

    if (!t.isVariableDeclaration(node)) {
      return;
    }

    if (!t.isForStatement(parent.node)) {
      const newNodes = globalizeDeclaration(node, bindings);
      isMapped = true;
      replaceNode(ancestors, newNodes);
    }
  });

  if (!shouldUpdate || !isMapped) {
    return expression;
  }

  return generate(ast).code;
}