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