@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.
Files changed (122) hide show
  1. package/LICENSE +96 -0
  2. package/README.fr.md +359 -0
  3. package/README.md +360 -0
  4. package/SECURITY.md +187 -0
  5. package/dist/client.bundle.js +357 -0
  6. package/dist/src/core/app-analyzer.js +134 -0
  7. package/dist/src/core/ast/matchers/html/html-attribute-matcher.js +86 -0
  8. package/dist/src/core/ast/matchers/html/html-component-matcher.js +40 -0
  9. package/dist/src/core/ast/matchers/html/html-element-matcher.js +54 -0
  10. package/dist/src/core/ast/matchers/html/html-parser.js +58 -0
  11. package/dist/src/core/ast/matchers/html/html-pipe-matcher.js +95 -0
  12. package/dist/src/core/ast/matchers/html/html-text-matcher.js +53 -0
  13. package/dist/src/core/ast/matchers/html/index.js +118 -0
  14. package/dist/src/core/ast/matchers/index.js +377 -0
  15. package/dist/src/core/ast/matchers/ts/collection-matcher.js +51 -0
  16. package/dist/src/core/ast/matchers/ts/context-matcher.js +275 -0
  17. package/dist/src/core/ast/matchers/ts/decorator-matcher.js +465 -0
  18. package/dist/src/core/ast/matchers/ts/expression-matcher.js +237 -0
  19. package/dist/src/core/ast/matchers/ts/file-matcher.js +97 -0
  20. package/dist/src/core/ast/matchers/ts/hierarchy-matcher.js +172 -0
  21. package/dist/src/core/ast/matchers/ts/import-matcher.js +39 -0
  22. package/dist/src/core/ast/matchers/ts/index.js +53 -0
  23. package/dist/src/core/ast/matchers/ts/node-matcher.js +156 -0
  24. package/dist/src/core/ast/matchers/ts/symbol-matcher.js +281 -0
  25. package/dist/src/core/ast/matchers/ts/type-matcher.js +207 -0
  26. package/dist/src/core/ast/matchers/utils/matcher-helpers.js +37 -0
  27. package/dist/src/core/ast/scanner-ast.js +444 -0
  28. package/dist/src/core/project-detector.js +196 -0
  29. package/dist/src/core/project-strategy/index.js +9 -0
  30. package/dist/src/core/project-strategy/nx-strategy.js +130 -0
  31. package/dist/src/core/project-strategy/project-strategy.interface.js +2 -0
  32. package/dist/src/core/project-strategy/standalone-strategy.js +74 -0
  33. package/dist/src/core/project-strategy/strategy-factory.js +15 -0
  34. package/dist/src/core/rules-loader.js +89 -0
  35. package/dist/src/core/scan-reporter.js +316 -0
  36. package/dist/src/core/scanner-delta.js +339 -0
  37. package/dist/src/core/scanner-orchestrator.js +266 -0
  38. package/dist/src/core/scanner-regex.js +298 -0
  39. package/dist/src/core/workload/calculator.js +82 -0
  40. package/dist/src/core/workload/constants.js +15 -0
  41. package/dist/src/core/workload/grouping.js +18 -0
  42. package/dist/src/core/workload/hierarchy-calculator.js +127 -0
  43. package/dist/src/core/workload/index.js +11 -0
  44. package/dist/src/core/workload/metadata.js +20 -0
  45. package/dist/src/core/workload/special-workload.js +101 -0
  46. package/dist/src/core/workload/target-resolver.js +34 -0
  47. package/dist/src/data/angular-migration-rules.json +2337 -0
  48. package/dist/src/data/markdown/angular-migration-17-18.md +408 -0
  49. package/dist/src/data/markdown/angular-migration-18-19.md +600 -0
  50. package/dist/src/data/markdown/angular-migration-19-20.md +521 -0
  51. package/dist/src/data/rules/rearchitecture/rearchitecture-rules.json +66 -0
  52. package/dist/src/data/rules/to18/rules-18-obligatoire.json +374 -0
  53. package/dist/src/data/rules/to18/rules-18-optionnelle.json +188 -0
  54. package/dist/src/data/rules/to18/rules-18-recommande.json +218 -0
  55. package/dist/src/data/rules/to19/rules-19-obligatoire.json +348 -0
  56. package/dist/src/data/rules/to19/rules-19-optionnelle.json +223 -0
  57. package/dist/src/data/rules/to19/rules-19-recommande.json +200 -0
  58. package/dist/src/data/rules/to20/rules-20-obligatoire.json +556 -0
  59. package/dist/src/data/rules/to20/rules-20-optionnelle.json +190 -0
  60. package/dist/src/data/rules/to20/rules-20-recommande.json +151 -0
  61. package/dist/src/index.js +161 -0
  62. package/dist/src/models/chip-config.js +45 -0
  63. package/dist/src/models/interfaces/app-details.interface.js +2 -0
  64. package/dist/src/models/interfaces/ast-interfaces.js +5 -0
  65. package/dist/src/models/interfaces/ast-pattern.interface.js +2 -0
  66. package/dist/src/models/interfaces/client-interfaces.js +6 -0
  67. package/dist/src/models/interfaces/detection-stats.interface.js +2 -0
  68. package/dist/src/models/interfaces/html-match.interface.js +2 -0
  69. package/dist/src/models/interfaces/html-report-data.interface.js +2 -0
  70. package/dist/src/models/interfaces/lib-details.interface.js +2 -0
  71. package/dist/src/models/interfaces/migration-rules.interface.js +2 -0
  72. package/dist/src/models/interfaces/parsed-args.interface.js +2 -0
  73. package/dist/src/models/interfaces/project-info.interface.js +2 -0
  74. package/dist/src/models/interfaces/project-overview-data.interface.js +2 -0
  75. package/dist/src/models/interfaces/rule-match.interface.js +2 -0
  76. package/dist/src/models/interfaces/rule.interface.js +2 -0
  77. package/dist/src/models/interfaces/rules-by-priority.interface.js +2 -0
  78. package/dist/src/models/interfaces/scanner-comparison.interface.js +2 -0
  79. package/dist/src/models/interfaces/special-workload.interface.js +2 -0
  80. package/dist/src/models/interfaces/workload-report.interface.js +2 -0
  81. package/dist/src/models/types/build-block-blob.type.js +2 -0
  82. package/dist/src/models/types/migration-version.type.js +2 -0
  83. package/dist/src/models/types/project-type.type.js +2 -0
  84. package/dist/src/models/types/risk-level.type.js +2 -0
  85. package/dist/src/models/types/rule-category.type.js +2 -0
  86. package/dist/src/models/types/rule-priority.type.js +2 -0
  87. package/dist/src/models/types/rule-workload-type.type.js +2 -0
  88. package/dist/src/templates/landing/applications-analyzed.template.js +18 -0
  89. package/dist/src/templates/landing/card-app-info.template.js +63 -0
  90. package/dist/src/templates/landing/card-lib-info.template.js +67 -0
  91. package/dist/src/templates/landing/libs-analyzed.template.js +22 -0
  92. package/dist/src/templates/landing/nx-summary.template.js +115 -0
  93. package/dist/src/templates/landing/project-overview.template.js +27 -0
  94. package/dist/src/templates/page/index-page.template.js +95 -0
  95. package/dist/src/templates/page/main.template.js +83 -0
  96. package/dist/src/templates/page/migration-guide.template.js +175 -0
  97. package/dist/src/templates/page/workload-report.template.js +53 -0
  98. package/dist/src/templates/workload/dashboard.template.js +184 -0
  99. package/dist/src/templates/workload/filters-panel.template.js +215 -0
  100. package/dist/src/templates/workload/guide-rule-card.template.js +107 -0
  101. package/dist/src/templates/workload/hierarchy-nx.template.js +104 -0
  102. package/dist/src/templates/workload/hierarchy-shared.js +163 -0
  103. package/dist/src/templates/workload/hierarchy-standalone.template.js +36 -0
  104. package/dist/src/templates/workload/hierarchy.template.js +35 -0
  105. package/dist/src/templates/workload/rule-modal.template.js +280 -0
  106. package/dist/src/utils/core/args-parser.js +123 -0
  107. package/dist/src/utils/core/array-helpers.js +18 -0
  108. package/dist/src/utils/core/ast-helpers.js +99 -0
  109. package/dist/src/utils/core/file-helpers.js +109 -0
  110. package/dist/src/utils/core/html-helpers.js +36 -0
  111. package/dist/src/utils/core/index.js +28 -0
  112. package/dist/src/utils/core/logger.js +38 -0
  113. package/dist/src/utils/core/rule-helpers.js +15 -0
  114. package/dist/src/utils/core/workload-formatter.js +6 -0
  115. package/dist/src/utils/shared/array-helpers.js +25 -0
  116. package/dist/src/utils/shared/date-helpers.js +109 -0
  117. package/dist/src/utils/shared/html-helpers.js +37 -0
  118. package/dist/src/utils/shared/index.js +25 -0
  119. package/dist/src/utils/shared/rule-helpers.js +20 -0
  120. package/dist/src/utils/shared/time-formatters.js +76 -0
  121. package/dist/styles.css +2 -0
  122. 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
+ }