UNPKG

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