| /** |
| * @fileoverview Helpers to debug for code path analysis. |
| * @author Toru Nagashima |
| */ |
| |
| "use strict"; |
| |
| //------------------------------------------------------------------------------ |
| // Requirements |
| //------------------------------------------------------------------------------ |
| |
| const debug = require("debug")("eslint:code-path"); |
| |
| //------------------------------------------------------------------------------ |
| // Helpers |
| //------------------------------------------------------------------------------ |
| |
| /** |
| * Gets id of a given segment. |
| * @param {CodePathSegment} segment - A segment to get. |
| * @returns {string} Id of the segment. |
| */ |
| /* istanbul ignore next */ |
| function getId(segment) { // eslint-disable-line require-jsdoc |
| return segment.id + (segment.reachable ? "" : "!"); |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Public Interface |
| //------------------------------------------------------------------------------ |
| |
| module.exports = { |
| |
| /** |
| * A flag that debug dumping is enabled or not. |
| * @type {boolean} |
| */ |
| enabled: debug.enabled, |
| |
| /** |
| * Dumps given objects. |
| * |
| * @param {...any} args - objects to dump. |
| * @returns {void} |
| */ |
| dump: debug, |
| |
| /** |
| * Dumps the current analyzing state. |
| * |
| * @param {ASTNode} node - A node to dump. |
| * @param {CodePathState} state - A state to dump. |
| * @param {boolean} leaving - A flag whether or not it's leaving |
| * @returns {void} |
| */ |
| dumpState: !debug.enabled ? debug : /* istanbul ignore next */ function(node, state, leaving) { |
| for (let i = 0; i < state.currentSegments.length; ++i) { |
| const segInternal = state.currentSegments[i].internal; |
| |
| if (leaving) { |
| segInternal.exitNodes.push(node); |
| } else { |
| segInternal.nodes.push(node); |
| } |
| } |
| |
| debug([ |
| `${state.currentSegments.map(getId).join(",")})`, |
| `${node.type}${leaving ? ":exit" : ""}` |
| ].join(" ")); |
| }, |
| |
| /** |
| * Dumps a DOT code of a given code path. |
| * The DOT code can be visialized with Graphvis. |
| * |
| * @param {CodePath} codePath - A code path to dump. |
| * @returns {void} |
| * @see http://www.graphviz.org |
| * @see http://www.webgraphviz.com |
| */ |
| dumpDot: !debug.enabled ? debug : /* istanbul ignore next */ function(codePath) { |
| let text = |
| "\n" + |
| "digraph {\n" + |
| "node[shape=box,style=\"rounded,filled\",fillcolor=white];\n" + |
| "initial[label=\"\",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];\n"; |
| |
| if (codePath.returnedSegments.length > 0) { |
| text += "final[label=\"\",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];\n"; |
| } |
| if (codePath.thrownSegments.length > 0) { |
| text += "thrown[label=\"✘\",shape=circle,width=0.3,height=0.3,fixedsize];\n"; |
| } |
| |
| const traceMap = Object.create(null); |
| const arrows = this.makeDotArrows(codePath, traceMap); |
| |
| for (const id in traceMap) { // eslint-disable-line guard-for-in |
| const segment = traceMap[id]; |
| |
| text += `${id}[`; |
| |
| if (segment.reachable) { |
| text += "label=\""; |
| } else { |
| text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n"; |
| } |
| |
| if (segment.internal.nodes.length > 0 || segment.internal.exitNodes.length > 0) { |
| text += [].concat( |
| segment.internal.nodes.map(node => { |
| switch (node.type) { |
| case "Identifier": return `${node.type} (${node.name})`; |
| case "Literal": return `${node.type} (${node.value})`; |
| default: return node.type; |
| } |
| }), |
| segment.internal.exitNodes.map(node => { |
| switch (node.type) { |
| case "Identifier": return `${node.type}:exit (${node.name})`; |
| case "Literal": return `${node.type}:exit (${node.value})`; |
| default: return `${node.type}:exit`; |
| } |
| }) |
| ).join("\\n"); |
| } else { |
| text += "????"; |
| } |
| |
| text += "\"];\n"; |
| } |
| |
| text += `${arrows}\n`; |
| text += "}"; |
| debug("DOT", text); |
| }, |
| |
| /** |
| * Makes a DOT code of a given code path. |
| * The DOT code can be visialized with Graphvis. |
| * |
| * @param {CodePath} codePath - A code path to make DOT. |
| * @param {Object} traceMap - Optional. A map to check whether or not segments had been done. |
| * @returns {string} A DOT code of the code path. |
| */ |
| makeDotArrows(codePath, traceMap) { |
| const stack = [[codePath.initialSegment, 0]]; |
| const done = traceMap || Object.create(null); |
| let lastId = codePath.initialSegment.id; |
| let text = `initial->${codePath.initialSegment.id}`; |
| |
| while (stack.length > 0) { |
| const item = stack.pop(); |
| const segment = item[0]; |
| const index = item[1]; |
| |
| if (done[segment.id] && index === 0) { |
| continue; |
| } |
| done[segment.id] = segment; |
| |
| const nextSegment = segment.allNextSegments[index]; |
| |
| if (!nextSegment) { |
| continue; |
| } |
| |
| if (lastId === segment.id) { |
| text += `->${nextSegment.id}`; |
| } else { |
| text += `;\n${segment.id}->${nextSegment.id}`; |
| } |
| lastId = nextSegment.id; |
| |
| stack.unshift([segment, 1 + index]); |
| stack.push([nextSegment, 0]); |
| } |
| |
| codePath.returnedSegments.forEach(finalSegment => { |
| if (lastId === finalSegment.id) { |
| text += "->final"; |
| } else { |
| text += `;\n${finalSegment.id}->final`; |
| } |
| lastId = null; |
| }); |
| |
| codePath.thrownSegments.forEach(finalSegment => { |
| if (lastId === finalSegment.id) { |
| text += "->thrown"; |
| } else { |
| text += `;\n${finalSegment.id}->thrown`; |
| } |
| lastId = null; |
| }); |
| |
| return `${text};`; |
| } |
| }; |