renderpilot 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -0
- package/package.json +65 -0
- package/src/cli/commands/scan.js +83 -0
- package/src/cli/index.js +49 -0
- package/src/cli/options.js +18 -0
- package/src/config/ConfigLoader.js +102 -0
- package/src/config/DefaultConfig.js +33 -0
- package/src/index.js +34 -0
- package/src/parser/ASTUtils.js +241 -0
- package/src/parser/BabelParser.js +66 -0
- package/src/parser/ComponentDetector.js +53 -0
- package/src/reporters/HtmlReporter.js +42 -0
- package/src/reporters/JsonReporter.js +52 -0
- package/src/reporters/TerminalReporter.js +91 -0
- package/src/reporters/templates/report.html.js +655 -0
- package/src/rules/base/AbstractRule.js +136 -0
- package/src/rules/engine/RuleEngine.js +67 -0
- package/src/rules/implementations/R001_MissingKeyProp.js +97 -0
- package/src/rules/implementations/R002_InfiniteUseEffect.js +86 -0
- package/src/rules/implementations/R003_IncorrectDepsArray.js +112 -0
- package/src/rules/implementations/R004_LargeComponent.js +47 -0
- package/src/rules/implementations/R005_InlineCallback.js +58 -0
- package/src/rules/implementations/R006_HeavyRenderCalc.js +86 -0
- package/src/rules/implementations/R007_MemoCandidate.js +87 -0
- package/src/rules/implementations/R008_NestedComponent.js +65 -0
- package/src/rules/implementations/R009_UnusedState.js +62 -0
- package/src/rules/index.js +37 -0
- package/src/rules/registry/RuleRegistry.js +67 -0
- package/src/scanner/FileScanner.js +42 -0
- package/src/scanner/ProjectScanner.js +116 -0
- package/src/score/CategoryScorer.js +53 -0
- package/src/score/ScoreCalculator.js +97 -0
- package/src/score/ScoreWeights.js +33 -0
- package/src/types/index.js +176 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file reporters/templates/report.html.js
|
|
3
|
+
* @description
|
|
4
|
+
* Standalone HTML template for the RenderPilot report.
|
|
5
|
+
* Uses vanilla JS and CSS to ensure zero runtime dependencies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns the full HTML string for the report.
|
|
12
|
+
* @param {string} jsonData - JSON string of the report data
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
function getHtmlTemplate(jsonData) {
|
|
16
|
+
return `<!DOCTYPE html>
|
|
17
|
+
<html lang="en">
|
|
18
|
+
<head>
|
|
19
|
+
<meta charset="UTF-8">
|
|
20
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
21
|
+
<title>RenderPilot Analysis Report</title>
|
|
22
|
+
<style>
|
|
23
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
24
|
+
|
|
25
|
+
:root {
|
|
26
|
+
--bg: #fafafa;
|
|
27
|
+
--surface: #ffffff;
|
|
28
|
+
--border: #eaeaea;
|
|
29
|
+
--text: #11181c;
|
|
30
|
+
--text-muted: #687076;
|
|
31
|
+
--primary: #0070f3;
|
|
32
|
+
--critical: #e5484d;
|
|
33
|
+
--warning: #f7ce00;
|
|
34
|
+
--warning-text: #705e00;
|
|
35
|
+
--info: #0090ff;
|
|
36
|
+
--success: #30a46c;
|
|
37
|
+
--code-bg: #f1f3f5;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@media (prefers-color-scheme: dark) {
|
|
41
|
+
:root {
|
|
42
|
+
--bg: #111111;
|
|
43
|
+
--surface: #1a1a1a;
|
|
44
|
+
--border: #333333;
|
|
45
|
+
--text: #ededed;
|
|
46
|
+
--text-muted: #a1a1a1;
|
|
47
|
+
--code-bg: #222222;
|
|
48
|
+
--warning-text: #f7ce00;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
body {
|
|
53
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
54
|
+
background-color: var(--bg);
|
|
55
|
+
color: var(--text);
|
|
56
|
+
margin: 0;
|
|
57
|
+
padding: 0;
|
|
58
|
+
line-height: 1.6;
|
|
59
|
+
-webkit-font-smoothing: antialiased;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.app-container {
|
|
63
|
+
max-width: 1200px;
|
|
64
|
+
margin: 0 auto;
|
|
65
|
+
padding: 3rem 2rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
header {
|
|
69
|
+
margin-bottom: 2rem;
|
|
70
|
+
border-bottom: 1px solid var(--border);
|
|
71
|
+
padding-bottom: 1rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.header-content {
|
|
75
|
+
display: flex;
|
|
76
|
+
justify-content: space-between;
|
|
77
|
+
align-items: flex-end;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
h1 {
|
|
81
|
+
margin: 0;
|
|
82
|
+
font-size: 2rem;
|
|
83
|
+
font-weight: 700;
|
|
84
|
+
letter-spacing: -0.02em;
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: 0.5rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.meta {
|
|
91
|
+
color: var(--text-muted);
|
|
92
|
+
font-size: 0.875rem;
|
|
93
|
+
margin-top: 0.5rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Top Stats Banner */
|
|
97
|
+
.top-banner {
|
|
98
|
+
background: var(--surface);
|
|
99
|
+
border: 1px solid var(--border);
|
|
100
|
+
border-radius: 12px;
|
|
101
|
+
padding: 2.5rem;
|
|
102
|
+
margin-bottom: 3rem;
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-direction: row;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: 4rem;
|
|
107
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@media (max-width: 900px) {
|
|
111
|
+
.top-banner {
|
|
112
|
+
flex-direction: column;
|
|
113
|
+
gap: 2rem;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.score-section {
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-direction: column;
|
|
120
|
+
align-items: center;
|
|
121
|
+
min-width: 250px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.score-circle {
|
|
125
|
+
width: 140px;
|
|
126
|
+
height: 140px;
|
|
127
|
+
border-radius: 50%;
|
|
128
|
+
display: flex;
|
|
129
|
+
flex-direction: column;
|
|
130
|
+
align-items: center;
|
|
131
|
+
justify-content: center;
|
|
132
|
+
border: 8px solid var(--border);
|
|
133
|
+
font-size: 3.5rem;
|
|
134
|
+
font-weight: 700;
|
|
135
|
+
letter-spacing: -0.04em;
|
|
136
|
+
margin-bottom: 1rem;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.score-title {
|
|
140
|
+
text-align: center;
|
|
141
|
+
font-weight: 600;
|
|
142
|
+
font-size: 1.25rem;
|
|
143
|
+
margin-bottom: 0.5rem;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.top-rec {
|
|
147
|
+
font-size: 0.875rem;
|
|
148
|
+
color: var(--text-muted);
|
|
149
|
+
text-align: center;
|
|
150
|
+
padding: 0.5rem 1rem;
|
|
151
|
+
background: var(--code-bg);
|
|
152
|
+
border-radius: 8px;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.stats-section {
|
|
156
|
+
flex: 1;
|
|
157
|
+
display: grid;
|
|
158
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
159
|
+
gap: 2rem;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.stats-group h3 {
|
|
163
|
+
margin-top: 0;
|
|
164
|
+
font-size: 0.9rem;
|
|
165
|
+
text-transform: uppercase;
|
|
166
|
+
letter-spacing: 0.05em;
|
|
167
|
+
color: var(--text-muted);
|
|
168
|
+
margin-bottom: 1rem;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.stats-list {
|
|
172
|
+
list-style: none;
|
|
173
|
+
padding: 0;
|
|
174
|
+
margin: 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.stats-list li {
|
|
178
|
+
display: flex;
|
|
179
|
+
justify-content: space-between;
|
|
180
|
+
padding: 0.5rem 0;
|
|
181
|
+
border-bottom: 1px solid var(--border);
|
|
182
|
+
font-size: 0.95rem;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.stats-list li:last-child {
|
|
186
|
+
border-bottom: none;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.stat-label {
|
|
190
|
+
color: var(--text-muted);
|
|
191
|
+
font-weight: 500;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.stat-value {
|
|
195
|
+
font-weight: 600;
|
|
196
|
+
font-variant-numeric: tabular-nums;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* Files Panel: Accordion Files */
|
|
200
|
+
.files-panel h2 {
|
|
201
|
+
margin-top: 0;
|
|
202
|
+
font-size: 1.5rem;
|
|
203
|
+
font-weight: 600;
|
|
204
|
+
letter-spacing: -0.02em;
|
|
205
|
+
margin-bottom: 1.5rem;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.file-accordion {
|
|
209
|
+
background: var(--surface);
|
|
210
|
+
border: 1px solid var(--border);
|
|
211
|
+
border-radius: 12px;
|
|
212
|
+
margin-bottom: 1rem;
|
|
213
|
+
overflow: hidden;
|
|
214
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.02);
|
|
215
|
+
transition: border-color 0.2s;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.file-accordion.all-fixed {
|
|
219
|
+
opacity: 0.6;
|
|
220
|
+
border-color: var(--success);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.file-header {
|
|
224
|
+
background: var(--surface);
|
|
225
|
+
padding: 1.25rem 1.5rem;
|
|
226
|
+
cursor: pointer;
|
|
227
|
+
display: flex;
|
|
228
|
+
align-items: center;
|
|
229
|
+
justify-content: space-between;
|
|
230
|
+
transition: background 0.2s;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.file-header:hover {
|
|
234
|
+
background: var(--code-bg);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.file-header-left {
|
|
238
|
+
display: flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
gap: 0.75rem;
|
|
241
|
+
font-family: 'JetBrains Mono', monospace;
|
|
242
|
+
font-size: 0.9rem;
|
|
243
|
+
font-weight: 500;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.file-header-right {
|
|
247
|
+
display: flex;
|
|
248
|
+
align-items: center;
|
|
249
|
+
gap: 1rem;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.file-stats {
|
|
253
|
+
font-size: 0.8rem;
|
|
254
|
+
color: var(--text-muted);
|
|
255
|
+
display: flex;
|
|
256
|
+
gap: 0.5rem;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.file-stats span {
|
|
260
|
+
background: var(--code-bg);
|
|
261
|
+
padding: 0.2rem 0.6rem;
|
|
262
|
+
border-radius: 99px;
|
|
263
|
+
border: 1px solid var(--border);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.chevron {
|
|
267
|
+
transition: transform 0.3s ease;
|
|
268
|
+
color: var(--text-muted);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.file-accordion.open .chevron {
|
|
272
|
+
transform: rotate(180deg);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.file-content {
|
|
276
|
+
display: none;
|
|
277
|
+
border-top: 1px solid var(--border);
|
|
278
|
+
background: var(--bg);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.file-accordion.open .file-content {
|
|
282
|
+
display: block;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Violations */
|
|
286
|
+
.violation-item {
|
|
287
|
+
padding: 1.5rem;
|
|
288
|
+
border-bottom: 1px solid var(--border);
|
|
289
|
+
transition: opacity 0.3s ease;
|
|
290
|
+
position: relative;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.violation-item.fixed {
|
|
294
|
+
opacity: 0.4;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.violation-item.fixed::after {
|
|
298
|
+
content: 'FIXED';
|
|
299
|
+
position: absolute;
|
|
300
|
+
top: 1.5rem;
|
|
301
|
+
right: 1.5rem;
|
|
302
|
+
background: var(--success);
|
|
303
|
+
color: white;
|
|
304
|
+
padding: 0.25rem 0.75rem;
|
|
305
|
+
border-radius: 99px;
|
|
306
|
+
font-size: 0.75rem;
|
|
307
|
+
font-weight: 700;
|
|
308
|
+
letter-spacing: 0.05em;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.violation-item:last-child {
|
|
312
|
+
border-bottom: none;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.v-header {
|
|
316
|
+
display: flex;
|
|
317
|
+
align-items: center;
|
|
318
|
+
gap: 0.75rem;
|
|
319
|
+
margin-bottom: 1rem;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.badge {
|
|
323
|
+
padding: 0.2rem 0.6rem;
|
|
324
|
+
border-radius: 9999px;
|
|
325
|
+
font-size: 0.7rem;
|
|
326
|
+
font-weight: 700;
|
|
327
|
+
text-transform: uppercase;
|
|
328
|
+
letter-spacing: 0.05em;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.badge.critical { background: rgba(229, 72, 77, 0.1); color: var(--critical); border: 1px solid rgba(229, 72, 77, 0.2); }
|
|
332
|
+
.badge.warning { background: rgba(247, 206, 0, 0.1); color: var(--warning-text); border: 1px solid rgba(247, 206, 0, 0.2); }
|
|
333
|
+
.badge.info { background: rgba(0, 144, 255, 0.1); color: var(--info); border: 1px solid rgba(0, 144, 255, 0.2); }
|
|
334
|
+
|
|
335
|
+
.v-title {
|
|
336
|
+
font-weight: 600;
|
|
337
|
+
font-size: 0.95rem;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.v-line {
|
|
341
|
+
color: var(--text-muted);
|
|
342
|
+
font-family: 'JetBrains Mono', monospace;
|
|
343
|
+
font-size: 0.85rem;
|
|
344
|
+
background: var(--surface);
|
|
345
|
+
border: 1px solid var(--border);
|
|
346
|
+
padding: 0.2rem 0.6rem;
|
|
347
|
+
border-radius: 6px;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.v-message {
|
|
351
|
+
font-size: 0.95rem;
|
|
352
|
+
margin-bottom: 1rem;
|
|
353
|
+
color: var(--text);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.code-block-wrapper {
|
|
357
|
+
position: relative;
|
|
358
|
+
margin-bottom: 1rem;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
pre {
|
|
362
|
+
background: var(--code-bg);
|
|
363
|
+
padding: 1rem;
|
|
364
|
+
border-radius: 8px;
|
|
365
|
+
overflow-x: auto;
|
|
366
|
+
font-family: 'JetBrains Mono', monospace;
|
|
367
|
+
font-size: 0.875rem;
|
|
368
|
+
margin: 0;
|
|
369
|
+
border: 1px solid var(--border);
|
|
370
|
+
color: var(--text);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.v-suggestion {
|
|
374
|
+
display: flex;
|
|
375
|
+
gap: 0.75rem;
|
|
376
|
+
padding: 1rem;
|
|
377
|
+
background: rgba(48, 164, 108, 0.05);
|
|
378
|
+
border-left: 4px solid var(--success);
|
|
379
|
+
border-radius: 0 8px 8px 0;
|
|
380
|
+
font-size: 0.9rem;
|
|
381
|
+
color: var(--text);
|
|
382
|
+
margin-bottom: 1.25rem;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.action-row {
|
|
386
|
+
display: flex;
|
|
387
|
+
justify-content: flex-start;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.btn-fix {
|
|
391
|
+
display: flex;
|
|
392
|
+
align-items: center;
|
|
393
|
+
gap: 0.5rem;
|
|
394
|
+
background: var(--surface);
|
|
395
|
+
border: 1px solid var(--border);
|
|
396
|
+
color: var(--text);
|
|
397
|
+
padding: 0.5rem 1rem;
|
|
398
|
+
border-radius: 6px;
|
|
399
|
+
font-size: 0.875rem;
|
|
400
|
+
font-weight: 500;
|
|
401
|
+
cursor: pointer;
|
|
402
|
+
transition: all 0.2s;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.btn-fix:hover {
|
|
406
|
+
background: var(--code-bg);
|
|
407
|
+
border-color: var(--success);
|
|
408
|
+
color: var(--success);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.violation-item.fixed .btn-fix {
|
|
412
|
+
background: var(--success);
|
|
413
|
+
color: white;
|
|
414
|
+
border-color: var(--success);
|
|
415
|
+
}
|
|
416
|
+
</style>
|
|
417
|
+
</head>
|
|
418
|
+
<body>
|
|
419
|
+
<div class="app-container">
|
|
420
|
+
<header>
|
|
421
|
+
<div class="header-content">
|
|
422
|
+
<div>
|
|
423
|
+
<h1>✈ RenderPilot Report</h1>
|
|
424
|
+
<div class="meta" id="scan-meta">Loading scan data...</div>
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
</header>
|
|
428
|
+
|
|
429
|
+
<div class="top-banner">
|
|
430
|
+
<div class="score-section">
|
|
431
|
+
<div class="score-circle" id="overall-score">--</div>
|
|
432
|
+
<div class="score-title" id="score-label">--</div>
|
|
433
|
+
<div class="top-rec" id="top-rec"></div>
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
<div class="stats-section">
|
|
437
|
+
<div class="stats-group">
|
|
438
|
+
<h3>Issues Summary</h3>
|
|
439
|
+
<ul class="stats-list" id="stats-list">
|
|
440
|
+
<!-- Stats injected here -->
|
|
441
|
+
</ul>
|
|
442
|
+
</div>
|
|
443
|
+
|
|
444
|
+
<div class="stats-group">
|
|
445
|
+
<h3>Category Grades</h3>
|
|
446
|
+
<ul class="stats-list" id="categories-list">
|
|
447
|
+
<!-- Categories injected here -->
|
|
448
|
+
</ul>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
<main class="files-panel">
|
|
454
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;">
|
|
455
|
+
<h2 style="margin: 0;">Detected Violations</h2>
|
|
456
|
+
<button onclick="toggleAllFiles()" style="background: var(--surface); border: 1px solid var(--border); padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; color: var(--text);">Expand / Collapse All</button>
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
<div id="violations-container">
|
|
460
|
+
<!-- Violations injected here -->
|
|
461
|
+
</div>
|
|
462
|
+
</main>
|
|
463
|
+
</div>
|
|
464
|
+
|
|
465
|
+
<script>
|
|
466
|
+
const reportData = ${jsonData};
|
|
467
|
+
|
|
468
|
+
// Meta Data
|
|
469
|
+
document.getElementById('scan-meta').innerText =
|
|
470
|
+
\`Scanned \${reportData.scan.totalFiles} files in \${reportData.scan.durationMs}ms — \${new Date(reportData.scan.completedAt).toLocaleString()}\`;
|
|
471
|
+
|
|
472
|
+
// Overall Score
|
|
473
|
+
const scoreCircle = document.getElementById('overall-score');
|
|
474
|
+
scoreCircle.innerText = reportData.score.overall;
|
|
475
|
+
|
|
476
|
+
let color = 'var(--success)';
|
|
477
|
+
if (reportData.score.grade === 'C' || reportData.score.grade === 'D') color = 'var(--warning-text)';
|
|
478
|
+
if (reportData.score.grade === 'F') color = 'var(--critical)';
|
|
479
|
+
scoreCircle.style.borderColor = color;
|
|
480
|
+
scoreCircle.style.color = color;
|
|
481
|
+
|
|
482
|
+
document.getElementById('score-label').innerText = \`Grade \${reportData.score.grade} • \${reportData.score.label}\`;
|
|
483
|
+
document.getElementById('top-rec').innerText = reportData.score.topRecommendation;
|
|
484
|
+
|
|
485
|
+
// Summary Stats
|
|
486
|
+
const statsList = document.getElementById('stats-list');
|
|
487
|
+
statsList.innerHTML = \`
|
|
488
|
+
<li>
|
|
489
|
+
<span class="stat-label">Critical Issues</span>
|
|
490
|
+
<span class="stat-value" style="color: var(--critical);">\${reportData.scan.violationsBySeverity.critical || 0}</span>
|
|
491
|
+
</li>
|
|
492
|
+
<li>
|
|
493
|
+
<span class="stat-label">Warnings</span>
|
|
494
|
+
<span class="stat-value" style="color: var(--warning-text);">\${reportData.scan.violationsBySeverity.warning || 0}</span>
|
|
495
|
+
</li>
|
|
496
|
+
<li>
|
|
497
|
+
<span class="stat-label">Info / Tips</span>
|
|
498
|
+
<span class="stat-value" style="color: var(--info);">\${reportData.scan.violationsBySeverity.info || 0}</span>
|
|
499
|
+
</li>
|
|
500
|
+
<li>
|
|
501
|
+
<span class="stat-label">Total Violations</span>
|
|
502
|
+
<span class="stat-value">\${reportData.scan.totalViolations}</span>
|
|
503
|
+
</li>
|
|
504
|
+
\`;
|
|
505
|
+
|
|
506
|
+
// Category Grades
|
|
507
|
+
const catList = document.getElementById('categories-list');
|
|
508
|
+
catList.innerHTML = reportData.score.categories.map(c => {
|
|
509
|
+
let catColor = 'var(--text)';
|
|
510
|
+
if (c.grade === 'A' || c.grade === 'B') catColor = 'var(--success)';
|
|
511
|
+
if (c.grade === 'C' || c.grade === 'D') catColor = 'var(--warning-text)';
|
|
512
|
+
if (c.grade === 'F') catColor = 'var(--critical)';
|
|
513
|
+
|
|
514
|
+
return \`
|
|
515
|
+
<li>
|
|
516
|
+
<span class="stat-label">\${c.category.charAt(0).toUpperCase() + c.category.slice(1)}</span>
|
|
517
|
+
<span class="stat-value" style="color: \${catColor};">\${c.score} (\${c.grade})</span>
|
|
518
|
+
</li>
|
|
519
|
+
\`}).join('');
|
|
520
|
+
|
|
521
|
+
// Populate Files
|
|
522
|
+
const container = document.getElementById('violations-container');
|
|
523
|
+
const fileGroups = {};
|
|
524
|
+
|
|
525
|
+
reportData.scan.allViolations.forEach((v, index) => {
|
|
526
|
+
// Attach a unique ID to each violation for the checkbox logic
|
|
527
|
+
v.uniqueId = 'v_' + index;
|
|
528
|
+
if (!fileGroups[v.filePath]) fileGroups[v.filePath] = [];
|
|
529
|
+
fileGroups[v.filePath].push(v);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
if (Object.keys(fileGroups).length === 0) {
|
|
533
|
+
container.innerHTML = \`
|
|
534
|
+
<div class="file-accordion" style="padding: 4rem 2rem; text-align: center; color: var(--text-muted);">
|
|
535
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-bottom: 1rem; color: var(--success);">
|
|
536
|
+
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
|
|
537
|
+
<polyline points="22 4 12 14.01 9 11.01"></polyline>
|
|
538
|
+
</svg>
|
|
539
|
+
<h3 style="margin:0; color: var(--text);">No violations found!</h3>
|
|
540
|
+
<p>Your codebase is perfectly optimized according to RenderPilot.</p>
|
|
541
|
+
</div>\`;
|
|
542
|
+
} else {
|
|
543
|
+
let html = '';
|
|
544
|
+
let fileIndex = 0;
|
|
545
|
+
|
|
546
|
+
for (const [filePath, violations] of Object.entries(fileGroups)) {
|
|
547
|
+
// Extract a clean relative path
|
|
548
|
+
const pathParts = filePath.split(/[\\\\/]/);
|
|
549
|
+
const relPath = pathParts.slice(Math.max(pathParts.length - 3, 0)).join('/');
|
|
550
|
+
|
|
551
|
+
let vHtml = '';
|
|
552
|
+
let criticalCount = 0;
|
|
553
|
+
let warningCount = 0;
|
|
554
|
+
|
|
555
|
+
violations.forEach(v => {
|
|
556
|
+
if (v.severity === 'critical') criticalCount++;
|
|
557
|
+
if (v.severity === 'warning') warningCount++;
|
|
558
|
+
|
|
559
|
+
vHtml += \`
|
|
560
|
+
<div class="violation-item" id="\${v.uniqueId}">
|
|
561
|
+
<div class="v-header">
|
|
562
|
+
<span class="badge \${v.severity}">\${v.severity}</span>
|
|
563
|
+
<span class="v-title">\${v.ruleName}</span>
|
|
564
|
+
<span class="v-line">Line \${v.line}</span>
|
|
565
|
+
</div>
|
|
566
|
+
<div class="v-message">\${v.message}</div>
|
|
567
|
+
\${v.codeSnippet ? \`<div class="code-block-wrapper"><pre><code>\${v.codeSnippet.replace(/</g, '<')}</code></pre></div>\` : ''}
|
|
568
|
+
<div class="v-suggestion">
|
|
569
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="flex-shrink: 0;"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
|
|
570
|
+
<span>\${v.suggestion}</span>
|
|
571
|
+
</div>
|
|
572
|
+
<div class="action-row">
|
|
573
|
+
<button class="btn-fix" onclick="toggleFix('\${v.uniqueId}', '\${filePath}')">
|
|
574
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
|
575
|
+
<span class="btn-text">Mark as Fixed</span>
|
|
576
|
+
</button>
|
|
577
|
+
</div>
|
|
578
|
+
</div>
|
|
579
|
+
\`;
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// NOTE: We omit 'open' class so it is closed by default
|
|
583
|
+
html += \`
|
|
584
|
+
<div class="file-accordion" id="file_\${fileIndex}" data-filepath="\${filePath}">
|
|
585
|
+
<div class="file-header" onclick="toggleAccordion('file_\${fileIndex}')">
|
|
586
|
+
<div class="file-header-left">
|
|
587
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: var(--primary);"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
|
|
588
|
+
\${relPath}
|
|
589
|
+
</div>
|
|
590
|
+
<div class="file-header-right">
|
|
591
|
+
<div class="file-stats">
|
|
592
|
+
\${criticalCount > 0 ? \`<span style="color: var(--critical); border-color: rgba(229, 72, 77, 0.3);">\${criticalCount} Crit</span>\` : ''}
|
|
593
|
+
\${warningCount > 0 ? \`<span style="color: var(--warning-text); border-color: rgba(247, 206, 0, 0.3);">\${warningCount} Warn</span>\` : ''}
|
|
594
|
+
<span>\${violations.length} Total</span>
|
|
595
|
+
</div>
|
|
596
|
+
<svg class="chevron" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
|
597
|
+
</div>
|
|
598
|
+
</div>
|
|
599
|
+
<div class="file-content">
|
|
600
|
+
\${vHtml}
|
|
601
|
+
</div>
|
|
602
|
+
</div>
|
|
603
|
+
\`;
|
|
604
|
+
|
|
605
|
+
fileIndex++;
|
|
606
|
+
}
|
|
607
|
+
container.innerHTML = html;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Interactivity Functions
|
|
611
|
+
function toggleAccordion(id) {
|
|
612
|
+
const el = document.getElementById(id);
|
|
613
|
+
if (el.classList.contains('open')) {
|
|
614
|
+
el.classList.remove('open');
|
|
615
|
+
} else {
|
|
616
|
+
el.classList.add('open');
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
let allOpen = false;
|
|
621
|
+
function toggleAllFiles() {
|
|
622
|
+
const accordions = document.querySelectorAll('.file-accordion');
|
|
623
|
+
allOpen = !allOpen;
|
|
624
|
+
accordions.forEach(acc => {
|
|
625
|
+
if (allOpen) acc.classList.add('open');
|
|
626
|
+
else acc.classList.remove('open');
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function toggleFix(violationId, filePath) {
|
|
631
|
+
const vEl = document.getElementById(violationId);
|
|
632
|
+
const isFixed = vEl.classList.toggle('fixed');
|
|
633
|
+
|
|
634
|
+
const btnText = vEl.querySelector('.btn-text');
|
|
635
|
+
btnText.innerText = isFixed ? 'Unmark Fixed' : 'Mark as Fixed';
|
|
636
|
+
|
|
637
|
+
// Check if all violations in this file are fixed
|
|
638
|
+
const fileAccordion = vEl.closest('.file-accordion');
|
|
639
|
+
const allViolations = fileAccordion.querySelectorAll('.violation-item');
|
|
640
|
+
const fixedViolations = fileAccordion.querySelectorAll('.violation-item.fixed');
|
|
641
|
+
|
|
642
|
+
if (allViolations.length === fixedViolations.length) {
|
|
643
|
+
fileAccordion.classList.add('all-fixed');
|
|
644
|
+
// Auto close it when done
|
|
645
|
+
fileAccordion.classList.remove('open');
|
|
646
|
+
} else {
|
|
647
|
+
fileAccordion.classList.remove('all-fixed');
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
</script>
|
|
651
|
+
</body>
|
|
652
|
+
</html>`;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
module.exports = { getHtmlTemplate };
|