attachScopes.js 3.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { walk } from 'estree-walker';
  2. const blockDeclarations = {
  3. 'const': true,
  4. 'let': true
  5. };
  6. const extractors = {
  7. Literal ( names, param ) {
  8. names.push( param.value );
  9. },
  10. Identifier ( names, param ) {
  11. names.push( param.name );
  12. },
  13. ObjectPattern ( names, param ) {
  14. param.properties.forEach( prop => {
  15. extractors[ (prop.value || prop.key).type ]( names, prop.value || prop.key );
  16. });
  17. },
  18. ArrayPattern ( names, param ) {
  19. param.elements.forEach( element => {
  20. if ( element ) extractors[ element.type ]( names, element );
  21. });
  22. },
  23. RestElement ( names, param ) {
  24. extractors[ param.argument.type ]( names, param.argument );
  25. },
  26. AssignmentPattern ( names, param ) {
  27. return extractors[ param.left.type ]( names, param.left );
  28. }
  29. };
  30. function extractNames ( param ) {
  31. let names = [];
  32. extractors[ param.type ]( names, param );
  33. return names;
  34. }
  35. class Scope {
  36. constructor ( options ) {
  37. options = options || {};
  38. this.parent = options.parent;
  39. this.isBlockScope = !!options.block;
  40. this.declarations = Object.create( null );
  41. if ( options.params ) {
  42. options.params.forEach( param => {
  43. extractNames( param ).forEach( name => {
  44. this.declarations[ name ] = true;
  45. });
  46. });
  47. }
  48. }
  49. addDeclaration ( node, isBlockDeclaration, isVar ) {
  50. if ( !isBlockDeclaration && this.isBlockScope ) {
  51. // it's a `var` or function node, and this
  52. // is a block scope, so we need to go up
  53. this.parent.addDeclaration( node, isBlockDeclaration, isVar );
  54. } else if ( node.id ) {
  55. extractNames( node.id ).forEach( name => {
  56. this.declarations[ name ] = true;
  57. });
  58. }
  59. }
  60. contains ( name ) {
  61. return this.declarations[ name ] ||
  62. ( this.parent ? this.parent.contains( name ) : false );
  63. }
  64. }
  65. export default function attachScopes ( ast, propertyName = 'scope' ) {
  66. let scope = new Scope();
  67. walk( ast, {
  68. enter ( node, parent ) {
  69. // function foo () {...}
  70. // class Foo {...}
  71. if ( /(Function|Class)Declaration/.test( node.type ) ) {
  72. scope.addDeclaration( node, false, false );
  73. }
  74. // var foo = 1
  75. if ( node.type === 'VariableDeclaration' ) {
  76. const isBlockDeclaration = blockDeclarations[ node.kind ];
  77. node.declarations.forEach( declaration => {
  78. scope.addDeclaration( declaration, isBlockDeclaration, true );
  79. });
  80. }
  81. let newScope;
  82. // create new function scope
  83. if ( /Function/.test( node.type ) ) {
  84. newScope = new Scope({
  85. parent: scope,
  86. block: false,
  87. params: node.params
  88. });
  89. // named function expressions - the name is considered
  90. // part of the function's scope
  91. if ( node.type === 'FunctionExpression' && node.id ) {
  92. newScope.addDeclaration( node, false, false );
  93. }
  94. }
  95. // create new block scope
  96. if ( node.type === 'BlockStatement' && !/Function/.test( parent.type ) ) {
  97. newScope = new Scope({
  98. parent: scope,
  99. block: true
  100. });
  101. }
  102. // catch clause has its own block scope
  103. if ( node.type === 'CatchClause' ) {
  104. newScope = new Scope({
  105. parent: scope,
  106. params: [ node.param ],
  107. block: true
  108. });
  109. }
  110. if ( newScope ) {
  111. Object.defineProperty( node, propertyName, {
  112. value: newScope,
  113. configurable: true
  114. });
  115. scope = newScope;
  116. }
  117. },
  118. leave ( node ) {
  119. if ( node[ propertyName ] ) scope = scope.parent;
  120. }
  121. });
  122. return scope;
  123. }