index.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. var xtend = require('xtend')
  2. var acorn = require('acorn-node')
  3. var dash = require('dash-ast')
  4. var getAssignedIdentifiers = require('get-assigned-identifiers')
  5. function visitFunction (node, state, ancestors) {
  6. if (node.params.length > 0) {
  7. var idents = []
  8. for (var i = 0; i < node.params.length; i++) {
  9. var sub = getAssignedIdentifiers(node.params[i])
  10. for (var j = 0; j < sub.length; j++) idents.push(sub[j])
  11. }
  12. declareNames(node, idents)
  13. }
  14. if (node.type === 'FunctionDeclaration') {
  15. var parent = getScopeNode(ancestors, 'const')
  16. declareNames(parent, [node.id])
  17. } else if (node.type === 'FunctionExpression' && node.id) {
  18. declareNames(node, [node.id])
  19. }
  20. }
  21. var scopeVisitor = {
  22. VariableDeclaration: function (node, state, ancestors) {
  23. var parent = getScopeNode(ancestors, node.kind)
  24. for (var i = 0; i < node.declarations.length; i++) {
  25. declareNames(parent, getAssignedIdentifiers(node.declarations[i].id))
  26. }
  27. },
  28. FunctionExpression: visitFunction,
  29. FunctionDeclaration: visitFunction,
  30. ArrowFunctionExpression: visitFunction,
  31. ClassDeclaration: function (node, state, ancestors) {
  32. var parent = getScopeNode(ancestors, 'const')
  33. if (node.id) {
  34. declareNames(parent, [node.id])
  35. }
  36. },
  37. ImportDeclaration: function (node, state, ancestors) {
  38. declareNames(ancestors[0] /* root */, getAssignedIdentifiers(node))
  39. },
  40. CatchClause: function (node) {
  41. if (node.param) declareNames(node, [node.param])
  42. }
  43. }
  44. var bindingVisitor = {
  45. Identifier: function (node, state, ancestors) {
  46. if (!state.identifiers) return
  47. var parent = ancestors[ancestors.length - 1]
  48. if (parent.type === 'MemberExpression' && parent.property === node) return
  49. if (parent.type === 'Property' && !parent.computed && parent.key === node) return
  50. if (parent.type === 'MethodDefinition' && !parent.computed && parent.key === node) return
  51. if (parent.type === 'LabeledStatement' && parent.label === node) return
  52. if (!has(state.undeclared, node.name)) {
  53. for (var i = ancestors.length - 1; i >= 0; i--) {
  54. if (ancestors[i]._names !== undefined && has(ancestors[i]._names, node.name)) {
  55. return
  56. }
  57. }
  58. state.undeclared[node.name] = true
  59. }
  60. if (state.wildcard &&
  61. !(parent.type === 'MemberExpression' && parent.object === node) &&
  62. !(parent.type === 'VariableDeclarator' && parent.id === node) &&
  63. !(parent.type === 'AssignmentExpression' && parent.left === node)) {
  64. state.undeclaredProps[node.name + '.*'] = true
  65. }
  66. },
  67. MemberExpression: function (node, state) {
  68. if (!state.properties) return
  69. if (node.object.type === 'Identifier' && has(state.undeclared, node.object.name)) {
  70. var prop = !node.computed && node.property.type === 'Identifier'
  71. ? node.property.name
  72. : node.computed && node.property.type === 'Literal'
  73. ? node.property.value
  74. : null
  75. if (prop) state.undeclaredProps[node.object.name + '.' + prop] = true
  76. }
  77. }
  78. }
  79. module.exports = function findUndeclared (src, opts) {
  80. opts = xtend({
  81. identifiers: true,
  82. properties: true,
  83. wildcard: false
  84. }, opts)
  85. var state = {
  86. undeclared: {},
  87. undeclaredProps: {},
  88. identifiers: opts.identifiers,
  89. properties: opts.properties,
  90. wildcard: opts.wildcard
  91. }
  92. // Parse if `src` is not already an AST.
  93. var ast = typeof src === 'object' && src !== null && typeof src.type === 'string'
  94. ? src
  95. : acorn.parse(src)
  96. var parents = []
  97. dash(ast, {
  98. enter: function (node, parent) {
  99. if (parent) parents.push(parent)
  100. var visit = scopeVisitor[node.type]
  101. if (visit) visit(node, state, parents)
  102. },
  103. leave: function (node, parent) {
  104. var visit = bindingVisitor[node.type]
  105. if (visit) visit(node, state, parents)
  106. if (parent) parents.pop()
  107. }
  108. })
  109. return {
  110. identifiers: Object.keys(state.undeclared),
  111. properties: Object.keys(state.undeclaredProps)
  112. }
  113. }
  114. function getScopeNode (parents, kind) {
  115. for (var i = parents.length - 1; i >= 0; i--) {
  116. if (parents[i].type === 'FunctionDeclaration' || parents[i].type === 'FunctionExpression' ||
  117. parents[i].type === 'ArrowFunctionExpression' || parents[i].type === 'Program') {
  118. return parents[i]
  119. }
  120. if (kind !== 'var' && parents[i].type === 'BlockStatement') {
  121. return parents[i]
  122. }
  123. }
  124. }
  125. function declareNames (node, names) {
  126. if (node._names === undefined) {
  127. node._names = Object.create(null)
  128. }
  129. for (var i = 0; i < names.length; i++) {
  130. node._names[names[i].name] = true
  131. }
  132. }
  133. function has (obj, name) { return Object.prototype.hasOwnProperty.call(obj, name) }