code-path-analyzer.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. /**
  2. * @fileoverview A class of the code path analyzer.
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const assert = require("../../shared/assert"),
  10. { breakableTypePattern } = require("../../shared/ast-utils"),
  11. CodePath = require("./code-path"),
  12. CodePathSegment = require("./code-path-segment"),
  13. IdGenerator = require("./id-generator"),
  14. debug = require("./debug-helpers");
  15. //------------------------------------------------------------------------------
  16. // Helpers
  17. //------------------------------------------------------------------------------
  18. /**
  19. * Checks whether or not a given node is a `case` node (not `default` node).
  20. * @param {ASTNode} node A `SwitchCase` node to check.
  21. * @returns {boolean} `true` if the node is a `case` node (not `default` node).
  22. */
  23. function isCaseNode(node) {
  24. return Boolean(node.test);
  25. }
  26. /**
  27. * Checks if a given node appears as the value of a PropertyDefinition node.
  28. * @param {ASTNode} node THe node to check.
  29. * @returns {boolean} `true` if the node is a PropertyDefinition value,
  30. * false if not.
  31. */
  32. function isPropertyDefinitionValue(node) {
  33. const parent = node.parent;
  34. return (
  35. parent && parent.type === "PropertyDefinition" && parent.value === node
  36. );
  37. }
  38. /**
  39. * Checks whether the given logical operator is taken into account for the code
  40. * path analysis.
  41. * @param {string} operator The operator found in the LogicalExpression node
  42. * @returns {boolean} `true` if the operator is "&&" or "||" or "??"
  43. */
  44. function isHandledLogicalOperator(operator) {
  45. return operator === "&&" || operator === "||" || operator === "??";
  46. }
  47. /**
  48. * Checks whether the given assignment operator is a logical assignment operator.
  49. * Logical assignments are taken into account for the code path analysis
  50. * because of their short-circuiting semantics.
  51. * @param {string} operator The operator found in the AssignmentExpression node
  52. * @returns {boolean} `true` if the operator is "&&=" or "||=" or "??="
  53. */
  54. function isLogicalAssignmentOperator(operator) {
  55. return operator === "&&=" || operator === "||=" || operator === "??=";
  56. }
  57. /**
  58. * Gets the label if the parent node of a given node is a LabeledStatement.
  59. * @param {ASTNode} node A node to get.
  60. * @returns {string|null} The label or `null`.
  61. */
  62. function getLabel(node) {
  63. if (node.parent.type === "LabeledStatement") {
  64. return node.parent.label.name;
  65. }
  66. return null;
  67. }
  68. /**
  69. * Checks whether or not a given logical expression node goes different path
  70. * between the `true` case and the `false` case.
  71. * @param {ASTNode} node A node to check.
  72. * @returns {boolean} `true` if the node is a test of a choice statement.
  73. */
  74. function isForkingByTrueOrFalse(node) {
  75. const parent = node.parent;
  76. switch (parent.type) {
  77. case "ConditionalExpression":
  78. case "IfStatement":
  79. case "WhileStatement":
  80. case "DoWhileStatement":
  81. case "ForStatement":
  82. return parent.test === node;
  83. case "LogicalExpression":
  84. return isHandledLogicalOperator(parent.operator);
  85. case "AssignmentExpression":
  86. return isLogicalAssignmentOperator(parent.operator);
  87. default:
  88. return false;
  89. }
  90. }
  91. /**
  92. * Gets the boolean value of a given literal node.
  93. *
  94. * This is used to detect infinity loops (e.g. `while (true) {}`).
  95. * Statements preceded by an infinity loop are unreachable if the loop didn't
  96. * have any `break` statement.
  97. * @param {ASTNode} node A node to get.
  98. * @returns {boolean|undefined} a boolean value if the node is a Literal node,
  99. * otherwise `undefined`.
  100. */
  101. function getBooleanValueIfSimpleConstant(node) {
  102. if (node.type === "Literal") {
  103. return Boolean(node.value);
  104. }
  105. return void 0;
  106. }
  107. /**
  108. * Checks that a given identifier node is a reference or not.
  109. *
  110. * This is used to detect the first throwable node in a `try` block.
  111. * @param {ASTNode} node An Identifier node to check.
  112. * @returns {boolean} `true` if the node is a reference.
  113. */
  114. function isIdentifierReference(node) {
  115. const parent = node.parent;
  116. switch (parent.type) {
  117. case "LabeledStatement":
  118. case "BreakStatement":
  119. case "ContinueStatement":
  120. case "ArrayPattern":
  121. case "RestElement":
  122. case "ImportSpecifier":
  123. case "ImportDefaultSpecifier":
  124. case "ImportNamespaceSpecifier":
  125. case "CatchClause":
  126. return false;
  127. case "FunctionDeclaration":
  128. case "FunctionExpression":
  129. case "ArrowFunctionExpression":
  130. case "ClassDeclaration":
  131. case "ClassExpression":
  132. case "VariableDeclarator":
  133. return parent.id !== node;
  134. case "Property":
  135. case "PropertyDefinition":
  136. case "MethodDefinition":
  137. return parent.key !== node || parent.computed || parent.shorthand;
  138. case "AssignmentPattern":
  139. return parent.key !== node;
  140. default:
  141. return true;
  142. }
  143. }
  144. /**
  145. * Updates the current segment with the head segment.
  146. * This is similar to local branches and tracking branches of git.
  147. *
  148. * To separate the current and the head is in order to not make useless segments.
  149. *
  150. * In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd"
  151. * events are fired.
  152. * @param {CodePathAnalyzer} analyzer The instance.
  153. * @param {ASTNode} node The current AST node.
  154. * @returns {void}
  155. */
  156. function forwardCurrentToHead(analyzer, node) {
  157. const codePath = analyzer.codePath;
  158. const state = CodePath.getState(codePath);
  159. const currentSegments = state.currentSegments;
  160. const headSegments = state.headSegments;
  161. const end = Math.max(currentSegments.length, headSegments.length);
  162. let i, currentSegment, headSegment;
  163. // Fires leaving events.
  164. for (i = 0; i < end; ++i) {
  165. currentSegment = currentSegments[i];
  166. headSegment = headSegments[i];
  167. if (currentSegment !== headSegment && currentSegment) {
  168. const eventName = currentSegment.reachable
  169. ? "onCodePathSegmentEnd"
  170. : "onUnreachableCodePathSegmentEnd";
  171. debug.dump(`${eventName} ${currentSegment.id}`);
  172. analyzer.emit(eventName, [currentSegment, node]);
  173. }
  174. }
  175. // Update state.
  176. state.currentSegments = headSegments;
  177. // Fires entering events.
  178. for (i = 0; i < end; ++i) {
  179. currentSegment = currentSegments[i];
  180. headSegment = headSegments[i];
  181. if (currentSegment !== headSegment && headSegment) {
  182. const eventName = headSegment.reachable
  183. ? "onCodePathSegmentStart"
  184. : "onUnreachableCodePathSegmentStart";
  185. debug.dump(`${eventName} ${headSegment.id}`);
  186. CodePathSegment.markUsed(headSegment);
  187. analyzer.emit(eventName, [headSegment, node]);
  188. }
  189. }
  190. }
  191. /**
  192. * Updates the current segment with empty.
  193. * This is called at the last of functions or the program.
  194. * @param {CodePathAnalyzer} analyzer The instance.
  195. * @param {ASTNode} node The current AST node.
  196. * @returns {void}
  197. */
  198. function leaveFromCurrentSegment(analyzer, node) {
  199. const state = CodePath.getState(analyzer.codePath);
  200. const currentSegments = state.currentSegments;
  201. for (let i = 0; i < currentSegments.length; ++i) {
  202. const currentSegment = currentSegments[i];
  203. const eventName = currentSegment.reachable
  204. ? "onCodePathSegmentEnd"
  205. : "onUnreachableCodePathSegmentEnd";
  206. debug.dump(`${eventName} ${currentSegment.id}`);
  207. analyzer.emit(eventName, [currentSegment, node]);
  208. }
  209. state.currentSegments = [];
  210. }
  211. /**
  212. * Updates the code path due to the position of a given node in the parent node
  213. * thereof.
  214. *
  215. * For example, if the node is `parent.consequent`, this creates a fork from the
  216. * current path.
  217. * @param {CodePathAnalyzer} analyzer The instance.
  218. * @param {ASTNode} node The current AST node.
  219. * @returns {void}
  220. */
  221. function preprocess(analyzer, node) {
  222. const codePath = analyzer.codePath;
  223. const state = CodePath.getState(codePath);
  224. const parent = node.parent;
  225. switch (parent.type) {
  226. // The `arguments.length == 0` case is in `postprocess` function.
  227. case "CallExpression":
  228. if (
  229. parent.optional === true &&
  230. parent.arguments.length >= 1 &&
  231. parent.arguments[0] === node
  232. ) {
  233. state.makeOptionalRight();
  234. }
  235. break;
  236. case "MemberExpression":
  237. if (parent.optional === true && parent.property === node) {
  238. state.makeOptionalRight();
  239. }
  240. break;
  241. case "LogicalExpression":
  242. if (
  243. parent.right === node &&
  244. isHandledLogicalOperator(parent.operator)
  245. ) {
  246. state.makeLogicalRight();
  247. }
  248. break;
  249. case "AssignmentExpression":
  250. if (
  251. parent.right === node &&
  252. isLogicalAssignmentOperator(parent.operator)
  253. ) {
  254. state.makeLogicalRight();
  255. }
  256. break;
  257. case "ConditionalExpression":
  258. case "IfStatement":
  259. /*
  260. * Fork if this node is at `consequent`/`alternate`.
  261. * `popForkContext()` exists at `IfStatement:exit` and
  262. * `ConditionalExpression:exit`.
  263. */
  264. if (parent.consequent === node) {
  265. state.makeIfConsequent();
  266. } else if (parent.alternate === node) {
  267. state.makeIfAlternate();
  268. }
  269. break;
  270. case "SwitchCase":
  271. if (parent.consequent[0] === node) {
  272. state.makeSwitchCaseBody(false, !parent.test);
  273. }
  274. break;
  275. case "TryStatement":
  276. if (parent.handler === node) {
  277. state.makeCatchBlock();
  278. } else if (parent.finalizer === node) {
  279. state.makeFinallyBlock();
  280. }
  281. break;
  282. case "WhileStatement":
  283. if (parent.test === node) {
  284. state.makeWhileTest(getBooleanValueIfSimpleConstant(node));
  285. } else {
  286. assert(parent.body === node);
  287. state.makeWhileBody();
  288. }
  289. break;
  290. case "DoWhileStatement":
  291. if (parent.body === node) {
  292. state.makeDoWhileBody();
  293. } else {
  294. assert(parent.test === node);
  295. state.makeDoWhileTest(getBooleanValueIfSimpleConstant(node));
  296. }
  297. break;
  298. case "ForStatement":
  299. if (parent.test === node) {
  300. state.makeForTest(getBooleanValueIfSimpleConstant(node));
  301. } else if (parent.update === node) {
  302. state.makeForUpdate();
  303. } else if (parent.body === node) {
  304. state.makeForBody();
  305. }
  306. break;
  307. case "ForInStatement":
  308. case "ForOfStatement":
  309. if (parent.left === node) {
  310. state.makeForInOfLeft();
  311. } else if (parent.right === node) {
  312. state.makeForInOfRight();
  313. } else {
  314. assert(parent.body === node);
  315. state.makeForInOfBody();
  316. }
  317. break;
  318. case "AssignmentPattern":
  319. /*
  320. * Fork if this node is at `right`.
  321. * `left` is executed always, so it uses the current path.
  322. * `popForkContext()` exists at `AssignmentPattern:exit`.
  323. */
  324. if (parent.right === node) {
  325. state.pushForkContext();
  326. state.forkBypassPath();
  327. state.forkPath();
  328. }
  329. break;
  330. default:
  331. break;
  332. }
  333. }
  334. /**
  335. * Updates the code path due to the type of a given node in entering.
  336. * @param {CodePathAnalyzer} analyzer The instance.
  337. * @param {ASTNode} node The current AST node.
  338. * @returns {void}
  339. */
  340. function processCodePathToEnter(analyzer, node) {
  341. let codePath = analyzer.codePath;
  342. let state = codePath && CodePath.getState(codePath);
  343. const parent = node.parent;
  344. /**
  345. * Creates a new code path and trigger the onCodePathStart event
  346. * based on the currently selected node.
  347. * @param {string} origin The reason the code path was started.
  348. * @returns {void}
  349. */
  350. function startCodePath(origin) {
  351. if (codePath) {
  352. // Emits onCodePathSegmentStart events if updated.
  353. forwardCurrentToHead(analyzer, node);
  354. debug.dumpState(node, state, false);
  355. }
  356. // Create the code path of this scope.
  357. codePath = analyzer.codePath = new CodePath({
  358. id: analyzer.idGenerator.next(),
  359. origin,
  360. upper: codePath,
  361. onLooped: analyzer.onLooped,
  362. });
  363. state = CodePath.getState(codePath);
  364. // Emits onCodePathStart events.
  365. debug.dump(`onCodePathStart ${codePath.id}`);
  366. analyzer.emit("onCodePathStart", [codePath, node]);
  367. }
  368. /*
  369. * Special case: The right side of class field initializer is considered
  370. * to be its own function, so we need to start a new code path in this
  371. * case.
  372. */
  373. if (isPropertyDefinitionValue(node)) {
  374. startCodePath("class-field-initializer");
  375. /*
  376. * Intentional fall through because `node` needs to also be
  377. * processed by the code below. For example, if we have:
  378. *
  379. * class Foo {
  380. * a = () => {}
  381. * }
  382. *
  383. * In this case, we also need start a second code path.
  384. */
  385. }
  386. switch (node.type) {
  387. case "Program":
  388. startCodePath("program");
  389. break;
  390. case "FunctionDeclaration":
  391. case "FunctionExpression":
  392. case "ArrowFunctionExpression":
  393. startCodePath("function");
  394. break;
  395. case "StaticBlock":
  396. startCodePath("class-static-block");
  397. break;
  398. case "ChainExpression":
  399. state.pushChainContext();
  400. break;
  401. case "CallExpression":
  402. if (node.optional === true) {
  403. state.makeOptionalNode();
  404. }
  405. break;
  406. case "MemberExpression":
  407. if (node.optional === true) {
  408. state.makeOptionalNode();
  409. }
  410. break;
  411. case "LogicalExpression":
  412. if (isHandledLogicalOperator(node.operator)) {
  413. state.pushChoiceContext(
  414. node.operator,
  415. isForkingByTrueOrFalse(node),
  416. );
  417. }
  418. break;
  419. case "AssignmentExpression":
  420. if (isLogicalAssignmentOperator(node.operator)) {
  421. state.pushChoiceContext(
  422. node.operator.slice(0, -1), // removes `=` from the end
  423. isForkingByTrueOrFalse(node),
  424. );
  425. }
  426. break;
  427. case "ConditionalExpression":
  428. case "IfStatement":
  429. state.pushChoiceContext("test", false);
  430. break;
  431. case "SwitchStatement":
  432. state.pushSwitchContext(
  433. node.cases.some(isCaseNode),
  434. getLabel(node),
  435. );
  436. break;
  437. case "TryStatement":
  438. state.pushTryContext(Boolean(node.finalizer));
  439. break;
  440. case "SwitchCase":
  441. /*
  442. * Fork if this node is after the 2st node in `cases`.
  443. * It's similar to `else` blocks.
  444. * The next `test` node is processed in this path.
  445. */
  446. if (parent.discriminant !== node && parent.cases[0] !== node) {
  447. state.forkPath();
  448. }
  449. break;
  450. case "WhileStatement":
  451. case "DoWhileStatement":
  452. case "ForStatement":
  453. case "ForInStatement":
  454. case "ForOfStatement":
  455. state.pushLoopContext(node.type, getLabel(node));
  456. break;
  457. case "LabeledStatement":
  458. if (!breakableTypePattern.test(node.body.type)) {
  459. state.pushBreakContext(false, node.label.name);
  460. }
  461. break;
  462. default:
  463. break;
  464. }
  465. // Emits onCodePathSegmentStart events if updated.
  466. forwardCurrentToHead(analyzer, node);
  467. debug.dumpState(node, state, false);
  468. }
  469. /**
  470. * Updates the code path due to the type of a given node in leaving.
  471. * @param {CodePathAnalyzer} analyzer The instance.
  472. * @param {ASTNode} node The current AST node.
  473. * @returns {void}
  474. */
  475. function processCodePathToExit(analyzer, node) {
  476. const codePath = analyzer.codePath;
  477. const state = CodePath.getState(codePath);
  478. let dontForward = false;
  479. switch (node.type) {
  480. case "ChainExpression":
  481. state.popChainContext();
  482. break;
  483. case "IfStatement":
  484. case "ConditionalExpression":
  485. state.popChoiceContext();
  486. break;
  487. case "LogicalExpression":
  488. if (isHandledLogicalOperator(node.operator)) {
  489. state.popChoiceContext();
  490. }
  491. break;
  492. case "AssignmentExpression":
  493. if (isLogicalAssignmentOperator(node.operator)) {
  494. state.popChoiceContext();
  495. }
  496. break;
  497. case "SwitchStatement":
  498. state.popSwitchContext();
  499. break;
  500. case "SwitchCase":
  501. /*
  502. * This is the same as the process at the 1st `consequent` node in
  503. * `preprocess` function.
  504. * Must do if this `consequent` is empty.
  505. */
  506. if (node.consequent.length === 0) {
  507. state.makeSwitchCaseBody(true, !node.test);
  508. }
  509. if (state.forkContext.reachable) {
  510. dontForward = true;
  511. }
  512. break;
  513. case "TryStatement":
  514. state.popTryContext();
  515. break;
  516. case "BreakStatement":
  517. forwardCurrentToHead(analyzer, node);
  518. state.makeBreak(node.label && node.label.name);
  519. dontForward = true;
  520. break;
  521. case "ContinueStatement":
  522. forwardCurrentToHead(analyzer, node);
  523. state.makeContinue(node.label && node.label.name);
  524. dontForward = true;
  525. break;
  526. case "ReturnStatement":
  527. forwardCurrentToHead(analyzer, node);
  528. state.makeReturn();
  529. dontForward = true;
  530. break;
  531. case "ThrowStatement":
  532. forwardCurrentToHead(analyzer, node);
  533. state.makeThrow();
  534. dontForward = true;
  535. break;
  536. case "Identifier":
  537. if (isIdentifierReference(node)) {
  538. state.makeFirstThrowablePathInTryBlock();
  539. dontForward = true;
  540. }
  541. break;
  542. case "CallExpression":
  543. case "ImportExpression":
  544. case "MemberExpression":
  545. case "NewExpression":
  546. case "YieldExpression":
  547. state.makeFirstThrowablePathInTryBlock();
  548. break;
  549. case "WhileStatement":
  550. case "DoWhileStatement":
  551. case "ForStatement":
  552. case "ForInStatement":
  553. case "ForOfStatement":
  554. state.popLoopContext();
  555. break;
  556. case "AssignmentPattern":
  557. state.popForkContext();
  558. break;
  559. case "LabeledStatement":
  560. if (!breakableTypePattern.test(node.body.type)) {
  561. state.popBreakContext();
  562. }
  563. break;
  564. default:
  565. break;
  566. }
  567. // Emits onCodePathSegmentStart events if updated.
  568. if (!dontForward) {
  569. forwardCurrentToHead(analyzer, node);
  570. }
  571. debug.dumpState(node, state, true);
  572. }
  573. /**
  574. * Updates the code path to finalize the current code path.
  575. * @param {CodePathAnalyzer} analyzer The instance.
  576. * @param {ASTNode} node The current AST node.
  577. * @returns {void}
  578. */
  579. function postprocess(analyzer, node) {
  580. /**
  581. * Ends the code path for the current node.
  582. * @returns {void}
  583. */
  584. function endCodePath() {
  585. let codePath = analyzer.codePath;
  586. // Mark the current path as the final node.
  587. CodePath.getState(codePath).makeFinal();
  588. // Emits onCodePathSegmentEnd event of the current segments.
  589. leaveFromCurrentSegment(analyzer, node);
  590. // Emits onCodePathEnd event of this code path.
  591. debug.dump(`onCodePathEnd ${codePath.id}`);
  592. analyzer.emit("onCodePathEnd", [codePath, node]);
  593. debug.dumpDot(codePath);
  594. codePath = analyzer.codePath = analyzer.codePath.upper;
  595. if (codePath) {
  596. debug.dumpState(node, CodePath.getState(codePath), true);
  597. }
  598. }
  599. switch (node.type) {
  600. case "Program":
  601. case "FunctionDeclaration":
  602. case "FunctionExpression":
  603. case "ArrowFunctionExpression":
  604. case "StaticBlock": {
  605. endCodePath();
  606. break;
  607. }
  608. // The `arguments.length >= 1` case is in `preprocess` function.
  609. case "CallExpression":
  610. if (node.optional === true && node.arguments.length === 0) {
  611. CodePath.getState(analyzer.codePath).makeOptionalRight();
  612. }
  613. break;
  614. default:
  615. break;
  616. }
  617. /*
  618. * Special case: The right side of class field initializer is considered
  619. * to be its own function, so we need to end a code path in this
  620. * case.
  621. *
  622. * We need to check after the other checks in order to close the
  623. * code paths in the correct order for code like this:
  624. *
  625. *
  626. * class Foo {
  627. * a = () => {}
  628. * }
  629. *
  630. * In this case, The ArrowFunctionExpression code path is closed first
  631. * and then we need to close the code path for the PropertyDefinition
  632. * value.
  633. */
  634. if (isPropertyDefinitionValue(node)) {
  635. endCodePath();
  636. }
  637. }
  638. //------------------------------------------------------------------------------
  639. // Public Interface
  640. //------------------------------------------------------------------------------
  641. /**
  642. * The class to analyze code paths.
  643. * This class implements the EventGenerator interface.
  644. */
  645. class CodePathAnalyzer {
  646. /**
  647. * @param {EventGenerator} eventGenerator An event generator to wrap.
  648. */
  649. constructor(eventGenerator) {
  650. this.original = eventGenerator;
  651. this.emit = eventGenerator.emit;
  652. this.codePath = null;
  653. this.idGenerator = new IdGenerator("s");
  654. this.currentNode = null;
  655. this.onLooped = this.onLooped.bind(this);
  656. }
  657. /**
  658. * Does the process to enter a given AST node.
  659. * This updates state of analysis and calls `enterNode` of the wrapped.
  660. * @param {ASTNode} node A node which is entering.
  661. * @returns {void}
  662. */
  663. enterNode(node) {
  664. this.currentNode = node;
  665. // Updates the code path due to node's position in its parent node.
  666. if (node.parent) {
  667. preprocess(this, node);
  668. }
  669. /*
  670. * Updates the code path.
  671. * And emits onCodePathStart/onCodePathSegmentStart events.
  672. */
  673. processCodePathToEnter(this, node);
  674. // Emits node events.
  675. this.original.enterNode(node);
  676. this.currentNode = null;
  677. }
  678. /**
  679. * Does the process to leave a given AST node.
  680. * This updates state of analysis and calls `leaveNode` of the wrapped.
  681. * @param {ASTNode} node A node which is leaving.
  682. * @returns {void}
  683. */
  684. leaveNode(node) {
  685. this.currentNode = node;
  686. /*
  687. * Updates the code path.
  688. * And emits onCodePathStart/onCodePathSegmentStart events.
  689. */
  690. processCodePathToExit(this, node);
  691. // Emits node events.
  692. this.original.leaveNode(node);
  693. // Emits the last onCodePathStart/onCodePathSegmentStart events.
  694. postprocess(this, node);
  695. this.currentNode = null;
  696. }
  697. /**
  698. * This is called on a code path looped.
  699. * Then this raises a looped event.
  700. * @param {CodePathSegment} fromSegment A segment of prev.
  701. * @param {CodePathSegment} toSegment A segment of next.
  702. * @returns {void}
  703. */
  704. onLooped(fromSegment, toSegment) {
  705. if (fromSegment.reachable && toSegment.reachable) {
  706. debug.dump(
  707. `onCodePathSegmentLoop ${fromSegment.id} -> ${toSegment.id}`,
  708. );
  709. this.emit("onCodePathSegmentLoop", [
  710. fromSegment,
  711. toSegment,
  712. this.currentNode,
  713. ]);
  714. }
  715. }
  716. }
  717. module.exports = CodePathAnalyzer;