@ovh-ux/manager-static-analysis-kit 0.9.0 → 0.10.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.
Files changed (29) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/bin/perf-budgets-cli.js +1 -1
  3. package/bin/tests-coverage-cli.js +4 -2
  4. package/bin/types-coverage-cli.js +68 -0
  5. package/bin/utils/args-parse-utils.js +8 -1
  6. package/bin/utils/turbo-utils.js +14 -3
  7. package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.d.ts +4 -1
  8. package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.d.ts.map +1 -1
  9. package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.js +72 -18
  10. package/dist/adapters/code-duplication/types/CodeDuplication.d.ts +3 -0
  11. package/dist/adapters/code-duplication/types/CodeDuplication.d.ts.map +1 -1
  12. package/dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.d.ts.map +1 -1
  13. package/dist/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.js +31 -8
  14. package/dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.d.ts.map +1 -1
  15. package/dist/adapters/types-coverage/helpers/types-coverage-analysis-helper.js +121 -48
  16. package/dist/adapters/types-coverage/types/TypesCoverage.d.ts +8 -0
  17. package/dist/adapters/types-coverage/types/TypesCoverage.d.ts.map +1 -1
  18. package/dist/configs/code-duplication-config.d.ts +1 -0
  19. package/dist/configs/code-duplication-config.d.ts.map +1 -1
  20. package/dist/configs/code-duplication-config.js +2 -1
  21. package/dist/configs/shared-exclusion-patterns.d.ts +1 -0
  22. package/dist/configs/shared-exclusion-patterns.d.ts.map +1 -1
  23. package/dist/configs/shared-exclusion-patterns.js +20 -0
  24. package/dist/configs/tests-coverage-config.js +1 -1
  25. package/dist/configs/types-coverage-config.d.ts +14 -0
  26. package/dist/configs/types-coverage-config.d.ts.map +1 -1
  27. package/dist/configs/types-coverage-config.js +13 -16
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,21 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [0.10.0](https://github.com/ovh/manager/compare/@ovh-ux/manager-static-analysis-kit@0.9.0...@ovh-ux/manager-static-analysis-kit@0.10.0) (2025-10-02)
7
+
8
+
9
+ ### Features
10
+
11
+ * add loose types calculation to types reports ([2398431](https://github.com/ovh/manager/commit/2398431ae3bf9e5300683894fa5fe6a12371108f)), closes [#MANAGER-19923](https://github.com/ovh/manager/issues/MANAGER-19923)
12
+ * clarify loose types metrics ([ef00a39](https://github.com/ovh/manager/commit/ef00a39fce3cd661ba89673cf9263637a5ec31b8)), closes [#MANAGER-19923](https://github.com/ovh/manager/issues/MANAGER-19923)
13
+ * enhance code duplication reporting ([bfbe364](https://github.com/ovh/manager/commit/bfbe3644d31bde25229dad7cfd2ff0446440fd49)), closes [#MANAGER-19923](https://github.com/ovh/manager/issues/MANAGER-19923)
14
+ * enhance loose types report format ([1688577](https://github.com/ovh/manager/commit/168857738ba67e1406e5fb138c2d2f0c3789305b)), closes [#MANAGER-19923](https://github.com/ovh/manager/issues/MANAGER-19923)
15
+ * improve static and dynamic reporting ([09de1f8](https://github.com/ovh/manager/commit/09de1f86d2e57a7ba61612ae3df45d121da39908)), closes [#MANAGER-19923](https://github.com/ovh/manager/issues/MANAGER-19923)
16
+
17
+
18
+
19
+
20
+
6
21
  # [0.9.0](https://github.com/ovh/manager/compare/@ovh-ux/manager-static-analysis-kit@0.8.0...@ovh-ux/manager-static-analysis-kit@0.9.0) (2025-09-18)
7
22
 
8
23
 
@@ -95,7 +95,7 @@ function main() {
95
95
  const turboFilters = resolveTurboFilters({ appsDir, appFolders, packageNames });
96
96
 
97
97
  // Step 1: build
98
- runTurboBuild(rootDir, turboFilters);
98
+ runTurboBuild(rootDir, turboFilters, ['--continue']);
99
99
 
100
100
  // Step 2: analyze apps + combined
101
101
  runAppsAnalysis({
@@ -93,13 +93,15 @@ function main() {
93
93
  const turboFilters = resolveTurboFilters({ appsDir, appFolders, packageNames });
94
94
 
95
95
  // 1) Build all selected targets (best-effort)
96
- runTurboBuild(rootDir, turboFilters);
96
+ runTurboBuild(rootDir, turboFilters, ['--continue']);
97
97
 
98
98
  // 2) Run tests with coverage; keep args minimal for Vitest/Jest compatibility
99
99
  // (Projects should already be configured to emit json-summary/lcov.)
100
100
  runTurboTests(rootDir, turboFilters, [
101
- '--',
101
+ '--continue', // turbo: keep going even if some tests fail
102
+ '--', // pass the rest to the test runner
102
103
  '--coverage',
104
+ '--coverage.reportOnFailure',
103
105
  '--coverage.reporter',
104
106
  'json-summary',
105
107
  '--coverage.reporter',
@@ -2,6 +2,7 @@
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import process from 'node:process';
5
+ import ts from 'typescript';
5
6
 
6
7
  import {
7
8
  collectTypesCoverage,
@@ -60,6 +61,66 @@ function patchTsConfig(appDir, fn) {
60
61
  }
61
62
  }
62
63
 
64
+ /**
65
+ * Count loose TypeScript constructs (`as`, `any`, `unknown`) in src/.
66
+ *
67
+ * Uses the TypeScript compiler API to avoid false positives from strings, comments, etc.
68
+ *
69
+ * @param {string} appDir Absolute path to app root
70
+ * @returns {{ as: number; any: number; unknown: number }}
71
+ */
72
+ export function countLooseTypes(appDir) {
73
+ const srcDir = path.join(appDir, 'src');
74
+ let asCount = 0;
75
+ let anyCount = 0;
76
+ let unknownCount = 0;
77
+
78
+ function analyzeFile(filePath) {
79
+ const content = fs.readFileSync(filePath, 'utf8');
80
+ const source = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
81
+
82
+ function visit(node) {
83
+ // "as" expressions: foo as string
84
+ if (ts.isAsExpression(node)) {
85
+ asCount++;
86
+ if (node.type.kind === ts.SyntaxKind.AnyKeyword) {
87
+ anyCount++;
88
+ } else if (node.type.kind === ts.SyntaxKind.UnknownKeyword) {
89
+ unknownCount++;
90
+ }
91
+ }
92
+
93
+ // Type annotations like let x: any or function foo(x: unknown)
94
+ if (node.kind === ts.SyntaxKind.AnyKeyword) {
95
+ anyCount++;
96
+ } else if (node.kind === ts.SyntaxKind.UnknownKeyword) {
97
+ unknownCount++;
98
+ }
99
+
100
+ ts.forEachChild(node, visit);
101
+ }
102
+
103
+ visit(source);
104
+ }
105
+
106
+ function walk(dir) {
107
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
108
+ const fullPath = path.join(dir, entry.name);
109
+ if (entry.isDirectory()) {
110
+ walk(fullPath);
111
+ } else if (/\.(ts|tsx)$/.test(entry.name)) {
112
+ analyzeFile(fullPath);
113
+ }
114
+ }
115
+ }
116
+
117
+ if (fs.existsSync(srcDir)) {
118
+ walk(srcDir);
119
+ }
120
+
121
+ return { as: asCount, any: anyCount, unknown: unknownCount };
122
+ }
123
+
63
124
  /**
64
125
  * Run TypeScript coverage for one app.
65
126
  *
@@ -82,6 +143,10 @@ function runAppTypesCoverage(appDir, appShortName) {
82
143
  const args = buildTypesCoverageArgs(relativeOutputDir);
83
144
  const ok = runCommand(tsTypesCoverageBin, args, appDir);
84
145
 
146
+ const looseTypes = countLooseTypes(appDir);
147
+ const looseTypeReport = path.join(absoluteOutputDir, 'loose-types.json');
148
+ fs.writeFileSync(looseTypeReport, JSON.stringify(looseTypes, null, 2));
149
+
85
150
  const jsonReport = path.join(absoluteOutputDir, 'typescript-coverage.json');
86
151
  const htmlReport = path.join(absoluteOutputDir, 'index.html');
87
152
 
@@ -91,6 +156,9 @@ function runAppTypesCoverage(appDir, appShortName) {
91
156
  if (!fs.existsSync(htmlReport)) {
92
157
  logWarn(`⚠️ Missing index.html at ${htmlReport}`);
93
158
  }
159
+ if (!fs.existsSync(looseTypeReport)) {
160
+ logWarn(`⚠️ Missing loose-types.json at ${looseTypeReport}`);
161
+ }
94
162
 
95
163
  return ok;
96
164
  });
@@ -217,6 +217,13 @@ export function buildCodeDuplicationArgs(outputDir) {
217
217
  // https://github.com/kucherenko/jscpd/blob/master/apps/jscpd/README.md#ignore
218
218
  args.push('--ignore', cfg.ignore.join(','));
219
219
  }
220
-
220
+ // jscpd accepts regex patterns via `--ignore-pattern` (singular).
221
+ // Multiple regexes should be joined by comma.
222
+ // https://github.com/kucherenko/jscpd/blob/master/apps/jscpd/README.md#ignore-pattern
223
+ if (Array.isArray(cfg.ignorePatterns) && cfg.ignorePatterns.length > 0) {
224
+ for (const pat of cfg.ignorePatterns) {
225
+ args.push('--ignore-pattern', `'${pat}'`);
226
+ }
227
+ }
221
228
  return args;
222
229
  }
@@ -81,19 +81,25 @@ export function resolveTurboFilters({ appsDir, appFolders, packageNames }) {
81
81
  *
82
82
  * @param {string} rootDir - Root directory where Turbo should be executed.
83
83
  * @param {string[]} filters - List of Turbo build filters (package names).
84
+ * @param {string[]} [extraArgs=[]] - Additional arguments to forward to Turbo.
84
85
  * @returns {void}
85
86
  *
86
87
  * @example
87
88
  * runTurboBuild("/workspace", ["@ovh-ux/manager-zimbra-app"]);
88
89
  * // Executes: turbo run build --filter @ovh-ux/manager-zimbra-app
89
90
  */
90
- export function runTurboBuild(rootDir, filters) {
91
+ export function runTurboBuild(rootDir, filters, extraArgs = []) {
91
92
  if (filters.length === 0) {
92
93
  logWarn('⚠️ No Turbo build filters provided, skipping build step.');
93
94
  return;
94
95
  }
95
96
 
96
- const turboArgs = ['run', 'build', ...filters.flatMap((pkgName) => ['--filter', pkgName])];
97
+ const turboArgs = [
98
+ 'run',
99
+ 'build',
100
+ ...filters.flatMap((pkgName) => ['--filter', pkgName]),
101
+ ...extraArgs,
102
+ ];
97
103
  logInfo(`Building with Turbo (filters: ${filters.join(', ')})`);
98
104
 
99
105
  const buildResult = spawnSync('turbo', turboArgs, {
@@ -124,7 +130,12 @@ export function runTurboBuild(rootDir, filters) {
124
130
  * runTurboTests(process.cwd(), ['web', 'zimbra'], ['--', '--coverage']);
125
131
  */
126
132
  export function runTurboTests(rootDir, filters, extraArgs = []) {
127
- const args = ['run', 'test', ...filters.flatMap((pkg) => ['--filter', pkg]), ...extraArgs];
133
+ const args = [
134
+ 'run',
135
+ 'test',
136
+ ...filters.flatMap((pkgName) => ['--filter', pkgName]),
137
+ ...extraArgs,
138
+ ];
128
139
  logInfo(`Testing with Turbo (filters: ${filters.join(', ')})`);
129
140
 
130
141
  const res = spawnSync('turbo', args, {
@@ -1,6 +1,9 @@
1
1
  import { CombinedCodeDuplicationSummary, JscpdReportV4, WorstCodeDuplicationFile } from '../types/CodeDuplication.js';
2
2
  export declare const extPriority: (file: string) => number;
3
- export declare function extractWorstFiles(report: JscpdReportV4): WorstCodeDuplicationFile[];
3
+ export declare function extractWorstFiles(report: JscpdReportV4): {
4
+ files: WorstCodeDuplicationFile[];
5
+ fileCount: number;
6
+ };
4
7
  export declare function collectCodeDuplication(reportOutputDir: string): CombinedCodeDuplicationSummary;
5
8
  export declare function generateCodeDuplicationHtml(summary: CombinedCodeDuplicationSummary): string;
6
9
  //# sourceMappingURL=code-duplication-analysis-helper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"code-duplication-analysis-helper.d.ts","sourceRoot":"","sources":["../../../../src/adapters/code-duplication/helpers/code-duplication-analysis-helper.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,8BAA8B,EAC9B,aAAa,EACb,wBAAwB,EAEzB,MAAM,0BAA0B,CAAC;AAUlC,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,MAc1C,CAAC;AAkEF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,wBAAwB,EAAE,CAsEnF;AAkED,wBAAgB,sBAAsB,CAAC,eAAe,EAAE,MAAM,GAAG,8BAA8B,CA+B9F;AAyGD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,8BAA8B,GAAG,MAAM,CAmC3F"}
1
+ {"version":3,"file":"code-duplication-analysis-helper.d.ts","sourceRoot":"","sources":["../../../../src/adapters/code-duplication/helpers/code-duplication-analysis-helper.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,8BAA8B,EAC9B,aAAa,EACb,wBAAwB,EAEzB,MAAM,0BAA0B,CAAC;AAUlC,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,MAc1C,CAAC;AAmEF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG;IACxD,KAAK,EAAE,wBAAwB,EAAE,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;CACnB,CAwEA;AA4ED,wBAAgB,sBAAsB,CAAC,eAAe,EAAE,MAAM,GAAG,8BAA8B,CA+B9F;AAsGD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,8BAA8B,GAAG,MAAM,CAyD3F"}
@@ -32,7 +32,7 @@ function shortPathFromAppsRoot(filePath, app) {
32
32
  export function extractWorstFiles(report) {
33
33
  const dups = Array.isArray(report?.duplicates) ? report.duplicates : [];
34
34
  if (dups.length === 0)
35
- return [];
35
+ return { files: [], fileCount: 0 };
36
36
  const { threshold, count } = codeDuplicationConfig.worstFiles;
37
37
  const fileMap = {};
38
38
  for (const dup of dups) {
@@ -56,7 +56,7 @@ export function extractWorstFiles(report) {
56
56
  const estTotal = fileMap[file].estimatedTotal;
57
57
  fileMap[file].total = Math.max(realTotal || estTotal || 0, fileMap[file].dup);
58
58
  }
59
- return Object.entries(fileMap)
59
+ const files = Object.entries(fileMap)
60
60
  .map(([file, { dup, total = 0 }]) => {
61
61
  const percentage = total > 0 ? (dup / total) * 100 : 0;
62
62
  const stats = {
@@ -84,6 +84,7 @@ export function extractWorstFiles(report) {
84
84
  return a[0].localeCompare(b[0]);
85
85
  })
86
86
  .slice(0, count);
87
+ return { files, fileCount: Object.keys(fileMap).length };
87
88
  }
88
89
  function parseAppCodeDuplicationReport(jsonPath) {
89
90
  try {
@@ -91,14 +92,22 @@ function parseAppCodeDuplicationReport(jsonPath) {
91
92
  const total = report.statistics?.total ?? {};
92
93
  const totalLines = total.lines ?? 0;
93
94
  const duplicateLines = total.duplicatedLines ?? 0;
95
+ const percentage = total.percentage ?? (totalLines > 0 ? (duplicateLines / totalLines) * 100 : 0);
96
+ const { files: worstFiles, fileCount } = extractWorstFiles(report);
97
+ const perFilePercentages = worstFiles.map(([, f]) => f.percentage);
98
+ const minPercentage = perFilePercentages.length ? Math.min(...perFilePercentages) : percentage;
99
+ const maxPercentage = perFilePercentages.length ? Math.max(...perFilePercentages) : percentage;
94
100
  return {
95
101
  totalLines,
96
102
  totalTokens: total.tokens ?? 0,
97
103
  duplicateLines,
98
104
  duplicateTokens: total.duplicatedTokens ?? 0,
99
105
  clonesCount: total.clones ?? 0,
100
- percentage: total.percentage ?? (totalLines > 0 ? (duplicateLines / totalLines) * 100 : 0),
101
- worstFiles: extractWorstFiles(report),
106
+ percentage,
107
+ minPercentage,
108
+ maxPercentage,
109
+ worstFiles,
110
+ filesWithDuplication: fileCount,
102
111
  };
103
112
  }
104
113
  catch (err) {
@@ -109,8 +118,11 @@ function parseAppCodeDuplicationReport(jsonPath) {
109
118
  duplicateTokens: 0,
110
119
  clonesCount: 0,
111
120
  percentage: 0,
121
+ minPercentage: 0,
122
+ maxPercentage: 0,
112
123
  error: err.message,
113
124
  worstFiles: [],
125
+ filesWithDuplication: 0,
114
126
  };
115
127
  }
116
128
  }
@@ -157,11 +169,12 @@ const getColorForDuplicationStatus = (pct) => {
157
169
  return pct < green ? 'green' : pct < orange ? 'orange' : 'red';
158
170
  };
159
171
  function renderWorstFiles(app, worstFiles = []) {
172
+ const threshold = codeDuplicationConfig.worstFiles.threshold;
160
173
  if (!worstFiles.length) {
161
174
  return `
162
- <details>
163
- <summary>⚠️ Worst duplicated files</summary>
164
- <p style="margin:0.5rem 0; color:#888;">No files above threshold 🎉</p>
175
+ <details class="coverage-details">
176
+ <summary>📊 Duplication Overview for <strong>${app}</strong></summary>
177
+ <p class="coverage-empty">No files above threshold 🎉</p>
165
178
  </details>`;
166
179
  }
167
180
  const rows = worstFiles
@@ -169,21 +182,39 @@ function renderWorstFiles(app, worstFiles = []) {
169
182
  const short = shortPathFromAppsRoot(file, app);
170
183
  const color = getColorForDuplicationStatus(f.percentage);
171
184
  return `
172
- <tr>
173
- <td colspan="2" title="${file}">${short}</td>
174
- <td>${f.duplicatedLines}/${f.totalLines}</td>
175
- <td style="color:${color}">${f.percentage.toFixed(2)}%</td>
176
- </tr>`;
185
+ <tr>
186
+ <td colspan="2" title="${file}">${short}</td>
187
+ <td>${f.duplicatedLines}/${f.totalLines}</td>
188
+ <td style="color:${color}">${f.percentage.toFixed(2)}%</td>
189
+ </tr>`;
177
190
  })
178
191
  .join('');
179
192
  return `
180
- <details>
181
- <summary>⚠️ Worst duplicated files (≥ ${codeDuplicationConfig.worstFiles.threshold}%)</summary>
182
- <table style="margin:0.5rem 0; border:1px solid #ccc; width:95%;">
193
+ <details class="coverage-details">
194
+ <summary>📊 Duplication Overview for <strong>${app}</strong> (≥ ${threshold}%)</summary>
195
+ <table class="coverage-subtable">
183
196
  <tbody>${rows}</tbody>
184
197
  </table>
185
198
  </details>`;
186
199
  }
200
+ function renderDuplicationCell(stats) {
201
+ const min = stats.minPercentage ?? stats.percentage;
202
+ const max = stats.maxPercentage ?? stats.percentage;
203
+ if (stats.duplicateLines === 0) {
204
+ return `<span style="color:${getColorForDuplicationStatus(0)}">0.00%</span>`;
205
+ }
206
+ if (min.toFixed(2) === max.toFixed(2)) {
207
+ return `
208
+ ${stats.percentage.toFixed(2)}% (${stats.filesWithDuplication} files cluster at
209
+ <span style="color:${getColorForDuplicationStatus(min)}">${min.toFixed(2)}%</span>)
210
+ `;
211
+ }
212
+ return `
213
+ ${stats.percentage.toFixed(2)}% (range:
214
+ <span style="color:${getColorForDuplicationStatus(min)}">${min.toFixed(2)}%</span> –
215
+ <span style="color:${getColorForDuplicationStatus(max)}">${max.toFixed(2)}%</span>)
216
+ `;
217
+ }
187
218
  function buildAppRow(app, stats) {
188
219
  if (stats.error) {
189
220
  return `<tr style="background:#ffe0e0">
@@ -191,15 +222,16 @@ function buildAppRow(app, stats) {
191
222
  <td colspan="5" style="color:red">${stats.error}</td>
192
223
  </tr>`;
193
224
  }
194
- const color = getColorForDuplicationStatus(stats.percentage);
195
- const bg = color === 'green' ? '#e6ffed' : color === 'orange' ? '#fff8e1' : '#ffe6e6';
225
+ const max = stats.maxPercentage ?? stats.percentage;
226
+ const rowColor = getColorForDuplicationStatus(max);
227
+ const bg = rowColor === 'green' ? '#e6ffed' : rowColor === 'orange' ? '#fff8e1' : '#ffe6e6';
196
228
  return `
197
229
  <tr style="background:${bg}">
198
230
  <td>${app}</td>
199
231
  <td>${stats.totalLines}</td>
200
232
  <td>${stats.duplicateLines}</td>
201
233
  <td>${stats.clonesCount}</td>
202
- <td style="font-weight:bold; color:${color};">${stats.percentage.toFixed(2)}%</td>
234
+ <td style="font-weight:bold;">${renderDuplicationCell(stats)}</td>
203
235
  </tr>
204
236
  <tr><td colspan="5">${renderWorstFiles(app, stats.worstFiles)}</td></tr>`;
205
237
  }
@@ -217,6 +249,28 @@ export function generateCodeDuplicationHtml(summary) {
217
249
  table { border-collapse: collapse; width: 100%; }
218
250
  th, td { border: 1px solid #ccc; padding: 0.5rem; text-align: left; }
219
251
  th { background: #f0f0f0; }
252
+ .coverage-details {
253
+ margin: 0.3rem 0 0.8rem 1.5rem;
254
+ padding: 0.5rem;
255
+ border-left: 3px solid #ccc;
256
+ background: #fafafa;
257
+ border-radius: 4px;
258
+ }
259
+ .coverage-details summary {
260
+ font-weight: bold;
261
+ cursor: pointer;
262
+ }
263
+ .coverage-subtable {
264
+ margin-top: 0.5rem;
265
+ border: 1px solid #ddd;
266
+ width: 95%;
267
+ font-size: 0.9em;
268
+ }
269
+ .coverage-empty {
270
+ margin: 0.5rem 0;
271
+ color: #888;
272
+ font-style: italic;
273
+ }
220
274
  </style>
221
275
  </head>
222
276
  <body>
@@ -4,7 +4,10 @@ export interface AppCodeDuplicationSummary {
4
4
  duplicateLines: number;
5
5
  duplicateTokens: number;
6
6
  percentage: number;
7
+ minPercentage: number;
8
+ maxPercentage: number;
7
9
  clonesCount: number;
10
+ filesWithDuplication: number;
8
11
  error?: string;
9
12
  worstFiles?: WorstCodeDuplicationFile[];
10
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CodeDuplication.d.ts","sourceRoot":"","sources":["../../../../src/adapters/code-duplication/types/CodeDuplication.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,yBAAyB;IAExC,UAAU,EAAE,MAAM,CAAC;IAGnB,WAAW,EAAE,MAAM,CAAC;IAGpB,cAAc,EAAE,MAAM,CAAC;IAGvB,eAAe,EAAE,MAAM,CAAC;IAMxB,UAAU,EAAE,MAAM,CAAC;IAMnB,WAAW,EAAE,MAAM,CAAC;IAMpB,KAAK,CAAC,EAAE,MAAM,CAAC;IAMf,UAAU,CAAC,EAAE,wBAAwB,EAAE,CAAC;CACzC;AAKD,MAAM,WAAW,8BAA8B;IAK7C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;IAGhD,UAAU,EAAE,MAAM,CAAC;IAGnB,WAAW,EAAE,MAAM,CAAC;IAGpB,mBAAmB,EAAE,MAAM,CAAC;IAG5B,oBAAoB,EAAE,MAAM,CAAC;IAM7B,UAAU,EAAE,MAAM,CAAC;CACpB;AAcD,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM;IACN;QAEE,eAAe,EAAE,MAAM,CAAC;QAGxB,UAAU,EAAE,MAAM,CAAC;QAGnB,UAAU,EAAE,MAAM,CAAC;KACpB;CACF,CAAC;AAKF,MAAM,WAAW,cAAc;IAE7B,eAAe,EAAE,MAAM,CAAC;IAGxB,UAAU,EAAE,MAAM,CAAC;IAGnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,UAAU,kBAAkB;IAE1B,IAAI,EAAE,MAAM,CAAC;IAMb,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAM7B,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5B;AAKD,UAAU,cAAc;IAKtB,KAAK,EAAE,MAAM,CAAC;IAGd,SAAS,EAAE,kBAAkB,CAAC;IAG9B,UAAU,EAAE,kBAAkB,CAAC;CAChC;AAMD,MAAM,WAAW,aAAa;IAK5B,UAAU,CAAC,EAAE;QACX,KAAK,CAAC,EAAE;YAEN,KAAK,CAAC,EAAE,MAAM,CAAC;YAGf,MAAM,CAAC,EAAE,MAAM,CAAC;YAGhB,eAAe,CAAC,EAAE,MAAM,CAAC;YAGzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;YAG1B,MAAM,CAAC,EAAE,MAAM,CAAC;YAMhB,UAAU,CAAC,EAAE,MAAM,CAAC;SACrB,CAAC;KACH,CAAC;IAMF,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;CAC/B"}
1
+ {"version":3,"file":"CodeDuplication.d.ts","sourceRoot":"","sources":["../../../../src/adapters/code-duplication/types/CodeDuplication.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,yBAAyB;IAExC,UAAU,EAAE,MAAM,CAAC;IAGnB,WAAW,EAAE,MAAM,CAAC;IAGpB,cAAc,EAAE,MAAM,CAAC;IAGvB,eAAe,EAAE,MAAM,CAAC;IAMxB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IAMtB,WAAW,EAAE,MAAM,CAAC;IAMpB,oBAAoB,EAAE,MAAM,CAAC;IAM7B,KAAK,CAAC,EAAE,MAAM,CAAC;IAMf,UAAU,CAAC,EAAE,wBAAwB,EAAE,CAAC;CACzC;AAKD,MAAM,WAAW,8BAA8B;IAK7C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;IAGhD,UAAU,EAAE,MAAM,CAAC;IAGnB,WAAW,EAAE,MAAM,CAAC;IAGpB,mBAAmB,EAAE,MAAM,CAAC;IAG5B,oBAAoB,EAAE,MAAM,CAAC;IAM7B,UAAU,EAAE,MAAM,CAAC;CACpB;AAcD,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM;IACN;QAEE,eAAe,EAAE,MAAM,CAAC;QAGxB,UAAU,EAAE,MAAM,CAAC;QAGnB,UAAU,EAAE,MAAM,CAAC;KACpB;CACF,CAAC;AAKF,MAAM,WAAW,cAAc;IAE7B,eAAe,EAAE,MAAM,CAAC;IAGxB,UAAU,EAAE,MAAM,CAAC;IAGnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,UAAU,kBAAkB;IAE1B,IAAI,EAAE,MAAM,CAAC;IAMb,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAM7B,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5B;AAKD,UAAU,cAAc;IAKtB,KAAK,EAAE,MAAM,CAAC;IAGd,SAAS,EAAE,kBAAkB,CAAC;IAG9B,UAAU,EAAE,kBAAkB,CAAC;CAChC;AAMD,MAAM,WAAW,aAAa;IAK5B,UAAU,CAAC,EAAE;QACX,KAAK,CAAC,EAAE;YAEN,KAAK,CAAC,EAAE,MAAM,CAAC;YAGf,MAAM,CAAC,EAAE,MAAM,CAAC;YAGhB,eAAe,CAAC,EAAE,MAAM,CAAC;YAGzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;YAG1B,MAAM,CAAC,EAAE,MAAM,CAAC;YAMhB,UAAU,CAAC,EAAE,MAAM,CAAC;SACrB,CAAC;KACH,CAAC;IAMF,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;CAC/B"}
@@ -1 +1 @@
1
- {"version":3,"file":"tests-coverage-analysis-helper.d.ts","sourceRoot":"","sources":["../../../../src/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,qBAAqB,EAKtB,MAAM,wBAAwB,CAAC;AA2HhC,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,qBAAqB,CAmC9E;AAiED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,qBAAqB,EAC9B,UAAU,EAAE,MAAM,GACjB,MAAM,CAuER"}
1
+ {"version":3,"file":"tests-coverage-analysis-helper.d.ts","sourceRoot":"","sources":["../../../../src/adapters/tests-coverage/helpers/tests-coverage-analysis-helper.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,qBAAqB,EAKtB,MAAM,wBAAwB,CAAC;AA2HhC,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,qBAAqB,CAmC9E;AAiED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,qBAAqB,EAC9B,UAAU,EAAE,MAAM,GACjB,MAAM,CA6FR"}
@@ -99,12 +99,11 @@ function getTestsCoverageStatusColor(pct) {
99
99
  return 'red';
100
100
  }
101
101
  function renderWorstFiles(app, worst) {
102
- const { threshold, metric } = testsCoverageConfig.worstFiles;
103
102
  if (!worst.length) {
104
103
  return `
105
- <details>
106
- <summary>⚠️ Worst covered files (${metric})</summary>
107
- <p style="margin:0.5rem 0; color:#888;">No files below threshold 🎉</p>
104
+ <details class="coverage-details">
105
+ <summary>📊 Test Coverage Overview for <strong>${app}</strong></summary>
106
+ <p class="coverage-empty">No files below threshold 🎉</p>
108
107
  </details>`;
109
108
  }
110
109
  const rows = worst
@@ -112,13 +111,15 @@ function renderWorstFiles(app, worst) {
112
111
  <tr>
113
112
  <td colspan="2">${file}</td>
114
113
  <td>${f.covered}/${f.total}</td>
115
- <td style="color:${getTestsCoverageStatusColor(f.percentage)}">${f.percentage.toFixed(2)}%</td>
114
+ <td style="color:${getTestsCoverageStatusColor(f.percentage)}">
115
+ ${f.percentage.toFixed(2)}%
116
+ </td>
116
117
  </tr>`)
117
118
  .join('');
118
119
  return `
119
- <details>
120
- <summary>⚠️ Worst covered files (below ${threshold}% — ${metric})</summary>
121
- <table style="margin:0.5rem 0; border:1px solid #ccc; width:95%;">
120
+ <details class="coverage-details">
121
+ <summary>📊 Test Coverage Overview for <strong>${app}</strong></summary>
122
+ <table class="coverage-subtable">
122
123
  <tbody>${rows}</tbody>
123
124
  </table>
124
125
  </details>`;
@@ -138,6 +139,28 @@ export function generateTestsCoverageHtml(summary, reportRoot) {
138
139
  .row-green { background:#e6ffed; }
139
140
  .row-orange { background:#fff8e1; }
140
141
  .row-red { background:#ffe6e6; }
142
+ .coverage-details {
143
+ margin: 0.3rem 0 0.8rem 1.5rem; /* indent */
144
+ padding: 0.5rem;
145
+ border-left: 3px solid #ccc;
146
+ background: #fafafa;
147
+ border-radius: 4px;
148
+ }
149
+ .coverage-details summary {
150
+ font-weight: bold;
151
+ cursor: pointer;
152
+ }
153
+ .coverage-subtable {
154
+ margin-top: 0.5rem;
155
+ border: 1px solid #ddd;
156
+ width: 95%;
157
+ font-size: 0.9em;
158
+ }
159
+ .coverage-empty {
160
+ margin: 0.5rem 0;
161
+ color: #888;
162
+ font-style: italic;
163
+ }
141
164
  </style>
142
165
  <h1>Tests Coverage</h1>
143
166
  `;
@@ -1 +1 @@
1
- {"version":3,"file":"types-coverage-analysis-helper.d.ts","sourceRoot":"","sources":["../../../../src/adapters/types-coverage/helpers/types-coverage-analysis-helper.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,eAAe,EAGhB,MAAM,wBAAwB,CAAC;AAoChC,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,eAAe,CAkD7E;AA6ED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAgC1E"}
1
+ {"version":3,"file":"types-coverage-analysis-helper.d.ts","sourceRoot":"","sources":["../../../../src/adapters/types-coverage/helpers/types-coverage-analysis-helper.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,eAAe,EAIhB,MAAM,wBAAwB,CAAC;AA2EhC,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,eAAe,CA4C7E;AA+HD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAwD1E"}