@silvestv/migration-planificator 3.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/LICENSE +96 -0
- package/README.fr.md +359 -0
- package/README.md +360 -0
- package/SECURITY.md +187 -0
- package/dist/client.bundle.js +357 -0
- package/dist/src/core/app-analyzer.js +134 -0
- package/dist/src/core/ast/matchers/html/html-attribute-matcher.js +86 -0
- package/dist/src/core/ast/matchers/html/html-component-matcher.js +40 -0
- package/dist/src/core/ast/matchers/html/html-element-matcher.js +54 -0
- package/dist/src/core/ast/matchers/html/html-parser.js +58 -0
- package/dist/src/core/ast/matchers/html/html-pipe-matcher.js +95 -0
- package/dist/src/core/ast/matchers/html/html-text-matcher.js +53 -0
- package/dist/src/core/ast/matchers/html/index.js +118 -0
- package/dist/src/core/ast/matchers/index.js +377 -0
- package/dist/src/core/ast/matchers/ts/collection-matcher.js +51 -0
- package/dist/src/core/ast/matchers/ts/context-matcher.js +275 -0
- package/dist/src/core/ast/matchers/ts/decorator-matcher.js +465 -0
- package/dist/src/core/ast/matchers/ts/expression-matcher.js +237 -0
- package/dist/src/core/ast/matchers/ts/file-matcher.js +97 -0
- package/dist/src/core/ast/matchers/ts/hierarchy-matcher.js +172 -0
- package/dist/src/core/ast/matchers/ts/import-matcher.js +39 -0
- package/dist/src/core/ast/matchers/ts/index.js +53 -0
- package/dist/src/core/ast/matchers/ts/node-matcher.js +156 -0
- package/dist/src/core/ast/matchers/ts/symbol-matcher.js +281 -0
- package/dist/src/core/ast/matchers/ts/type-matcher.js +207 -0
- package/dist/src/core/ast/matchers/utils/matcher-helpers.js +37 -0
- package/dist/src/core/ast/scanner-ast.js +444 -0
- package/dist/src/core/project-detector.js +196 -0
- package/dist/src/core/project-strategy/index.js +9 -0
- package/dist/src/core/project-strategy/nx-strategy.js +130 -0
- package/dist/src/core/project-strategy/project-strategy.interface.js +2 -0
- package/dist/src/core/project-strategy/standalone-strategy.js +74 -0
- package/dist/src/core/project-strategy/strategy-factory.js +15 -0
- package/dist/src/core/rules-loader.js +89 -0
- package/dist/src/core/scan-reporter.js +316 -0
- package/dist/src/core/scanner-delta.js +339 -0
- package/dist/src/core/scanner-orchestrator.js +266 -0
- package/dist/src/core/scanner-regex.js +298 -0
- package/dist/src/core/workload/calculator.js +82 -0
- package/dist/src/core/workload/constants.js +15 -0
- package/dist/src/core/workload/grouping.js +18 -0
- package/dist/src/core/workload/hierarchy-calculator.js +127 -0
- package/dist/src/core/workload/index.js +11 -0
- package/dist/src/core/workload/metadata.js +20 -0
- package/dist/src/core/workload/special-workload.js +101 -0
- package/dist/src/core/workload/target-resolver.js +34 -0
- package/dist/src/data/angular-migration-rules.json +2337 -0
- package/dist/src/data/markdown/angular-migration-17-18.md +408 -0
- package/dist/src/data/markdown/angular-migration-18-19.md +600 -0
- package/dist/src/data/markdown/angular-migration-19-20.md +521 -0
- package/dist/src/data/rules/rearchitecture/rearchitecture-rules.json +66 -0
- package/dist/src/data/rules/to18/rules-18-obligatoire.json +374 -0
- package/dist/src/data/rules/to18/rules-18-optionnelle.json +188 -0
- package/dist/src/data/rules/to18/rules-18-recommande.json +218 -0
- package/dist/src/data/rules/to19/rules-19-obligatoire.json +348 -0
- package/dist/src/data/rules/to19/rules-19-optionnelle.json +223 -0
- package/dist/src/data/rules/to19/rules-19-recommande.json +200 -0
- package/dist/src/data/rules/to20/rules-20-obligatoire.json +556 -0
- package/dist/src/data/rules/to20/rules-20-optionnelle.json +190 -0
- package/dist/src/data/rules/to20/rules-20-recommande.json +151 -0
- package/dist/src/index.js +161 -0
- package/dist/src/models/chip-config.js +45 -0
- package/dist/src/models/interfaces/app-details.interface.js +2 -0
- package/dist/src/models/interfaces/ast-interfaces.js +5 -0
- package/dist/src/models/interfaces/ast-pattern.interface.js +2 -0
- package/dist/src/models/interfaces/client-interfaces.js +6 -0
- package/dist/src/models/interfaces/detection-stats.interface.js +2 -0
- package/dist/src/models/interfaces/html-match.interface.js +2 -0
- package/dist/src/models/interfaces/html-report-data.interface.js +2 -0
- package/dist/src/models/interfaces/lib-details.interface.js +2 -0
- package/dist/src/models/interfaces/migration-rules.interface.js +2 -0
- package/dist/src/models/interfaces/parsed-args.interface.js +2 -0
- package/dist/src/models/interfaces/project-info.interface.js +2 -0
- package/dist/src/models/interfaces/project-overview-data.interface.js +2 -0
- package/dist/src/models/interfaces/rule-match.interface.js +2 -0
- package/dist/src/models/interfaces/rule.interface.js +2 -0
- package/dist/src/models/interfaces/rules-by-priority.interface.js +2 -0
- package/dist/src/models/interfaces/scanner-comparison.interface.js +2 -0
- package/dist/src/models/interfaces/special-workload.interface.js +2 -0
- package/dist/src/models/interfaces/workload-report.interface.js +2 -0
- package/dist/src/models/types/build-block-blob.type.js +2 -0
- package/dist/src/models/types/migration-version.type.js +2 -0
- package/dist/src/models/types/project-type.type.js +2 -0
- package/dist/src/models/types/risk-level.type.js +2 -0
- package/dist/src/models/types/rule-category.type.js +2 -0
- package/dist/src/models/types/rule-priority.type.js +2 -0
- package/dist/src/models/types/rule-workload-type.type.js +2 -0
- package/dist/src/templates/landing/applications-analyzed.template.js +18 -0
- package/dist/src/templates/landing/card-app-info.template.js +63 -0
- package/dist/src/templates/landing/card-lib-info.template.js +67 -0
- package/dist/src/templates/landing/libs-analyzed.template.js +22 -0
- package/dist/src/templates/landing/nx-summary.template.js +115 -0
- package/dist/src/templates/landing/project-overview.template.js +27 -0
- package/dist/src/templates/page/index-page.template.js +95 -0
- package/dist/src/templates/page/main.template.js +83 -0
- package/dist/src/templates/page/migration-guide.template.js +175 -0
- package/dist/src/templates/page/workload-report.template.js +53 -0
- package/dist/src/templates/workload/dashboard.template.js +184 -0
- package/dist/src/templates/workload/filters-panel.template.js +215 -0
- package/dist/src/templates/workload/guide-rule-card.template.js +107 -0
- package/dist/src/templates/workload/hierarchy-nx.template.js +104 -0
- package/dist/src/templates/workload/hierarchy-shared.js +163 -0
- package/dist/src/templates/workload/hierarchy-standalone.template.js +36 -0
- package/dist/src/templates/workload/hierarchy.template.js +35 -0
- package/dist/src/templates/workload/rule-modal.template.js +280 -0
- package/dist/src/utils/core/args-parser.js +123 -0
- package/dist/src/utils/core/array-helpers.js +18 -0
- package/dist/src/utils/core/ast-helpers.js +99 -0
- package/dist/src/utils/core/file-helpers.js +109 -0
- package/dist/src/utils/core/html-helpers.js +36 -0
- package/dist/src/utils/core/index.js +28 -0
- package/dist/src/utils/core/logger.js +38 -0
- package/dist/src/utils/core/rule-helpers.js +15 -0
- package/dist/src/utils/core/workload-formatter.js +6 -0
- package/dist/src/utils/shared/array-helpers.js +25 -0
- package/dist/src/utils/shared/date-helpers.js +109 -0
- package/dist/src/utils/shared/html-helpers.js +37 -0
- package/dist/src/utils/shared/index.js +25 -0
- package/dist/src/utils/shared/rule-helpers.js +20 -0
- package/dist/src/utils/shared/time-formatters.js +76 -0
- package/dist/styles.css +2 -0
- package/package.json +107 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compareScans = compareScans;
|
|
4
|
+
exports.logDeltaReport = logDeltaReport;
|
|
5
|
+
const core_1 = require("../utils/core");
|
|
6
|
+
/**
|
|
7
|
+
* Compare les résultats de deux scans et génère une analyse détaillée
|
|
8
|
+
*/
|
|
9
|
+
function compareScans(astResult, regexResult, options = {}) {
|
|
10
|
+
const startTime = Date.now();
|
|
11
|
+
// 1. Construire le résumé global
|
|
12
|
+
const summary = buildComparisonSummary(astResult.matches, regexResult.matches);
|
|
13
|
+
// 2. Analyser règle par règle
|
|
14
|
+
const byRule = buildRuleComparisons(astResult.matches, regexResult.matches, options);
|
|
15
|
+
// 3. Identifier les divergences
|
|
16
|
+
const divergences = options.analyzeDivergences !== false
|
|
17
|
+
? buildDivergenceDetails(astResult.matches, regexResult.matches)
|
|
18
|
+
: createEmptyDivergences();
|
|
19
|
+
// 4. Comparer les performances
|
|
20
|
+
const performance = buildPerformanceComparison(astResult, regexResult);
|
|
21
|
+
const comparison = {
|
|
22
|
+
summary,
|
|
23
|
+
byRule,
|
|
24
|
+
divergences,
|
|
25
|
+
performance
|
|
26
|
+
};
|
|
27
|
+
const result = {
|
|
28
|
+
astResult,
|
|
29
|
+
regexResult,
|
|
30
|
+
comparison,
|
|
31
|
+
timestamp: new Date().toISOString()
|
|
32
|
+
};
|
|
33
|
+
core_1.logger.debug(`Analyse comparative complétée en ${Date.now() - startTime}ms`);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Construit le résumé global de la comparaison
|
|
38
|
+
*/
|
|
39
|
+
function buildComparisonSummary(astMatches, regexMatches) {
|
|
40
|
+
const totalAst = astMatches.length;
|
|
41
|
+
const totalRegex = regexMatches.length;
|
|
42
|
+
const delta = totalAst - totalRegex;
|
|
43
|
+
const astSet = createMatchSignatureSet(astMatches);
|
|
44
|
+
const regexSet = createMatchSignatureSet(regexMatches);
|
|
45
|
+
const commonMatches = astMatches.filter(m => regexSet.has(createMatchSignature(m))).length;
|
|
46
|
+
const maxTotal = Math.max(totalAst, totalRegex);
|
|
47
|
+
const matchRate = maxTotal > 0 ? (commonMatches / maxTotal) * 100 : 100;
|
|
48
|
+
return {
|
|
49
|
+
totalAst,
|
|
50
|
+
totalRegex,
|
|
51
|
+
delta,
|
|
52
|
+
deltaPercentage: totalRegex > 0 ? (delta / totalRegex) * 100 : 0,
|
|
53
|
+
commonMatches,
|
|
54
|
+
matchRate
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Construit la comparaison détaillée par règle
|
|
59
|
+
*/
|
|
60
|
+
function buildRuleComparisons(astMatches, regexMatches, options) {
|
|
61
|
+
const astByRule = groupMatchesByRule(astMatches);
|
|
62
|
+
const regexByRule = groupMatchesByRule(regexMatches);
|
|
63
|
+
const allRuleKeys = new Set([...astByRule.keys(), ...regexByRule.keys()]);
|
|
64
|
+
const comparisons = [];
|
|
65
|
+
for (const ruleKey of allRuleKeys) {
|
|
66
|
+
const astCount = astByRule.get(ruleKey)?.length || 0;
|
|
67
|
+
const regexCount = regexByRule.get(ruleKey)?.length || 0;
|
|
68
|
+
const delta = astCount - regexCount;
|
|
69
|
+
const status = determineComparisonStatus(astCount, regexCount);
|
|
70
|
+
// Filtrer les identiques si demandé
|
|
71
|
+
if (!options.includeIdentical && status === 'identical') {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// Filtrer par seuil de delta si spécifié
|
|
75
|
+
if (options.minDeltaThreshold && Math.abs(delta) < options.minDeltaThreshold) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const ruleSummary = astMatches.find(m => m.ruleKey === ruleKey)?.ruleSummary
|
|
79
|
+
|| regexMatches.find(m => m.ruleKey === ruleKey)?.ruleSummary;
|
|
80
|
+
comparisons.push({
|
|
81
|
+
ruleKey,
|
|
82
|
+
ruleSummary,
|
|
83
|
+
astCount,
|
|
84
|
+
regexCount,
|
|
85
|
+
delta,
|
|
86
|
+
deltaPercentage: regexCount > 0 ? (delta / regexCount) * 100 : 0,
|
|
87
|
+
status,
|
|
88
|
+
confidence: determineConfidenceLevel(status, astCount, regexCount)
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// Trier par delta absolu décroissant
|
|
92
|
+
return comparisons.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Construit les détails sur les divergences
|
|
96
|
+
*/
|
|
97
|
+
function buildDivergenceDetails(astMatches, regexMatches) {
|
|
98
|
+
const astSet = createMatchSignatureSet(astMatches);
|
|
99
|
+
const regexSet = createMatchSignatureSet(regexMatches);
|
|
100
|
+
const onlyInAst = astMatches
|
|
101
|
+
.filter(m => !regexSet.has(createMatchSignature(m)))
|
|
102
|
+
.map(m => createMatchDivergence(m, 'ast'));
|
|
103
|
+
const onlyInRegex = regexMatches
|
|
104
|
+
.filter(m => !astSet.has(createMatchSignature(m)))
|
|
105
|
+
.map(m => createMatchDivergence(m, 'regex'));
|
|
106
|
+
return {
|
|
107
|
+
onlyInAst,
|
|
108
|
+
onlyInRegex,
|
|
109
|
+
locationMismatch: [] // À implémenter si nécessaire
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Crée un objet MatchDivergence avec analyse de la catégorie
|
|
114
|
+
*/
|
|
115
|
+
function createMatchDivergence(match, source) {
|
|
116
|
+
const category = analyzeDivergenceCategory(match, source);
|
|
117
|
+
const possibleReason = getDivergenceReason(category);
|
|
118
|
+
return {
|
|
119
|
+
match,
|
|
120
|
+
category,
|
|
121
|
+
possibleReason
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Analyse la catégorie probable d'une divergence
|
|
126
|
+
*/
|
|
127
|
+
function analyzeDivergenceCategory(match, source) {
|
|
128
|
+
// Heuristiques simples pour classifier la divergence
|
|
129
|
+
const text = match.matchedText.toLowerCase();
|
|
130
|
+
if (source === 'ast') {
|
|
131
|
+
// AST a trouvé quelque chose que Regex n'a pas trouvé
|
|
132
|
+
if (text.includes('comment') || text.includes('//') || text.includes('/*')) {
|
|
133
|
+
return 'ast-precision'; // AST ignore les commentaires correctement
|
|
134
|
+
}
|
|
135
|
+
return 'ast-precision';
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Regex a trouvé quelque chose que AST n'a pas trouvé
|
|
139
|
+
if (text.length < 5) {
|
|
140
|
+
return 'false-positive-regex'; // Probablement un match trop court
|
|
141
|
+
}
|
|
142
|
+
return 'regex-coverage';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Retourne une raison explicative pour une catégorie de divergence
|
|
147
|
+
*/
|
|
148
|
+
function getDivergenceReason(category) {
|
|
149
|
+
const reasons = {
|
|
150
|
+
'false-positive-ast': 'AST peut détecter des patterns ambigus',
|
|
151
|
+
'false-positive-regex': 'Regex peut matcher du texte non pertinent',
|
|
152
|
+
'ast-precision': 'AST offre une détection contextuelle plus précise',
|
|
153
|
+
'regex-coverage': 'Regex capture une portée plus large',
|
|
154
|
+
'unknown': 'Raison de divergence à analyser manuellement'
|
|
155
|
+
};
|
|
156
|
+
return reasons[category];
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Construit la comparaison de performance
|
|
160
|
+
*/
|
|
161
|
+
function buildPerformanceComparison(astResult, regexResult) {
|
|
162
|
+
const filesProcessed = Math.max(astResult.filesScanned || 0, regexResult.filesScanned || 0);
|
|
163
|
+
const performance = {
|
|
164
|
+
filesProcessed
|
|
165
|
+
};
|
|
166
|
+
if (astResult.duration !== undefined) {
|
|
167
|
+
performance.astDuration = astResult.duration;
|
|
168
|
+
}
|
|
169
|
+
if (regexResult.duration !== undefined) {
|
|
170
|
+
performance.regexDuration = regexResult.duration;
|
|
171
|
+
}
|
|
172
|
+
if (performance.astDuration && performance.regexDuration) {
|
|
173
|
+
performance.speedRatio = performance.astDuration / performance.regexDuration;
|
|
174
|
+
performance.averageTimePerFile = {
|
|
175
|
+
ast: performance.astDuration / filesProcessed,
|
|
176
|
+
regex: performance.regexDuration / filesProcessed
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return performance;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Détermine le statut de comparaison
|
|
183
|
+
*/
|
|
184
|
+
function determineComparisonStatus(astCount, regexCount) {
|
|
185
|
+
if (astCount === 0 && regexCount === 0)
|
|
186
|
+
return 'identical';
|
|
187
|
+
if (astCount === 0)
|
|
188
|
+
return 'regex-only';
|
|
189
|
+
if (regexCount === 0)
|
|
190
|
+
return 'ast-only';
|
|
191
|
+
if (astCount === regexCount)
|
|
192
|
+
return 'identical';
|
|
193
|
+
return astCount > regexCount ? 'ast-more' : 'regex-more';
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Détermine le niveau de confiance d'une détection
|
|
197
|
+
*/
|
|
198
|
+
function determineConfidenceLevel(status, astCount, regexCount) {
|
|
199
|
+
if (status === 'identical')
|
|
200
|
+
return 'high';
|
|
201
|
+
const maxCount = Math.max(astCount, regexCount);
|
|
202
|
+
const minCount = Math.min(astCount, regexCount);
|
|
203
|
+
if (minCount === 0)
|
|
204
|
+
return 'low'; // Un seul détecte
|
|
205
|
+
const ratio = minCount / maxCount;
|
|
206
|
+
if (ratio >= 0.9)
|
|
207
|
+
return 'high'; // Différence < 10%
|
|
208
|
+
if (ratio >= 0.7)
|
|
209
|
+
return 'medium'; // Différence 10-30%
|
|
210
|
+
return 'low'; // Différence > 30%
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Crée un objet divergences vide
|
|
214
|
+
*/
|
|
215
|
+
function createEmptyDivergences() {
|
|
216
|
+
return {
|
|
217
|
+
onlyInAst: [],
|
|
218
|
+
onlyInRegex: [],
|
|
219
|
+
locationMismatch: []
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Affiche le rapport de delta dans la console
|
|
224
|
+
*/
|
|
225
|
+
function logDeltaReport(result) {
|
|
226
|
+
const { comparison } = result;
|
|
227
|
+
const { summary, byRule, divergences, performance } = comparison;
|
|
228
|
+
core_1.logger.blank();
|
|
229
|
+
core_1.logger.info('═══════════════════════════════════════════════════════════');
|
|
230
|
+
core_1.logger.info(' RAPPORT DE COMPARAISON AST vs REGEX');
|
|
231
|
+
core_1.logger.info('═══════════════════════════════════════════════════════════');
|
|
232
|
+
core_1.logger.blank();
|
|
233
|
+
// Vue d'ensemble
|
|
234
|
+
core_1.logger.info('Vue d\'ensemble:');
|
|
235
|
+
core_1.logger.info(` • Total AST: ${summary.totalAst} occurrences`);
|
|
236
|
+
core_1.logger.info(` • Total Regex: ${summary.totalRegex} occurrences`);
|
|
237
|
+
core_1.logger.info(` • Delta: ${summary.delta > 0 ? '+' : ''}${summary.delta} (${summary.deltaPercentage.toFixed(1)}%)`);
|
|
238
|
+
core_1.logger.info(` • Taux de correspondance: ${summary.matchRate.toFixed(1)}%`);
|
|
239
|
+
core_1.logger.blank();
|
|
240
|
+
core_1.logger.info('Répartition:');
|
|
241
|
+
core_1.logger.info(` • Commun aux deux: ${summary.commonMatches} occurrences`);
|
|
242
|
+
core_1.logger.info(` • Uniquement AST: ${divergences.onlyInAst.length} occurrences`);
|
|
243
|
+
core_1.logger.info(` • Uniquement Regex: ${divergences.onlyInRegex.length} occurrences`);
|
|
244
|
+
core_1.logger.blank();
|
|
245
|
+
// Performance
|
|
246
|
+
if (performance.astDuration && performance.regexDuration) {
|
|
247
|
+
core_1.logger.info('Performance:');
|
|
248
|
+
core_1.logger.info(` • Durée AST: ${(performance.astDuration / 1000).toFixed(2)}s`);
|
|
249
|
+
core_1.logger.info(` • Durée Regex: ${(performance.regexDuration / 1000).toFixed(2)}s`);
|
|
250
|
+
core_1.logger.info(` • Ratio: ${performance.speedRatio.toFixed(2)}x ${performance.speedRatio > 1 ? '(Regex plus rapide)' : '(AST plus rapide)'}`);
|
|
251
|
+
core_1.logger.blank();
|
|
252
|
+
}
|
|
253
|
+
// Comparaison par règle
|
|
254
|
+
const identicalRules = byRule.filter(r => r.status === 'identical');
|
|
255
|
+
const divergentRules = byRule.filter(r => r.status !== 'identical');
|
|
256
|
+
core_1.logger.info(`Règles analysées: ${byRule.length}`);
|
|
257
|
+
core_1.logger.info(` • Identiques: ${identicalRules.length}`);
|
|
258
|
+
core_1.logger.info(` • Divergentes: ${divergentRules.length}`);
|
|
259
|
+
core_1.logger.blank();
|
|
260
|
+
// Top règles divergentes
|
|
261
|
+
if (divergentRules.length > 0) {
|
|
262
|
+
core_1.logger.info('Top 10 règles avec le plus grand écart:');
|
|
263
|
+
for (const rule of divergentRules.slice(0, 10)) {
|
|
264
|
+
const statusIcon = getStatusIcon(rule.status);
|
|
265
|
+
const confidenceIcon = getConfidenceIcon(rule.confidence);
|
|
266
|
+
const sign = rule.delta > 0 ? '+' : '';
|
|
267
|
+
core_1.logger.debug(` ${statusIcon} ${confidenceIcon} ${rule.ruleKey}: AST=${rule.astCount}, Regex=${rule.regexCount} (${sign}${rule.delta})`);
|
|
268
|
+
}
|
|
269
|
+
core_1.logger.blank();
|
|
270
|
+
}
|
|
271
|
+
// Règles uniquement détectées par un scanner
|
|
272
|
+
const astOnlyRules = byRule.filter(r => r.status === 'ast-only');
|
|
273
|
+
const regexOnlyRules = byRule.filter(r => r.status === 'regex-only');
|
|
274
|
+
if (astOnlyRules.length > 0) {
|
|
275
|
+
core_1.logger.info(`Règles détectées uniquement par AST (${astOnlyRules.length}):`);
|
|
276
|
+
for (const rule of astOnlyRules) {
|
|
277
|
+
core_1.logger.debug(` ✓ ${rule.ruleKey}: ${rule.astCount} occurrences`);
|
|
278
|
+
}
|
|
279
|
+
core_1.logger.blank();
|
|
280
|
+
}
|
|
281
|
+
if (regexOnlyRules.length > 0) {
|
|
282
|
+
core_1.logger.info(`Règles détectées uniquement par Regex (${regexOnlyRules.length}):`);
|
|
283
|
+
for (const rule of regexOnlyRules) {
|
|
284
|
+
core_1.logger.debug(` ⚠ ${rule.ruleKey}: ${rule.regexCount} occurrences`);
|
|
285
|
+
}
|
|
286
|
+
core_1.logger.blank();
|
|
287
|
+
}
|
|
288
|
+
core_1.logger.info('═══════════════════════════════════════════════════════════');
|
|
289
|
+
core_1.logger.blank();
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Groupe les matches par règle
|
|
293
|
+
*/
|
|
294
|
+
function groupMatchesByRule(matches) {
|
|
295
|
+
const grouped = new Map();
|
|
296
|
+
for (const match of matches) {
|
|
297
|
+
if (!grouped.has(match.ruleKey)) {
|
|
298
|
+
grouped.set(match.ruleKey, []);
|
|
299
|
+
}
|
|
300
|
+
grouped.get(match.ruleKey).push(match);
|
|
301
|
+
}
|
|
302
|
+
return grouped;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Crée une signature unique pour un match (fichier:ligne:règle)
|
|
306
|
+
*/
|
|
307
|
+
function createMatchSignature(match) {
|
|
308
|
+
return `${match.filePath}:${match.lineNumber}:${match.ruleKey}`;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Crée un Set de signatures pour recherche rapide
|
|
312
|
+
*/
|
|
313
|
+
function createMatchSignatureSet(matches) {
|
|
314
|
+
return new Set(matches.map(createMatchSignature));
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Retourne l'icône correspondant au status
|
|
318
|
+
*/
|
|
319
|
+
function getStatusIcon(status) {
|
|
320
|
+
const icons = {
|
|
321
|
+
'identical': '=',
|
|
322
|
+
'ast-more': '↑',
|
|
323
|
+
'regex-more': '↓',
|
|
324
|
+
'ast-only': '✓',
|
|
325
|
+
'regex-only': '⚠'
|
|
326
|
+
};
|
|
327
|
+
return icons[status];
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Retourne l'icône correspondant au niveau de confiance
|
|
331
|
+
*/
|
|
332
|
+
function getConfidenceIcon(confidence) {
|
|
333
|
+
const icons = {
|
|
334
|
+
'high': '●',
|
|
335
|
+
'medium': '◐',
|
|
336
|
+
'low': '○'
|
|
337
|
+
};
|
|
338
|
+
return icons[confidence];
|
|
339
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeScan = executeScan;
|
|
4
|
+
const scanner_ast_1 = require("./ast/scanner-ast");
|
|
5
|
+
const scanner_regex_1 = require("./scanner-regex");
|
|
6
|
+
const scanner_delta_1 = require("./scanner-delta");
|
|
7
|
+
const core_1 = require("../utils/core");
|
|
8
|
+
const scan_reporter_1 = require("./scan-reporter");
|
|
9
|
+
const core_2 = require("../utils/core");
|
|
10
|
+
const target_resolver_1 = require("./workload/target-resolver");
|
|
11
|
+
const project_strategy_1 = require("./project-strategy");
|
|
12
|
+
/**
|
|
13
|
+
* Affiche un rapport concis des occurrences par règle, groupé par migration et priorité
|
|
14
|
+
*/
|
|
15
|
+
function logScannerReport(matches, scannerName, allRules) {
|
|
16
|
+
core_1.logger.blank();
|
|
17
|
+
core_1.logger.info(`═══════════════════════════════════════════════════════════`);
|
|
18
|
+
core_1.logger.info(` RAPPORT ${scannerName.toUpperCase()}`);
|
|
19
|
+
core_1.logger.info(`═══════════════════════════════════════════════════════════`);
|
|
20
|
+
core_1.logger.blank();
|
|
21
|
+
// Grouper les matches par ruleKey (DRY: utilise groupBy helper)
|
|
22
|
+
const matchesByRule = (0, core_1.groupBy)(matches, match => match.ruleKey);
|
|
23
|
+
// Charger la structure MigrationRules pour avoir migration + priority
|
|
24
|
+
const { loadMigrationRules } = require('./rules-loader');
|
|
25
|
+
const migrationRules = loadMigrationRules();
|
|
26
|
+
// Créer une map ruleKey -> { migration, priority, rule }
|
|
27
|
+
const ruleMetadata = new Map();
|
|
28
|
+
for (const [migration, priorities] of Object.entries(migrationRules)) {
|
|
29
|
+
for (const [priority, rules] of Object.entries(priorities)) {
|
|
30
|
+
for (const rule of rules) {
|
|
31
|
+
ruleMetadata.set(rule.key, { migration, priority, rule });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const organized = new Map();
|
|
36
|
+
for (const [ruleKey, ruleMatches] of matchesByRule) {
|
|
37
|
+
const metadata = ruleMetadata.get(ruleKey);
|
|
38
|
+
if (!metadata)
|
|
39
|
+
continue;
|
|
40
|
+
const { migration, priority, rule } = metadata;
|
|
41
|
+
if (!organized.has(migration)) {
|
|
42
|
+
organized.set(migration, new Map());
|
|
43
|
+
}
|
|
44
|
+
const migrationMap = organized.get(migration);
|
|
45
|
+
if (!migrationMap.has(priority)) {
|
|
46
|
+
migrationMap.set(priority, []);
|
|
47
|
+
}
|
|
48
|
+
migrationMap.get(priority).push({
|
|
49
|
+
rule,
|
|
50
|
+
count: ruleMatches.length
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Afficher par migration (trié)
|
|
54
|
+
const migrations = ['to18', 'to19', 'to20'].filter(m => organized.has(m));
|
|
55
|
+
for (const migration of migrations) {
|
|
56
|
+
const prioritiesMap = organized.get(migration);
|
|
57
|
+
// Calculer total migration
|
|
58
|
+
const totalMigration = Array.from(prioritiesMap.values())
|
|
59
|
+
.flat()
|
|
60
|
+
.reduce((sum, e) => sum + e.count, 0);
|
|
61
|
+
core_1.logger.info(`📦 Migration ${migration.toUpperCase()} (${totalMigration} occurrences)`);
|
|
62
|
+
core_1.logger.blank();
|
|
63
|
+
// Afficher par priorité
|
|
64
|
+
const priorities = ['obligatoire', 'recommande', 'optionnelle'];
|
|
65
|
+
for (const priority of priorities) {
|
|
66
|
+
const entries = prioritiesMap.get(priority) || [];
|
|
67
|
+
if (entries.length === 0)
|
|
68
|
+
continue;
|
|
69
|
+
const totalPriority = entries.reduce((sum, e) => sum + e.count, 0);
|
|
70
|
+
const priorityIcon = priority === 'obligatoire' ? '🔴' : priority === 'recommande' ? '🟡' : '🟢';
|
|
71
|
+
core_1.logger.info(` ${priorityIcon} ${priority.toUpperCase()} (${totalPriority} occurrences)`);
|
|
72
|
+
// Trier par nombre d'occurrences décroissant
|
|
73
|
+
entries.sort((a, b) => b.count - a.count);
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const countStr = `${entry.count}`.padStart(3);
|
|
76
|
+
core_1.logger.debug(` ${countStr} × ${entry.rule.summary}`);
|
|
77
|
+
}
|
|
78
|
+
core_1.logger.blank();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
core_1.logger.info(`═══════════════════════════════════════════════════════════`);
|
|
82
|
+
core_1.logger.blank();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Orchestre le scan selon le type de scanner demandé
|
|
86
|
+
*/
|
|
87
|
+
function executeScan(projectInfo, rules, scannerType) {
|
|
88
|
+
// Afficher compteurs règles spéciales (une seule fois, peu importe le scanner)
|
|
89
|
+
const specialWorkloadCount = rules.filter(r => r.special_workload).length;
|
|
90
|
+
const routineCount = rules.filter(r => (0, core_2.isRoutineRule)(r)).length;
|
|
91
|
+
(0, scan_reporter_1.logSpecialRulesInfo)(specialWorkloadCount, routineCount);
|
|
92
|
+
if (scannerType === 'both') {
|
|
93
|
+
return executeBothScans(projectInfo, rules);
|
|
94
|
+
}
|
|
95
|
+
else if (scannerType === 'ast') {
|
|
96
|
+
return executeAstScan(projectInfo, rules);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
return executeRegexScan(projectInfo, rules);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Exécute uniquement le scanner AST
|
|
104
|
+
*/
|
|
105
|
+
function executeAstScan(projectInfo, rules) {
|
|
106
|
+
core_1.logger.blank();
|
|
107
|
+
core_1.logger.step('Analyse des applications (AST)...');
|
|
108
|
+
core_1.logger.blank();
|
|
109
|
+
const allMatches = [];
|
|
110
|
+
// Séparer les règles de routine (onFile) des règles normales
|
|
111
|
+
const routineRules = rules.filter(r => (0, core_2.isRoutineRule)(r));
|
|
112
|
+
const nonRoutineRules = rules.filter(r => !(0, core_2.isRoutineRule)(r));
|
|
113
|
+
// Scanner les règles de routine UNE SEULE FOIS à la racine du projet (avec fallback regex)
|
|
114
|
+
if (routineRules.length > 0) {
|
|
115
|
+
const routineMatches = (0, scanner_regex_1.scanDirectory)(projectInfo.path, routineRules, projectInfo.path);
|
|
116
|
+
allMatches.push(...routineMatches);
|
|
117
|
+
}
|
|
118
|
+
// Scanner les règles normales par target avec AST
|
|
119
|
+
const strategy = (0, project_strategy_1.createProjectStrategy)(projectInfo);
|
|
120
|
+
const targets = strategy.getTargetsList(projectInfo);
|
|
121
|
+
const astRulesCount = nonRoutineRules.filter(r => r.astPattern).length;
|
|
122
|
+
const regexRulesCount = nonRoutineRules.filter(r => !r.astPattern && r.regex).length;
|
|
123
|
+
for (const target of targets) {
|
|
124
|
+
const targetType = (0, target_resolver_1.getTargetType)(target.name, projectInfo);
|
|
125
|
+
const targetPath = (0, target_resolver_1.getTargetPath)(projectInfo, target.name);
|
|
126
|
+
(0, scan_reporter_1.logTargetStart)(target.name, targetType);
|
|
127
|
+
(0, scan_reporter_1.logScannerInfo)(astRulesCount, regexRulesCount);
|
|
128
|
+
const startTime = Date.now();
|
|
129
|
+
const result = (0, scanner_ast_1.scanDirectoryWithAST)(targetPath, nonRoutineRules, projectInfo.path);
|
|
130
|
+
const duration = Date.now() - startTime;
|
|
131
|
+
allMatches.push(...result.matches);
|
|
132
|
+
(0, scan_reporter_1.logTargetComplete)(target.name, result.matches, duration);
|
|
133
|
+
}
|
|
134
|
+
core_1.logger.blank();
|
|
135
|
+
core_1.logger.success(`Analyse AST terminée: ${allMatches.length} occurrences totales`);
|
|
136
|
+
return { matches: allMatches };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Exécute uniquement le scanner Regex
|
|
140
|
+
*/
|
|
141
|
+
function executeRegexScan(projectInfo, rules) {
|
|
142
|
+
core_1.logger.blank();
|
|
143
|
+
core_1.logger.step('Analyse des applications (Regex)...');
|
|
144
|
+
core_1.logger.blank();
|
|
145
|
+
const allMatches = [];
|
|
146
|
+
// Séparer les règles de routine (onFile) des règles normales
|
|
147
|
+
const routineRules = rules.filter(r => (0, core_2.isRoutineRule)(r));
|
|
148
|
+
const nonRoutineRules = rules.filter(r => !(0, core_2.isRoutineRule)(r));
|
|
149
|
+
// Scanner les règles de routine UNE SEULE FOIS à la racine du projet
|
|
150
|
+
if (routineRules.length > 0) {
|
|
151
|
+
const routineMatches = (0, scanner_regex_1.scanDirectory)(projectInfo.path, routineRules, projectInfo.path);
|
|
152
|
+
allMatches.push(...routineMatches);
|
|
153
|
+
}
|
|
154
|
+
// Scanner les règles normales par target
|
|
155
|
+
const strategy = (0, project_strategy_1.createProjectStrategy)(projectInfo);
|
|
156
|
+
const targets = strategy.getTargetsList(projectInfo);
|
|
157
|
+
const regexRulesCount = nonRoutineRules.filter(r => r.regex).length;
|
|
158
|
+
for (const target of targets) {
|
|
159
|
+
const targetType = (0, target_resolver_1.getTargetType)(target.name, projectInfo);
|
|
160
|
+
const targetPath = (0, target_resolver_1.getTargetPath)(projectInfo, target.name);
|
|
161
|
+
(0, scan_reporter_1.logTargetStart)(target.name, targetType);
|
|
162
|
+
(0, scan_reporter_1.logScannerInfo)(0, regexRulesCount);
|
|
163
|
+
const startTime = Date.now();
|
|
164
|
+
const matches = (0, scanner_regex_1.scanDirectory)(targetPath, nonRoutineRules, projectInfo.path);
|
|
165
|
+
const duration = Date.now() - startTime;
|
|
166
|
+
allMatches.push(...matches);
|
|
167
|
+
(0, scan_reporter_1.logTargetComplete)(target.name, matches, duration);
|
|
168
|
+
}
|
|
169
|
+
core_1.logger.blank();
|
|
170
|
+
core_1.logger.success(`Analyse Regex terminée: ${allMatches.length} occurrences totales`);
|
|
171
|
+
return { matches: allMatches };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Exécute les deux scanners et génère une comparaison
|
|
175
|
+
*/
|
|
176
|
+
function executeBothScans(projectInfo, rules) {
|
|
177
|
+
const strategy = (0, project_strategy_1.createProjectStrategy)(projectInfo);
|
|
178
|
+
const targets = strategy.getTargetsList(projectInfo);
|
|
179
|
+
const fileCount = targets.length;
|
|
180
|
+
// Séparer les règles de routine (onFile) des règles normales UNE SEULE FOIS
|
|
181
|
+
const routineRules = rules.filter(r => (0, core_2.isRoutineRule)(r));
|
|
182
|
+
const nonRoutineRules = rules.filter(r => !(0, core_2.isRoutineRule)(r));
|
|
183
|
+
const astRulesCount = nonRoutineRules.filter(r => r.astPattern).length;
|
|
184
|
+
const regexRulesCount = nonRoutineRules.filter(r => r.regex).length;
|
|
185
|
+
// 1. Scanner avec AST
|
|
186
|
+
core_1.logger.blank();
|
|
187
|
+
core_1.logger.step('Analyse AST en cours...');
|
|
188
|
+
core_1.logger.blank();
|
|
189
|
+
const astStartTime = Date.now();
|
|
190
|
+
const astMatches = [];
|
|
191
|
+
const astStats = [];
|
|
192
|
+
// Scanner les règles de routine UNE SEULE FOIS à la racine du projet (avec fallback regex)
|
|
193
|
+
if (routineRules.length > 0) {
|
|
194
|
+
const routineMatches = (0, scanner_regex_1.scanDirectory)(projectInfo.path, routineRules, projectInfo.path);
|
|
195
|
+
astMatches.push(...routineMatches);
|
|
196
|
+
}
|
|
197
|
+
// Scanner les règles normales par target avec AST
|
|
198
|
+
for (const target of targets) {
|
|
199
|
+
const targetType = (0, target_resolver_1.getTargetType)(target.name, projectInfo);
|
|
200
|
+
const targetPath = (0, target_resolver_1.getTargetPath)(projectInfo, target.name);
|
|
201
|
+
(0, scan_reporter_1.logTargetStart)(target.name, targetType);
|
|
202
|
+
(0, scan_reporter_1.logScannerInfo)(astRulesCount, 0);
|
|
203
|
+
const startTime = Date.now();
|
|
204
|
+
const result = (0, scanner_ast_1.scanDirectoryWithAST)(targetPath, nonRoutineRules, projectInfo.path);
|
|
205
|
+
const duration = Date.now() - startTime;
|
|
206
|
+
astMatches.push(...result.matches);
|
|
207
|
+
astStats.push(...result.stats);
|
|
208
|
+
(0, scan_reporter_1.logTargetComplete)(target.name, result.matches, duration);
|
|
209
|
+
}
|
|
210
|
+
const astDuration = Date.now() - astStartTime;
|
|
211
|
+
core_1.logger.blank();
|
|
212
|
+
core_1.logger.success(`AST terminé: ${astMatches.length} occurrences en ${(astDuration / 1000).toFixed(2)}s`);
|
|
213
|
+
// 2. Scanner avec Regex
|
|
214
|
+
core_1.logger.blank();
|
|
215
|
+
core_1.logger.step('Analyse Regex en cours...');
|
|
216
|
+
core_1.logger.blank();
|
|
217
|
+
const regexStartTime = Date.now();
|
|
218
|
+
const regexMatches = [];
|
|
219
|
+
// Scanner les règles de routine UNE SEULE FOIS à la racine du projet
|
|
220
|
+
if (routineRules.length > 0) {
|
|
221
|
+
const routineMatches = (0, scanner_regex_1.scanDirectory)(projectInfo.path, routineRules, projectInfo.path);
|
|
222
|
+
regexMatches.push(...routineMatches);
|
|
223
|
+
}
|
|
224
|
+
// Scanner les règles normales par target
|
|
225
|
+
for (const target of targets) {
|
|
226
|
+
const targetType = (0, target_resolver_1.getTargetType)(target.name, projectInfo);
|
|
227
|
+
const targetPath = (0, target_resolver_1.getTargetPath)(projectInfo, target.name);
|
|
228
|
+
(0, scan_reporter_1.logTargetStart)(target.name, targetType);
|
|
229
|
+
(0, scan_reporter_1.logScannerInfo)(0, regexRulesCount);
|
|
230
|
+
const startTime = Date.now();
|
|
231
|
+
const matches = (0, scanner_regex_1.scanDirectory)(targetPath, nonRoutineRules, projectInfo.path);
|
|
232
|
+
const duration = Date.now() - startTime;
|
|
233
|
+
regexMatches.push(...matches);
|
|
234
|
+
(0, scan_reporter_1.logTargetComplete)(target.name, matches, duration);
|
|
235
|
+
}
|
|
236
|
+
const regexDuration = Date.now() - regexStartTime;
|
|
237
|
+
core_1.logger.blank();
|
|
238
|
+
core_1.logger.success(`Regex terminé: ${regexMatches.length} occurrences en ${(regexDuration / 1000).toFixed(2)}s`);
|
|
239
|
+
// 3. Afficher les rapports détaillés par scanner
|
|
240
|
+
logScannerReport(astMatches, 'AST', rules);
|
|
241
|
+
logScannerReport(regexMatches, 'REGEX', rules);
|
|
242
|
+
// 4. Construire les résultats de scan
|
|
243
|
+
const astResult = {
|
|
244
|
+
matches: astMatches,
|
|
245
|
+
stats: astStats,
|
|
246
|
+
scannerType: 'ast',
|
|
247
|
+
duration: astDuration,
|
|
248
|
+
filesScanned: fileCount
|
|
249
|
+
};
|
|
250
|
+
const regexResult = {
|
|
251
|
+
matches: regexMatches,
|
|
252
|
+
stats: [],
|
|
253
|
+
scannerType: 'regex',
|
|
254
|
+
duration: regexDuration,
|
|
255
|
+
filesScanned: fileCount
|
|
256
|
+
};
|
|
257
|
+
// 5. Comparer les résultats
|
|
258
|
+
const comparison = (0, scanner_delta_1.compareScans)(astResult, regexResult);
|
|
259
|
+
// 6. Afficher le rapport de comparaison (delta)
|
|
260
|
+
(0, scanner_delta_1.logDeltaReport)(comparison);
|
|
261
|
+
// Utiliser les résultats AST par défaut
|
|
262
|
+
return {
|
|
263
|
+
matches: astMatches,
|
|
264
|
+
comparison
|
|
265
|
+
};
|
|
266
|
+
}
|