]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview HTML reporter | |
3 | * @author Julian Laval | |
4 | */ | |
5 | "use strict"; | |
6 | ||
eb39fafa DC |
7 | //------------------------------------------------------------------------------ |
8 | // Helpers | |
9 | //------------------------------------------------------------------------------ | |
10 | ||
5422a9cc TL |
11 | const encodeHTML = (function() { |
12 | const encodeHTMLRules = { | |
13 | "&": "&", | |
14 | "<": "<", | |
15 | ">": ">", | |
16 | '"': """, | |
17 | "'": "'" | |
18 | }; | |
19 | const matchHTML = /[&<>"']/ug; | |
20 | ||
21 | return function(code) { | |
22 | return code | |
23 | ? code.toString().replace(matchHTML, m => encodeHTMLRules[m] || m) | |
24 | : ""; | |
25 | }; | |
26 | }()); | |
27 | ||
28 | /** | |
29 | * Get the final HTML document. | |
30 | * @param {Object} it data for the document. | |
31 | * @returns {string} HTML document. | |
32 | */ | |
33 | function pageTemplate(it) { | |
34 | const { reportColor, reportSummary, date, results } = it; | |
35 | ||
36 | return ` | |
37 | <!DOCTYPE html> | |
38 | <html> | |
39 | <head> | |
40 | <meta charset="UTF-8"> | |
41 | <title>ESLint Report</title> | |
8f9d1d4d DC |
42 | <link rel="icon" type="image/png" sizes="any" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAHaAAAB2gGFomX7AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAABD1JREFUWMPFl11sk2UUx3/nbYtjxS1MF7MLMTECMgSTtSSyrQkLhAj7UBPnDSEGoxegGzMwojhXVpmTAA5iYpSoMQa8GBhFOrMFk03buei6yRAlcmOM0SEmU9d90b19jxcM1o5+sGnsc/e+z/l6ztf/HFFVMnns6QieeOCHBePGsHM+wrOtvLG2C4WRVDSSygNV7sCjlspxwDnPB44aols/DXk+mbMBmx/6OseITF1CuOtfevkPh2Uu+/jbdX8lujSScRlT5r7/QDlAfsRmfzmpnkQ/H3H13gf6bBrBn1uqK8WylgEnU8eZmk1repbfchJG1TyKyIKEwuBHFd3lD3naY3O1siiwXsVoBV2VgM1ht/QQUJk2ByqKghsQziYQ8ifKgexIXmuyzC4r67Y7R+xPAfuB/Nn3Cpva+0s7khpQVtZtd4bt51BWxtBYAiciprG7c7D4SixzU9PYalDL6110Ifb/w8W9eY7JqFeFHbO8fPGyLHwwFHJNJTSgwtVTB9oaw9BlQ+tO93vOxypoaQnfEYlI43SeCHDC4TDq9+51/h5fxr33q0ZfV9g04wat9Q943rjJgCp3952W2i8Bi6eDvdsfKj0cK/DYMRyXL4/sUJUmIHd2zYMezsvLaamp4WpcWN3BXSiHpuMwbGbZlnZ8tXY4rgosy+G7oRwQ0cAsd28YGgqfU5UjCZQDLALxDg+Hv/P5Rqvj4hwrS8izXzWb4spwc1GgENFnkpWRzxeuB+ssUHgLdb9UVdt8vpGdKQpze7n7y1U3DBChNRUuqOo9c+0+qpKKxyZqtAIYla7gY4JszAAQri93BSsMRZoyBcUC+w3Q3AyOA4sNhAOZ0q7Iq0b2vUNvK5zPgP+/H8+Zetdoa6uOikhdGurxebwvJY8Iz3V1rTMNAH+opEuQj5KTT/qA1yC+wyUjBm12OidaUtCcPNNX2h0Hx2JG69VulANZAJZJwfU7rzd/FHixuXniTdM0m4GtSQT7bTartqEh9yfImUEzkwKZmTwmo5a5JwkYBfcDL01/RkR5y8iWhtPBknB8ZxwtU9UjwOrrKCeizzc25nTGg1F/turEHoU9wMLpDvWKf8DTmNCAKnd/tqUTF4ElMXJ+A5rWDJS+41WsGWzALhJ+ErBWrLj9g+pqojHxlXJX8HGUg0BsR/x1yhxf3jm4cSzpQFLp6tmi6PEE7g1ZhtZ91ufpSZUAFa6gC+UoQslNaSmypT1U8mHKiUgEKS8KfgF4EpYunFI16tsHin+OG0LcgQK7yj7g6cSzpva2D3hKVNG0Y3mVO1BkqfSlmJrHBQ4uvM12gJHc6ETW8HZVfMRmXvyxxNC1Z/o839zyXlDuCr4nsC11J+MXueaVJWn6yPv+/pJtc9oLTNN4AeTvNGByd3rlhE2x9s5pLwDoHCy+grDzWmOZ95lUtLYj5Bma126Y8eX0/zj/ADxGyViSg4BXAAAAAElFTkSuQmCC"> |
43 | <link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PScwIDAgMjk0LjgyNSAyNTguOTgyJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPg0KPHBhdGggZmlsbD0nIzgwODBGMicgZD0nTTk3LjAyMSw5OS4wMTZsNDguNDMyLTI3Ljk2MmMxLjIxMi0wLjcsMi43MDYtMC43LDMuOTE4LDBsNDguNDMzLDI3Ljk2MiBjMS4yMTEsMC43LDEuOTU5LDEuOTkzLDEuOTU5LDMuMzkzdjU1LjkyNGMwLDEuMzk5LTAuNzQ4LDIuNjkzLTEuOTU5LDMuMzk0bC00OC40MzMsMjcuOTYyYy0xLjIxMiwwLjctMi43MDYsMC43LTMuOTE4LDAgbC00OC40MzItMjcuOTYyYy0xLjIxMi0wLjctMS45NTktMS45OTQtMS45NTktMy4zOTR2LTU1LjkyNEM5NS4wNjMsMTAxLjAwOSw5NS44MSw5OS43MTYsOTcuMDIxLDk5LjAxNicvPg0KPHBhdGggZmlsbD0nIzRCMzJDMycgZD0nTTI3My4zMzYsMTI0LjQ4OEwyMTUuNDY5LDIzLjgxNmMtMi4xMDItMy42NC01Ljk4NS02LjMyNS0xMC4xODgtNi4zMjVIODkuNTQ1IGMtNC4yMDQsMC04LjA4OCwyLjY4NS0xMC4xOSw2LjMyNWwtNTcuODY3LDEwMC40NWMtMi4xMDIsMy42NDEtMi4xMDIsOC4yMzYsMCwxMS44NzdsNTcuODY3LDk5Ljg0NyBjMi4xMDIsMy42NCw1Ljk4Niw1LjUwMSwxMC4xOSw1LjUwMWgxMTUuNzM1YzQuMjAzLDAsOC4wODctMS44MDUsMTAuMTg4LTUuNDQ2bDU3Ljg2Ny0xMDAuMDEgQzI3NS40MzksMTMyLjM5NiwyNzUuNDM5LDEyOC4xMjgsMjczLjMzNiwxMjQuNDg4IE0yMjUuNDE5LDE3Mi44OThjMCwxLjQ4LTAuODkxLDIuODQ5LTIuMTc0LDMuNTlsLTczLjcxLDQyLjUyNyBjLTEuMjgyLDAuNzQtMi44ODgsMC43NC00LjE3LDBsLTczLjc2Ny00Mi41MjdjLTEuMjgyLTAuNzQxLTIuMTc5LTIuMTA5LTIuMTc5LTMuNTlWODcuODQzYzAtMS40ODEsMC44ODQtMi44NDksMi4xNjctMy41OSBsNzMuNzA3LTQyLjUyN2MxLjI4Mi0wLjc0MSwyLjg4Ni0wLjc0MSw0LjE2OCwwbDczLjc3Miw0Mi41MjdjMS4yODMsMC43NDEsMi4xODYsMi4xMDksMi4xODYsMy41OVYxNzIuODk4eicvPg0KPC9zdmc+"> | |
5422a9cc TL |
44 | <style> |
45 | body { | |
f2a92ac6 DC |
46 | font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; |
47 | font-size: 16px; | |
48 | font-weight: normal; | |
49 | margin: 0; | |
50 | padding: 0; | |
51 | color: #333; | |
5422a9cc | 52 | } |
f2a92ac6 | 53 | |
5422a9cc | 54 | #overview { |
f2a92ac6 | 55 | padding: 20px 30px; |
5422a9cc | 56 | } |
f2a92ac6 DC |
57 | |
58 | td, | |
59 | th { | |
60 | padding: 5px 10px; | |
5422a9cc | 61 | } |
f2a92ac6 | 62 | |
5422a9cc | 63 | h1 { |
f2a92ac6 | 64 | margin: 0; |
5422a9cc | 65 | } |
f2a92ac6 | 66 | |
5422a9cc | 67 | table { |
f2a92ac6 DC |
68 | margin: 30px; |
69 | width: calc(100% - 60px); | |
70 | max-width: 1000px; | |
71 | border-radius: 5px; | |
72 | border: 1px solid #ddd; | |
73 | border-spacing: 0; | |
5422a9cc | 74 | } |
f2a92ac6 | 75 | |
5422a9cc | 76 | th { |
f2a92ac6 DC |
77 | font-weight: 400; |
78 | font-size: medium; | |
79 | text-align: left; | |
80 | cursor: pointer; | |
5422a9cc | 81 | } |
f2a92ac6 DC |
82 | |
83 | td.clr-1, | |
84 | td.clr-2, | |
85 | th span { | |
86 | font-weight: 700; | |
5422a9cc | 87 | } |
f2a92ac6 | 88 | |
5422a9cc | 89 | th span { |
f2a92ac6 DC |
90 | float: right; |
91 | margin-left: 20px; | |
5422a9cc | 92 | } |
f2a92ac6 DC |
93 | |
94 | th span::after { | |
95 | content: ""; | |
96 | clear: both; | |
97 | display: block; | |
5422a9cc | 98 | } |
f2a92ac6 | 99 | |
5422a9cc | 100 | tr:last-child td { |
f2a92ac6 | 101 | border-bottom: none; |
5422a9cc | 102 | } |
f2a92ac6 DC |
103 | |
104 | tr td:first-child, | |
105 | tr td:last-child { | |
106 | color: #9da0a4; | |
5422a9cc | 107 | } |
f2a92ac6 DC |
108 | |
109 | #overview.bg-0, | |
110 | tr.bg-0 th { | |
111 | color: #468847; | |
112 | background: #dff0d8; | |
113 | border-bottom: 1px solid #d6e9c6; | |
5422a9cc | 114 | } |
f2a92ac6 DC |
115 | |
116 | #overview.bg-1, | |
117 | tr.bg-1 th { | |
118 | color: #f0ad4e; | |
119 | background: #fcf8e3; | |
120 | border-bottom: 1px solid #fbeed5; | |
5422a9cc | 121 | } |
f2a92ac6 DC |
122 | |
123 | #overview.bg-2, | |
124 | tr.bg-2 th { | |
125 | color: #b94a48; | |
126 | background: #f2dede; | |
127 | border-bottom: 1px solid #eed3d7; | |
5422a9cc | 128 | } |
f2a92ac6 | 129 | |
5422a9cc | 130 | td { |
f2a92ac6 | 131 | border-bottom: 1px solid #ddd; |
5422a9cc | 132 | } |
f2a92ac6 | 133 | |
5422a9cc | 134 | td.clr-1 { |
f2a92ac6 | 135 | color: #f0ad4e; |
5422a9cc | 136 | } |
f2a92ac6 | 137 | |
5422a9cc | 138 | td.clr-2 { |
f2a92ac6 | 139 | color: #b94a48; |
5422a9cc | 140 | } |
f2a92ac6 | 141 | |
5422a9cc | 142 | td a { |
f2a92ac6 DC |
143 | color: #3a33d1; |
144 | text-decoration: none; | |
5422a9cc | 145 | } |
f2a92ac6 | 146 | |
5422a9cc | 147 | td a:hover { |
f2a92ac6 DC |
148 | color: #272296; |
149 | text-decoration: underline; | |
5422a9cc TL |
150 | } |
151 | </style> | |
152 | </head> | |
153 | <body> | |
154 | <div id="overview" class="bg-${reportColor}"> | |
155 | <h1>ESLint Report</h1> | |
156 | <div> | |
157 | <span>${reportSummary}</span> - Generated on ${date} | |
158 | </div> | |
159 | </div> | |
160 | <table> | |
161 | <tbody> | |
162 | ${results} | |
163 | </tbody> | |
164 | </table> | |
165 | <script type="text/javascript"> | |
166 | var groups = document.querySelectorAll("tr[data-group]"); | |
167 | for (i = 0; i < groups.length; i++) { | |
168 | groups[i].addEventListener("click", function() { | |
169 | var inGroup = document.getElementsByClassName(this.getAttribute("data-group")); | |
170 | this.innerHTML = (this.innerHTML.indexOf("+") > -1) ? this.innerHTML.replace("+", "-") : this.innerHTML.replace("-", "+"); | |
171 | for (var j = 0; j < inGroup.length; j++) { | |
172 | inGroup[j].style.display = (inGroup[j].style.display !== "none") ? "none" : "table-row"; | |
173 | } | |
174 | }); | |
175 | } | |
176 | </script> | |
177 | </body> | |
178 | </html> | |
8f9d1d4d | 179 | `.trimStart(); |
5422a9cc | 180 | } |
eb39fafa DC |
181 | |
182 | /** | |
183 | * Given a word and a count, append an s if count is not one. | |
184 | * @param {string} word A word in its singular form. | |
185 | * @param {int} count A number controlling whether word should be pluralized. | |
186 | * @returns {string} The original word with an s on the end if count is not one. | |
187 | */ | |
188 | function pluralize(word, count) { | |
189 | return (count === 1 ? word : `${word}s`); | |
190 | } | |
191 | ||
192 | /** | |
193 | * Renders text along the template of x problems (x errors, x warnings) | |
194 | * @param {string} totalErrors Total errors | |
195 | * @param {string} totalWarnings Total warnings | |
196 | * @returns {string} The formatted string, pluralized where necessary | |
197 | */ | |
198 | function renderSummary(totalErrors, totalWarnings) { | |
199 | const totalProblems = totalErrors + totalWarnings; | |
200 | let renderedText = `${totalProblems} ${pluralize("problem", totalProblems)}`; | |
201 | ||
202 | if (totalProblems !== 0) { | |
203 | renderedText += ` (${totalErrors} ${pluralize("error", totalErrors)}, ${totalWarnings} ${pluralize("warning", totalWarnings)})`; | |
204 | } | |
205 | return renderedText; | |
206 | } | |
207 | ||
208 | /** | |
209 | * Get the color based on whether there are errors/warnings... | |
210 | * @param {string} totalErrors Total errors | |
211 | * @param {string} totalWarnings Total warnings | |
212 | * @returns {int} The color code (0 = green, 1 = yellow, 2 = red) | |
213 | */ | |
214 | function renderColor(totalErrors, totalWarnings) { | |
215 | if (totalErrors !== 0) { | |
216 | return 2; | |
217 | } | |
218 | if (totalWarnings !== 0) { | |
219 | return 1; | |
220 | } | |
221 | return 0; | |
222 | } | |
223 | ||
5422a9cc TL |
224 | /** |
225 | * Get HTML (table row) describing a single message. | |
226 | * @param {Object} it data for the message. | |
227 | * @returns {string} HTML (table row) describing the message. | |
228 | */ | |
229 | function messageTemplate(it) { | |
230 | const { | |
231 | parentIndex, | |
232 | lineNumber, | |
233 | columnNumber, | |
234 | severityNumber, | |
235 | severityName, | |
236 | message, | |
237 | ruleUrl, | |
238 | ruleId | |
239 | } = it; | |
240 | ||
241 | return ` | |
f2a92ac6 | 242 | <tr style="display: none;" class="f-${parentIndex}"> |
5422a9cc TL |
243 | <td>${lineNumber}:${columnNumber}</td> |
244 | <td class="clr-${severityNumber}">${severityName}</td> | |
245 | <td>${encodeHTML(message)}</td> | |
246 | <td> | |
247 | <a href="${ruleUrl ? ruleUrl : ""}" target="_blank" rel="noopener noreferrer">${ruleId ? ruleId : ""}</a> | |
248 | </td> | |
249 | </tr> | |
8f9d1d4d | 250 | `.trimStart(); |
5422a9cc TL |
251 | } |
252 | ||
eb39fafa DC |
253 | /** |
254 | * Get HTML (table rows) describing the messages. | |
255 | * @param {Array} messages Messages. | |
256 | * @param {int} parentIndex Index of the parent HTML row. | |
257 | * @param {Object} rulesMeta Dictionary containing metadata for each rule executed by the analysis. | |
258 | * @returns {string} HTML (table rows) describing the messages. | |
259 | */ | |
260 | function renderMessages(messages, parentIndex, rulesMeta) { | |
261 | ||
262 | /** | |
263 | * Get HTML (table row) describing a message. | |
264 | * @param {Object} message Message. | |
265 | * @returns {string} HTML (table row) describing a message. | |
266 | */ | |
5422a9cc | 267 | return messages.map(message => { |
eb39fafa DC |
268 | const lineNumber = message.line || 0; |
269 | const columnNumber = message.column || 0; | |
270 | let ruleUrl; | |
271 | ||
272 | if (rulesMeta) { | |
273 | const meta = rulesMeta[message.ruleId]; | |
274 | ||
5422a9cc TL |
275 | if (meta && meta.docs && meta.docs.url) { |
276 | ruleUrl = meta.docs.url; | |
277 | } | |
eb39fafa DC |
278 | } |
279 | ||
280 | return messageTemplate({ | |
281 | parentIndex, | |
282 | lineNumber, | |
283 | columnNumber, | |
284 | severityNumber: message.severity, | |
285 | severityName: message.severity === 1 ? "Warning" : "Error", | |
286 | message: message.message, | |
287 | ruleId: message.ruleId, | |
288 | ruleUrl | |
289 | }); | |
290 | }).join("\n"); | |
291 | } | |
292 | ||
5422a9cc TL |
293 | /** |
294 | * Get HTML (table row) describing the result for a single file. | |
295 | * @param {Object} it data for the file. | |
296 | * @returns {string} HTML (table row) describing the result for the file. | |
297 | */ | |
298 | function resultTemplate(it) { | |
299 | const { color, index, filePath, summary } = it; | |
300 | ||
301 | return ` | |
302 | <tr class="bg-${color}" data-group="f-${index}"> | |
303 | <th colspan="4"> | |
304 | [+] ${encodeHTML(filePath)} | |
305 | <span>${encodeHTML(summary)}</span> | |
306 | </th> | |
307 | </tr> | |
8f9d1d4d | 308 | `.trimStart(); |
5422a9cc TL |
309 | } |
310 | ||
eb39fafa | 311 | /** |
609c276f | 312 | * Render the results. |
eb39fafa DC |
313 | * @param {Array} results Test results. |
314 | * @param {Object} rulesMeta Dictionary containing metadata for each rule executed by the analysis. | |
315 | * @returns {string} HTML string describing the results. | |
316 | */ | |
317 | function renderResults(results, rulesMeta) { | |
5422a9cc | 318 | return results.map((result, index) => resultTemplate({ |
eb39fafa DC |
319 | index, |
320 | color: renderColor(result.errorCount, result.warningCount), | |
321 | filePath: result.filePath, | |
322 | summary: renderSummary(result.errorCount, result.warningCount) | |
eb39fafa DC |
323 | }) + renderMessages(result.messages, index, rulesMeta)).join("\n"); |
324 | } | |
325 | ||
326 | //------------------------------------------------------------------------------ | |
327 | // Public Interface | |
328 | //------------------------------------------------------------------------------ | |
329 | ||
330 | module.exports = function(results, data) { | |
331 | let totalErrors, | |
332 | totalWarnings; | |
333 | ||
334 | const metaData = data ? data.rulesMeta : {}; | |
335 | ||
336 | totalErrors = 0; | |
337 | totalWarnings = 0; | |
338 | ||
339 | // Iterate over results to get totals | |
340 | results.forEach(result => { | |
341 | totalErrors += result.errorCount; | |
342 | totalWarnings += result.warningCount; | |
343 | }); | |
344 | ||
345 | return pageTemplate({ | |
346 | date: new Date(), | |
347 | reportColor: renderColor(totalErrors, totalWarnings), | |
348 | reportSummary: renderSummary(totalErrors, totalWarnings), | |
349 | results: renderResults(results, metaData) | |
350 | }); | |
351 | }; |