Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

[scopes] Use the scope visitor to avoid rewriting shadowed bindings in console expressions #6087

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/utils/function.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export function findFunctionText(
return null;
}

const { location: { start, end } } = func;
const {
location: { start, end }
} = func;
const lines = source.text.split("\n");
const firstLine = lines[start.line - 1].slice(start.column);
const lastLine = lines[end.line - 1].slice(0, end.column);
Expand Down
3 changes: 3 additions & 0 deletions src/workers/parser/getScopes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// @flow

import {
buildScopeList,
parseSourceScopes,
type SourceScope,
type ParsedScope,
Expand Down Expand Up @@ -42,6 +43,8 @@ export function clearScopes() {
parsedScopesCache = new Map();
}

export { buildScopeList };

/**
* Searches all scopes and their bindings at the specific location.
*/
Expand Down
4 changes: 4 additions & 0 deletions src/workers/parser/getScopes/visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ export function parseSourceScopes(sourceId: SourceId): ?Array<ParsedScope> {
return null;
}

return buildScopeList(ast, sourceId);
}

export function buildScopeList(ast: Node, sourceId: SourceId) {
const { global, lexical } = createGlobalScope(ast, sourceId);

const state = {
Expand Down
73 changes: 50 additions & 23 deletions src/workers/parser/mapOriginalExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

// @flow

import type { ColumnPosition } from "../../types";
import { parseScript } from "./utils/ast";
import { buildScopeList } from "./getScopes";
import generate from "@babel/generator";
import * as t from "@babel/types";

Expand All @@ -28,48 +30,73 @@ function getFirstExpression(ast) {
return statements[0].expression;
}

function locationKey(start: ColumnPosition): string {
return `${start.line}:${start.column}`;
}

export default function mapOriginalExpression(
expression: string,
mappings: {
[string]: string | null
}
): string {
let didReplace = false;

const ast = parseScript(expression);
t.traverse(ast, (node, ancestors) => {
const parent = ancestors[ancestors.length - 1];
if (!parent) {
return;
}
const scopes = buildScopeList(ast, "");

const parentNode = parent.node;
const nodes = new Map();

let name = null;
if (t.isIdentifier(node) && t.isReferenced(node, parentNode)) {
name = node.name;
} else if (t.isThisExpression(node)) {
name = "this";
} else {
return;
const replacements = new Map();

// The ref-only global bindings are the ones that are accessed, but not
// declared anywhere in the parsed code, meaning they are either global,
// or declared somewhere in a scope outside the parsed code, so we
// rewrite all of those specifically to avoid rewritting declarations that
// shadow outer mappings.
for (const name of Object.keys(scopes[0].bindings)) {
const { refs } = scopes[0].bindings[name];
const mapping = mappings[name];
if (
!refs.every(ref => ref.type === "ref") ||
!mapping ||
mapping === name
) {
continue;
}

let node = nodes.get(name);
if (!node) {
node = getFirstExpression(parseScript(mapping));
nodes.set(name, node);
}

if (mappings.hasOwnProperty(name)) {
const mapping = mappings[name];
if (mapping && mapping !== name) {
const mappingNode = getFirstExpression(parseScript(mapping));
replaceNode(ancestors, mappingNode);
for (const ref of refs) {
let { line, column } = ref.start;

didReplace = true;
// This shouldn't happen, just keeping Flow happy.
if (typeof column !== "number") {
column = 0;
}

replacements.set(locationKey({ line, column }), node);
}
});
}

if (!didReplace) {
if (replacements.size === 0) {
// Avoid the extra code generation work and also avoid potentially
// reformatting the user's code unnecessarily.
return expression;
}

t.traverse(ast, (node, ancestors) => {
if (!t.isIdentifier(node) && !t.isThisExpression(node)) {
return;
}

const replacement = replacements.get(locationKey(node.loc.start));
if (replacement) {
replaceNode(ancestors, t.cloneNode(replacement));
}
});

return generate(ast).code;
}
13 changes: 13 additions & 0 deletions src/workers/parser/tests/mapOriginalExpression.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,17 @@ describe("mapOriginalExpression", () => {
});
expect(generatedExpression).toEqual("a + b");
});

it("shadowed bindings", () => {
const generatedExpression = mapOriginalExpression(
"window.thing = function fn(){ var a; a; b; }; a; b; ",
{
a: "_a",
b: "_b"
}
);
expect(generatedExpression).toEqual(
"window.thing = function fn() {\n var a;\n a;\n _b;\n};\n\n_a;\n_b;"
);
});
});