@silvestv/migration-planificator 3.0.0 → 4.1.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 +201 -96
- package/NOTICE +105 -0
- package/README.fr.md +30 -24
- package/README.md +29 -25
- package/SECURITY.md +223 -187
- package/dist/client.bundle.js +51 -48
- package/dist/src/core/ast/matchers/html/html-pipe-variable-matcher.js +250 -0
- package/dist/src/core/ast/matchers/html/html-variable-resolver.js +105 -0
- package/dist/src/core/ast/matchers/index.js +7 -0
- package/dist/src/core/ast/matchers/ts/hierarchy-matcher.js +2 -1
- package/dist/src/core/ast/matchers/ts/rxjs-matcher.js +128 -0
- package/dist/src/core/workload/hierarchy-calculator.js +4 -4
- package/dist/src/core/workload/special-workload.js +2 -2
- package/dist/src/data/angular-migration-rules.json +2336 -2336
- package/dist/src/data/rules/rearchitecture/rearchitecture-rules.json +2 -2
- package/dist/src/data/rules/to18/rules-18-obligatoire.json +373 -373
- package/dist/src/data/rules/to18/rules-18-optionnelle.json +187 -187
- package/dist/src/data/rules/to18/rules-18-recommande.json +217 -217
- package/dist/src/data/rules/to19/rules-19-obligatoire.json +347 -347
- package/dist/src/data/rules/to19/rules-19-optionnelle.json +217 -222
- package/dist/src/data/rules/to19/rules-19-recommande.json +174 -199
- package/dist/src/data/rules/to20/rules-20-obligatoire.json +558 -555
- package/dist/src/data/rules/to20/rules-20-optionnelle.json +189 -189
- package/dist/src/data/rules/to20/rules-20-recommande.json +150 -150
- package/dist/src/models/interfaces/{ast-interfaces.js → angular-artifact-counts.interface.js} +0 -3
- package/dist/src/models/interfaces/base-target.interface.js +2 -0
- package/dist/src/models/interfaces/checklist-state.interface.js +2 -0
- package/dist/src/models/interfaces/dependency-counts.interface.js +2 -0
- package/dist/src/models/interfaces/migration-guide-data.interface.js +2 -0
- package/dist/src/models/interfaces/module-info.interface.js +2 -0
- package/dist/src/models/interfaces/parallelization.interface.js +2 -0
- package/dist/src/models/interfaces/refer-to-pattern.interface.js +2 -0
- package/dist/src/models/interfaces/summary-totals.interface.js +2 -0
- package/dist/src/models/interfaces/window-with-data.interface.js +2 -0
- package/dist/src/templates/workload/dashboard.template.js +7 -44
- package/dist/src/templates/workload/guide-rule-card.template.js +1 -1
- package/dist/src/templates/workload/rule-modal.template.js +6 -21
- package/dist/src/templates/workload/unified-settings-panel.template.js +149 -0
- package/dist/src/utils/core/args-parser.js +16 -1
- package/dist/styles.css +1 -1
- package/package.json +8 -7
- /package/dist/src/models/{chip-config.js → constants/chip-config.js} +0 -0
- /package/dist/src/models/interfaces/{client-interfaces.js → client-interfaces.interface.js} +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
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.matchesHtmlPipeVariable = matchesHtmlPipeVariable;
|
|
37
|
+
const ts_morph_1 = require("ts-morph");
|
|
38
|
+
const html_parser_1 = require("./html-parser");
|
|
39
|
+
const html_variable_resolver_1 = require("./html-variable-resolver");
|
|
40
|
+
const rxjs_matcher_1 = require("../ts/rxjs-matcher");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
/**
|
|
44
|
+
* Extrait le contenu du template d'un @Component (inline ou externe)
|
|
45
|
+
* Supporte les fichiers in-memory (ts-morph) et les fichiers réels (fs)
|
|
46
|
+
*/
|
|
47
|
+
function getTemplateContent(componentNode) {
|
|
48
|
+
if (!ts_morph_1.Node.isDecorator(componentNode)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const expression = componentNode.getExpression();
|
|
52
|
+
if (!ts_morph_1.Node.isCallExpression(expression)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const args = expression.getArguments();
|
|
56
|
+
if (args.length === 0) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const configObject = args[0];
|
|
60
|
+
if (!ts_morph_1.Node.isObjectLiteralExpression(configObject)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
// Cas 1: Template inline
|
|
64
|
+
const templateProperty = configObject.getProperty('template');
|
|
65
|
+
if (templateProperty && ts_morph_1.Node.isPropertyAssignment(templateProperty)) {
|
|
66
|
+
const templateValue = templateProperty.getInitializer();
|
|
67
|
+
if (templateValue) {
|
|
68
|
+
return templateValue.getText().replace(/[`'"]/g, '');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Cas 2: Template externe (templateUrl)
|
|
72
|
+
const templateUrlProperty = configObject.getProperty('templateUrl');
|
|
73
|
+
if (templateUrlProperty && ts_morph_1.Node.isPropertyAssignment(templateUrlProperty)) {
|
|
74
|
+
const templateUrlValue = templateUrlProperty.getInitializer();
|
|
75
|
+
if (!templateUrlValue) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const templateUrl = templateUrlValue.getText().replace(/[`'"]/g, '');
|
|
79
|
+
const sourceFile = componentNode.getSourceFile();
|
|
80
|
+
const project = sourceFile.getProject();
|
|
81
|
+
const fileSystem = project.getFileSystem();
|
|
82
|
+
const tsFilePath = sourceFile.getFilePath();
|
|
83
|
+
const tsDir = path.dirname(tsFilePath);
|
|
84
|
+
// APPROCHE 1: Essayer de lire depuis le file system de ts-morph (in-memory ou réel)
|
|
85
|
+
try {
|
|
86
|
+
// Essayer plusieurs variantes du chemin
|
|
87
|
+
// IMPORTANT: Pour in-memory file system, les chemins doivent correspondre exactement
|
|
88
|
+
const resolvedPath = path.resolve(tsDir, templateUrl);
|
|
89
|
+
const joinedPath = path.join(tsDir, templateUrl);
|
|
90
|
+
// Normaliser les chemins pour Windows (utiliser /)
|
|
91
|
+
const pathsToTry = [
|
|
92
|
+
templateUrl.replace(/^\.\//, '/'), // './test.html' → '/test.html'
|
|
93
|
+
resolvedPath,
|
|
94
|
+
resolvedPath.replace(/\\/g, '/'),
|
|
95
|
+
joinedPath,
|
|
96
|
+
joinedPath.replace(/\\/g, '/'),
|
|
97
|
+
templateUrl,
|
|
98
|
+
];
|
|
99
|
+
for (const tryPath of pathsToTry) {
|
|
100
|
+
if (fileSystem.fileExistsSync(tryPath)) {
|
|
101
|
+
return fileSystem.readFileSync(tryPath, 'utf-8');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
// Continue vers approche 2
|
|
107
|
+
}
|
|
108
|
+
// APPROCHE 2: Essayer de récupérer comme SourceFile (pour templates TypeScript)
|
|
109
|
+
const htmlFilePath = path.resolve(tsDir, templateUrl);
|
|
110
|
+
let htmlSourceFile = project.getSourceFile(htmlFilePath);
|
|
111
|
+
if (!htmlSourceFile) {
|
|
112
|
+
htmlSourceFile = project.getSourceFile(htmlFilePath.replace(/\\/g, '/'));
|
|
113
|
+
}
|
|
114
|
+
if (htmlSourceFile) {
|
|
115
|
+
return htmlSourceFile.getFullText();
|
|
116
|
+
}
|
|
117
|
+
// APPROCHE 3: Fallback sur fs réel (si pas in-memory)
|
|
118
|
+
try {
|
|
119
|
+
if (fs.existsSync(htmlFilePath)) {
|
|
120
|
+
return fs.readFileSync(htmlFilePath, 'utf-8');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Trouve toutes les expressions de pipes dans un template HTML
|
|
131
|
+
* @param htmlContent Contenu du template
|
|
132
|
+
* @param pipeName Nom de la pipe à rechercher (ex: "async")
|
|
133
|
+
* @returns Liste des expressions de pipe trouvées
|
|
134
|
+
*/
|
|
135
|
+
function findPipeExpressions(htmlContent, pipeName) {
|
|
136
|
+
const pipeExpressions = [];
|
|
137
|
+
// Parser le template HTML
|
|
138
|
+
const ast = (0, html_parser_1.parseHtmlTemplate)(htmlContent);
|
|
139
|
+
// Pattern factice pour utiliser traversePipeExpression
|
|
140
|
+
const pattern = { nodeType: 'PipeExpression', pipeName };
|
|
141
|
+
// Parcourir l'AST pour trouver les pipes
|
|
142
|
+
function traverse(nodes) {
|
|
143
|
+
for (const node of nodes) {
|
|
144
|
+
// Vérifier les expressions liées (BoundText, BoundAttribute, etc.)
|
|
145
|
+
if ('value' in node && node.value) {
|
|
146
|
+
const value = node.value;
|
|
147
|
+
const astNode = value.ast || value;
|
|
148
|
+
// Parcourir l'expression pour trouver les BindingPipe
|
|
149
|
+
traverseForBindingPipe(astNode);
|
|
150
|
+
}
|
|
151
|
+
// Vérifier les templates (structural directives: *ngFor, *ngIf, etc.)
|
|
152
|
+
// Les bindings sont dans node.templateAttrs (ex: ngForOf pour *ngFor)
|
|
153
|
+
if ('templateAttrs' in node && Array.isArray(node.templateAttrs)) {
|
|
154
|
+
for (const attr of node.templateAttrs) {
|
|
155
|
+
if (attr.value) {
|
|
156
|
+
const astNode = attr.value.ast || attr.value;
|
|
157
|
+
traverseForBindingPipe(astNode);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Parcourir les enfants
|
|
162
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
163
|
+
traverse(node.children);
|
|
164
|
+
}
|
|
165
|
+
// Parcourir les branches (@if, @else)
|
|
166
|
+
if ('branches' in node && Array.isArray(node.branches)) {
|
|
167
|
+
for (const branch of node.branches) {
|
|
168
|
+
if (branch.children)
|
|
169
|
+
traverse(branch.children);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Parcourir les cases (@switch, @case)
|
|
173
|
+
if ('cases' in node && Array.isArray(node.cases)) {
|
|
174
|
+
for (const caseNode of node.cases) {
|
|
175
|
+
if (caseNode.children)
|
|
176
|
+
traverse(caseNode.children);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Parcourir @defer blocks
|
|
180
|
+
const deferBlocks = ['mainBlock', 'placeholder', 'loading', 'error', 'empty'];
|
|
181
|
+
for (const blockName of deferBlocks) {
|
|
182
|
+
if (blockName in node && node[blockName]?.children) {
|
|
183
|
+
traverse(node[blockName].children);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function traverseForBindingPipe(expr) {
|
|
189
|
+
if (!expr)
|
|
190
|
+
return;
|
|
191
|
+
// Si c'est un BindingPipe avec le bon nom, l'ajouter
|
|
192
|
+
if (expr.constructor?.name === 'BindingPipe' && expr.name === pipeName) {
|
|
193
|
+
pipeExpressions.push(expr);
|
|
194
|
+
}
|
|
195
|
+
// Parcourir récursivement
|
|
196
|
+
if (expr.exp)
|
|
197
|
+
traverseForBindingPipe(expr.exp);
|
|
198
|
+
if (expr.expressions && Array.isArray(expr.expressions)) {
|
|
199
|
+
expr.expressions.forEach(traverseForBindingPipe);
|
|
200
|
+
}
|
|
201
|
+
if (expr.args && Array.isArray(expr.args)) {
|
|
202
|
+
expr.args.forEach(traverseForBindingPipe);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
traverse(ast);
|
|
206
|
+
return pipeExpressions;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Vérifie si un @Component contient des pipes HTML dont les variables TS
|
|
210
|
+
* n'ont PAS de gestion d'erreur RxJS
|
|
211
|
+
*
|
|
212
|
+
* @param componentNode Nœud Decorator @Component
|
|
213
|
+
* @param pattern Pattern de vérification
|
|
214
|
+
* @returns true si au moins une variable sans gestion d'erreur est trouvée
|
|
215
|
+
*/
|
|
216
|
+
function matchesHtmlPipeVariable(componentNode, pattern) {
|
|
217
|
+
// Vérifier que c'est bien un @Component
|
|
218
|
+
if (!ts_morph_1.Node.isDecorator(componentNode)) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
// Extraire le template
|
|
222
|
+
const templateContent = getTemplateContent(componentNode);
|
|
223
|
+
if (!templateContent) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
// Trouver toutes les expressions avec la pipe spécifiée
|
|
227
|
+
const pipeExpressions = findPipeExpressions(templateContent, pattern.pipeName);
|
|
228
|
+
if (pipeExpressions.length === 0) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
// Pour chaque pipe expression, vérifier la gestion d'erreur
|
|
232
|
+
const operators = pattern.resolvedProperty.missingRxjsOperators || [];
|
|
233
|
+
for (const pipeExpr of pipeExpressions) {
|
|
234
|
+
// Résoudre la variable HTML vers la PropertyDeclaration TS
|
|
235
|
+
const propertyDeclaration = (0, html_variable_resolver_1.resolveVariableFromHtmlPipe)(componentNode, pipeExpr);
|
|
236
|
+
// Si on ne peut pas résoudre la variable, on ne peut pas vérifier
|
|
237
|
+
// (skip ce cas, car il peut s'agir d'une variable locale, etc.)
|
|
238
|
+
if (!propertyDeclaration) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
// Vérifier si la propriété a une gestion d'erreur
|
|
242
|
+
const hasError = (0, rxjs_matcher_1.hasErrorHandling)(propertyDeclaration, operators);
|
|
243
|
+
// Si AUCUNE gestion d'erreur trouvée, c'est un match !
|
|
244
|
+
if (!hasError) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Toutes les variables ont une gestion d'erreur, pas de match
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractVariableNameFromPipe = extractVariableNameFromPipe;
|
|
4
|
+
exports.resolveVariableFromComponent = resolveVariableFromComponent;
|
|
5
|
+
exports.resolveVariableFromHtmlPipe = resolveVariableFromHtmlPipe;
|
|
6
|
+
const ts_morph_1 = require("ts-morph");
|
|
7
|
+
/**
|
|
8
|
+
* Extrait le nom de variable depuis une expression de pipe HTML
|
|
9
|
+
* Exemples:
|
|
10
|
+
* "data$ | async" → "data$"
|
|
11
|
+
* "users | filter: 'active' | async" → "users"
|
|
12
|
+
* "product?.name | uppercase" → "product"
|
|
13
|
+
*/
|
|
14
|
+
function extractVariableNameFromPipe(pipeExpression) {
|
|
15
|
+
if (!pipeExpression || !pipeExpression.exp) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
// Naviguer vers l'expression de base (avant le premier pipe)
|
|
19
|
+
let baseExpr = pipeExpression.exp;
|
|
20
|
+
// Si c'est une chaîne de pipes, remonter jusqu'à l'expression originale
|
|
21
|
+
while (baseExpr && baseExpr.exp && baseExpr.constructor?.name === 'BindingPipe') {
|
|
22
|
+
baseExpr = baseExpr.exp;
|
|
23
|
+
}
|
|
24
|
+
// Cas 1: PropertyRead (ex: data$, users)
|
|
25
|
+
if (baseExpr.constructor?.name === 'PropertyRead') {
|
|
26
|
+
return baseExpr.name || null;
|
|
27
|
+
}
|
|
28
|
+
// Cas 2: SafePropertyRead (ex: product?.name)
|
|
29
|
+
if (baseExpr.constructor?.name === 'SafePropertyRead') {
|
|
30
|
+
// On veut uniquement la première propriété (product, pas product.name)
|
|
31
|
+
if (baseExpr.receiver && baseExpr.receiver.name) {
|
|
32
|
+
return baseExpr.receiver.name;
|
|
33
|
+
}
|
|
34
|
+
return baseExpr.name || null;
|
|
35
|
+
}
|
|
36
|
+
// Cas 3: ImplicitReceiver (ex: dans *ngIf="data$ | async as data")
|
|
37
|
+
if (baseExpr.constructor?.name === 'ImplicitReceiver') {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
// Cas 4: Autres types d'expressions (LiteralPrimitive, etc.)
|
|
41
|
+
// Pas de variable à résoudre
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Trouve la ClassDeclaration d'un nœud @Component
|
|
46
|
+
* @param componentDecorator Nœud Decorator avec name="Component"
|
|
47
|
+
* @returns ClassDeclaration ou undefined
|
|
48
|
+
*/
|
|
49
|
+
function findComponentClass(componentDecorator) {
|
|
50
|
+
if (!ts_morph_1.Node.isDecorator(componentDecorator)) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const parent = componentDecorator.getParent();
|
|
54
|
+
if (!parent) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
// Le parent du Decorator est typiquement la ClassDeclaration
|
|
58
|
+
if (ts_morph_1.Node.isClassDeclaration(parent)) {
|
|
59
|
+
return parent;
|
|
60
|
+
}
|
|
61
|
+
// Sinon, chercher dans les siblings ou parents proches
|
|
62
|
+
let current = parent;
|
|
63
|
+
while (current && !ts_morph_1.Node.isClassDeclaration(current)) {
|
|
64
|
+
current = current.getParent();
|
|
65
|
+
}
|
|
66
|
+
return current;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Résout un nom de variable HTML vers sa PropertyDeclaration TypeScript
|
|
70
|
+
* @param componentNode Nœud @Component (Decorator)
|
|
71
|
+
* @param variableName Nom de la variable extraite du HTML (ex: "data$")
|
|
72
|
+
* @returns PropertyDeclaration ou undefined
|
|
73
|
+
*/
|
|
74
|
+
function resolveVariableFromComponent(componentNode, variableName) {
|
|
75
|
+
if (!variableName) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
// Trouver la classe du composant
|
|
79
|
+
const componentClass = findComponentClass(componentNode);
|
|
80
|
+
if (!componentClass) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
// Chercher la propriété avec ce nom
|
|
84
|
+
const properties = componentClass.getProperties();
|
|
85
|
+
const matchingProperty = properties.find(prop => {
|
|
86
|
+
const propName = prop.getName();
|
|
87
|
+
return propName === variableName;
|
|
88
|
+
});
|
|
89
|
+
return matchingProperty;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Vérifie si une variable HTML utilisée avec une pipe est résolue vers une PropertyDeclaration TS
|
|
93
|
+
* @param componentNode Nœud @Component
|
|
94
|
+
* @param pipeExpression Expression de pipe depuis l'AST HTML (@angular/compiler)
|
|
95
|
+
* @returns PropertyDeclaration ou undefined
|
|
96
|
+
*/
|
|
97
|
+
function resolveVariableFromHtmlPipe(componentNode, pipeExpression) {
|
|
98
|
+
// Étape 1: Extraire le nom de variable depuis l'expression de pipe
|
|
99
|
+
const variableName = extractVariableNameFromPipe(pipeExpression);
|
|
100
|
+
if (!variableName) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
// Étape 2: Résoudre vers la PropertyDeclaration
|
|
104
|
+
return resolveVariableFromComponent(componentNode, variableName);
|
|
105
|
+
}
|
|
@@ -60,6 +60,7 @@ exports.SymbolMatcher = SymbolMatcher;
|
|
|
60
60
|
// Import matchers HTML (depuis html/)
|
|
61
61
|
const HtmlMatcher = __importStar(require("./html"));
|
|
62
62
|
exports.HtmlMatcher = HtmlMatcher;
|
|
63
|
+
const html_pipe_variable_matcher_1 = require("./html/html-pipe-variable-matcher");
|
|
63
64
|
/**
|
|
64
65
|
* Fonction principale : vérifie si un nœud correspond à un pattern AST
|
|
65
66
|
* OPTIMISÉ : excludeContext en premier (88% des règles l'utilisent)
|
|
@@ -370,6 +371,12 @@ function matchesAstPattern(node, pattern) {
|
|
|
370
371
|
return false;
|
|
371
372
|
}
|
|
372
373
|
}
|
|
374
|
+
// Vérifier htmlPipeVariable (résolution HTML pipe → TS variable → RxJS error handling)
|
|
375
|
+
if (pattern.htmlPipeVariable !== undefined) {
|
|
376
|
+
if (!(0, html_pipe_variable_matcher_1.matchesHtmlPipeVariable)(node, pattern.htmlPipeVariable)) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
373
380
|
return true;
|
|
374
381
|
}
|
|
375
382
|
// Export des fonctions utilitaires de cache
|
|
@@ -25,7 +25,8 @@ function matchesParent(node, parentPattern, matchesAstPatternFn) {
|
|
|
25
25
|
// Vérifier notFunctionName si spécifié
|
|
26
26
|
if (parentPattern.notFunctionName && ts_morph_1.Node.isCallExpression(current)) {
|
|
27
27
|
const funcName = current.getExpression().getText().split('.').pop() || '';
|
|
28
|
-
|
|
28
|
+
const notFunctionNames = (0, matcher_helpers_1.ensureArray)(parentPattern.notFunctionName);
|
|
29
|
+
if (notFunctionNames.includes(funcName)) {
|
|
29
30
|
return false; // Trouvé le pattern à exclure
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasRxjsErrorHandling = hasRxjsErrorHandling;
|
|
4
|
+
exports.hasSubscribeErrorCallback = hasSubscribeErrorCallback;
|
|
5
|
+
exports.hasErrorHandling = hasErrorHandling;
|
|
6
|
+
const ts_morph_1 = require("ts-morph");
|
|
7
|
+
/**
|
|
8
|
+
* Liste des opérateurs RxJS de gestion d'erreur
|
|
9
|
+
*/
|
|
10
|
+
const RXJS_ERROR_OPERATORS = [
|
|
11
|
+
'catchError',
|
|
12
|
+
'retry',
|
|
13
|
+
'retryWhen',
|
|
14
|
+
'onErrorResumeNext'
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Cherche les assignations à une propriété dans le constructeur ou autres méthodes
|
|
18
|
+
* @param propertyNode PropertyDeclaration
|
|
19
|
+
* @returns Nœuds d'assignation trouvés
|
|
20
|
+
*/
|
|
21
|
+
function findPropertyAssignments(propertyNode) {
|
|
22
|
+
const propertyName = propertyNode.getName();
|
|
23
|
+
const classDecl = propertyNode.getParent();
|
|
24
|
+
if (!ts_morph_1.Node.isClassDeclaration(classDecl)) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const assignments = [];
|
|
28
|
+
// Chercher dans le constructeur
|
|
29
|
+
const constructor = classDecl.getConstructors()[0];
|
|
30
|
+
if (constructor) {
|
|
31
|
+
const body = constructor.getBody();
|
|
32
|
+
if (body) {
|
|
33
|
+
// Chercher les this.propertyName = ...
|
|
34
|
+
body.forEachDescendant((node) => {
|
|
35
|
+
if (ts_morph_1.Node.isBinaryExpression(node)) {
|
|
36
|
+
const left = node.getLeft();
|
|
37
|
+
// Vérifier si c'est this.propertyName
|
|
38
|
+
if (ts_morph_1.Node.isPropertyAccessExpression(left)) {
|
|
39
|
+
const expr = left.getExpression();
|
|
40
|
+
if (ts_morph_1.Node.isThisExpression(expr) && left.getName() === propertyName) {
|
|
41
|
+
assignments.push(node.getRight());
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return assignments;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Vérifie si une PropertyDeclaration utilise .pipe() avec des opérateurs de gestion d'erreur
|
|
52
|
+
* @param propertyNode PropertyDeclaration à analyser
|
|
53
|
+
* @param operators Liste des opérateurs RxJS à rechercher (par défaut: catchError, retry, etc.)
|
|
54
|
+
* @returns true si au moins un opérateur de gestion d'erreur est trouvé
|
|
55
|
+
*/
|
|
56
|
+
function hasRxjsErrorHandling(propertyNode, operators = RXJS_ERROR_OPERATORS) {
|
|
57
|
+
// Cas 1: Propriété avec initializer direct (data$ = this.http.get(...).pipe(...))
|
|
58
|
+
const initializer = propertyNode.getInitializer();
|
|
59
|
+
const nodesToCheck = initializer ? [initializer] : [];
|
|
60
|
+
// Cas 2: Propriété assignée dans le constructeur (this.data$ = ...)
|
|
61
|
+
const assignments = findPropertyAssignments(propertyNode);
|
|
62
|
+
nodesToCheck.push(...assignments);
|
|
63
|
+
// Pas d'initializer ni d'assignation trouvée
|
|
64
|
+
if (nodesToCheck.length === 0) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
// APPROCHE SIMPLE : Vérifier le texte brut pour les opérateurs
|
|
68
|
+
for (const nodeToCheck of nodesToCheck) {
|
|
69
|
+
const codeText = nodeToCheck.getText();
|
|
70
|
+
// Vérifier si le code contient `.pipe(` et un opérateur de gestion d'erreur
|
|
71
|
+
if (codeText.includes('.pipe(')) {
|
|
72
|
+
for (const operator of operators) {
|
|
73
|
+
// Chercher "operator(" dans le code
|
|
74
|
+
const regex = new RegExp(`\\b${operator}\\s*\\(`);
|
|
75
|
+
if (regex.test(codeText)) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Vérifie si une PropertyDeclaration utilise .subscribe() avec un callback d'erreur
|
|
85
|
+
* @param propertyNode PropertyDeclaration à analyser
|
|
86
|
+
* @returns true si .subscribe() a un deuxième argument (error callback)
|
|
87
|
+
*/
|
|
88
|
+
function hasSubscribeErrorCallback(propertyNode) {
|
|
89
|
+
const initializer = propertyNode.getInitializer();
|
|
90
|
+
if (!initializer) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
// Chercher tous les .subscribe()
|
|
94
|
+
const callExpressions = initializer.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression);
|
|
95
|
+
for (const callExpr of callExpressions) {
|
|
96
|
+
if (!ts_morph_1.Node.isCallExpression(callExpr)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const expression = callExpr.getExpression();
|
|
100
|
+
// Vérifier si c'est .subscribe()
|
|
101
|
+
if (ts_morph_1.Node.isPropertyAccessExpression(expression) && expression.getName() === 'subscribe') {
|
|
102
|
+
const args = callExpr.getArguments();
|
|
103
|
+
// .subscribe(next, error, complete) - au moins 2 arguments
|
|
104
|
+
if (args.length >= 2) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
// .subscribe({ next, error, complete })
|
|
108
|
+
if (args.length === 1 && ts_morph_1.Node.isObjectLiteralExpression(args[0])) {
|
|
109
|
+
const obj = args[0];
|
|
110
|
+
const errorProp = obj.getProperty('error');
|
|
111
|
+
if (errorProp) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Vérifie si une PropertyDeclaration a une gestion d'erreur (RxJS ou subscribe)
|
|
121
|
+
* @param propertyNode PropertyDeclaration à analyser
|
|
122
|
+
* @param operators Liste des opérateurs RxJS à rechercher
|
|
123
|
+
* @returns true si au moins une gestion d'erreur est trouvée
|
|
124
|
+
*/
|
|
125
|
+
function hasErrorHandling(propertyNode, operators = RXJS_ERROR_OPERATORS) {
|
|
126
|
+
return hasRxjsErrorHandling(propertyNode, operators) ||
|
|
127
|
+
hasSubscribeErrorCallback(propertyNode);
|
|
128
|
+
}
|
|
@@ -104,10 +104,10 @@ function calculateRuleWorkload(rule, matches, targetName, projectInfo) {
|
|
|
104
104
|
baseOccurrences = occurrences;
|
|
105
105
|
type = occurrences > 0 ? 'combined' : 'special';
|
|
106
106
|
if (fileCount > 0) {
|
|
107
|
-
const specialMinutes = fileCount * rule.special_workload.
|
|
108
|
-
logger_1.logger.debug(` 📦 Special workload [${rule.key}]: ${fileCount} fichiers × ${rule.special_workload.
|
|
107
|
+
const specialMinutes = fileCount * rule.special_workload.timePerOccurrence;
|
|
108
|
+
logger_1.logger.debug(` 📦 Special workload [${rule.key}]: ${fileCount} fichiers × ${rule.special_workload.timePerOccurrence}min = ${specialMinutes}min`);
|
|
109
109
|
totalMinutes += specialMinutes;
|
|
110
|
-
rule.special_workload.
|
|
110
|
+
rule.special_workload.occurrencesFound = fileCount;
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
const ruleWorkload = {
|
|
@@ -121,7 +121,7 @@ function calculateRuleWorkload(rule, matches, targetName, projectInfo) {
|
|
|
121
121
|
if (specialWorkloadFiles !== undefined) {
|
|
122
122
|
ruleWorkload.specialWorkloadFiles = specialWorkloadFiles;
|
|
123
123
|
ruleWorkload.baseOccurrences = baseOccurrences;
|
|
124
|
-
ruleWorkload.specialTimePerFile = rule.special_workload?.
|
|
124
|
+
ruleWorkload.specialTimePerFile = rule.special_workload?.timePerOccurrence;
|
|
125
125
|
}
|
|
126
126
|
return ruleWorkload;
|
|
127
127
|
}
|
|
@@ -60,10 +60,10 @@ function isRootFilePattern(pattern) {
|
|
|
60
60
|
* FALLBACK : Scanne récursivement les patterns avec wildcard dans targetPath
|
|
61
61
|
*/
|
|
62
62
|
function countSpecialWorkloadFiles(targetPath, rule, projectRoot) {
|
|
63
|
-
if (!rule.special_workload || !rule.special_workload.
|
|
63
|
+
if (!rule.special_workload || !rule.special_workload.fileTypes) {
|
|
64
64
|
return 0;
|
|
65
65
|
}
|
|
66
|
-
const patterns = rule.special_workload.
|
|
66
|
+
const patterns = rule.special_workload.fileTypes;
|
|
67
67
|
const cacheKey = `${targetPath}::${projectRoot}::${patterns.join(',')}`;
|
|
68
68
|
// Vérifier le cache
|
|
69
69
|
if (specialWorkloadCache.has(cacheKey)) {
|