Spaces:
Sleeping
Sleeping
| /** | |
| * @fileoverview Helpers to debug for code path analysis. | |
| * @author Toru Nagashima | |
| */ | |
| ; | |
| //------------------------------------------------------------------------------ | |
| // 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. | |
| */ | |
| /* c8 ignore next */ | |
| // eslint-disable-next-line jsdoc/require-jsdoc -- Ignoring | |
| function getId(segment) { | |
| return segment.id + (segment.reachable ? "" : "!"); | |
| } | |
| /** | |
| * Get string for the given node and operation. | |
| * @param {ASTNode} node The node to convert. | |
| * @param {"enter" | "exit" | undefined} label The operation label. | |
| * @returns {string} The string representation. | |
| */ | |
| function nodeToString(node, label) { | |
| const suffix = label ? `:${label}` : ""; | |
| switch (node.type) { | |
| case "Identifier": | |
| return `${node.type}${suffix} (${node.name})`; | |
| case "Literal": | |
| return `${node.type}${suffix} (${node.value})`; | |
| default: | |
| return `${node.type}${suffix}`; | |
| } | |
| } | |
| //------------------------------------------------------------------------------ | |
| // 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 | |
| : /* c8 ignore next */ function (node, state, leaving) { | |
| for (let i = 0; i < state.currentSegments.length; ++i) { | |
| const segInternal = state.currentSegments[i].internal; | |
| if (leaving) { | |
| const last = segInternal.nodes.length - 1; | |
| if ( | |
| last >= 0 && | |
| segInternal.nodes[last] === | |
| nodeToString(node, "enter") | |
| ) { | |
| segInternal.nodes[last] = nodeToString( | |
| node, | |
| void 0, | |
| ); | |
| } else { | |
| segInternal.nodes.push(nodeToString(node, "exit")); | |
| } | |
| } else { | |
| segInternal.nodes.push(nodeToString(node, "enter")); | |
| } | |
| } | |
| 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 visualized 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 | |
| : /* c8 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=true];\n'; | |
| } | |
| const traceMap = Object.create(null); | |
| const arrows = this.makeDotArrows(codePath, traceMap); | |
| // eslint-disable-next-line guard-for-in -- Want ability to traverse prototype | |
| for (const id in traceMap) { | |
| 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) { | |
| text += segment.internal.nodes.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 visualized 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};`; | |
| }, | |
| }; | |