@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.
Files changed (43) hide show
  1. package/LICENSE +201 -96
  2. package/NOTICE +105 -0
  3. package/README.fr.md +30 -24
  4. package/README.md +29 -25
  5. package/SECURITY.md +223 -187
  6. package/dist/client.bundle.js +51 -48
  7. package/dist/src/core/ast/matchers/html/html-pipe-variable-matcher.js +250 -0
  8. package/dist/src/core/ast/matchers/html/html-variable-resolver.js +105 -0
  9. package/dist/src/core/ast/matchers/index.js +7 -0
  10. package/dist/src/core/ast/matchers/ts/hierarchy-matcher.js +2 -1
  11. package/dist/src/core/ast/matchers/ts/rxjs-matcher.js +128 -0
  12. package/dist/src/core/workload/hierarchy-calculator.js +4 -4
  13. package/dist/src/core/workload/special-workload.js +2 -2
  14. package/dist/src/data/angular-migration-rules.json +2336 -2336
  15. package/dist/src/data/rules/rearchitecture/rearchitecture-rules.json +2 -2
  16. package/dist/src/data/rules/to18/rules-18-obligatoire.json +373 -373
  17. package/dist/src/data/rules/to18/rules-18-optionnelle.json +187 -187
  18. package/dist/src/data/rules/to18/rules-18-recommande.json +217 -217
  19. package/dist/src/data/rules/to19/rules-19-obligatoire.json +347 -347
  20. package/dist/src/data/rules/to19/rules-19-optionnelle.json +217 -222
  21. package/dist/src/data/rules/to19/rules-19-recommande.json +174 -199
  22. package/dist/src/data/rules/to20/rules-20-obligatoire.json +558 -555
  23. package/dist/src/data/rules/to20/rules-20-optionnelle.json +189 -189
  24. package/dist/src/data/rules/to20/rules-20-recommande.json +150 -150
  25. package/dist/src/models/interfaces/{ast-interfaces.js → angular-artifact-counts.interface.js} +0 -3
  26. package/dist/src/models/interfaces/base-target.interface.js +2 -0
  27. package/dist/src/models/interfaces/checklist-state.interface.js +2 -0
  28. package/dist/src/models/interfaces/dependency-counts.interface.js +2 -0
  29. package/dist/src/models/interfaces/migration-guide-data.interface.js +2 -0
  30. package/dist/src/models/interfaces/module-info.interface.js +2 -0
  31. package/dist/src/models/interfaces/parallelization.interface.js +2 -0
  32. package/dist/src/models/interfaces/refer-to-pattern.interface.js +2 -0
  33. package/dist/src/models/interfaces/summary-totals.interface.js +2 -0
  34. package/dist/src/models/interfaces/window-with-data.interface.js +2 -0
  35. package/dist/src/templates/workload/dashboard.template.js +7 -44
  36. package/dist/src/templates/workload/guide-rule-card.template.js +1 -1
  37. package/dist/src/templates/workload/rule-modal.template.js +6 -21
  38. package/dist/src/templates/workload/unified-settings-panel.template.js +149 -0
  39. package/dist/src/utils/core/args-parser.js +16 -1
  40. package/dist/styles.css +1 -1
  41. package/package.json +8 -7
  42. /package/dist/src/models/{chip-config.js → constants/chip-config.js} +0 -0
  43. /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
- if (funcName === parentPattern.notFunctionName) {
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.bb_time_by_occurrence;
108
- logger_1.logger.debug(` 📦 Special workload [${rule.key}]: ${fileCount} fichiers × ${rule.special_workload.bb_time_by_occurrence}min = ${specialMinutes}min`);
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.occurrences_found = fileCount;
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?.bb_time_by_occurrence;
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.bb_fileTypes) {
63
+ if (!rule.special_workload || !rule.special_workload.fileTypes) {
64
64
  return 0;
65
65
  }
66
- const patterns = rule.special_workload.bb_fileTypes;
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)) {