UNPKG

2.83 kBJavaScriptView Raw
1import {
2 isBlockScopedDeclaration,
3 isFunctionScopedDeclaration,
4 isNonTopLevelDeclaration,
5} from "./parser/tokenizer";
6
7import {TokenType as tt} from "./parser/tokenizer/types";
8
9
10/**
11 * Traverse the given tokens and modify them if necessary to indicate that some names shadow global
12 * variables.
13 */
14export default function identifyShadowedGlobals(
15 tokens,
16 scopes,
17 globalNames,
18) {
19 if (!hasShadowedGlobals(tokens, globalNames)) {
20 return;
21 }
22 markShadowedGlobals(tokens, scopes, globalNames);
23}
24
25/**
26 * We can do a fast up-front check to see if there are any declarations to global names. If not,
27 * then there's no point in computing scope assignments.
28 */
29// Exported for testing.
30export function hasShadowedGlobals(tokens, globalNames) {
31 for (const token of tokens.tokens) {
32 if (
33 token.type === tt.name &&
34 isNonTopLevelDeclaration(token) &&
35 globalNames.has(tokens.identifierNameForToken(token))
36 ) {
37 return true;
38 }
39 }
40 return false;
41}
42
43function markShadowedGlobals(
44 tokens,
45 scopes,
46 globalNames,
47) {
48 const scopeStack = [];
49 let scopeIndex = scopes.length - 1;
50 // Scopes were generated at completion time, so they're sorted by end index, so we can maintain a
51 // good stack by going backwards through them.
52 for (let i = tokens.tokens.length - 1; ; i--) {
53 while (scopeStack.length > 0 && scopeStack[scopeStack.length - 1].startTokenIndex === i + 1) {
54 scopeStack.pop();
55 }
56 while (scopeIndex >= 0 && scopes[scopeIndex].endTokenIndex === i + 1) {
57 scopeStack.push(scopes[scopeIndex]);
58 scopeIndex--;
59 }
60 // Process scopes after the last iteration so we can make sure we pop all of them.
61 if (i < 0) {
62 break;
63 }
64
65 const token = tokens.tokens[i];
66 const name = tokens.identifierNameForToken(token);
67 if (scopeStack.length > 1 && token.type === tt.name && globalNames.has(name)) {
68 if (isBlockScopedDeclaration(token)) {
69 markShadowedForScope(scopeStack[scopeStack.length - 1], tokens, name);
70 } else if (isFunctionScopedDeclaration(token)) {
71 let stackIndex = scopeStack.length - 1;
72 while (stackIndex > 0 && !scopeStack[stackIndex].isFunctionScope) {
73 stackIndex--;
74 }
75 if (stackIndex < 0) {
76 throw new Error("Did not find parent function scope.");
77 }
78 markShadowedForScope(scopeStack[stackIndex], tokens, name);
79 }
80 }
81 }
82 if (scopeStack.length > 0) {
83 throw new Error("Expected empty scope stack after processing file.");
84 }
85}
86
87function markShadowedForScope(scope, tokens, name) {
88 for (let i = scope.startTokenIndex; i < scope.endTokenIndex; i++) {
89 const token = tokens.tokens[i];
90 if (token.type === tt.name && tokens.identifierNameForToken(token) === name) {
91 token.shadowsGlobal = true;
92 }
93 }
94}