[d565449] | 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 |
|
---|
| 12 | const 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 | /* c8 ignore next */
|
---|
| 24 | function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc -- Ignoring
|
---|
| 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 | */
|
---|
| 34 | function 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 |
|
---|
| 48 | module.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 : /* c8 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 : /* c8 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=true];\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 -- Want ability to traverse prototype
|
---|
| 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 | };
|
---|