debug-helpers.js 6.3 KB

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