@ovh-ux/manager-static-analysis-kit 0.12.0 → 0.14.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/CHANGELOG.md +22 -0
- package/bin/lint-cli.js +26 -10
- package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.d.ts +1 -1
- package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.d.ts.map +1 -1
- package/dist/adapters/code-duplication/helpers/code-duplication-analysis-helper.js +16 -146
- package/dist/adapters/code-duplication/helpers/code-duplication-reports-render.d.ts +4 -0
- package/dist/adapters/code-duplication/helpers/code-duplication-reports-render.d.ts.map +1 -0
- package/dist/adapters/code-duplication/helpers/code-duplication-reports-render.js +141 -0
- package/dist/adapters/eslint/rules/eslint-javascript.d.ts.map +1 -1
- package/dist/adapters/eslint/rules/eslint-javascript.js +2 -0
- package/dist/adapters/eslint/rules/eslint-prettier.d.ts.map +1 -1
- package/dist/adapters/eslint/rules/eslint-prettier.js +3 -2
- package/dist/adapters/eslint/rules/eslint-tailwind-jsx.d.ts.map +1 -1
- package/dist/adapters/eslint/rules/eslint-tailwind-jsx.js +8 -0
- package/dist/adapters/html-w3c-validation/helpers/html-w3c-tests-setup.d.ts +2 -2
- package/dist/configs/code-formatter-config.d.ts +0 -1
- package/dist/configs/code-formatter-config.d.ts.map +1 -1
- package/dist/configs/code-formatter-config.js +0 -10
- package/dist/configs/shared-exclusion-patterns.d.ts +2 -0
- package/dist/configs/shared-exclusion-patterns.d.ts.map +1 -1
- package/dist/configs/shared-exclusion-patterns.js +39 -27
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +28 -28
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
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.14.0](https://github.com/ovh/manager/compare/@ovh-ux/manager-static-analysis-kit@0.13.0...@ovh-ux/manager-static-analysis-kit@0.14.0) (2025-11-27)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* extends forge cli to create module and static kit update ([992f693](https://github.com/ovh/manager/commit/992f693fdd17ba82f2afb8f6cdfeb8fbd063929c))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# [0.13.0](https://github.com/ovh/manager/compare/@ovh-ux/manager-static-analysis-kit@0.12.0...@ovh-ux/manager-static-analysis-kit@0.13.0) (2025-11-16)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* pnpm hybrid improvements ([3b45577](https://github.com/ovh/manager/commit/3b455770da0c7aeb7603f7c731abf6fac9eb4b3f))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
# [0.12.0](https://github.com/ovh/manager/compare/@ovh-ux/manager-static-analysis-kit@0.11.2...@ovh-ux/manager-static-analysis-kit@0.12.0) (2025-11-02)
|
|
7
29
|
|
|
8
30
|
|
package/bin/lint-cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
4
5
|
import { dirname, join, resolve } from 'node:path';
|
|
5
6
|
import process, { cwd, execPath } from 'node:process';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import { createRequire } from 'node:module';
|
|
8
8
|
|
|
9
9
|
import { logError, logInfo } from './utils/log-utils.js';
|
|
10
10
|
|
|
@@ -44,16 +44,25 @@ const firstNonFlag = () => args.find((a) => !a.startsWith('--'));
|
|
|
44
44
|
|
|
45
45
|
const hasExplicitConfig = hasFlag('--config');
|
|
46
46
|
const localConfigCandidates = [
|
|
47
|
-
'eslint.config.ts',
|
|
48
|
-
'eslint.config.
|
|
47
|
+
'eslint.config.ts',
|
|
48
|
+
'eslint.config.mts',
|
|
49
|
+
'eslint.config.cts',
|
|
50
|
+
'eslint.config.mjs',
|
|
51
|
+
'eslint.config.cjs',
|
|
52
|
+
'eslint.config.js',
|
|
49
53
|
];
|
|
50
54
|
const localConfigPath = localConfigCandidates
|
|
51
55
|
.map((f) => join(userCwd, f))
|
|
52
56
|
.find((p) => existsSync(p));
|
|
53
57
|
|
|
54
|
-
const sharedDefaultPath = resolve(
|
|
58
|
+
const sharedDefaultPath = resolve(
|
|
59
|
+
__dirname,
|
|
60
|
+
'../dist/adapters/eslint/config/eslint-shared-config.js',
|
|
61
|
+
);
|
|
55
62
|
|
|
56
|
-
const configPath = hasExplicitConfig
|
|
63
|
+
const configPath = hasExplicitConfig
|
|
64
|
+
? getFlagValue('--config')
|
|
65
|
+
: (localConfigPath ?? sharedDefaultPath);
|
|
57
66
|
|
|
58
67
|
// ---------------------------------------------------------------------------
|
|
59
68
|
// Node preload for TS configs
|
|
@@ -67,14 +76,14 @@ if (needsTsx) {
|
|
|
67
76
|
require.resolve('tsx');
|
|
68
77
|
} catch {
|
|
69
78
|
logError('[manager-lint] A TypeScript eslint config was detected, but "tsx" is not installed.');
|
|
70
|
-
logError(
|
|
79
|
+
logError(
|
|
80
|
+
'Install it in @ovh-ux/manager-static-analysis-kit (or the invoking package) and retry.',
|
|
81
|
+
);
|
|
71
82
|
process.exit(1);
|
|
72
83
|
}
|
|
73
84
|
}
|
|
74
85
|
|
|
75
|
-
const nodePreload = needsTsx
|
|
76
|
-
? (nodeMajor >= 20 ? ['--import', 'tsx'] : ['--loader', 'tsx'])
|
|
77
|
-
: [];
|
|
86
|
+
const nodePreload = needsTsx ? (nodeMajor >= 20 ? ['--import', 'tsx'] : ['--loader', 'tsx']) : [];
|
|
78
87
|
|
|
79
88
|
// ---------------------------------------------------------------------------
|
|
80
89
|
// Build final ESLint argv
|
|
@@ -109,7 +118,14 @@ function run(procArgs) {
|
|
|
109
118
|
if (hasFlag('--print-config')) {
|
|
110
119
|
const target = firstNonFlag() ?? '.';
|
|
111
120
|
logInfo('[manager-lint] Using config:', configPath);
|
|
112
|
-
const code = await run([
|
|
121
|
+
const code = await run([
|
|
122
|
+
...nodePreload,
|
|
123
|
+
eslintCli,
|
|
124
|
+
'--config',
|
|
125
|
+
configPath,
|
|
126
|
+
'--print-config',
|
|
127
|
+
target,
|
|
128
|
+
]);
|
|
113
129
|
process.exit(code);
|
|
114
130
|
}
|
|
115
131
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CombinedCodeDuplicationSummary, JscpdReportV4, WorstCodeDuplicationFile } from '../types/CodeDuplication.js';
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const buildExtensionPriority: (file: string) => number;
|
|
3
3
|
export declare function extractWorstFiles(report: JscpdReportV4): {
|
|
4
4
|
files: WorstCodeDuplicationFile[];
|
|
5
5
|
fileCount: number;
|
|
@@ -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;
|
|
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;AAelC,eAAO,MAAM,sBAAsB,GAAI,MAAM,MAAM,KAAG,MAMrD,CAAC;AAmBF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa;;;EAsDtD;AAyED,wBAAgB,sBAAsB,CAAC,eAAe,EAAE,MAAM,GAAG,8BAA8B,CA+B9F;AAOD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,8BAA8B,GAAG,MAAM,CAQ3F"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { codeDuplicationConfig } from '../../../configs/code-duplication-config.js';
|
|
4
|
-
|
|
4
|
+
import { buildDuplicationReportBody, buildDuplicationReportHead, } from './code-duplication-reports-render.js';
|
|
5
|
+
export const buildExtensionPriority = (file) => {
|
|
5
6
|
const f = file.toLowerCase();
|
|
6
7
|
if (/\.(d\.ts)$/.test(f))
|
|
7
8
|
return 2;
|
|
@@ -11,7 +12,7 @@ export const extPriority = (file) => {
|
|
|
11
12
|
return 1;
|
|
12
13
|
return 2;
|
|
13
14
|
};
|
|
14
|
-
const
|
|
15
|
+
const countRealLines = (file) => {
|
|
15
16
|
try {
|
|
16
17
|
const txt = fs.readFileSync(file, 'utf8');
|
|
17
18
|
return txt.split(/\r?\n/).length;
|
|
@@ -20,15 +21,6 @@ const safeCountLines = (file) => {
|
|
|
20
21
|
return 0;
|
|
21
22
|
}
|
|
22
23
|
};
|
|
23
|
-
function shortPathFromAppsRoot(filePath, app) {
|
|
24
|
-
const norm = filePath.replace(/\\/g, '/');
|
|
25
|
-
const appsIdx = norm.indexOf('/apps/');
|
|
26
|
-
if (appsIdx >= 0) {
|
|
27
|
-
return norm.slice(appsIdx + 1);
|
|
28
|
-
}
|
|
29
|
-
const parts = norm.split('/');
|
|
30
|
-
return parts.slice(Math.max(0, parts.length - 5)).join('/');
|
|
31
|
-
}
|
|
32
24
|
export function extractWorstFiles(report) {
|
|
33
25
|
const dups = Array.isArray(report?.duplicates) ? report.duplicates : [];
|
|
34
26
|
if (dups.length === 0)
|
|
@@ -52,7 +44,7 @@ export function extractWorstFiles(report) {
|
|
|
52
44
|
}
|
|
53
45
|
}
|
|
54
46
|
for (const file of Object.keys(fileMap)) {
|
|
55
|
-
const realTotal =
|
|
47
|
+
const realTotal = countRealLines(file);
|
|
56
48
|
const estTotal = fileMap[file].estimatedTotal;
|
|
57
49
|
fileMap[file].total = Math.max(realTotal || estTotal || 0, fileMap[file].dup);
|
|
58
50
|
}
|
|
@@ -68,19 +60,17 @@ export function extractWorstFiles(report) {
|
|
|
68
60
|
})
|
|
69
61
|
.filter(([, s]) => s.percentage >= threshold)
|
|
70
62
|
.sort((a, b) => {
|
|
71
|
-
const pa =
|
|
72
|
-
const pb =
|
|
63
|
+
const pa = buildExtensionPriority(a[0]);
|
|
64
|
+
const pb = buildExtensionPriority(b[0]);
|
|
73
65
|
if (pa !== pb)
|
|
74
66
|
return pa - pb;
|
|
75
67
|
const pct = b[1].percentage - a[1].percentage;
|
|
76
68
|
if (pct !== 0)
|
|
77
69
|
return pct;
|
|
78
|
-
if (b[1].duplicatedLines !== a[1].duplicatedLines)
|
|
70
|
+
if (b[1].duplicatedLines !== a[1].duplicatedLines)
|
|
79
71
|
return b[1].duplicatedLines - a[1].duplicatedLines;
|
|
80
|
-
|
|
81
|
-
if (b[1].totalLines !== a[1].totalLines) {
|
|
72
|
+
if (b[1].totalLines !== a[1].totalLines)
|
|
82
73
|
return b[1].totalLines - a[1].totalLines;
|
|
83
|
-
}
|
|
84
74
|
return a[0].localeCompare(b[0]);
|
|
85
75
|
})
|
|
86
76
|
.slice(0, count);
|
|
@@ -126,7 +116,7 @@ function parseAppCodeDuplicationReport(jsonPath) {
|
|
|
126
116
|
};
|
|
127
117
|
}
|
|
128
118
|
}
|
|
129
|
-
function
|
|
119
|
+
function aggregateDuplicationTotals(apps) {
|
|
130
120
|
return Object.values(apps).reduce((acc, app) => {
|
|
131
121
|
acc.totalLines += app.totalLines;
|
|
132
122
|
acc.totalTokens += app.totalTokens;
|
|
@@ -156,139 +146,19 @@ export function collectCodeDuplication(reportOutputDir) {
|
|
|
156
146
|
apps[appDirName] = parseAppCodeDuplicationReport(jsonPath);
|
|
157
147
|
}
|
|
158
148
|
}
|
|
159
|
-
const totals =
|
|
149
|
+
const totals = aggregateDuplicationTotals(apps);
|
|
160
150
|
return {
|
|
161
151
|
apps,
|
|
162
152
|
...totals,
|
|
163
153
|
percentage: totals.totalLines > 0 ? (totals.totalDuplicateLines / totals.totalLines) * 100 : 0,
|
|
164
154
|
};
|
|
165
155
|
}
|
|
166
|
-
const getColorForDuplicationStatus = (pct) => {
|
|
167
|
-
const green = codeDuplicationConfig.thresholds?.green ?? 10;
|
|
168
|
-
const orange = codeDuplicationConfig.thresholds?.orange ?? 20;
|
|
169
|
-
return pct < green ? 'green' : pct < orange ? 'orange' : 'red';
|
|
170
|
-
};
|
|
171
|
-
function renderWorstFiles(app, worstFiles = []) {
|
|
172
|
-
const threshold = codeDuplicationConfig.worstFiles.threshold;
|
|
173
|
-
if (!worstFiles.length) {
|
|
174
|
-
return `
|
|
175
|
-
<details class="coverage-details">
|
|
176
|
-
<summary>📊 Duplication Overview for <strong>${app}</strong></summary>
|
|
177
|
-
<p class="coverage-empty">No files above threshold 🎉</p>
|
|
178
|
-
</details>`;
|
|
179
|
-
}
|
|
180
|
-
const rows = worstFiles
|
|
181
|
-
.map(([file, f]) => {
|
|
182
|
-
const short = shortPathFromAppsRoot(file, app);
|
|
183
|
-
const color = getColorForDuplicationStatus(f.percentage);
|
|
184
|
-
return `
|
|
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>`;
|
|
190
|
-
})
|
|
191
|
-
.join('');
|
|
192
|
-
return `
|
|
193
|
-
<details class="coverage-details">
|
|
194
|
-
<summary>📊 Duplication Overview for <strong>${app}</strong> (≥ ${threshold}%)</summary>
|
|
195
|
-
<table class="coverage-subtable">
|
|
196
|
-
<tbody>${rows}</tbody>
|
|
197
|
-
</table>
|
|
198
|
-
</details>`;
|
|
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
|
-
}
|
|
218
|
-
function buildAppRow(app, stats) {
|
|
219
|
-
if (stats.error) {
|
|
220
|
-
return `<tr style="background:#ffe0e0">
|
|
221
|
-
<td>${app}</td>
|
|
222
|
-
<td colspan="5" style="color:red">${stats.error}</td>
|
|
223
|
-
</tr>`;
|
|
224
|
-
}
|
|
225
|
-
const max = stats.maxPercentage ?? stats.percentage;
|
|
226
|
-
const rowColor = getColorForDuplicationStatus(max);
|
|
227
|
-
const bg = rowColor === 'green' ? '#e6ffed' : rowColor === 'orange' ? '#fff8e1' : '#ffe6e6';
|
|
228
|
-
return `
|
|
229
|
-
<tr style="background:${bg}">
|
|
230
|
-
<td>${app}</td>
|
|
231
|
-
<td>${stats.totalLines}</td>
|
|
232
|
-
<td>${stats.duplicateLines}</td>
|
|
233
|
-
<td>${stats.clonesCount}</td>
|
|
234
|
-
<td style="font-weight:bold;">${renderDuplicationCell(stats)}</td>
|
|
235
|
-
</tr>
|
|
236
|
-
<tr><td colspan="5">${renderWorstFiles(app, stats.worstFiles)}</td></tr>`;
|
|
237
|
-
}
|
|
238
156
|
export function generateCodeDuplicationHtml(summary) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
.join('');
|
|
242
|
-
return `<!DOCTYPE html>
|
|
157
|
+
return `
|
|
158
|
+
<!DOCTYPE html>
|
|
243
159
|
<html lang="en">
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
body { font-family: Arial, sans-serif; margin: 2rem; }
|
|
249
|
-
table { border-collapse: collapse; width: 100%; }
|
|
250
|
-
th, td { border: 1px solid #ccc; padding: 0.5rem; text-align: left; }
|
|
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
|
-
}
|
|
274
|
-
</style>
|
|
275
|
-
</head>
|
|
276
|
-
<body>
|
|
277
|
-
<h1>Code Duplication Report</h1>
|
|
278
|
-
<p>Total lines: ${summary.totalLines}, Duplicate lines: ${summary.totalDuplicateLines},
|
|
279
|
-
Percentage: ${summary.percentage.toFixed(2)}%</p>
|
|
280
|
-
<table>
|
|
281
|
-
<thead>
|
|
282
|
-
<tr>
|
|
283
|
-
<th>App</th>
|
|
284
|
-
<th>Total lines</th>
|
|
285
|
-
<th>Duplicate lines</th>
|
|
286
|
-
<th>Clones</th>
|
|
287
|
-
<th>Duplication %</th>
|
|
288
|
-
</tr>
|
|
289
|
-
</thead>
|
|
290
|
-
<tbody>${rows}</tbody>
|
|
291
|
-
</table>
|
|
292
|
-
</body>
|
|
293
|
-
</html>`;
|
|
160
|
+
${buildDuplicationReportHead()}
|
|
161
|
+
${buildDuplicationReportBody(summary)}
|
|
162
|
+
</html>
|
|
163
|
+
`.trim();
|
|
294
164
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { CombinedCodeDuplicationSummary } from '../types/CodeDuplication.js';
|
|
2
|
+
export declare function buildDuplicationReportHead(): string;
|
|
3
|
+
export declare function buildDuplicationReportBody(summary: CombinedCodeDuplicationSummary): string;
|
|
4
|
+
//# sourceMappingURL=code-duplication-reports-render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-duplication-reports-render.d.ts","sourceRoot":"","sources":["../../../../src/adapters/code-duplication/helpers/code-duplication-reports-render.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,8BAA8B,EAC/B,MAAM,0BAA0B,CAAC;AA4HlC,wBAAgB,0BAA0B,IAAI,MAAM,CAgCnD;AAOD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,8BAA8B,GAAG,MAAM,CA0B1F"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { codeDuplicationConfig } from '../../../configs/code-duplication-config.js';
|
|
2
|
+
function buildDisplayedFilePath(filePath) {
|
|
3
|
+
const norm = filePath.replace(/\\/g, '/');
|
|
4
|
+
const appsIdx = norm.indexOf('/apps/');
|
|
5
|
+
if (appsIdx >= 0)
|
|
6
|
+
return norm.slice(appsIdx + 1);
|
|
7
|
+
const parts = norm.split('/');
|
|
8
|
+
return parts.slice(Math.max(0, parts.length - 5)).join('/');
|
|
9
|
+
}
|
|
10
|
+
const buildDuplicationColorStatus = (pct) => {
|
|
11
|
+
const green = codeDuplicationConfig.thresholds?.green ?? 10;
|
|
12
|
+
const orange = codeDuplicationConfig.thresholds?.orange ?? 20;
|
|
13
|
+
return pct < green ? 'green' : pct < orange ? 'orange' : 'red';
|
|
14
|
+
};
|
|
15
|
+
function buildDuplicationWorstFiles(app, worstFiles = []) {
|
|
16
|
+
const threshold = codeDuplicationConfig.worstFiles.threshold;
|
|
17
|
+
if (!worstFiles.length) {
|
|
18
|
+
return `
|
|
19
|
+
<details class="coverage-details">
|
|
20
|
+
<summary>📊 Duplication Overview for <strong>${app}</strong></summary>
|
|
21
|
+
<p class="coverage-empty">No files above threshold 🎉</p>
|
|
22
|
+
</details>`;
|
|
23
|
+
}
|
|
24
|
+
const rows = worstFiles
|
|
25
|
+
.map(([file, f]) => {
|
|
26
|
+
const short = buildDisplayedFilePath(file);
|
|
27
|
+
const color = buildDuplicationColorStatus(f.percentage);
|
|
28
|
+
return `
|
|
29
|
+
<tr>
|
|
30
|
+
<td colspan="2" title="${file}">${short}</td>
|
|
31
|
+
<td>${f.duplicatedLines}/${f.totalLines}</td>
|
|
32
|
+
<td style="color:${color}">${f.percentage.toFixed(2)}%</td>
|
|
33
|
+
</tr>`;
|
|
34
|
+
})
|
|
35
|
+
.join('');
|
|
36
|
+
return `
|
|
37
|
+
<details class="coverage-details">
|
|
38
|
+
<summary>📊 Duplication Overview for <strong>${app}</strong> (≥ ${threshold}%)</summary>
|
|
39
|
+
<table class="coverage-subtable">
|
|
40
|
+
<tbody>${rows}</tbody>
|
|
41
|
+
</table>
|
|
42
|
+
</details>`;
|
|
43
|
+
}
|
|
44
|
+
function buildDuplicationCell(stats) {
|
|
45
|
+
const min = stats.minPercentage ?? stats.percentage;
|
|
46
|
+
const max = stats.maxPercentage ?? stats.percentage;
|
|
47
|
+
if (stats.duplicateLines === 0) {
|
|
48
|
+
return `<span style="color:${buildDuplicationColorStatus(0)}">0.00%</span>`;
|
|
49
|
+
}
|
|
50
|
+
if (min.toFixed(2) === max.toFixed(2)) {
|
|
51
|
+
return `
|
|
52
|
+
${stats.percentage.toFixed(2)}% (${stats.filesWithDuplication} files cluster at
|
|
53
|
+
<span style="color:${buildDuplicationColorStatus(min)}">${min.toFixed(2)}%</span>)
|
|
54
|
+
`;
|
|
55
|
+
}
|
|
56
|
+
return `
|
|
57
|
+
${stats.percentage.toFixed(2)}% (range:
|
|
58
|
+
<span style="color:${buildDuplicationColorStatus(min)}">${min.toFixed(2)}%</span> –
|
|
59
|
+
<span style="color:${buildDuplicationColorStatus(max)}">${max.toFixed(2)}%</span>)
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
function buildAppRow(app, stats) {
|
|
63
|
+
if (stats.error) {
|
|
64
|
+
return `<tr style="background:#ffe0e0">
|
|
65
|
+
<td>${app}</td>
|
|
66
|
+
<td colspan="5" style="color:red">${stats.error}</td>
|
|
67
|
+
</tr>`;
|
|
68
|
+
}
|
|
69
|
+
const max = stats.maxPercentage ?? stats.percentage;
|
|
70
|
+
const rowColor = buildDuplicationColorStatus(max);
|
|
71
|
+
const bg = rowColor === 'green' ? '#e6ffed' : rowColor === 'orange' ? '#fff8e1' : '#ffe6e6';
|
|
72
|
+
return `
|
|
73
|
+
<tr style="background:${bg}">
|
|
74
|
+
<td>${app}</td>
|
|
75
|
+
<td>${stats.totalLines}</td>
|
|
76
|
+
<td>${stats.duplicateLines}</td>
|
|
77
|
+
<td>${stats.clonesCount}</td>
|
|
78
|
+
<td style="font-weight:bold;">${buildDuplicationCell(stats)}</td>
|
|
79
|
+
</tr>
|
|
80
|
+
<tr><td colspan="5">${buildDuplicationWorstFiles(app, stats.worstFiles)}</td></tr>`;
|
|
81
|
+
}
|
|
82
|
+
export function buildDuplicationReportHead() {
|
|
83
|
+
return `
|
|
84
|
+
<head>
|
|
85
|
+
<meta charset="UTF-8"/>
|
|
86
|
+
<title>Code Duplication Report</title>
|
|
87
|
+
<style>
|
|
88
|
+
body { font-family: Arial, sans-serif; margin: 2rem; }
|
|
89
|
+
table { border-collapse: collapse; width: 100%; }
|
|
90
|
+
th, td { border: 1px solid #ccc; padding: 0.5rem; text-align: left; }
|
|
91
|
+
th { background: #f0f0f0; }
|
|
92
|
+
.coverage-details {
|
|
93
|
+
margin: 0.3rem 0 0.8rem 1.5rem;
|
|
94
|
+
padding: 0.5rem;
|
|
95
|
+
border-left: 3px solid #ccc;
|
|
96
|
+
background: #fafafa;
|
|
97
|
+
border-radius: 4px;
|
|
98
|
+
}
|
|
99
|
+
.coverage-details summary { font-weight: bold; cursor: pointer; }
|
|
100
|
+
.coverage-subtable {
|
|
101
|
+
margin-top: 0.5rem;
|
|
102
|
+
border: 1px solid #ddd;
|
|
103
|
+
width: 95%;
|
|
104
|
+
font-size: 0.9em;
|
|
105
|
+
}
|
|
106
|
+
.coverage-empty {
|
|
107
|
+
margin: 0.5rem 0;
|
|
108
|
+
color: #888;
|
|
109
|
+
font-style: italic;
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
112
|
+
</head>
|
|
113
|
+
`.trim();
|
|
114
|
+
}
|
|
115
|
+
export function buildDuplicationReportBody(summary) {
|
|
116
|
+
const rows = Object.entries(summary.apps)
|
|
117
|
+
.map(([app, stats]) => buildAppRow(app, stats))
|
|
118
|
+
.join('');
|
|
119
|
+
return `
|
|
120
|
+
<body>
|
|
121
|
+
<h1>Code Duplication Report</h1>
|
|
122
|
+
<p>
|
|
123
|
+
Total lines: ${summary.totalLines},
|
|
124
|
+
Duplicate lines: ${summary.totalDuplicateLines},
|
|
125
|
+
Percentage: ${summary.percentage.toFixed(2)}%
|
|
126
|
+
</p>
|
|
127
|
+
<table>
|
|
128
|
+
<thead>
|
|
129
|
+
<tr>
|
|
130
|
+
<th>App</th>
|
|
131
|
+
<th>Total lines</th>
|
|
132
|
+
<th>Duplicate lines</th>
|
|
133
|
+
<th>Clones</th>
|
|
134
|
+
<th>Duplication %</th>
|
|
135
|
+
</tr>
|
|
136
|
+
</thead>
|
|
137
|
+
<tbody>${rows}</tbody>
|
|
138
|
+
</table>
|
|
139
|
+
</body>
|
|
140
|
+
`.trim();
|
|
141
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eslint-javascript.d.ts","sourceRoot":"","sources":["../../../../src/adapters/eslint/rules/eslint-javascript.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAchC,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"eslint-javascript.d.ts","sourceRoot":"","sources":["../../../../src/adapters/eslint/rules/eslint-javascript.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAchC,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,UAY3C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eslint-prettier.d.ts","sourceRoot":"","sources":["../../../../src/adapters/eslint/rules/eslint-prettier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"eslint-prettier.d.ts","sourceRoot":"","sources":["../../../../src/adapters/eslint/rules/eslint-prettier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAmBhC,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,UAUzC,CAAC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import prettier from 'eslint-plugin-prettier';
|
|
2
|
-
import { prettierConfig
|
|
2
|
+
import { prettierConfig } from '../../../configs/code-formatter-config.js';
|
|
3
3
|
import { commonTextFiles } from '../../../configs/file-globs-config.js';
|
|
4
|
+
import { sharedCodeFormatterIgnorePatterns } from '../../../configs/shared-exclusion-patterns.js';
|
|
4
5
|
export const prettierEslintConfig = {
|
|
5
6
|
files: [commonTextFiles],
|
|
6
|
-
ignores:
|
|
7
|
+
ignores: sharedCodeFormatterIgnorePatterns,
|
|
7
8
|
plugins: {
|
|
8
9
|
prettier,
|
|
9
10
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eslint-tailwind-jsx.d.ts","sourceRoot":"","sources":["../../../../src/adapters/eslint/rules/eslint-tailwind-jsx.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAYhC,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"eslint-tailwind-jsx.d.ts","sourceRoot":"","sources":["../../../../src/adapters/eslint/rules/eslint-tailwind-jsx.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAYhC,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,UAgBtC,CAAC"}
|
|
@@ -7,5 +7,13 @@ export const tailwindJsxConfig = {
|
|
|
7
7
|
},
|
|
8
8
|
rules: {
|
|
9
9
|
...tailwindcss.configs['flat/recommended'].rules,
|
|
10
|
+
'tailwindcss/classnames-order': 'warn',
|
|
11
|
+
'tailwindcss/enforces-negative-arbitrary-values': 'warn',
|
|
12
|
+
'tailwindcss/enforces-shorthand': 'warn',
|
|
13
|
+
'tailwindcss/migration-from-tailwind-2': 'warn',
|
|
14
|
+
'tailwindcss/no-arbitrary-value': 'warn',
|
|
15
|
+
'tailwindcss/no-custom-classname': 'warn',
|
|
16
|
+
'tailwindcss/no-contradicting-classname': 'warn',
|
|
17
|
+
'tailwindcss/no-unnecessary-arbitrary-value': 'warn',
|
|
10
18
|
},
|
|
11
19
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-formatter-config.d.ts","sourceRoot":"","sources":["../../src/configs/code-formatter-config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"code-formatter-config.d.ts","sourceRoot":"","sources":["../../src/configs/code-formatter-config.ts"],"names":[],"mappings":"AA8DA,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;CAqB1B,CAAC"}
|
|
@@ -6,16 +6,6 @@ const require = createRequire(import.meta.url);
|
|
|
6
6
|
const tailwindPluginPath = require.resolve('prettier-plugin-tailwindcss');
|
|
7
7
|
const sortImportsPluginPath = require.resolve('@trivago/prettier-plugin-sort-imports');
|
|
8
8
|
const tailwindConfigPath = resolve(cwd(), 'tailwind.config.js');
|
|
9
|
-
export const prettierIgnorePatterns = [
|
|
10
|
-
'**/dist/**',
|
|
11
|
-
'**/custom-elements*/**',
|
|
12
|
-
'**/loader/**',
|
|
13
|
-
'**/react/**',
|
|
14
|
-
'**/vue/**',
|
|
15
|
-
'**/apps/container/**',
|
|
16
|
-
'**/apps/pci-databases-analytics/src/components/ui/**',
|
|
17
|
-
'**/apps/pci-databases-analytics/src/lib/utils.ts',
|
|
18
|
-
];
|
|
19
9
|
export const prettierConfig = {
|
|
20
10
|
semi: true,
|
|
21
11
|
singleQuote: true,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export declare const sharedExclusionPatterns: string[];
|
|
2
|
+
export declare const sharedLintExclusionPatterns: string[];
|
|
2
3
|
export declare const sharedTypesExclusionPatterns: string[];
|
|
4
|
+
export declare const sharedCodeFormatterIgnorePatterns: string[];
|
|
3
5
|
//# sourceMappingURL=shared-exclusion-patterns.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared-exclusion-patterns.d.ts","sourceRoot":"","sources":["../../src/configs/shared-exclusion-patterns.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shared-exclusion-patterns.d.ts","sourceRoot":"","sources":["../../src/configs/shared-exclusion-patterns.ts"],"names":[],"mappings":"AA6EA,eAAO,MAAM,uBAAuB,UAOnC,CAAC;AAQF,eAAO,MAAM,2BAA2B,UAOvC,CAAC;AAaF,eAAO,MAAM,4BAA4B,UAKxC,CAAC;AAWF,eAAO,MAAM,iCAAiC,UAY7C,CAAC"}
|