debug-helpers.js 6.4 KB

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