147 lines
4.6 KiB
JavaScript
147 lines
4.6 KiB
JavaScript
var xtend = require('xtend')
|
|
var acorn = require('acorn-node')
|
|
var dash = require('dash-ast')
|
|
var getAssignedIdentifiers = require('get-assigned-identifiers')
|
|
|
|
function visitFunction (node, state, ancestors) {
|
|
if (node.params.length > 0) {
|
|
var idents = []
|
|
for (var i = 0; i < node.params.length; i++) {
|
|
var sub = getAssignedIdentifiers(node.params[i])
|
|
for (var j = 0; j < sub.length; j++) idents.push(sub[j])
|
|
}
|
|
declareNames(node, idents)
|
|
}
|
|
if (node.type === 'FunctionDeclaration') {
|
|
var parent = getScopeNode(ancestors, 'const')
|
|
declareNames(parent, [node.id])
|
|
} else if (node.type === 'FunctionExpression' && node.id) {
|
|
declareNames(node, [node.id])
|
|
}
|
|
}
|
|
|
|
var scopeVisitor = {
|
|
VariableDeclaration: function (node, state, ancestors) {
|
|
var parent = getScopeNode(ancestors, node.kind)
|
|
for (var i = 0; i < node.declarations.length; i++) {
|
|
declareNames(parent, getAssignedIdentifiers(node.declarations[i].id))
|
|
}
|
|
},
|
|
FunctionExpression: visitFunction,
|
|
FunctionDeclaration: visitFunction,
|
|
ArrowFunctionExpression: visitFunction,
|
|
ClassDeclaration: function (node, state, ancestors) {
|
|
var parent = getScopeNode(ancestors, 'const')
|
|
if (node.id) {
|
|
declareNames(parent, [node.id])
|
|
}
|
|
},
|
|
ImportDeclaration: function (node, state, ancestors) {
|
|
declareNames(ancestors[0] /* root */, getAssignedIdentifiers(node))
|
|
},
|
|
CatchClause: function (node) {
|
|
if (node.param) declareNames(node, [node.param])
|
|
}
|
|
}
|
|
|
|
var bindingVisitor = {
|
|
Identifier: function (node, state, ancestors) {
|
|
if (!state.identifiers) return
|
|
var parent = ancestors[ancestors.length - 1]
|
|
if (parent.type === 'MemberExpression' && parent.property === node) return
|
|
if (parent.type === 'Property' && !parent.computed && parent.key === node) return
|
|
if (parent.type === 'MethodDefinition' && !parent.computed && parent.key === node) return
|
|
if (parent.type === 'LabeledStatement' && parent.label === node) return
|
|
if (!has(state.undeclared, node.name)) {
|
|
for (var i = ancestors.length - 1; i >= 0; i--) {
|
|
if (ancestors[i]._names !== undefined && has(ancestors[i]._names, node.name)) {
|
|
return
|
|
}
|
|
}
|
|
|
|
state.undeclared[node.name] = true
|
|
}
|
|
|
|
if (state.wildcard &&
|
|
!(parent.type === 'MemberExpression' && parent.object === node) &&
|
|
!(parent.type === 'VariableDeclarator' && parent.id === node) &&
|
|
!(parent.type === 'AssignmentExpression' && parent.left === node)) {
|
|
state.undeclaredProps[node.name + '.*'] = true
|
|
}
|
|
},
|
|
MemberExpression: function (node, state) {
|
|
if (!state.properties) return
|
|
if (node.object.type === 'Identifier' && has(state.undeclared, node.object.name)) {
|
|
var prop = !node.computed && node.property.type === 'Identifier'
|
|
? node.property.name
|
|
: node.computed && node.property.type === 'Literal'
|
|
? node.property.value
|
|
: null
|
|
if (prop) state.undeclaredProps[node.object.name + '.' + prop] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = function findUndeclared (src, opts) {
|
|
opts = xtend({
|
|
identifiers: true,
|
|
properties: true,
|
|
wildcard: false
|
|
}, opts)
|
|
|
|
var state = {
|
|
undeclared: {},
|
|
undeclaredProps: {},
|
|
identifiers: opts.identifiers,
|
|
properties: opts.properties,
|
|
wildcard: opts.wildcard
|
|
}
|
|
|
|
// Parse if `src` is not already an AST.
|
|
var ast = typeof src === 'object' && src !== null && typeof src.type === 'string'
|
|
? src
|
|
: acorn.parse(src)
|
|
|
|
var parents = []
|
|
dash(ast, {
|
|
enter: function (node, parent) {
|
|
if (parent) parents.push(parent)
|
|
var visit = scopeVisitor[node.type]
|
|
if (visit) visit(node, state, parents)
|
|
},
|
|
leave: function (node, parent) {
|
|
var visit = bindingVisitor[node.type]
|
|
if (visit) visit(node, state, parents)
|
|
if (parent) parents.pop()
|
|
}
|
|
})
|
|
|
|
return {
|
|
identifiers: Object.keys(state.undeclared),
|
|
properties: Object.keys(state.undeclaredProps)
|
|
}
|
|
}
|
|
|
|
function getScopeNode (parents, kind) {
|
|
for (var i = parents.length - 1; i >= 0; i--) {
|
|
if (parents[i].type === 'FunctionDeclaration' || parents[i].type === 'FunctionExpression' ||
|
|
parents[i].type === 'ArrowFunctionExpression' || parents[i].type === 'Program') {
|
|
return parents[i]
|
|
}
|
|
if (kind !== 'var' && parents[i].type === 'BlockStatement') {
|
|
return parents[i]
|
|
}
|
|
}
|
|
}
|
|
|
|
function declareNames (node, names) {
|
|
if (node._names === undefined) {
|
|
node._names = Object.create(null)
|
|
}
|
|
for (var i = 0; i < names.length; i++) {
|
|
node._names[names[i].name] = true
|
|
}
|
|
}
|
|
|
|
function has (obj, name) { return Object.prototype.hasOwnProperty.call(obj, name) }
|