debug-helpers.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /**
  2. * @fileoverview Helpers to debug for code path analysis.
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const debug = require("debug")("eslint:code-path");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. /**
  14. * Gets id of a given segment.
  15. * @param {CodePathSegment} segment A segment to get.
  16. * @returns {string} Id of the segment.
  17. */
  18. /* c8 ignore next */
  19. // eslint-disable-next-line jsdoc/require-jsdoc -- Ignoring
  20. function getId(segment) {
  21. return segment.id + (segment.reachable ? "" : "!");
  22. }
  23. /**
  24. * Get string for the given node and operation.
  25. * @param {ASTNode} node The node to convert.
  26. * @param {"enter" | "exit" | undefined} label The operation label.
  27. * @returns {string} The string representation.
  28. */
  29. function nodeToString(node, label) {
  30. const suffix = label ? `:${label}` : "";
  31. switch (node.type) {
  32. case "Identifier":
  33. return `${node.type}${suffix} (${node.name})`;
  34. case "Literal":
  35. return `${node.type}${suffix} (${node.value})`;
  36. default:
  37. return `${node.type}${suffix}`;
  38. }
  39. }
  40. //------------------------------------------------------------------------------
  41. // Public Interface
  42. //------------------------------------------------------------------------------
  43. module.exports = {
  44. /**
  45. * A flag that debug dumping is enabled or not.
  46. * @type {boolean}
  47. */
  48. enabled: debug.enabled,
  49. /**
  50. * Dumps given objects.
  51. * @param {...any} args objects to dump.
  52. * @returns {void}
  53. */
  54. dump: debug,
  55. /**
  56. * Dumps the current analyzing state.
  57. * @param {ASTNode} node A node to dump.
  58. * @param {CodePathState} state A state to dump.
  59. * @param {boolean} leaving A flag whether or not it's leaving
  60. * @returns {void}
  61. */
  62. dumpState: !debug.enabled
  63. ? debug
  64. : /* c8 ignore next */ function (node, state, leaving) {
  65. for (let i = 0; i < state.currentSegments.length; ++i) {
  66. const segInternal = state.currentSegments[i].internal;
  67. if (leaving) {
  68. const last = segInternal.nodes.length - 1;
  69. if (
  70. last >= 0 &&
  71. segInternal.nodes[last] ===
  72. nodeToString(node, "enter")
  73. ) {
  74. segInternal.nodes[last] = nodeToString(
  75. node,
  76. void 0,
  77. );
  78. } else {
  79. segInternal.nodes.push(nodeToString(node, "exit"));
  80. }
  81. } else {
  82. segInternal.nodes.push(nodeToString(node, "enter"));
  83. }
  84. }
  85. debug(
  86. [
  87. `${state.currentSegments.map(getId).join(",")})`,
  88. `${node.type}${leaving ? ":exit" : ""}`,
  89. ].join(" "),
  90. );
  91. },
  92. /**
  93. * Dumps a DOT code of a given code path.
  94. * The DOT code can be visualized with Graphvis.
  95. * @param {CodePath} codePath A code path to dump.
  96. * @returns {void}
  97. * @see http://www.graphviz.org
  98. * @see http://www.webgraphviz.com
  99. */
  100. dumpDot: !debug.enabled
  101. ? debug
  102. : /* c8 ignore next */ function (codePath) {
  103. let text =
  104. "\n" +
  105. "digraph {\n" +
  106. 'node[shape=box,style="rounded,filled",fillcolor=white];\n' +
  107. 'initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];\n';
  108. if (codePath.returnedSegments.length > 0) {
  109. text +=
  110. 'final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];\n';
  111. }
  112. if (codePath.thrownSegments.length > 0) {
  113. text +=
  114. 'thrown[label="✘",shape=circle,width=0.3,height=0.3,fixedsize=true];\n';
  115. }
  116. const traceMap = Object.create(null);
  117. const arrows = this.makeDotArrows(codePath, traceMap);
  118. // eslint-disable-next-line guard-for-in -- Want ability to traverse prototype
  119. for (const id in traceMap) {
  120. const segment = traceMap[id];
  121. text += `${id}[`;
  122. if (segment.reachable) {
  123. text += 'label="';
  124. } else {
  125. text +=
  126. 'style="rounded,dashed,filled",fillcolor="#FF9800",label="<<unreachable>>\\n';
  127. }
  128. if (segment.internal.nodes.length > 0) {
  129. text += segment.internal.nodes.join("\\n");
  130. } else {
  131. text += "????";
  132. }
  133. text += '"];\n';
  134. }
  135. text += `${arrows}\n`;
  136. text += "}";
  137. debug("DOT", text);
  138. },
  139. /**
  140. * Makes a DOT code of a given code path.
  141. * The DOT code can be visualized with Graphvis.
  142. * @param {CodePath} codePath A code path to make DOT.
  143. * @param {Object} traceMap Optional. A map to check whether or not segments had been done.
  144. * @returns {string} A DOT code of the code path.
  145. */
  146. makeDotArrows(codePath, traceMap) {
  147. const stack = [[codePath.initialSegment, 0]];
  148. const done = traceMap || Object.create(null);
  149. let lastId = codePath.initialSegment.id;
  150. let text = `initial->${codePath.initialSegment.id}`;
  151. while (stack.length > 0) {
  152. const item = stack.pop();
  153. const segment = item[0];
  154. const index = item[1];
  155. if (done[segment.id] && index === 0) {
  156. continue;
  157. }
  158. done[segment.id] = segment;
  159. const nextSegment = segment.allNextSegments[index];
  160. if (!nextSegment) {
  161. continue;
  162. }
  163. if (lastId === segment.id) {
  164. text += `->${nextSegment.id}`;
  165. } else {
  166. text += `;\n${segment.id}->${nextSegment.id}`;
  167. }
  168. lastId = nextSegment.id;
  169. stack.unshift([segment, 1 + index]);
  170. stack.push([nextSegment, 0]);
  171. }
  172. codePath.returnedSegments.forEach(finalSegment => {
  173. if (lastId === finalSegment.id) {
  174. text += "->final";
  175. } else {
  176. text += `;\n${finalSegment.id}->final`;
  177. }
  178. lastId = null;
  179. });
  180. codePath.thrownSegments.forEach(finalSegment => {
  181. if (lastId === finalSegment.id) {
  182. text += "->thrown";
  183. } else {
  184. text += `;\n${finalSegment.id}->thrown`;
  185. }
  186. lastId = null;
  187. });
  188. return `${text};`;
  189. },
  190. };