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 | };
|
---|