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

Commit c05f410

Browse files
loganfsmythjasonLaster
authored andcommitted
Handle out-of-order declarations in scopes. (#5690)
1 parent fb67a4f commit c05f410

File tree

12 files changed

+4976
-1319
lines changed

12 files changed

+4976
-1319
lines changed

src/test/mochitest/browser.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ support-files =
2727
examples/babel/fixtures/modules/output.js.map
2828
examples/babel/fixtures/non-modules/output.js
2929
examples/babel/fixtures/non-modules/output.js.map
30+
examples/babel/fixtures/out-of-order-declarations/output.js
31+
examples/babel/fixtures/out-of-order-declarations/output.js.map
3032
examples/babel/fixtures/flowtype-bindings/output.js
3133
examples/babel/fixtures/flowtype-bindings/output.js.map
3234
examples/babel/fixtures/step-over-for-of/output.js

src/test/mochitest/browser_dbg-babel-scopes.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,23 @@ add_task(async function() {
251251
"thirdModuleScoped()"
252252
]);
253253

254+
await breakpointScopes(dbg, "out-of-order-declarations", { line: 8, column: 4 }, [
255+
"callback",
256+
"fn()",
257+
["val", "undefined"],
258+
"root",
259+
["callback", "(optimized away)"],
260+
["fn", "(optimized away)"],
261+
["val", "(optimized away)"],
262+
"Module",
263+
264+
// This value is currently unmapped because import declarations don't map
265+
// very well and ones at the end of the file map especially badly.
266+
["aDefault", "(unmapped)"],
267+
["root", "(optimized away)"],
268+
["val", "(optimized away)"],
269+
]);
270+
254271
await breakpointScopes(dbg, "non-modules", { line: 7, column: 2 }, []);
255272

256273
await breakpointScopes(dbg, "flowtype-bindings", { line: 8, column: 2 }, [
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
var val = "outer-value";
2+
3+
export default function root() {
4+
var val = "middle-value";
5+
var fn = function outerFn(outer){};
6+
7+
function callback() {
8+
console.log("pause here", val, aDefault, fn);
9+
10+
var val = "inner-value";
11+
function fn(inner){};
12+
}
13+
14+
callback();
15+
}
16+
17+
import aDefault from "./src/mod";

src/test/mochitest/examples/babel/fixtures/out-of-order-declarations/output.js

Lines changed: 118 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/test/mochitest/examples/babel/fixtures/out-of-order-declarations/output.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default "a-default";

src/test/mochitest/examples/doc-babel.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
<button onclick="modules()">Run modules</button>
3232
<script src="babel/fixtures/non-modules/output.js"></script>
3333
<button onclick="nonModules()">Run nonModules</button>
34+
<script src="babel/fixtures/out-of-order-declarations/output.js"></script>
35+
<button onclick="outOfOrderDeclarations()">Run outOfOrderDeclarations</button>
3436
<script src="babel/fixtures/shadowed-vars/output.js"></script>
3537
<button onclick="shadowedVars()">Run shadowedVars</button>
3638
<script src="babel/fixtures/step-over-for-of/output.js"></script>

src/workers/parser/getScopes/visitor.js

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ type TempScope = {
119119

120120
type ScopeCollectionVisitorState = {
121121
sourceId: SourceId,
122+
freeVariables: Map<string, Array<BindingLocation>>,
123+
freeVariableStack: Array<Map<string, Array<BindingLocation>>>,
122124
scope: TempScope,
123125
scopeStack: Array<TempScope>
124126
};
@@ -133,11 +135,20 @@ export function parseSourceScopes(sourceId: SourceId): ?Array<ParsedScope> {
133135

134136
const state = {
135137
sourceId,
138+
freeVariables: new Map(),
139+
freeVariableStack: [],
136140
scope: lexical,
137141
scopeStack: []
138142
};
139143
t.traverse(ast, scopeCollectionVisitor, state);
140144

145+
for (const [key, freeVariables] of state.freeVariables) {
146+
const binding = global.bindings[key];
147+
if (binding) {
148+
binding.refs = freeVariables.concat(binding.refs);
149+
}
150+
}
151+
141152
// TODO: This should probably check for ".mjs" extension on the
142153
// original file, and should also be skipped if the the generated
143154
// code is an ES6 module rather than a script.
@@ -206,6 +217,9 @@ function pushTempScope(
206217
const scope = createTempScope(type, displayName, state.scope, loc);
207218

208219
state.scope = scope;
220+
221+
state.freeVariableStack.push(state.freeVariables);
222+
state.freeVariables = new Map();
209223
return scope;
210224
}
211225

@@ -305,19 +319,6 @@ function isLexicalVariable(node) {
305319
return isNode(node, "VariableDeclaration") && isLetOrConst(node);
306320
}
307321

308-
function findIdentifierInScopes(
309-
scope: TempScope,
310-
name: string
311-
): TempScope | null {
312-
// Find nearest outer scope with the specifed name and add reference.
313-
for (let s = scope; s; s = s.parent) {
314-
if (name in s.bindings) {
315-
return s;
316-
}
317-
}
318-
return null;
319-
}
320-
321322
function createGlobalScope(
322323
ast: BabelNode,
323324
sourceId: SourceId
@@ -579,25 +580,31 @@ const scopeCollectionVisitor = {
579580
}
580581
});
581582
} else if (t.isIdentifier(node) && t.isReferenced(node, parentNode)) {
582-
const identScope = findIdentifierInScopes(state.scope, node.name);
583-
if (identScope) {
584-
identScope.bindings[node.name].refs.push({
585-
type: "ref",
586-
start: fromBabelLocation(node.loc.start, state.sourceId),
587-
end: fromBabelLocation(node.loc.end, state.sourceId),
588-
meta: buildMetaBindings(state.sourceId, node, ancestors)
589-
});
583+
let freeVariables = state.freeVariables.get(node.name);
584+
if (!freeVariables) {
585+
freeVariables = [];
586+
state.freeVariables.set(node.name, freeVariables);
590587
}
588+
589+
freeVariables.push({
590+
type: "ref",
591+
start: fromBabelLocation(node.loc.start, state.sourceId),
592+
end: fromBabelLocation(node.loc.end, state.sourceId),
593+
meta: buildMetaBindings(state.sourceId, node, ancestors)
594+
});
591595
} else if (t.isThisExpression(node)) {
592-
const identScope = findIdentifierInScopes(state.scope, "this");
593-
if (identScope) {
594-
identScope.bindings.this.refs.push({
595-
type: "ref",
596-
start: fromBabelLocation(node.loc.start, state.sourceId),
597-
end: fromBabelLocation(node.loc.end, state.sourceId),
598-
meta: buildMetaBindings(state.sourceId, node, ancestors)
599-
});
596+
let freeVariables = state.freeVariables.get("this");
597+
if (!freeVariables) {
598+
freeVariables = [];
599+
state.freeVariables.set("this", freeVariables);
600600
}
601+
602+
freeVariables.push({
603+
type: "ref",
604+
start: fromBabelLocation(node.loc.start, state.sourceId),
605+
end: fromBabelLocation(node.loc.end, state.sourceId),
606+
meta: buildMetaBindings(state.sourceId, node, ancestors)
607+
});
601608
} else if (t.isClassProperty(parentNode, { value: node })) {
602609
const scope = pushTempScope(state, "function", "Class Field", {
603610
start: fromBabelLocation(node.loc.start, state.sourceId),
@@ -628,12 +635,49 @@ const scopeCollectionVisitor = {
628635
ancestors: TraversalAncestors,
629636
state: ScopeCollectionVisitorState
630637
) {
631-
const scope = state.scopeStack.pop();
632-
if (!scope) {
638+
const currentScope = state.scope;
639+
const parentScope = state.scopeStack.pop();
640+
if (!parentScope) {
633641
throw new Error("Assertion failure - unsynchronized pop");
634642
}
643+
state.scope = parentScope;
644+
645+
// It is possible, as in the case of function expressions, that a single
646+
// node has added multiple scopes, so we need to traverse upward here
647+
// rather than jumping stright to 'parentScope'.
648+
for (
649+
let scope = currentScope;
650+
scope && scope !== parentScope;
651+
scope = scope.parent
652+
) {
653+
const freeVariables = state.freeVariables;
654+
state.freeVariables = state.freeVariableStack.pop();
655+
const parentFreeVariables = state.freeVariables;
656+
657+
// Match up any free variables that match this scope's bindings and
658+
// merge then into the refs.
659+
for (const key of Object.keys(scope.bindings)) {
660+
const binding = scope.bindings[key];
661+
662+
const freeVars = freeVariables.get(key);
663+
if (freeVars) {
664+
binding.refs.push(...freeVars);
665+
freeVariables.delete(key);
666+
}
667+
}
635668

636-
state.scope = scope;
669+
// Move any undeclared references in this scope into the parent for
670+
// processing in higher scopes.
671+
for (const [key, value] of freeVariables) {
672+
let refs = parentFreeVariables.get(key);
673+
if (!refs) {
674+
refs = [];
675+
parentFreeVariables.set(key, refs);
676+
}
677+
678+
refs.push(...value);
679+
}
680+
}
637681
}
638682
};
639683

0 commit comments

Comments
 (0)