UNPKG

5.72 kBJavaScriptView Raw
1'use strict';
2
3var acorn = require('acorn');
4var walk = require('acorn-walk');
5
6function isScope(node) {
7 return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression' || node.type === 'Program';
8}
9function isBlockScope(node) {
10 // The body of switch statement is a block.
11 return node.type === 'BlockStatement' || node.type === 'SwitchStatement' || isScope(node);
12}
13
14function declaresArguments(node) {
15 return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration';
16}
17
18function 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}
30module.exports = findGlobals;
31module.exports.parse = reallyParse;
32function findGlobals(source, options) {
33 options = options || {};
34 var globals = [];
35 var ast;
36 // istanbul ignore else
37 if (typeof source === 'string') {
38 ast = reallyParse(source, options);
39 } else {
40 ast = source;
41 }
42 // istanbul ignore if
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 // istanbul ignore next
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}