@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,444 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.scanDirectoryWithAST = scanDirectoryWithAST;
|
|
37
|
+
exports.logDetectionStats = logDetectionStats;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const ts_morph_1 = require("ts-morph");
|
|
41
|
+
const file_helpers_1 = require("../../utils/core/file-helpers");
|
|
42
|
+
const logger_1 = require("../../utils/core/logger");
|
|
43
|
+
const matchers_1 = require("./matchers");
|
|
44
|
+
const context_matcher_1 = require("./matchers/ts/context-matcher");
|
|
45
|
+
const scanner_regex_1 = require("../scanner-regex");
|
|
46
|
+
const scan_reporter_1 = require("../scan-reporter");
|
|
47
|
+
/**
|
|
48
|
+
* Groupe les règles AST par SyntaxKind pour optimisation
|
|
49
|
+
* Permet de ne tester que les règles pertinentes pour chaque type de nœud
|
|
50
|
+
*/
|
|
51
|
+
function groupRulesByNodeType(rules) {
|
|
52
|
+
const grouped = new Map();
|
|
53
|
+
for (const rule of rules) {
|
|
54
|
+
if (!rule.astPattern)
|
|
55
|
+
continue;
|
|
56
|
+
const nodeTypes = Array.isArray(rule.astPattern.nodeType)
|
|
57
|
+
? rule.astPattern.nodeType
|
|
58
|
+
: [rule.astPattern.nodeType];
|
|
59
|
+
for (const typeName of nodeTypes) {
|
|
60
|
+
if (!typeName)
|
|
61
|
+
continue;
|
|
62
|
+
const kind = ts_morph_1.SyntaxKind[typeName];
|
|
63
|
+
if (kind !== undefined) {
|
|
64
|
+
if (!grouped.has(kind)) {
|
|
65
|
+
grouped.set(kind, []);
|
|
66
|
+
}
|
|
67
|
+
grouped.get(kind).push(rule);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
logger_1.logger.debug(`Règles groupées en ${grouped.size} types de nœuds`);
|
|
72
|
+
return grouped;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Scan un batch de fichiers avec les règles groupées par nodeType
|
|
76
|
+
* OPTIMISÉ : stratégie hybride selon nombre de types
|
|
77
|
+
*/
|
|
78
|
+
function scanBatch(project, rulesByNodeType, projectRoot, matches) {
|
|
79
|
+
const kindCount = rulesByNodeType.size;
|
|
80
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
81
|
+
const filePath = sourceFile.getFilePath();
|
|
82
|
+
const relativeFilePath = path.relative(projectRoot, filePath);
|
|
83
|
+
// Stratégie adaptative selon nombre de types
|
|
84
|
+
if (kindCount <= 10) {
|
|
85
|
+
// Peu de types : getDescendantsOfKind() est optimal
|
|
86
|
+
scanWithGetDescendants(sourceFile, relativeFilePath, rulesByNodeType, matches);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Beaucoup de types : un seul parcours est plus rapide
|
|
90
|
+
scanWithSingleTraversal(sourceFile, relativeFilePath, rulesByNodeType, matches);
|
|
91
|
+
}
|
|
92
|
+
// IMPORTANT: Libérer mémoire des nœuds
|
|
93
|
+
sourceFile.forgetDescendants();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Scan avec getDescendantsOfKind() pour chaque type (optimal si peu de types)
|
|
98
|
+
*/
|
|
99
|
+
function scanWithGetDescendants(sourceFile, relativeFilePath, rulesByNodeType, matches) {
|
|
100
|
+
for (const [kind, rulesForKind] of rulesByNodeType) {
|
|
101
|
+
const nodes = sourceFile.getDescendantsOfKind(kind);
|
|
102
|
+
for (const node of nodes) {
|
|
103
|
+
processNodeWithRules(node, rulesForKind, sourceFile, relativeFilePath, matches);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Scan avec un seul forEachDescendant() et dispatch (optimal si beaucoup de types)
|
|
109
|
+
*/
|
|
110
|
+
function scanWithSingleTraversal(sourceFile, relativeFilePath, rulesByNodeType, matches) {
|
|
111
|
+
sourceFile.forEachDescendant((node) => {
|
|
112
|
+
const kind = node.getKind();
|
|
113
|
+
const rulesForKind = rulesByNodeType.get(kind);
|
|
114
|
+
if (rulesForKind) {
|
|
115
|
+
processNodeWithRules(node, rulesForKind, sourceFile, relativeFilePath, matches);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Traite un nœud avec les règles associées (logique commune)
|
|
121
|
+
*/
|
|
122
|
+
function processNodeWithRules(node, rules, sourceFile, relativeFilePath, matches) {
|
|
123
|
+
for (const rule of rules) {
|
|
124
|
+
// Vérifier inFile filter (early exit)
|
|
125
|
+
if (rule.astPattern.inFile &&
|
|
126
|
+
!matchers_1.FileMatcher.matchesInFilePattern(relativeFilePath, rule.astPattern.inFile)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if ((0, matchers_1.matchesAstPattern)(node, rule.astPattern)) {
|
|
130
|
+
// Vérifier testSetup si présent
|
|
131
|
+
if (rule.astPattern.testSetup &&
|
|
132
|
+
!matchers_1.FileMatcher.matchesTestSetup(sourceFile, rule.astPattern.testSetup)) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
// Optimisation getText() : vérifier width d'abord
|
|
136
|
+
const width = node.getWidth();
|
|
137
|
+
const matchedText = width > 100
|
|
138
|
+
? node.getText().substring(0, 100).trim()
|
|
139
|
+
: node.getText().trim();
|
|
140
|
+
matches.push({
|
|
141
|
+
ruleKey: rule.key,
|
|
142
|
+
ruleSummary: rule.summary,
|
|
143
|
+
filePath: relativeFilePath,
|
|
144
|
+
lineNumber: node.getStartLineNumber(),
|
|
145
|
+
matchedText
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Scan un répertoire avec analyse AST TypeScript
|
|
152
|
+
* @param targetDir Répertoire à scanner (app ou lib)
|
|
153
|
+
* @param rules Règles avec astPattern défini
|
|
154
|
+
* @param projectRoot Racine du projet
|
|
155
|
+
* @returns Liste des occurrences trouvées et statistiques
|
|
156
|
+
*/
|
|
157
|
+
function scanDirectoryWithAST(targetDir, rules, projectRoot) {
|
|
158
|
+
const matches = [];
|
|
159
|
+
const detectionMap = new Map();
|
|
160
|
+
// Séparer règles AST vs regex
|
|
161
|
+
// Note: regexRules inclut TOUTES les règles non-AST, y compris celles avec onFile (qui ont regex: null)
|
|
162
|
+
const astRules = rules.filter(r => r.astPattern);
|
|
163
|
+
const regexRules = rules.filter(r => !r.astPattern);
|
|
164
|
+
// Fallback vers scanner regex si pas d'AST
|
|
165
|
+
if (astRules.length === 0) {
|
|
166
|
+
logger_1.logger.debug('Aucune règle AST, utilisation du scanner regex');
|
|
167
|
+
const regexMatches = (0, scanner_regex_1.scanDirectory)(targetDir, regexRules, projectRoot);
|
|
168
|
+
// Tracker les stats regex
|
|
169
|
+
for (const match of regexMatches) {
|
|
170
|
+
const existing = detectionMap.get(match.ruleKey);
|
|
171
|
+
if (existing) {
|
|
172
|
+
existing.count++;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
detectionMap.set(match.ruleKey, { method: 'regex', count: 1 });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const stats = Array.from(detectionMap.entries()).map(([ruleKey, data]) => ({
|
|
179
|
+
ruleKey,
|
|
180
|
+
method: data.method,
|
|
181
|
+
occurrences: data.count
|
|
182
|
+
}));
|
|
183
|
+
return { matches: regexMatches, stats };
|
|
184
|
+
}
|
|
185
|
+
logger_1.logger.info(`Scanner: ${astRules.length} règles AST, ${regexRules.length} règles Regex (fallback)`);
|
|
186
|
+
// Grouper les règles par nodeType UNE SEULE FOIS
|
|
187
|
+
const rulesByNodeType = groupRulesByNodeType(astRules);
|
|
188
|
+
// Créer un projet ts-morph
|
|
189
|
+
const project = createTsMorphProject();
|
|
190
|
+
// Collecter tous les fichiers .ts récursivement
|
|
191
|
+
const tsFiles = (0, file_helpers_1.collectFiles)(targetDir, /\.ts$/);
|
|
192
|
+
const totalFiles = tsFiles.length;
|
|
193
|
+
(0, scan_reporter_1.logFilesDetected)('TypeScript', totalFiles);
|
|
194
|
+
// Charger TOUS les fichiers dans le projet (pas de batch)
|
|
195
|
+
// Permet la résolution cross-file (referTo, getType(), findReferences())
|
|
196
|
+
logger_1.logger.info('Chargement de tous les fichiers TypeScript...');
|
|
197
|
+
addFilesToProject(project, tsFiles);
|
|
198
|
+
logger_1.logger.success(`${totalFiles} fichiers chargés en mémoire`);
|
|
199
|
+
// Scanner tous les fichiers en une seule passe
|
|
200
|
+
logger_1.logger.info('Analyse AST en cours...');
|
|
201
|
+
scanBatch(project, rulesByNodeType, projectRoot, matches);
|
|
202
|
+
logger_1.logger.success('Analyse AST terminée');
|
|
203
|
+
// Libérer la mémoire À LA FIN uniquement
|
|
204
|
+
logger_1.logger.debug('Libération mémoire...');
|
|
205
|
+
const sourceFiles = project.getSourceFiles();
|
|
206
|
+
for (const sourceFile of sourceFiles) {
|
|
207
|
+
project.removeSourceFile(sourceFile);
|
|
208
|
+
}
|
|
209
|
+
// Vider les caches
|
|
210
|
+
(0, context_matcher_1.clearContextCaches)();
|
|
211
|
+
(0, matchers_1.clearTemplateCache)();
|
|
212
|
+
// Scanner les fichiers HTML pour les patterns HTML
|
|
213
|
+
const htmlFiles = (0, file_helpers_1.collectFiles)(targetDir, /\.html$/);
|
|
214
|
+
if (htmlFiles.length > 0) {
|
|
215
|
+
(0, scan_reporter_1.logFilesDetected)('HTML', htmlFiles.length);
|
|
216
|
+
}
|
|
217
|
+
scanHtmlFiles(targetDir, astRules, projectRoot, matches);
|
|
218
|
+
// Tracker les stats AST
|
|
219
|
+
for (const match of matches) {
|
|
220
|
+
const existing = detectionMap.get(match.ruleKey);
|
|
221
|
+
if (existing) {
|
|
222
|
+
existing.count++;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
detectionMap.set(match.ruleKey, { method: 'ast', count: 1 });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const astMatchCount = matches.length;
|
|
229
|
+
logger_1.logger.success(`AST: ${astMatchCount} occurrences`);
|
|
230
|
+
// Fallback: Scanner les règles sans astPattern avec Regex
|
|
231
|
+
if (regexRules.length > 0) {
|
|
232
|
+
const regexMatches = (0, scanner_regex_1.scanDirectory)(targetDir, regexRules, projectRoot);
|
|
233
|
+
// Tracker les stats Regex
|
|
234
|
+
for (const match of regexMatches) {
|
|
235
|
+
const existing = detectionMap.get(match.ruleKey);
|
|
236
|
+
if (existing) {
|
|
237
|
+
existing.count++;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
detectionMap.set(match.ruleKey, { method: 'regex', count: 1 });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
matches.push(...regexMatches);
|
|
244
|
+
logger_1.logger.success(`Regex (fallback): ${regexMatches.length} occurrences`);
|
|
245
|
+
}
|
|
246
|
+
// Convertir la map en array de stats
|
|
247
|
+
const stats = Array.from(detectionMap.entries()).map(([ruleKey, data]) => ({
|
|
248
|
+
ruleKey,
|
|
249
|
+
method: data.method,
|
|
250
|
+
occurrences: data.count
|
|
251
|
+
}));
|
|
252
|
+
return { matches, stats };
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Affiche un rapport détaillé des statistiques de détection
|
|
256
|
+
*/
|
|
257
|
+
function logDetectionStats(stats) {
|
|
258
|
+
logger_1.logger.blank();
|
|
259
|
+
logger_1.logger.info('═══════════════════════════════════════════════════════════');
|
|
260
|
+
logger_1.logger.info(' RAPPORT DE DÉTECTION PAR MÉTHODE');
|
|
261
|
+
logger_1.logger.info('═══════════════════════════════════════════════════════════');
|
|
262
|
+
logger_1.logger.blank();
|
|
263
|
+
// Séparer AST et Regex
|
|
264
|
+
const astStats = stats.filter(s => s.method === 'ast').sort((a, b) => b.occurrences - a.occurrences);
|
|
265
|
+
const regexStats = stats.filter(s => s.method === 'regex').sort((a, b) => b.occurrences - a.occurrences);
|
|
266
|
+
const totalAstOccurrences = astStats.reduce((sum, s) => sum + s.occurrences, 0);
|
|
267
|
+
const totalRegexOccurrences = regexStats.reduce((sum, s) => sum + s.occurrences, 0);
|
|
268
|
+
const totalOccurrences = totalAstOccurrences + totalRegexOccurrences;
|
|
269
|
+
// Résumé global
|
|
270
|
+
logger_1.logger.info(`Total occurrences: ${totalOccurrences}`);
|
|
271
|
+
logger_1.logger.info(` • AST: ${totalAstOccurrences} (${getPercentage(totalAstOccurrences, totalOccurrences)}%)`);
|
|
272
|
+
logger_1.logger.info(` • Regex: ${totalRegexOccurrences} (${getPercentage(totalRegexOccurrences, totalOccurrences)}%)`);
|
|
273
|
+
logger_1.logger.blank();
|
|
274
|
+
logger_1.logger.info(`Règles actives: ${stats.length}`);
|
|
275
|
+
logger_1.logger.info(` • Avec AST: ${astStats.length}`);
|
|
276
|
+
logger_1.logger.info(` • Regex fallback: ${regexStats.length}`);
|
|
277
|
+
logger_1.logger.blank();
|
|
278
|
+
// Détail règles AST
|
|
279
|
+
if (astStats.length > 0) {
|
|
280
|
+
logger_1.logger.info('Règles détectées par AST:');
|
|
281
|
+
for (const stat of astStats) {
|
|
282
|
+
logger_1.logger.debug(` ✓ ${stat.ruleKey}: ${stat.occurrences} occurrences`);
|
|
283
|
+
}
|
|
284
|
+
logger_1.logger.blank();
|
|
285
|
+
}
|
|
286
|
+
// Détail règles Regex (fallback)
|
|
287
|
+
if (regexStats.length > 0) {
|
|
288
|
+
logger_1.logger.info('Règles détectées par Regex (fallback):');
|
|
289
|
+
for (const stat of regexStats) {
|
|
290
|
+
logger_1.logger.debug(` ⚠ ${stat.ruleKey}: ${stat.occurrences} occurrences`);
|
|
291
|
+
}
|
|
292
|
+
logger_1.logger.blank();
|
|
293
|
+
}
|
|
294
|
+
logger_1.logger.info('═══════════════════════════════════════════════════════════');
|
|
295
|
+
logger_1.logger.blank();
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Calcule un pourcentage arrondi
|
|
299
|
+
*/
|
|
300
|
+
function getPercentage(value, total) {
|
|
301
|
+
if (total === 0)
|
|
302
|
+
return '0.0';
|
|
303
|
+
return ((value / total) * 100).toFixed(1);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Scan les fichiers HTML avec les patterns HTML (Attribute, PipeExpression, Element, etc.)
|
|
307
|
+
* Scanne à la fois :
|
|
308
|
+
* - Les fichiers .html externes (95% des cas en Angular)
|
|
309
|
+
* - Les templates inline dans les .ts (5% des cas, pour patterns standalone)
|
|
310
|
+
*
|
|
311
|
+
* Note: Les patterns Decorator avec template/templateUrl sont déjà gérés
|
|
312
|
+
* par le DecoratorMatcher lors du scan TypeScript principal.
|
|
313
|
+
*/
|
|
314
|
+
function scanHtmlFiles(targetDir, rules, projectRoot, matches) {
|
|
315
|
+
// Filtrer uniquement les règles HTML
|
|
316
|
+
const htmlRules = rules.filter(r => r.astPattern && matchers_1.HtmlMatcher.isHtmlPattern(r.astPattern));
|
|
317
|
+
if (htmlRules.length === 0) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
// 1. Scanner les fichiers .html externes (cas le plus courant)
|
|
321
|
+
const htmlFiles = (0, file_helpers_1.collectFiles)(targetDir, /\.html$/);
|
|
322
|
+
for (const htmlFile of htmlFiles) {
|
|
323
|
+
const relativeFilePath = path.relative(projectRoot, htmlFile);
|
|
324
|
+
try {
|
|
325
|
+
const htmlContent = fs.readFileSync(htmlFile, 'utf-8');
|
|
326
|
+
for (const rule of htmlRules) {
|
|
327
|
+
const htmlMatches = matchers_1.HtmlMatcher.findHtmlPatternMatches(htmlContent, rule.astPattern);
|
|
328
|
+
for (const match of htmlMatches) {
|
|
329
|
+
matches.push({
|
|
330
|
+
ruleKey: rule.key,
|
|
331
|
+
ruleSummary: rule.summary,
|
|
332
|
+
filePath: relativeFilePath,
|
|
333
|
+
lineNumber: match.lineNumber,
|
|
334
|
+
matchedText: match.matchedText
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
logger_1.logger.warning(`Impossible de lire le fichier HTML: ${htmlFile}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// 2. Scanner les templates inline dans les fichiers .ts (moins courant mais nécessaire)
|
|
344
|
+
scanInlineTemplates(targetDir, htmlRules, projectRoot, matches);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Scan les templates inline dans les fichiers TypeScript
|
|
348
|
+
* Détecte les patterns HTML standalone dans les templates définis avec template: `...`
|
|
349
|
+
*
|
|
350
|
+
* Note: Les patterns Decorator (avec contexte @Component) sont déjà gérés par DecoratorMatcher.
|
|
351
|
+
* Cette fonction gère uniquement les patterns HTML sans contexte Decorator (Element, Attribute, etc.)
|
|
352
|
+
*/
|
|
353
|
+
function scanInlineTemplates(targetDir, htmlRules, projectRoot, matches) {
|
|
354
|
+
const tsFiles = (0, file_helpers_1.collectFiles)(targetDir, /\.ts$/);
|
|
355
|
+
for (const tsFile of tsFiles) {
|
|
356
|
+
try {
|
|
357
|
+
const fileContent = fs.readFileSync(tsFile, 'utf-8');
|
|
358
|
+
const relativeFilePath = path.relative(projectRoot, tsFile);
|
|
359
|
+
// Regex pour extraire templates inline (supporte backticks simples)
|
|
360
|
+
// Capture: template: `...` avec support multiline
|
|
361
|
+
const templateRegex = /template\s*:\s*`([^`]*)`/gs;
|
|
362
|
+
let match;
|
|
363
|
+
while ((match = templateRegex.exec(fileContent)) !== null) {
|
|
364
|
+
const htmlContent = match[1];
|
|
365
|
+
const templateStartIndex = match.index + match[0].indexOf(match[1]);
|
|
366
|
+
// Calculer la ligne de début du template dans le fichier .ts
|
|
367
|
+
const linesBefore = fileContent.substring(0, templateStartIndex).split('\n').length;
|
|
368
|
+
for (const rule of htmlRules) {
|
|
369
|
+
const htmlMatches = matchers_1.HtmlMatcher.findHtmlPatternMatches(htmlContent, rule.astPattern);
|
|
370
|
+
for (const htmlMatch of htmlMatches) {
|
|
371
|
+
// Ajuster le numéro de ligne pour pointer vers le fichier .ts
|
|
372
|
+
const actualLineNumber = linesBefore + htmlMatch.lineNumber - 1;
|
|
373
|
+
matches.push({
|
|
374
|
+
ruleKey: rule.key,
|
|
375
|
+
ruleSummary: rule.summary,
|
|
376
|
+
filePath: relativeFilePath,
|
|
377
|
+
lineNumber: actualLineNumber,
|
|
378
|
+
matchedText: htmlMatch.matchedText
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
// Silencieux - pas critique si on ne peut pas lire un fichier .ts
|
|
386
|
+
// (le scan TypeScript principal l'aurait déjà signalé si vraiment problématique)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Crée un projet ts-morph avec configuration optimale
|
|
392
|
+
*/
|
|
393
|
+
function createTsMorphProject() {
|
|
394
|
+
return new ts_morph_1.Project({
|
|
395
|
+
skipAddingFilesFromTsConfig: true,
|
|
396
|
+
compilerOptions: {
|
|
397
|
+
target: 99, // ESNext
|
|
398
|
+
module: 99, // ESNext
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Ajoute les fichiers au projet ts-morph avec gestion d'erreurs
|
|
404
|
+
*/
|
|
405
|
+
function addFilesToProject(project, tsFiles) {
|
|
406
|
+
for (const filePath of tsFiles) {
|
|
407
|
+
try {
|
|
408
|
+
project.addSourceFileAtPath(filePath);
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
logger_1.logger.warning(`Impossible d'analyser: ${filePath}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Trouve les matches d'un pattern AST dans un fichier
|
|
417
|
+
*/
|
|
418
|
+
function findAstPatternMatches(sourceFile, rule, relativeFilePath) {
|
|
419
|
+
const matches = [];
|
|
420
|
+
const pattern = rule.astPattern;
|
|
421
|
+
// Vérifier le filtre inFile (filtrage par nom de fichier)
|
|
422
|
+
if (pattern.inFile && !matchers_1.FileMatcher.matchesInFilePattern(relativeFilePath, pattern.inFile)) {
|
|
423
|
+
return matches;
|
|
424
|
+
}
|
|
425
|
+
// Parcourir tous les nœuds de l'AST
|
|
426
|
+
sourceFile.forEachDescendant((node) => {
|
|
427
|
+
if ((0, matchers_1.matchesAstPattern)(node, pattern)) {
|
|
428
|
+
// Vérifier testSetup si présent dans le pattern
|
|
429
|
+
if (pattern.testSetup && !matchers_1.FileMatcher.matchesTestSetup(sourceFile, pattern.testSetup)) {
|
|
430
|
+
return; // Skip ce nœud si testSetup ne correspond pas
|
|
431
|
+
}
|
|
432
|
+
const lineNumber = node.getStartLineNumber();
|
|
433
|
+
const matchedText = node.getText().substring(0, 100); // Limiter à 100 caractères
|
|
434
|
+
matches.push({
|
|
435
|
+
ruleKey: rule.key,
|
|
436
|
+
ruleSummary: rule.summary,
|
|
437
|
+
filePath: relativeFilePath,
|
|
438
|
+
lineNumber,
|
|
439
|
+
matchedText: matchedText.trim()
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
return matches;
|
|
444
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.detectProject = detectProject;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const app_analyzer_1 = require("./app-analyzer");
|
|
40
|
+
/**
|
|
41
|
+
* Détecte le type de projet Angular (Nx Monorepo ou Standalone)
|
|
42
|
+
*/
|
|
43
|
+
function detectProject(projectPath) {
|
|
44
|
+
const nxJsonPath = path.join(projectPath, 'nx.json');
|
|
45
|
+
const angularJsonPath = path.join(projectPath, 'angular.json');
|
|
46
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
47
|
+
const appsDir = path.join(projectPath, 'apps');
|
|
48
|
+
const libsDir = path.join(projectPath, 'libs');
|
|
49
|
+
if (!fs.existsSync(projectPath)) {
|
|
50
|
+
throw new Error(`Le chemin du projet n'existe pas: ${projectPath}`);
|
|
51
|
+
}
|
|
52
|
+
const hasNxJson = fs.existsSync(nxJsonPath);
|
|
53
|
+
const hasAngularJson = fs.existsSync(angularJsonPath);
|
|
54
|
+
const hasPackageJson = fs.existsSync(packageJsonPath);
|
|
55
|
+
const hasAppsDir = fs.existsSync(appsDir);
|
|
56
|
+
const hasLibsDir = fs.existsSync(libsDir);
|
|
57
|
+
let type = 'unknown';
|
|
58
|
+
let name = path.basename(projectPath);
|
|
59
|
+
let angularVersion;
|
|
60
|
+
let appNames = [];
|
|
61
|
+
let libNames = [];
|
|
62
|
+
if (hasNxJson && hasAppsDir) {
|
|
63
|
+
type = 'nx-monorepo';
|
|
64
|
+
appNames = extractNxApps(projectPath);
|
|
65
|
+
if (hasLibsDir) {
|
|
66
|
+
libNames = extractNxLibs(projectPath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (hasAngularJson) {
|
|
70
|
+
type = 'angular-standalone';
|
|
71
|
+
appNames = extractAngularApps(angularJsonPath);
|
|
72
|
+
}
|
|
73
|
+
if (hasPackageJson) {
|
|
74
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
75
|
+
name = packageJson.name || name;
|
|
76
|
+
angularVersion = extractAngularVersion(packageJson);
|
|
77
|
+
}
|
|
78
|
+
const isNxMonorepo = type === 'nx-monorepo';
|
|
79
|
+
const apps = appNames.map((appName) => (0, app_analyzer_1.analyzeApp)(appName, projectPath, isNxMonorepo));
|
|
80
|
+
const libs = isNxMonorepo
|
|
81
|
+
? libNames.map((libName) => (0, app_analyzer_1.analyzeLib)(libName, projectPath))
|
|
82
|
+
: [];
|
|
83
|
+
return {
|
|
84
|
+
path: projectPath,
|
|
85
|
+
type,
|
|
86
|
+
name,
|
|
87
|
+
angularVersion,
|
|
88
|
+
apps,
|
|
89
|
+
libs
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function extractNxApps(projectPath) {
|
|
93
|
+
const appsDir = path.join(projectPath, 'apps');
|
|
94
|
+
if (!fs.existsSync(appsDir)) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const entries = fs.readdirSync(appsDir, { withFileTypes: true });
|
|
99
|
+
return entries
|
|
100
|
+
.filter((entry) => entry.isDirectory())
|
|
101
|
+
.filter((dir) => {
|
|
102
|
+
const projectJsonPath = path.join(appsDir, dir.name, 'project.json');
|
|
103
|
+
return fs.existsSync(projectJsonPath);
|
|
104
|
+
})
|
|
105
|
+
.map((dir) => dir.name);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function extractAngularApps(angularJsonPath) {
|
|
112
|
+
if (!fs.existsSync(angularJsonPath)) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf-8'));
|
|
117
|
+
return Object.keys(angularJson.projects || {}).filter((projectName) => {
|
|
118
|
+
const project = angularJson.projects[projectName];
|
|
119
|
+
return project.projectType === 'application';
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function extractNxLibs(projectPath) {
|
|
127
|
+
const libsDir = path.join(projectPath, 'libs');
|
|
128
|
+
if (!fs.existsSync(libsDir)) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const libs = [];
|
|
133
|
+
// Fonction récursive pour scanner les libs (support structure groupée)
|
|
134
|
+
function scanLibsRecursive(dir, relativePath = '') {
|
|
135
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
if (!entry.isDirectory())
|
|
138
|
+
continue;
|
|
139
|
+
const fullPath = path.join(dir, entry.name);
|
|
140
|
+
const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
141
|
+
if (isValidNxLib(fullPath)) {
|
|
142
|
+
// C'est une lib valide Nx
|
|
143
|
+
libs.push(newRelativePath);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// C'est un dossier de groupe, scanner récursivement
|
|
147
|
+
scanLibsRecursive(fullPath, newRelativePath);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
scanLibsRecursive(libsDir);
|
|
152
|
+
return libs;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Vérifie si un dossier est une lib Nx valide
|
|
160
|
+
* Critères obligatoires :
|
|
161
|
+
* 1. project.json (configuration Nx)
|
|
162
|
+
* 2. index.ts (racine OU src/) OU src/lib/ (code de la lib)
|
|
163
|
+
* 3. tsconfig.json ou tsconfig.lib.json (configuration TypeScript)
|
|
164
|
+
*/
|
|
165
|
+
function isValidNxLib(libPath) {
|
|
166
|
+
// 1. project.json obligatoire
|
|
167
|
+
const projectJsonPath = path.join(libPath, 'project.json');
|
|
168
|
+
if (!fs.existsSync(projectJsonPath)) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
// 2. Vérifier présence de code source
|
|
172
|
+
// Option A : index.ts à la racine (ex: libs/apis/deployment-tool/index.ts)
|
|
173
|
+
const hasRootIndexTs = fs.existsSync(path.join(libPath, 'index.ts'));
|
|
174
|
+
// Option B : src/index.ts (structure standard)
|
|
175
|
+
const hasSrcIndexTs = fs.existsSync(path.join(libPath, 'src', 'index.ts'));
|
|
176
|
+
// Option C : src/lib/ (barrel export dans lib/)
|
|
177
|
+
const hasSrcLibDir = fs.existsSync(path.join(libPath, 'src', 'lib'));
|
|
178
|
+
if (!hasRootIndexTs && !hasSrcIndexTs && !hasSrcLibDir) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
function extractAngularVersion(packageJson) {
|
|
184
|
+
if (typeof packageJson !== 'object' ||
|
|
185
|
+
packageJson === null ||
|
|
186
|
+
!('dependencies' in packageJson)) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
const deps = packageJson.dependencies;
|
|
190
|
+
const angularCore = deps['@angular/core'];
|
|
191
|
+
if (!angularCore) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
const versionMatch = angularCore.match(/(\d+)\./);
|
|
195
|
+
return versionMatch ? versionMatch[1] : undefined;
|
|
196
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createProjectStrategy = exports.StandaloneStrategy = exports.NxStrategy = void 0;
|
|
4
|
+
var nx_strategy_1 = require("./nx-strategy");
|
|
5
|
+
Object.defineProperty(exports, "NxStrategy", { enumerable: true, get: function () { return nx_strategy_1.NxStrategy; } });
|
|
6
|
+
var standalone_strategy_1 = require("./standalone-strategy");
|
|
7
|
+
Object.defineProperty(exports, "StandaloneStrategy", { enumerable: true, get: function () { return standalone_strategy_1.StandaloneStrategy; } });
|
|
8
|
+
var strategy_factory_1 = require("./strategy-factory");
|
|
9
|
+
Object.defineProperty(exports, "createProjectStrategy", { enumerable: true, get: function () { return strategy_factory_1.createProjectStrategy; } });
|