1 | 'use strict';
|
2 |
|
3 | var acorn = require('acorn');
|
4 | var walk = require('acorn-walk');
|
5 |
|
6 | function isScope(node) {
|
7 | return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression' || node.type === 'Program';
|
8 | }
|
9 | function isBlockScope(node) {
|
10 |
|
11 | return node.type === 'BlockStatement' || node.type === 'SwitchStatement' || isScope(node);
|
12 | }
|
13 |
|
14 | function declaresArguments(node) {
|
15 | return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration';
|
16 | }
|
17 |
|
18 | function reallyParse(source, options) {
|
19 | var parseOptions = Object.assign(
|
20 | {
|
21 | allowReturnOutsideFunction: true,
|
22 | allowImportExportEverywhere: true,
|
23 | allowHashBang: true,
|
24 | ecmaVersion: "latest"
|
25 | },
|
26 | options
|
27 | );
|
28 | return acorn.parse(source, parseOptions);
|
29 | }
|
30 | module.exports = findGlobals;
|
31 | module.exports.parse = reallyParse;
|
32 | function findGlobals(source, options) {
|
33 | options = options || {};
|
34 | var globals = [];
|
35 | var ast;
|
36 |
|
37 | if (typeof source === 'string') {
|
38 | ast = reallyParse(source, options);
|
39 | } else {
|
40 | ast = source;
|
41 | }
|
42 |
|
43 | if (!(ast && typeof ast === 'object' && ast.type === 'Program')) {
|
44 | throw new TypeError('Source must be either a string of JavaScript or an acorn AST');
|
45 | }
|
46 | var declareFunction = function (node) {
|
47 | var fn = node;
|
48 | fn.locals = fn.locals || Object.create(null);
|
49 | node.params.forEach(function (node) {
|
50 | declarePattern(node, fn);
|
51 | });
|
52 | if (node.id) {
|
53 | fn.locals[node.id.name] = true;
|
54 | }
|
55 | };
|
56 | var declareClass = function (node) {
|
57 | node.locals = node.locals || Object.create(null);
|
58 | if (node.id) {
|
59 | node.locals[node.id.name] = true;
|
60 | }
|
61 | };
|
62 | var declarePattern = function (node, parent) {
|
63 | switch (node.type) {
|
64 | case 'Identifier':
|
65 | parent.locals[node.name] = true;
|
66 | break;
|
67 | case 'ObjectPattern':
|
68 | node.properties.forEach(function (node) {
|
69 | declarePattern(node.value || node.argument, parent);
|
70 | });
|
71 | break;
|
72 | case 'ArrayPattern':
|
73 | node.elements.forEach(function (node) {
|
74 | if (node) declarePattern(node, parent);
|
75 | });
|
76 | break;
|
77 | case 'RestElement':
|
78 | declarePattern(node.argument, parent);
|
79 | break;
|
80 | case 'AssignmentPattern':
|
81 | declarePattern(node.left, parent);
|
82 | break;
|
83 |
|
84 | default:
|
85 | throw new Error('Unrecognized pattern type: ' + node.type);
|
86 | }
|
87 | };
|
88 | var declareModuleSpecifier = function (node, parents) {
|
89 | ast.locals = ast.locals || Object.create(null);
|
90 | ast.locals[node.local.name] = true;
|
91 | };
|
92 | walk.ancestor(ast, {
|
93 | 'VariableDeclaration': function (node, parents) {
|
94 | var parent = null;
|
95 | for (var i = parents.length - 1; i >= 0 && parent === null; i--) {
|
96 | if (node.kind === 'var' ? isScope(parents[i]) : isBlockScope(parents[i])) {
|
97 | parent = parents[i];
|
98 | }
|
99 | }
|
100 | parent.locals = parent.locals || Object.create(null);
|
101 | node.declarations.forEach(function (declaration) {
|
102 | declarePattern(declaration.id, parent);
|
103 | });
|
104 | },
|
105 | 'FunctionDeclaration': function (node, parents) {
|
106 | var parent = null;
|
107 | for (var i = parents.length - 2; i >= 0 && parent === null; i--) {
|
108 | if (isScope(parents[i])) {
|
109 | parent = parents[i];
|
110 | }
|
111 | }
|
112 | parent.locals = parent.locals || Object.create(null);
|
113 | if (node.id) {
|
114 | parent.locals[node.id.name] = true;
|
115 | }
|
116 | declareFunction(node);
|
117 | },
|
118 | 'Function': declareFunction,
|
119 | 'ClassDeclaration': function (node, parents) {
|
120 | var parent = null;
|
121 | for (var i = parents.length - 2; i >= 0 && parent === null; i--) {
|
122 | if (isBlockScope(parents[i])) {
|
123 | parent = parents[i];
|
124 | }
|
125 | }
|
126 | parent.locals = parent.locals || Object.create(null);
|
127 | if (node.id) {
|
128 | parent.locals[node.id.name] = true;
|
129 | }
|
130 | declareClass(node);
|
131 | },
|
132 | 'Class': declareClass,
|
133 | 'TryStatement': function (node) {
|
134 | if (node.handler === null || node.handler.param === null) return;
|
135 | node.handler.locals = node.handler.locals || Object.create(null);
|
136 | declarePattern(node.handler.param, node.handler);
|
137 | },
|
138 | 'ImportDefaultSpecifier': declareModuleSpecifier,
|
139 | 'ImportSpecifier': declareModuleSpecifier,
|
140 | 'ImportNamespaceSpecifier': declareModuleSpecifier
|
141 | });
|
142 | function identifier(node, parents) {
|
143 | var name = node.name;
|
144 | if (name === 'undefined') return;
|
145 | for (var i = 0; i < parents.length; i++) {
|
146 | if (name === 'arguments' && declaresArguments(parents[i])) {
|
147 | return;
|
148 | }
|
149 | if (parents[i].locals && name in parents[i].locals) {
|
150 | return;
|
151 | }
|
152 | }
|
153 | node.parents = parents.slice();
|
154 | globals.push(node);
|
155 | }
|
156 | walk.ancestor(ast, {
|
157 | 'VariablePattern': identifier,
|
158 | 'Identifier': identifier,
|
159 | 'ThisExpression': function (node, parents) {
|
160 | for (var i = 0; i < parents.length; i++) {
|
161 | var parent = parents[i];
|
162 | if ( parent.type === 'FunctionExpression' || parent.type === 'FunctionDeclaration' ) { return; }
|
163 | if ( parent.type === 'PropertyDefinition' && parents[i+1]===parent.value ) { return; }
|
164 | }
|
165 | node.parents = parents.slice();
|
166 | globals.push(node);
|
167 | }
|
168 | });
|
169 | var groupedGlobals = Object.create(null);
|
170 | globals.forEach(function (node) {
|
171 | var name = node.type === 'ThisExpression' ? 'this' : node.name;
|
172 | groupedGlobals[name] = (groupedGlobals[name] || []);
|
173 | groupedGlobals[name].push(node);
|
174 | });
|
175 | return Object.keys(groupedGlobals).sort().map(function (name) {
|
176 | return {name: name, nodes: groupedGlobals[name]};
|
177 | });
|
178 | }
|