@pythonidaer/complexity-report 1.0.2

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/LICENSE +21 -0
  3. package/README.md +103 -0
  4. package/assets/prettify.css +1 -0
  5. package/assets/prettify.js +2 -0
  6. package/assets/sort-arrow-sprite.png +0 -0
  7. package/complexity-breakdown.js +53 -0
  8. package/decision-points/ast-utils.js +127 -0
  9. package/decision-points/decision-type.js +92 -0
  10. package/decision-points/function-matching.js +185 -0
  11. package/decision-points/in-params.js +262 -0
  12. package/decision-points/index.js +6 -0
  13. package/decision-points/node-helpers.js +89 -0
  14. package/decision-points/parent-map.js +62 -0
  15. package/decision-points/parse-main.js +101 -0
  16. package/decision-points/ternary-multiline.js +86 -0
  17. package/export-generators/helpers.js +309 -0
  18. package/export-generators/index.js +143 -0
  19. package/export-generators/md-exports.js +160 -0
  20. package/export-generators/txt-exports.js +262 -0
  21. package/function-boundaries/arrow-brace-body.js +302 -0
  22. package/function-boundaries/arrow-helpers.js +93 -0
  23. package/function-boundaries/arrow-jsx.js +73 -0
  24. package/function-boundaries/arrow-object-literal.js +65 -0
  25. package/function-boundaries/arrow-single-expr.js +72 -0
  26. package/function-boundaries/brace-scanning.js +151 -0
  27. package/function-boundaries/index.js +67 -0
  28. package/function-boundaries/named-helpers.js +227 -0
  29. package/function-boundaries/parse-utils.js +456 -0
  30. package/function-extraction/ast-utils.js +112 -0
  31. package/function-extraction/extract-callback.js +65 -0
  32. package/function-extraction/extract-from-eslint.js +91 -0
  33. package/function-extraction/extract-name-ast.js +133 -0
  34. package/function-extraction/extract-name-regex.js +267 -0
  35. package/function-extraction/index.js +6 -0
  36. package/function-extraction/utils.js +29 -0
  37. package/function-hierarchy.js +427 -0
  38. package/html-generators/about.js +75 -0
  39. package/html-generators/file-boundary-builders.js +36 -0
  40. package/html-generators/file-breakdown.js +412 -0
  41. package/html-generators/file-data.js +50 -0
  42. package/html-generators/file-helpers.js +100 -0
  43. package/html-generators/file-javascript.js +430 -0
  44. package/html-generators/file-line-render.js +160 -0
  45. package/html-generators/file.css +370 -0
  46. package/html-generators/file.js +207 -0
  47. package/html-generators/folder.js +424 -0
  48. package/html-generators/index.js +6 -0
  49. package/html-generators/main-index.js +346 -0
  50. package/html-generators/shared.css +471 -0
  51. package/html-generators/utils.js +15 -0
  52. package/index.js +36 -0
  53. package/integration/eslint/index.js +94 -0
  54. package/integration/threshold/index.js +45 -0
  55. package/package.json +64 -0
  56. package/report/cli.js +58 -0
  57. package/report/index.js +559 -0
@@ -0,0 +1,370 @@
1
+ /* File-specific styles - coverage table and breakdown */
2
+ /* Note: Common styles are in shared.css and should be linked separately */
3
+
4
+ .coverage-block {
5
+ padding-left: 0 !important;
6
+ padding-right: 0 !important;
7
+ }
8
+
9
+ table.coverage {
10
+ border-collapse: collapse;
11
+ margin: 0;
12
+ padding: 0;
13
+ width: 100%;
14
+ }
15
+
16
+ table.coverage td {
17
+ margin: 0;
18
+ padding: 0;
19
+ vertical-align: top;
20
+ }
21
+
22
+ table.coverage td.line-count {
23
+ text-align: right;
24
+ padding: 0 5px 0 5px;
25
+ }
26
+
27
+ table.coverage td.line-coverage {
28
+ text-align: right;
29
+ padding-right: 10px;
30
+ min-width: 20px;
31
+ }
32
+
33
+ table.coverage td span.cline-any {
34
+ display: inline-block;
35
+ padding: 0 5px;
36
+ width: 100%;
37
+ }
38
+
39
+ span.cline-neutral {
40
+ background: #eaeaea;
41
+ }
42
+
43
+ span.cline-yes {
44
+ background: rgb(230, 245, 208);
45
+ }
46
+
47
+ pre.prettyprint {
48
+ border: none !important;
49
+ padding: 0 !important;
50
+ margin: 0 !important;
51
+ }
52
+
53
+ .wrapper {
54
+ min-height: 100%;
55
+ height: auto !important;
56
+ height: 100%;
57
+ margin: 0 auto -48px;
58
+ }
59
+
60
+ .scroll-to-top {
61
+ position: fixed;
62
+ bottom: 1rem;
63
+ right: 1rem;
64
+ z-index: 100;
65
+ padding: 0.5rem 0.75rem;
66
+ font-size: 1.25rem;
67
+ line-height: 1;
68
+ border-radius: 4px;
69
+ border: 1px solid var(--color-muted, #ccc);
70
+ background: var(--color-bg, #fff);
71
+ color: var(--color-text, #333);
72
+ cursor: pointer;
73
+ opacity: 0;
74
+ visibility: hidden;
75
+ transition: opacity 0.2s, visibility 0.2s;
76
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
77
+ }
78
+
79
+ .scroll-to-top.scroll-to-top-visible {
80
+ opacity: 1;
81
+ visibility: visible;
82
+ }
83
+
84
+ .scroll-to-top:hover {
85
+ background: var(--color-muted, #eee);
86
+ }
87
+
88
+ .footer,
89
+ .push {
90
+ height: 48px;
91
+ }
92
+
93
+ .footer {
94
+ padding: 20px;
95
+ }
96
+
97
+ .code-line {
98
+ display: inline-block;
99
+ white-space: pre;
100
+ width: max-content;
101
+ font: inherit;
102
+ }
103
+
104
+ .decision-point-line {
105
+ background: #F6C6CE;
106
+ }
107
+
108
+ /* When "Show Highlights & Borders" is unchecked: hide pink highlights */
109
+ .coverage-table-wrapper.hide-highlights .decision-point-line {
110
+ background: transparent;
111
+ }
112
+
113
+ .complexity-breakdown {
114
+ margin: 0;
115
+ padding: 0;
116
+ max-width: 100%;
117
+ overflow-x: auto;
118
+ display: block;
119
+ width: auto;
120
+ }
121
+
122
+ .complexity-breakdown h2 {
123
+ margin: 0 0 10px 0;
124
+ font-size: 14px;
125
+ font-weight: bold;
126
+ color: #333;
127
+ }
128
+
129
+ .complexity-breakdown-table {
130
+ width: auto;
131
+ max-width: 100%;
132
+ border-collapse: collapse;
133
+ margin: 10px 0 0;
134
+ font-size: 13px;
135
+ }
136
+
137
+ .complexity-breakdown-table th {
138
+ padding: 8px 4px;
139
+ text-align: center;
140
+ border: 1px solid #ddd;
141
+ background-color: #f5f5f5;
142
+ font-weight: bold;
143
+ vertical-align: middle;
144
+ }
145
+
146
+ .complexity-breakdown-table td {
147
+ padding: 10px;
148
+ text-align: center;
149
+ border: 1px solid #ddd;
150
+ vertical-align: middle;
151
+ }
152
+
153
+ .complexity-breakdown-table td.function-name {
154
+ text-align: left;
155
+ }
156
+
157
+ .function-name-parent {
158
+ font-weight: 600;
159
+ }
160
+
161
+ .breakdown-header {
162
+ text-align: center;
163
+ font-size: 16px;
164
+ background-color: #e8e8e8;
165
+ font-weight: bold;
166
+ padding: 10px;
167
+ }
168
+
169
+ .breakdown-group-header {
170
+ background-color: #f0f0f0;
171
+ font-weight: 600;
172
+ font-size: 13px;
173
+ }
174
+
175
+ .breakdown-col-header {
176
+ font-weight: normal;
177
+ font-size: 11px;
178
+ background-color: #fafafa;
179
+ white-space: nowrap;
180
+ }
181
+
182
+ .breakdown-value {
183
+ text-align: center;
184
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
185
+ font-size: 12px;
186
+ color: #333;
187
+ }
188
+
189
+ .breakdown-value-empty {
190
+ background: #fff4c2;
191
+ }
192
+
193
+ .breakdown-col-empty {
194
+ display: none;
195
+ }
196
+
197
+ .breakdown-group-empty {
198
+ display: none;
199
+ }
200
+
201
+ .no-matches-message {
202
+ text-align: center;
203
+ padding: 30px 20px;
204
+ color: #666;
205
+ font-style: italic;
206
+ background-color: #f9f9f9;
207
+ }
208
+
209
+ .function-name {
210
+ text-align: left;
211
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
212
+ font-size: 11px;
213
+ padding-left: 10px;
214
+ padding-right: 10px;
215
+ white-space: nowrap;
216
+ }
217
+
218
+ .function-name .function-name-parent {
219
+ font-weight: 600;
220
+ }
221
+
222
+ .function-name .function-name-leaf {
223
+ font-weight: 600;
224
+ }
225
+
226
+ .breakdown-function-header {
227
+ text-align: left;
228
+ vertical-align: middle;
229
+ font-weight: bold;
230
+ }
231
+
232
+ .breakdown-line-header {
233
+ text-align: center;
234
+ vertical-align: middle;
235
+ font-weight: bold;
236
+ }
237
+
238
+ .breakdown-complexity-header {
239
+ text-align: center;
240
+ vertical-align: middle;
241
+ font-weight: bold;
242
+ }
243
+
244
+ .breakdown-line-value {
245
+ text-align: center;
246
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
247
+ font-size: 12px;
248
+ }
249
+
250
+ .complexity-breakdown-table.hide-lines .breakdown-line-column {
251
+ display: none;
252
+ }
253
+
254
+ .sortable {
255
+ cursor: pointer;
256
+ user-select: none;
257
+ position: relative;
258
+ }
259
+
260
+ .sortable:hover {
261
+ background-color: #e8e8e8;
262
+ }
263
+
264
+ .sort-indicator {
265
+ display: inline-block;
266
+ margin-left: 5px;
267
+ font-size: 10px;
268
+ color: #666;
269
+ width: 12px;
270
+ min-width: 12px;
271
+ text-align: center;
272
+ vertical-align: middle;
273
+ line-height: 1;
274
+ }
275
+
276
+ .sort-indicator::after {
277
+ content: '';
278
+ display: inline-block;
279
+ width: 10px;
280
+ height: 10px;
281
+ }
282
+
283
+ .sort-indicator.sort-asc::after {
284
+ content: '▲';
285
+ }
286
+
287
+ .sort-indicator.sort-desc::after {
288
+ content: '▼';
289
+ }
290
+
291
+ .complexity-value {
292
+ text-align: center;
293
+ font-weight: bold;
294
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
295
+ font-size: 13px;
296
+ }
297
+
298
+ .complexity-value.complexity-above-threshold {
299
+ box-shadow: inset 0 0 0 2px #c00;
300
+ }
301
+
302
+ .complexity-breakdown-table tbody tr {
303
+ background-color: rgb(230, 245, 208);
304
+ }
305
+
306
+ .complexity-breakdown-table tbody tr:hover {
307
+ background-color: rgb(220, 235, 198);
308
+ }
309
+
310
+ .complexity-breakdown-table tbody tr.breakdown-row-selected {
311
+ background-color: #e0e0e0 !important;
312
+ }
313
+
314
+ .complexity-breakdown-table tbody tr.breakdown-function-row {
315
+ cursor: pointer;
316
+ }
317
+
318
+ table.coverage tr.function-range-highlight {
319
+ background-color: #e8e8e8;
320
+ }
321
+
322
+ .breakdown-function {
323
+ font-weight: bold;
324
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
325
+ }
326
+
327
+ .breakdown-complexity {
328
+ text-align: center;
329
+ }
330
+
331
+ .breakdown-details {
332
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
333
+ font-size: 13px;
334
+ }
335
+
336
+ .breakdown-divider {
337
+ color: #999;
338
+ margin: 0 4px;
339
+ font-weight: normal;
340
+ }
341
+
342
+ .complexity-number {
343
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
344
+ font-size: 10px;
345
+ color: #555;
346
+ background: #E8E8E8;
347
+ padding: 4px 5px;
348
+ border-radius: 3px;
349
+ vertical-align: middle;
350
+ margin: 0 2px;
351
+ display: inline-block;
352
+ }
353
+
354
+ .coverage-table-wrapper {
355
+ position: relative;
356
+ }
357
+
358
+ #hover-vertical-line {
359
+ position: absolute;
360
+ left: 0;
361
+ width: 0;
362
+ border-left: 1px solid #0074D9;
363
+ pointer-events: none;
364
+ visibility: hidden;
365
+ z-index: 1;
366
+ }
367
+
368
+ #hover-vertical-line.visible {
369
+ visibility: visible;
370
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Main file page HTML generator
3
+ * Orchestrates file-helpers, file-breakdown, file-boundary-builders,
4
+ * file-line-render, file-data
5
+ */
6
+ import { resolve } from 'path';
7
+ import { generateJavaScriptCode } from './file-javascript.js';
8
+ import { detectLanguage, getFilePagePaths, readSourceFile } from './file-helpers.js';
9
+ import {
10
+ calculateFunctionBreakdowns,
11
+ generateSummarySection,
12
+ generateBreakdownSectionHTML,
13
+ prepareFileLevelData,
14
+ prepareBreakdownColumns,
15
+ } from './file-breakdown.js';
16
+ import { buildLineToSpan } from './file-boundary-builders.js';
17
+ import { generateLineRowHTML } from './file-line-render.js';
18
+ import { createLineToFunctionMap, createDecisionPointLineMap } from './file-data.js';
19
+
20
+ /**
21
+ * Generates file HTML page with line-by-line complexity annotations
22
+ * @param {string} filePath - Relative file path
23
+ * @param {Array} functions - Array of function objects
24
+ * @param {string} projectRoot - Project root directory
25
+ * @param {Function} findFunctionBoundaries - Find function boundaries
26
+ * @param {Function} parseDecisionPoints - Parse decision points
27
+ * @param {Function} calculateComplexityBreakdown - Calculate breakdown
28
+ * @param {Function} formatFunctionHierarchy - Format hierarchy
29
+ * @param {Function} getComplexityLevel - Get complexity level
30
+ * @param {Function} getDirectory - Get directory from file path
31
+ * @param {Function} escapeHtml - Escape HTML
32
+ * @param {'classic'|'modified'} [variant='classic'] - Complexity variant
33
+ * (controls switch vs case column in FCB)
34
+ * @returns {string} Full HTML document string
35
+ */
36
+ export async function generateFileHTML(
37
+ filePath,
38
+ functions,
39
+ projectRoot,
40
+ findFunctionBoundaries,
41
+ parseDecisionPoints,
42
+ calculateComplexityBreakdown,
43
+ formatFunctionHierarchy,
44
+ getComplexityLevel,
45
+ getDirectory,
46
+ escapeHtml,
47
+ showAllColumnsInitially = false,
48
+ hideTableInitially = false,
49
+ complexityThreshold = 10,
50
+ hideLinesInitially = false,
51
+ hideHighlightsInitially = false,
52
+ variant = 'classic'
53
+ ) {
54
+ const fullPath = resolve(projectRoot, filePath);
55
+ const { sourceCode, sourceLines } = readSourceFile(fullPath, filePath);
56
+
57
+ const lineToFunction = createLineToFunctionMap(functions);
58
+ const functionBoundaries = findFunctionBoundaries(sourceCode, functions);
59
+ const decisionPoints = await parseDecisionPoints(
60
+ sourceCode,
61
+ functionBoundaries,
62
+ functions,
63
+ filePath,
64
+ projectRoot
65
+ );
66
+ const functionBreakdowns = calculateFunctionBreakdowns(
67
+ functions,
68
+ functionBoundaries,
69
+ decisionPoints,
70
+ calculateComplexityBreakdown
71
+ );
72
+ const lineToDecisionPoint = createDecisionPointLineMap(decisionPoints);
73
+
74
+ const fileData = prepareFileLevelData(
75
+ functions,
76
+ functionBreakdowns,
77
+ complexityThreshold
78
+ );
79
+ const {
80
+ totalFunctions,
81
+ withinThreshold,
82
+ level,
83
+ decisionPointTotals,
84
+ withinThresholdPercentage,
85
+ } = fileData;
86
+ const summarySection = generateSummarySection(
87
+ decisionPointTotals,
88
+ totalFunctions,
89
+ withinThreshold,
90
+ withinThresholdPercentage
91
+ );
92
+
93
+ const fileDir = getDirectory(filePath);
94
+ const fileName = filePath.split('/').pop();
95
+ const paths = getFilePagePaths(filePath, fileDir);
96
+
97
+ const breakdownData = prepareBreakdownColumns(
98
+ functions,
99
+ functionBreakdowns,
100
+ showAllColumnsInitially,
101
+ variant
102
+ );
103
+ const { columnStructure, emptyColumns, initialColumns } = breakdownData;
104
+ const breakdownItems = formatFunctionHierarchy(
105
+ functions,
106
+ functionBoundaries,
107
+ functionBreakdowns,
108
+ sourceCode,
109
+ columnStructure,
110
+ emptyColumns,
111
+ showAllColumnsInitially,
112
+ complexityThreshold
113
+ );
114
+ const breakdownSection = generateBreakdownSectionHTML(
115
+ functions,
116
+ initialColumns,
117
+ breakdownItems,
118
+ columnStructure,
119
+ showAllColumnsInitially,
120
+ hideTableInitially,
121
+ hideLinesInitially,
122
+ hideHighlightsInitially
123
+ );
124
+
125
+ const lineToSpan = buildLineToSpan(
126
+ functionBoundaries,
127
+ sourceLines.length,
128
+ sourceLines
129
+ );
130
+
131
+ const languageClass = detectLanguage(filePath);
132
+ const lineRows = sourceLines.map((line, index) =>
133
+ generateLineRowHTML(
134
+ line,
135
+ index,
136
+ lineToFunction,
137
+ lineToDecisionPoint,
138
+ getComplexityLevel,
139
+ escapeHtml,
140
+ languageClass
141
+ )
142
+ ).join('\n');
143
+
144
+ const javascriptCode = generateJavaScriptCode(
145
+ showAllColumnsInitially,
146
+ columnStructure,
147
+ emptyColumns,
148
+ lineToSpan,
149
+ !hideLinesInitially
150
+ );
151
+
152
+ const {
153
+ backLink,
154
+ folderIndexPath,
155
+ aboutPath,
156
+ prettifyCssPath,
157
+ prettifyJsPath,
158
+ sharedCssPath,
159
+ fileCssPath,
160
+ } = paths;
161
+
162
+ return `<!doctype html>
163
+ <html lang="en">
164
+ <head>
165
+ <title>Complexity report for ${filePath}</title>
166
+ <meta charset="utf-8" />
167
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
168
+ <link rel="stylesheet" href="${sharedCssPath}" />
169
+ <link rel="stylesheet" href="${prettifyCssPath}" />
170
+ <link rel="stylesheet" href="${fileCssPath}" />
171
+ </head>
172
+ <body>
173
+ <div class='wrapper'>
174
+ <div class='pad1'>
175
+ <div class="header-row">
176
+ <h1>
177
+ <a href="${backLink}">All files</a>${
178
+ fileDir ? ` / <a href="${folderIndexPath}">${fileDir}</a>` : ''
179
+ } / ${fileName}
180
+ </h1>
181
+ <a href="${aboutPath}" class="about-link">
182
+ About Cyclomatic Complexity
183
+ </a>
184
+ </div>
185
+ ${summarySection}
186
+ ${breakdownSection}
187
+ </div>
188
+ <div class='status-line ${level}'></div>
189
+ <div class="pad1 coverage-block">
190
+ <div class="coverage-table-wrapper${hideHighlightsInitially ? ' hide-highlights' : ''}">
191
+ <div id="hover-vertical-line" aria-hidden="true"></div>
192
+ <pre><table class="coverage" id="coverage-table">
193
+ ${lineRows}
194
+ </table></pre>
195
+ </div>
196
+ </div>
197
+ <div class='push'></div>
198
+ </div>
199
+ <div class='footer quiet pad2 space-top1 center small'>
200
+ Complexity report generated by <a href="https://www.github.com/pythonidaer" target="_blank" rel="noopener noreferrer">pythonidaer</a> at ${new Date().toISOString()}
201
+ </div>
202
+ <button type="button" id="scroll-to-top" class="scroll-to-top" aria-label="Scroll to top" title="Scroll to top">↑</button>
203
+ <script src="${prettifyJsPath}"></script>
204
+ <script>${javascriptCode}</script>
205
+ </body>
206
+ </html>`;
207
+ }