tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

mapOriginalExpression.js (2996B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
      4 
      5 import { parseScript } from "./utils/ast";
      6 import { buildScopeList } from "./getScopes";
      7 import generate from "@babel/generator";
      8 import * as t from "@babel/types";
      9 
     10 // NOTE: this will only work if we are replacing an original identifier
     11 function replaceNode(ancestors, node) {
     12  const ancestor = ancestors[ancestors.length - 1];
     13 
     14  if (typeof ancestor.index === "number") {
     15    ancestor.node[ancestor.key][ancestor.index] = node;
     16  } else {
     17    ancestor.node[ancestor.key] = node;
     18  }
     19 }
     20 
     21 function getFirstExpression(ast) {
     22  const statements = ast.program.body;
     23  if (!statements.length) {
     24    return null;
     25  }
     26 
     27  return statements[0].expression;
     28 }
     29 
     30 function locationKey(start) {
     31  return `${start.line}:${start.column}`;
     32 }
     33 
     34 export default function mapOriginalExpression(expression, ast, mappings) {
     35  const scopes = buildScopeList(ast, "");
     36  let shouldUpdate = false;
     37 
     38  const nodes = new Map();
     39  const replacements = new Map();
     40 
     41  // The ref-only global bindings are the ones that are accessed, but not
     42  // declared anywhere in the parsed code, meaning they are either global,
     43  // or declared somewhere in a scope outside the parsed code, so we
     44  // rewrite all of those specifically to avoid rewritting declarations that
     45  // shadow outer mappings.
     46  for (const name of Object.keys(scopes[0].bindings)) {
     47    const { refs } = scopes[0].bindings[name];
     48    const mapping = mappings[name];
     49 
     50    if (
     51      !refs.every(ref => ref.type === "ref") ||
     52      !mapping ||
     53      mapping === name
     54    ) {
     55      continue;
     56    }
     57 
     58    let node = nodes.get(name);
     59    if (!node) {
     60      node = getFirstExpression(parseScript(mapping));
     61      nodes.set(name, node);
     62    }
     63 
     64    for (const ref of refs) {
     65      let { line, column } = ref.start;
     66 
     67      // This shouldn't happen, just keeping Flow happy.
     68      if (typeof column !== "number") {
     69        column = 0;
     70      }
     71 
     72      replacements.set(locationKey({ line, column }), node);
     73    }
     74  }
     75 
     76  if (replacements.size === 0) {
     77    // Avoid the extra code generation work and also avoid potentially
     78    // reformatting the user's code unnecessarily.
     79    return expression;
     80  }
     81 
     82  t.traverse(ast, (node, ancestors) => {
     83    if (!t.isIdentifier(node) && !t.isThisExpression(node)) {
     84      return;
     85    }
     86 
     87    const ancestor = ancestors[ancestors.length - 1];
     88    // Shorthand properties can have a key and value with `node.loc.start` value
     89    // and we only want to replace the value.
     90    if (t.isObjectProperty(ancestor.node) && ancestor.key !== "value") {
     91      return;
     92    }
     93 
     94    const replacement = replacements.get(locationKey(node.loc.start));
     95    if (replacement) {
     96      replaceNode(ancestors, t.cloneNode(replacement));
     97      shouldUpdate = true;
     98    }
     99  });
    100 
    101  if (shouldUpdate) {
    102    return generate(ast).code;
    103  }
    104 
    105  return expression;
    106 }