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 }