Created
December 18, 2015 00:29
-
-
Save bmeck/9c0a65ba5937f5470dda to your computer and use it in GitHub Desktop.
finding globals in an ES6 world
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
// usage: node globals.js $FILE_TO_FIND_GLOBALS | |
var fs = require('fs'); | |
var src = fs.readFileSync(process.argv[2]).toString(); | |
//src = 'function foo() {foo()}' | |
var esprima = require('esprima'); | |
var globals = new Set(); | |
function eswalk(node, pre, post, path) { | |
if (path === undefined) path = [{node:node}]; | |
pre(node,path); | |
// literals are special since they could | |
// look like an AST since they are raw | |
if (node.type !== 'Literal') { | |
for (var key of Object.keys(node)) { | |
var val = node[key]; | |
flat_arr_each(val, val => { | |
if (val && typeof val.type === 'string') { | |
path.push({key:key,node:val}); | |
eswalk(val, pre, post, path); | |
path.pop(); | |
} | |
}) | |
} | |
} | |
post(node,path); | |
} | |
function flat_arr_each(node,fn) { | |
if (Array.isArray(node)) { | |
node.forEach(n => flat_arr_each(n, fn)); | |
} | |
else { | |
fn(node); | |
} | |
} | |
// where declarations are scoped | |
var ENV_RECORD = { | |
'Program': {}, | |
'ArrowFunctionExpression': {params:'params'}, | |
'FunctionExpression': {variables:['this','arguments']}, | |
'FunctionDeclaration': {variables:['this','arguments']}, | |
'BlockStatement': {type:'block'}, | |
'CatchClause': {type:'catch',params:'param'} | |
} | |
var IGNORE_PATTERNS = [ | |
[{node:{type:'LabeledStatement'}},{key:'label'}], | |
[{node:{type:'ContinueStatement'}},{key:'label'}], | |
[{node:{type:'BreakStatement'}},{key:'label'}], | |
] | |
// declarations can include expressions | |
// we need to know what identifiers are declaration vs access | |
// we always start in an access scope | |
var MEMBER_PATTERNS = [ | |
[{node:{type:'Property',computed:false}},{key:'key'}], | |
[{node:{type:'MemberExpression',computed:false}},{key:'property'}], | |
]; | |
var ACCESS_PATTERNS = [ | |
[{node:{type:'Program'}}], | |
[{node:{type:'Property'}},{key:'value'}], | |
[{node:{type:'VariableDeclarator'}},{key:'init'}], | |
[{node:{type:'AssignmentPattern'}},{key:'right'}], | |
// snowflake since it can be an expression | |
[{node:{type:'ArrowFunctionExpression'}},{key:'body'}], | |
[{node:{type:'FunctionExpression'}},{key:'body'}], | |
[{node:{type:'FunctionDeclaration'}},{key:'body'}], | |
[{node:{type:'BlockStatement'}}] | |
] | |
var DECL_PATTERNS = [ | |
[{node:{type:'VariableDeclaration'}}], | |
[{node:{type:'ArrowFunctionExpression'}}], | |
[{node:{type:'FunctionExpression'}}], | |
[{node:{type:'FunctionDeclaration'}}], | |
[{node:{type:'CatchClause'}}], | |
] | |
function matchesPatterns(path, patterns) { | |
check_pattern: | |
for (var pattern of patterns) { | |
if (pattern.length > path.length) continue; | |
var to_check = path.slice(-pattern.length); | |
for (var i = 0; i < to_check.length; i++) { | |
if (!match(to_check[i], pattern[i])) continue check_pattern; | |
} | |
return true; | |
} | |
return false; | |
} | |
function match(obj,criteria) { | |
if (typeof obj !== typeof criteria) return false; | |
for (var key of Object.keys(criteria)) { | |
var crit_val = criteria[key]; | |
var obj_val = obj[key]; | |
if (typeof obj_val === 'object') { | |
if (!match(obj_val, crit_val)) { | |
return false; | |
} | |
} | |
else if (obj_val !== crit_val) { | |
return false; | |
} | |
} | |
return true; | |
} | |
var env_records = []; | |
var identifier_scopes = []; | |
eswalk(esprima.parse(src), (node,path) => { | |
//console.log(node) | |
// snowflake, hoisted out and within | |
if (node.type === 'FunctionDeclaration') { | |
var env = env_records.slice(-1)[0]; | |
// must be a block or function scope | |
env.variables.add(node.id.name); | |
} | |
var env_desc = ENV_RECORD[node.type]; | |
if (env_desc) { | |
var scope = { | |
type: env_desc.type||'function', | |
variables: new Set(env_desc.variables||[]), | |
lookups: new Set() | |
} | |
env_records.push(scope); | |
} | |
if (matchesPatterns(path, IGNORE_PATTERNS)) { | |
identifier_scopes.push({type:'ignore'}); | |
} | |
else if (matchesPatterns(path, MEMBER_PATTERNS)) { | |
identifier_scopes.push({type:'member'}); | |
} | |
else if (matchesPatterns(path, ACCESS_PATTERNS)) { | |
identifier_scopes.push({type:'access'}); | |
} | |
else if (matchesPatterns(path, DECL_PATTERNS)) { | |
var decl_scope = null; | |
if (node.type === 'VariableDeclaration') { | |
var i = env_records.length - 1; | |
for (; i >= 0; i--) { | |
if (node.kind === 'const' || node.kind === 'let') { | |
if (env_records[i].type !== 'catch') { | |
break; | |
} | |
} | |
else if (node.kind === 'var') { | |
if (env_records[i].type === 'function') { | |
break; | |
} | |
} | |
} | |
if (i >= 0) { | |
decl_scope = env_records[i]; | |
} | |
} | |
else { | |
decl_scope = env_records[env_records.length - 1]; | |
} | |
identifier_scopes.push({type:'declare',env:decl_scope}); | |
} | |
if (node.type === 'Identifier') { | |
var id_scope = identifier_scopes.slice(-1)[0]; | |
if (id_scope.type === 'ignore') {} | |
else if (id_scope.type === 'declare') { | |
id_scope.env.variables.add(node.name); | |
} | |
else if (id_scope.type === 'access') { | |
env_records[env_records.length-1].lookups.add(node.name); | |
} | |
else if (id_scope.type === 'member') { | |
// TODO show how what members are associated with each access | |
// HOW | |
// - add a new scope type 'obj_scope' | |
// - when lookup occurs, or a member occurs | |
// - attach it to the obj_scope | |
// - create a new sub 'obj_scope' | |
} | |
else { | |
throw new Error('unknown id scope type:' + id_scope); | |
} | |
} | |
}, (node,path) => { | |
if (matchesPatterns(path, IGNORE_PATTERNS)) { | |
identifier_scopes.pop(); | |
} | |
else if (matchesPatterns(path, MEMBER_PATTERNS)) { | |
identifier_scopes.pop(); | |
} | |
else if (matchesPatterns(path, ACCESS_PATTERNS)) { | |
identifier_scopes.pop(); | |
} | |
else if (matchesPatterns(path, DECL_PATTERNS)) { | |
identifier_scopes.pop(); | |
} | |
if (ENV_RECORD[node.type]) { | |
var env = env_records.pop(); | |
for (var name of env.lookups) { | |
if (!env.variables.has(name)) { | |
if (env_records.length) { | |
env_records[env_records.length-1].lookups.add(name); | |
} | |
else { | |
globals.add(name); | |
} | |
} | |
} | |
} | |
}); | |
console.log('globals', globals) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment