UNPKG

5.09 kBPlain TextView Raw
1import assertNever from 'assert-never';
2import {ancestor as walk} from 'babel-walk';
3import * as t from '@babel/types';
4import isReferenced from './reference';
5
6const isScope = (node: t.Node) => t.isFunctionParent(node) || t.isProgram(node);
7const isBlockScope = (node: t.Node) =>
8 t.isBlockStatement(node) || isScope(node);
9
10const declaresArguments = (node: t.Node) =>
11 t.isFunction(node) && !t.isArrowFunctionExpression(node);
12
13const declaresThis = declaresArguments;
14
15const LOCALS_SYMBOL = Symbol('locals');
16
17const getLocals = (node: t.Node): Set<string> | undefined =>
18 (node as any)[LOCALS_SYMBOL];
19const declareLocals = (node: t.Node): Set<string> =>
20 ((node as any)[LOCALS_SYMBOL] = (node as any)[LOCALS_SYMBOL] || new Set());
21
22const setLocal = (node: t.Node, name: string) => declareLocals(node).add(name);
23
24// First pass
25
26function declareFunction(node: t.Function) {
27 for (const param of node.params) {
28 declarePattern(param, node);
29 }
30 const id = (node as t.FunctionDeclaration).id;
31 if (id) {
32 setLocal(node, id.name);
33 }
34}
35
36function declarePattern(node: t.LVal, parent: t.Node) {
37 switch (node.type) {
38 case 'Identifier':
39 setLocal(parent, node.name);
40 break;
41 case 'ObjectPattern':
42 for (const prop of node.properties) {
43 switch (prop.type) {
44 case 'RestElement':
45 declarePattern(prop.argument, parent);
46 break;
47 case 'ObjectProperty':
48 declarePattern(prop.value as t.LVal, parent);
49 break;
50 default:
51 assertNever(prop);
52 break;
53 }
54 }
55 break;
56 case 'ArrayPattern':
57 for (const element of node.elements) {
58 if (element) declarePattern(element, parent);
59 }
60 break;
61 case 'RestElement':
62 declarePattern(node.argument, parent);
63 break;
64 case 'AssignmentPattern':
65 declarePattern(node.left, parent);
66 break;
67 // istanbul ignore next
68 default:
69 throw new Error('Unrecognized pattern type: ' + node.type);
70 }
71}
72
73function declareModuleSpecifier(
74 node:
75 | t.ImportSpecifier
76 | t.ImportDefaultSpecifier
77 | t.ImportNamespaceSpecifier,
78 _state: unknown,
79 parents: t.Node[],
80) {
81 for (let i = parents.length - 2; i >= 0; i--) {
82 if (isScope(parents[i])) {
83 setLocal(parents[i], node.local.name);
84 return;
85 }
86 }
87}
88
89const firstPass = walk({
90 VariableDeclaration(node, _state, parents) {
91 for (let i = parents.length - 2; i >= 0; i--) {
92 if (
93 node.kind === 'var'
94 ? t.isFunctionParent(parents[i])
95 : isBlockScope(parents[i])
96 ) {
97 for (const declaration of node.declarations) {
98 declarePattern(declaration.id, parents[i]);
99 }
100 return;
101 }
102 }
103 },
104 FunctionDeclaration(node, _state, parents) {
105 if (node.id) {
106 for (let i = parents.length - 2; i >= 0; i--) {
107 if (isScope(parents[i])) {
108 setLocal(parents[i], node.id.name);
109 return;
110 }
111 }
112 }
113 },
114 Function: declareFunction,
115 ClassDeclaration(node, _state, parents) {
116 for (let i = parents.length - 2; i >= 0; i--) {
117 if (isScope(parents[i])) {
118 setLocal(parents[i], node.id.name);
119 return;
120 }
121 }
122 },
123 TryStatement(node) {
124 if (node.handler === null) return;
125 if (node.handler.param === null) return;
126 declarePattern(node.handler.param, node.handler);
127 },
128 ImportDefaultSpecifier: declareModuleSpecifier,
129 ImportSpecifier: declareModuleSpecifier,
130 ImportNamespaceSpecifier: declareModuleSpecifier,
131});
132
133// Second pass
134
135const secondPass = walk<{
136 globals: (t.Identifier | t.ThisExpression)[];
137}>({
138 Identifier(node, state, parents) {
139 const name = node.name;
140 if (name === 'undefined') return;
141
142 const lastParent = parents[parents.length - 2];
143 if (lastParent) {
144 if (!isReferenced(node, lastParent)) return;
145
146 for (const parent of parents) {
147 if (name === 'arguments' && declaresArguments(parent)) {
148 return;
149 }
150 if (getLocals(parent)?.has(name)) {
151 return;
152 }
153 }
154 }
155
156 state.globals.push(node);
157 },
158
159 ThisExpression(node, state, parents) {
160 for (const parent of parents) {
161 if (declaresThis(parent)) {
162 return;
163 }
164 }
165
166 state.globals.push(node);
167 },
168});
169
170export default function findGlobals(ast: t.Node) {
171 const globals: (t.Identifier | t.ThisExpression)[] = [];
172
173 // istanbul ignore if
174 if (!t.isNode(ast)) {
175 throw new TypeError('Source must be a Babylon AST');
176 }
177
178 firstPass(ast, undefined);
179 secondPass(ast, {globals});
180
181 const groupedGlobals = new Map<string, (t.Identifier | t.ThisExpression)[]>();
182 for (const node of globals) {
183 const name: string = node.type === 'ThisExpression' ? 'this' : node.name;
184 const existing = groupedGlobals.get(name);
185 if (existing) {
186 existing.push(node);
187 } else {
188 groupedGlobals.set(name, [node]);
189 }
190 }
191
192 return [...groupedGlobals]
193 .map(([name, nodes]) => ({name, nodes}))
194 .sort((a, b) => (a.name < b.name ? -1 : 1));
195}