referencer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. /*
  2. Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>
  3. Redistribution and use in source and binary forms, with or without
  4. modification, are permitted provided that the following conditions are met:
  5. * Redistributions of source code must retain the above copyright
  6. notice, this list of conditions and the following disclaimer.
  7. * Redistributions in binary form must reproduce the above copyright
  8. notice, this list of conditions and the following disclaimer in the
  9. documentation and/or other materials provided with the distribution.
  10. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  11. AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  12. IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  13. ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  14. DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  15. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  16. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  17. ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  18. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  19. THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  20. */
  21. import { Syntax } from 'estraverse';
  22. import esrecurse from 'esrecurse';
  23. import Reference from './reference';
  24. import Variable from './variable';
  25. import PatternVisitor from './pattern-visitor';
  26. import { ParameterDefinition, Definition } from './definition';
  27. import assert from 'assert';
  28. function traverseIdentifierInPattern(options, rootPattern, referencer, callback) {
  29. // Call the callback at left hand identifier nodes, and Collect right hand nodes.
  30. var visitor = new PatternVisitor(options, rootPattern, callback);
  31. visitor.visit(rootPattern);
  32. // Process the right hand nodes recursively.
  33. if (referencer != null) {
  34. visitor.rightHandNodes.forEach(referencer.visit, referencer);
  35. }
  36. }
  37. // Importing ImportDeclaration.
  38. // http://people.mozilla.org/~jorendorff/es6-draft.html#sec-moduledeclarationinstantiation
  39. // https://github.com/estree/estree/blob/master/es6.md#importdeclaration
  40. // FIXME: Now, we don't create module environment, because the context is
  41. // implementation dependent.
  42. class Importer extends esrecurse.Visitor {
  43. constructor(declaration, referencer) {
  44. super(null, referencer.options);
  45. this.declaration = declaration;
  46. this.referencer = referencer;
  47. }
  48. visitImport(id, specifier) {
  49. this.referencer.visitPattern(id, (pattern) => {
  50. this.referencer.currentScope().__define(pattern,
  51. new Definition(
  52. Variable.ImportBinding,
  53. pattern,
  54. specifier,
  55. this.declaration,
  56. null,
  57. null
  58. ));
  59. });
  60. }
  61. ImportNamespaceSpecifier(node) {
  62. let local = (node.local || node.id);
  63. if (local) {
  64. this.visitImport(local, node);
  65. }
  66. }
  67. ImportDefaultSpecifier(node) {
  68. let local = (node.local || node.id);
  69. this.visitImport(local, node);
  70. }
  71. ImportSpecifier(node) {
  72. let local = (node.local || node.id);
  73. if (node.name) {
  74. this.visitImport(node.name, node);
  75. } else {
  76. this.visitImport(local, node);
  77. }
  78. }
  79. }
  80. // Referencing variables and creating bindings.
  81. export default class Referencer extends esrecurse.Visitor {
  82. constructor(options, scopeManager) {
  83. super(null, options);
  84. this.options = options;
  85. this.scopeManager = scopeManager;
  86. this.parent = null;
  87. this.isInnerMethodDefinition = false;
  88. }
  89. currentScope() {
  90. return this.scopeManager.__currentScope;
  91. }
  92. close(node) {
  93. while (this.currentScope() && node === this.currentScope().block) {
  94. this.scopeManager.__currentScope = this.currentScope().__close(this.scopeManager);
  95. }
  96. }
  97. pushInnerMethodDefinition(isInnerMethodDefinition) {
  98. var previous = this.isInnerMethodDefinition;
  99. this.isInnerMethodDefinition = isInnerMethodDefinition;
  100. return previous;
  101. }
  102. popInnerMethodDefinition(isInnerMethodDefinition) {
  103. this.isInnerMethodDefinition = isInnerMethodDefinition;
  104. }
  105. materializeTDZScope(node, iterationNode) {
  106. // http://people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-forin-div-ofexpressionevaluation-abstract-operation
  107. // TDZ scope hides the declaration's names.
  108. this.scopeManager.__nestTDZScope(node, iterationNode);
  109. this.visitVariableDeclaration(this.currentScope(), Variable.TDZ, iterationNode.left, 0, true);
  110. }
  111. materializeIterationScope(node) {
  112. // Generate iteration scope for upper ForIn/ForOf Statements.
  113. var letOrConstDecl;
  114. this.scopeManager.__nestForScope(node);
  115. letOrConstDecl = node.left;
  116. this.visitVariableDeclaration(this.currentScope(), Variable.Variable, letOrConstDecl, 0);
  117. this.visitPattern(letOrConstDecl.declarations[0].id, (pattern) => {
  118. this.currentScope().__referencing(pattern, Reference.WRITE, node.right, null, true, true);
  119. });
  120. }
  121. referencingDefaultValue(pattern, assignments, maybeImplicitGlobal, init) {
  122. const scope = this.currentScope();
  123. assignments.forEach(assignment => {
  124. scope.__referencing(
  125. pattern,
  126. Reference.WRITE,
  127. assignment.right,
  128. maybeImplicitGlobal,
  129. pattern !== assignment.left,
  130. init);
  131. });
  132. }
  133. visitPattern(node, options, callback) {
  134. if (typeof options === 'function') {
  135. callback = options;
  136. options = {processRightHandNodes: false}
  137. }
  138. traverseIdentifierInPattern(
  139. this.options,
  140. node,
  141. options.processRightHandNodes ? this : null,
  142. callback);
  143. }
  144. visitFunction(node) {
  145. var i, iz;
  146. // FunctionDeclaration name is defined in upper scope
  147. // NOTE: Not referring variableScope. It is intended.
  148. // Since
  149. // in ES5, FunctionDeclaration should be in FunctionBody.
  150. // in ES6, FunctionDeclaration should be block scoped.
  151. if (node.type === Syntax.FunctionDeclaration) {
  152. // id is defined in upper scope
  153. this.currentScope().__define(node.id,
  154. new Definition(
  155. Variable.FunctionName,
  156. node.id,
  157. node,
  158. null,
  159. null,
  160. null
  161. ));
  162. }
  163. // FunctionExpression with name creates its special scope;
  164. // FunctionExpressionNameScope.
  165. if (node.type === Syntax.FunctionExpression && node.id) {
  166. this.scopeManager.__nestFunctionExpressionNameScope(node);
  167. }
  168. // Consider this function is in the MethodDefinition.
  169. this.scopeManager.__nestFunctionScope(node, this.isInnerMethodDefinition);
  170. // Process parameter declarations.
  171. for (i = 0, iz = node.params.length; i < iz; ++i) {
  172. this.visitPattern(node.params[i], {processRightHandNodes: true}, (pattern, info) => {
  173. this.currentScope().__define(pattern,
  174. new ParameterDefinition(
  175. pattern,
  176. node,
  177. i,
  178. info.rest
  179. ));
  180. this.referencingDefaultValue(pattern, info.assignments, null, true);
  181. });
  182. }
  183. // if there's a rest argument, add that
  184. if (node.rest) {
  185. this.visitPattern({
  186. type: 'RestElement',
  187. argument: node.rest
  188. }, (pattern) => {
  189. this.currentScope().__define(pattern,
  190. new ParameterDefinition(
  191. pattern,
  192. node,
  193. node.params.length,
  194. true
  195. ));
  196. });
  197. }
  198. // Skip BlockStatement to prevent creating BlockStatement scope.
  199. if (node.body.type === Syntax.BlockStatement) {
  200. this.visitChildren(node.body);
  201. } else {
  202. this.visit(node.body);
  203. }
  204. this.close(node);
  205. }
  206. visitClass(node) {
  207. if (node.type === Syntax.ClassDeclaration) {
  208. this.currentScope().__define(node.id,
  209. new Definition(
  210. Variable.ClassName,
  211. node.id,
  212. node,
  213. null,
  214. null,
  215. null
  216. ));
  217. }
  218. // FIXME: Maybe consider TDZ.
  219. this.visit(node.superClass);
  220. this.scopeManager.__nestClassScope(node);
  221. if (node.id) {
  222. this.currentScope().__define(node.id,
  223. new Definition(
  224. Variable.ClassName,
  225. node.id,
  226. node
  227. ));
  228. }
  229. this.visit(node.body);
  230. this.close(node);
  231. }
  232. visitProperty(node) {
  233. var previous, isMethodDefinition;
  234. if (node.computed) {
  235. this.visit(node.key);
  236. }
  237. isMethodDefinition = node.type === Syntax.MethodDefinition;
  238. if (isMethodDefinition) {
  239. previous = this.pushInnerMethodDefinition(true);
  240. }
  241. this.visit(node.value);
  242. if (isMethodDefinition) {
  243. this.popInnerMethodDefinition(previous);
  244. }
  245. }
  246. visitForIn(node) {
  247. if (node.left.type === Syntax.VariableDeclaration && node.left.kind !== 'var') {
  248. this.materializeTDZScope(node.right, node);
  249. this.visit(node.right);
  250. this.close(node.right);
  251. this.materializeIterationScope(node);
  252. this.visit(node.body);
  253. this.close(node);
  254. } else {
  255. if (node.left.type === Syntax.VariableDeclaration) {
  256. this.visit(node.left);
  257. this.visitPattern(node.left.declarations[0].id, (pattern) => {
  258. this.currentScope().__referencing(pattern, Reference.WRITE, node.right, null, true, true);
  259. });
  260. } else {
  261. this.visitPattern(node.left, {processRightHandNodes: true}, (pattern, info) => {
  262. var maybeImplicitGlobal = null;
  263. if (!this.currentScope().isStrict) {
  264. maybeImplicitGlobal = {
  265. pattern: pattern,
  266. node: node
  267. };
  268. }
  269. this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
  270. this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, true, false);
  271. });
  272. }
  273. this.visit(node.right);
  274. this.visit(node.body);
  275. }
  276. }
  277. visitVariableDeclaration(variableTargetScope, type, node, index, fromTDZ) {
  278. // If this was called to initialize a TDZ scope, this needs to make definitions, but doesn't make references.
  279. var decl, init;
  280. decl = node.declarations[index];
  281. init = decl.init;
  282. this.visitPattern(decl.id, {processRightHandNodes: !fromTDZ}, (pattern, info) => {
  283. variableTargetScope.__define(pattern,
  284. new Definition(
  285. type,
  286. pattern,
  287. decl,
  288. node,
  289. index,
  290. node.kind
  291. ));
  292. if (!fromTDZ) {
  293. this.referencingDefaultValue(pattern, info.assignments, null, true);
  294. }
  295. if (init) {
  296. this.currentScope().__referencing(pattern, Reference.WRITE, init, null, !info.topLevel, true);
  297. }
  298. });
  299. }
  300. AssignmentExpression(node) {
  301. if (PatternVisitor.isPattern(node.left)) {
  302. if (node.operator === '=') {
  303. this.visitPattern(node.left, {processRightHandNodes: true}, (pattern, info) => {
  304. var maybeImplicitGlobal = null;
  305. if (!this.currentScope().isStrict) {
  306. maybeImplicitGlobal = {
  307. pattern: pattern,
  308. node: node
  309. };
  310. }
  311. this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
  312. this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, !info.topLevel, false);
  313. });
  314. } else {
  315. this.currentScope().__referencing(node.left, Reference.RW, node.right);
  316. }
  317. } else {
  318. this.visit(node.left);
  319. }
  320. this.visit(node.right);
  321. }
  322. CatchClause(node) {
  323. this.scopeManager.__nestCatchScope(node);
  324. this.visitPattern(node.param, {processRightHandNodes: true}, (pattern, info) => {
  325. this.currentScope().__define(pattern,
  326. new Definition(
  327. Variable.CatchClause,
  328. node.param,
  329. node,
  330. null,
  331. null,
  332. null
  333. ));
  334. this.referencingDefaultValue(pattern, info.assignments, null, true);
  335. });
  336. this.visit(node.body);
  337. this.close(node);
  338. }
  339. Program(node) {
  340. this.scopeManager.__nestGlobalScope(node);
  341. if (this.scopeManager.__isNodejsScope()) {
  342. // Force strictness of GlobalScope to false when using node.js scope.
  343. this.currentScope().isStrict = false;
  344. this.scopeManager.__nestFunctionScope(node, false);
  345. }
  346. if (this.scopeManager.__isES6() && this.scopeManager.isModule()) {
  347. this.scopeManager.__nestModuleScope(node);
  348. }
  349. if (this.scopeManager.isStrictModeSupported() && this.scopeManager.isImpliedStrict()) {
  350. this.currentScope().isStrict = true;
  351. }
  352. this.visitChildren(node);
  353. this.close(node);
  354. }
  355. Identifier(node) {
  356. this.currentScope().__referencing(node);
  357. }
  358. UpdateExpression(node) {
  359. if (PatternVisitor.isPattern(node.argument)) {
  360. this.currentScope().__referencing(node.argument, Reference.RW, null);
  361. } else {
  362. this.visitChildren(node);
  363. }
  364. }
  365. MemberExpression(node) {
  366. this.visit(node.object);
  367. if (node.computed) {
  368. this.visit(node.property);
  369. }
  370. }
  371. Property(node) {
  372. this.visitProperty(node);
  373. }
  374. MethodDefinition(node) {
  375. this.visitProperty(node);
  376. }
  377. BreakStatement() {}
  378. ContinueStatement() {}
  379. LabeledStatement(node) {
  380. this.visit(node.body);
  381. }
  382. ForStatement(node) {
  383. // Create ForStatement declaration.
  384. // NOTE: In ES6, ForStatement dynamically generates
  385. // per iteration environment. However, escope is
  386. // a static analyzer, we only generate one scope for ForStatement.
  387. if (node.init && node.init.type === Syntax.VariableDeclaration && node.init.kind !== 'var') {
  388. this.scopeManager.__nestForScope(node);
  389. }
  390. this.visitChildren(node);
  391. this.close(node);
  392. }
  393. ClassExpression(node) {
  394. this.visitClass(node);
  395. }
  396. ClassDeclaration(node) {
  397. this.visitClass(node);
  398. }
  399. CallExpression(node) {
  400. // Check this is direct call to eval
  401. if (!this.scopeManager.__ignoreEval() && node.callee.type === Syntax.Identifier && node.callee.name === 'eval') {
  402. // NOTE: This should be `variableScope`. Since direct eval call always creates Lexical environment and
  403. // let / const should be enclosed into it. Only VariableDeclaration affects on the caller's environment.
  404. this.currentScope().variableScope.__detectEval();
  405. }
  406. this.visitChildren(node);
  407. }
  408. BlockStatement(node) {
  409. if (this.scopeManager.__isES6()) {
  410. this.scopeManager.__nestBlockScope(node);
  411. }
  412. this.visitChildren(node);
  413. this.close(node);
  414. }
  415. ThisExpression() {
  416. this.currentScope().variableScope.__detectThis();
  417. }
  418. WithStatement(node) {
  419. this.visit(node.object);
  420. // Then nest scope for WithStatement.
  421. this.scopeManager.__nestWithScope(node);
  422. this.visit(node.body);
  423. this.close(node);
  424. }
  425. VariableDeclaration(node) {
  426. var variableTargetScope, i, iz, decl;
  427. variableTargetScope = (node.kind === 'var') ? this.currentScope().variableScope : this.currentScope();
  428. for (i = 0, iz = node.declarations.length; i < iz; ++i) {
  429. decl = node.declarations[i];
  430. this.visitVariableDeclaration(variableTargetScope, Variable.Variable, node, i);
  431. if (decl.init) {
  432. this.visit(decl.init);
  433. }
  434. }
  435. }
  436. // sec 13.11.8
  437. SwitchStatement(node) {
  438. var i, iz;
  439. this.visit(node.discriminant);
  440. if (this.scopeManager.__isES6()) {
  441. this.scopeManager.__nestSwitchScope(node);
  442. }
  443. for (i = 0, iz = node.cases.length; i < iz; ++i) {
  444. this.visit(node.cases[i]);
  445. }
  446. this.close(node);
  447. }
  448. FunctionDeclaration(node) {
  449. this.visitFunction(node);
  450. }
  451. FunctionExpression(node) {
  452. this.visitFunction(node);
  453. }
  454. ForOfStatement(node) {
  455. this.visitForIn(node);
  456. }
  457. ForInStatement(node) {
  458. this.visitForIn(node);
  459. }
  460. ArrowFunctionExpression(node) {
  461. this.visitFunction(node);
  462. }
  463. ImportDeclaration(node) {
  464. var importer;
  465. assert(this.scopeManager.__isES6() && this.scopeManager.isModule(), 'ImportDeclaration should appear when the mode is ES6 and in the module context.');
  466. importer = new Importer(node, this);
  467. importer.visit(node);
  468. }
  469. visitExportDeclaration(node) {
  470. if (node.source) {
  471. return;
  472. }
  473. if (node.declaration) {
  474. this.visit(node.declaration);
  475. return;
  476. }
  477. this.visitChildren(node);
  478. }
  479. ExportDeclaration(node) {
  480. this.visitExportDeclaration(node);
  481. }
  482. ExportNamedDeclaration(node) {
  483. this.visitExportDeclaration(node);
  484. }
  485. ExportSpecifier(node) {
  486. let local = (node.id || node.local);
  487. this.visit(local);
  488. }
  489. MetaProperty() {
  490. // do nothing.
  491. }
  492. }
  493. /* vim: set sw=4 ts=4 et tw=80 : */