@silvestv/migration-planificator 5.0.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.fr.md +10 -11
  2. package/README.md +10 -11
  3. package/dist/client.bundle.js +98 -98
  4. package/dist/src/config/migration.config.js +69 -0
  5. package/dist/src/core/app-analyzer.js +61 -79
  6. package/dist/src/core/ast/matchers/html/html-element-matcher.js +32 -7
  7. package/dist/src/core/ast/matchers/html/index.js +36 -4
  8. package/dist/src/core/ast/matchers/ts/collection-matcher.js +18 -0
  9. package/dist/src/core/ast/matchers/ts/decorator-matcher.js +33 -4
  10. package/dist/src/core/ast/matchers/ts/expression-matcher.js +1 -1
  11. package/dist/src/core/ast/matchers/ts/node-matcher.js +4 -0
  12. package/dist/src/core/project-detector.js +78 -102
  13. package/dist/src/core/project-strategy/standalone-strategy.js +43 -14
  14. package/dist/src/core/rules-loader.js +21 -13
  15. package/dist/src/core/scan-reporter.js +3 -1
  16. package/dist/src/core/scanner-orchestrator.js +1 -1
  17. package/dist/src/core/workload/constants.js +7 -4
  18. package/dist/src/data/markdown/angular-migration-20-21.md +1010 -0
  19. package/dist/src/data/rules/to21/rules-21-obligatoire.json +423 -0
  20. package/dist/src/data/rules/to21/rules-21-optionnelle.json +253 -0
  21. package/dist/src/data/rules/to21/rules-21-recommande.json +139 -0
  22. package/dist/src/index.js +1 -1
  23. package/dist/src/templates/landing/project-overview.template.js +2 -1
  24. package/dist/src/templates/page/migration-guide.template.js +1 -0
  25. package/dist/src/templates/workload/guide-rule-card.template.js +3 -1
  26. package/dist/src/templates/workload/hierarchy-shared.js +6 -5
  27. package/dist/src/templates/workload/unified-settings-panel.template.js +27 -33
  28. package/dist/src/utils/core/args-parser.js +19 -18
  29. package/dist/styles.css +1 -1
  30. package/package.json +3 -2
@@ -0,0 +1,423 @@
1
+ [
2
+ {
3
+ "key": "angular_21_environment",
4
+ "summary": "Mise a jour environnement Angular 21 (Node 20.19+/22.12+/24+, TypeScript 5.9+)",
5
+ "description": "Angular 21 requiert Node.js 20.19.0+, 22.12.0+ ou 24.0.0+ et TypeScript 5.9+. Cette version active le mode zoneless par defaut pour les nouvelles applications et introduit Vitest comme test runner stable. Les applications existantes utilisant Zone.js doivent ajouter explicitement provideZoneChangeDetection().",
6
+ "estimated_time_per_occurrence": 30,
7
+ "onFile": "package.json",
8
+ "fileTypes": ["package.json"],
9
+ "regex": "\"@angular/(core|cli)\"\\s*:\\s*\"(?:(?:\\^|~)?(?:1[0-9]|20)(?:\\.\\d+){0,2})\"",
10
+ "category": "environment",
11
+ "isAutoFixable": false,
12
+ "migration_command": "npx ng update @angular/core@21 @angular/cli@21",
13
+ "risk_level": "critical",
14
+ "code_description": "// Migration Angular 21\n// Node : ^20.19.0 || ^22.12.0 || ^24.0.0\n// TypeScript : ~5.9.0\n// Commande :\n// nvm install 22.12.0\n// npm i typescript@~5.9.0\n// npx ng update @angular/core@21 @angular/cli@21",
15
+ "doc_url": "https://angular.dev/reference/migrations"
16
+ },
17
+ {
18
+ "key": "typescript_5_9",
19
+ "summary": "Mise a jour TypeScript vers 5.9+",
20
+ "description": "TypeScript 5.9 est requis pour Angular 21. Cette version apporte des ameliorations de performance et de nouveaux controles de type necessaires aux nouvelles fonctionnalites du compilateur Angular, notamment le support ameliore des signals et du host binding type checking.",
21
+ "estimated_time_per_occurrence": 10,
22
+ "onFile": "package.json",
23
+ "fileTypes": ["package.json"],
24
+ "regex": "^(?![\\s\\S]*\"typescript\"[\\s\\S]*\"[<>=~^]*(?:5\\.9|[6-9]\\d*)\\.)[\\s\\S]+",
25
+ "category": "environment",
26
+ "isAutoFixable": true,
27
+ "migration_command": "npm install typescript@~5.9.0",
28
+ "risk_level": "medium",
29
+ "code_description": "// package.json\n// Avant: \"typescript\": \"~5.8.0\"\n// Apres: \"typescript\": \"~5.9.0\"",
30
+ "doc_url": "https://angular.dev/reference/migrations"
31
+ },
32
+ {
33
+ "key": "zoneless_default_bootstrapApplication",
34
+ "summary": "Ajouter provideZoneChangeDetection() dans bootstrapApplication",
35
+ "description": "BREAKING CHANGE: Angular 21 est zoneless par defaut. Les applications standalone existantes utilisant Zone.js doivent ajouter explicitement provideZoneChangeDetection() dans bootstrapApplication. Sans ce provider, la detection de changement ne fonctionnera plus correctement. Migration automatique via ng update.",
36
+ "estimated_time_per_occurrence": 5,
37
+ "onFile": null,
38
+ "fileTypes": ["*.ts"],
39
+ "regex": "bootstrapApplication\\s*\\(",
40
+ "astPattern": {
41
+ "nodeType": "CallExpression",
42
+ "functionName": "bootstrapApplication",
43
+ "arguments": {
44
+ "properties": {
45
+ "providers": {
46
+ "exists": true,
47
+ "elements": {
48
+ "missing": "provideZoneChangeDetection"
49
+ }
50
+ }
51
+ }
52
+ },
53
+ "excludeContext": ["StringLiteral", "Comment"]
54
+ },
55
+ "category": "environment",
56
+ "isAutoFixable": true,
57
+ "migration_command": null,
58
+ "risk_level": "critical",
59
+ "code_description": "// BREAKING CHANGE: Zoneless par defaut en Angular 21\n\n// Avant (Zone.js implicite):\nbootstrapApplication(AppComponent, {\n providers: [provideRouter(routes)]\n});\n\n// Apres:\nimport { provideZoneChangeDetection } from '@angular/core';\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideZoneChangeDetection(), // OBLIGATOIRE pour Zone.js\n provideRouter(routes)\n ]\n});",
60
+ "doc_url": "https://angular.dev/guide/zoneless"
61
+ },
62
+ {
63
+ "key": "zoneless_default_ngModule",
64
+ "summary": "Ajouter provideZoneChangeDetection() dans AppModule (NgModule)",
65
+ "description": "BREAKING CHANGE: Les applications NgModule existantes utilisant Zone.js doivent ajouter provideZoneChangeDetection() dans les providers du AppModule. Cette regle detecte les AppModule sans ce provider.",
66
+ "estimated_time_per_occurrence": 5,
67
+ "onFile": null,
68
+ "fileTypes": ["*.ts"],
69
+ "regex": "@NgModule\\s*\\(\\s*\\{[^}]*providers\\s*:",
70
+ "astPattern": {
71
+ "nodeType": "Decorator",
72
+ "name": "NgModule",
73
+ "properties": {
74
+ "bootstrap": {
75
+ "exists": true
76
+ },
77
+ "providers": {
78
+ "elements": {
79
+ "missing": "provideZoneChangeDetection"
80
+ }
81
+ }
82
+ },
83
+ "excludeContext": ["StringLiteral", "Comment"]
84
+ },
85
+ "category": "environment",
86
+ "isAutoFixable": true,
87
+ "migration_command": null,
88
+ "risk_level": "critical",
89
+ "code_description": "// BREAKING CHANGE: Zoneless par defaut en Angular 21\n\n// Avant:\n@NgModule({\n bootstrap: [AppComponent],\n providers: []\n})\nexport class AppModule {}\n\n// Apres:\nimport { provideZoneChangeDetection } from '@angular/core';\n\n@NgModule({\n bootstrap: [AppComponent],\n providers: [\n provideZoneChangeDetection() // OBLIGATOIRE pour Zone.js\n ]\n})\nexport class AppModule {}",
90
+ "doc_url": "https://angular.dev/guide/zoneless"
91
+ },
92
+ {
93
+ "key": "remove_zone_js_polyfills",
94
+ "summary": "Supprimer zone.js des polyfills (si zoneless)",
95
+ "description": "Si vous migrez vers le mode zoneless, supprimez zone.js et zone.js/testing des polyfills dans angular.json (ou project.json pour Nx) et de polyfills.ts. Cette etape est optionnelle si vous gardez Zone.js avec provideZoneChangeDetection().",
96
+ "estimated_time_per_occurrence": 5,
97
+ "onFile": "angular.json",
98
+ "onFileNx": "project.json",
99
+ "fileTypes": ["angular.json", "project.json", "polyfills.ts"],
100
+ "regex": "zone\\.js",
101
+ "category": "environment",
102
+ "isAutoFixable": true,
103
+ "migration_command": null,
104
+ "risk_level": "high",
105
+ "code_description": "// Si migration vers zoneless:\n\n// angular.json - Supprimer de polyfills:\n\"polyfills\": [\n // \"zone.js\" - SUPPRIMER\n // \"zone.js/testing\" - SUPPRIMER\n]\n\n// polyfills.ts - Supprimer:\n// import 'zone.js'; - SUPPRIMER\n\n// ATTENTION: Garder zone.js si vous utilisez provideZoneChangeDetection()",
106
+ "doc_url": "https://angular.dev/guide/zoneless"
107
+ },
108
+ {
109
+ "key": "ngZone_observables_removed",
110
+ "summary": "NgZone.onMicrotaskEmpty/onUnstable/onStable ne fonctionnent plus",
111
+ "description": "BREAKING CHANGE: Les observables NgZone.onMicrotaskEmpty, NgZone.onUnstable et NgZone.onStable n'emettent jamais en mode zoneless. NgZone.isStable retourne toujours true. Remplacer par afterNextRender() ou afterEveryRender().",
112
+ "estimated_time_per_occurrence": 15,
113
+ "onFile": null,
114
+ "fileTypes": ["*.ts"],
115
+ "regex": "ngZone\\.(onMicrotaskEmpty|onUnstable|onStable|isStable)",
116
+ "astPattern": {
117
+ "nodeType": "PropertyAccessExpression",
118
+ "expression": {
119
+ "valueMatches": "(^|\\.)(_?ngZone|zone)$"
120
+ },
121
+ "propertyName": ["onMicrotaskEmpty", "onUnstable", "onStable", "isStable"],
122
+ "excludeContext": ["StringLiteral", "Comment"]
123
+ },
124
+ "category": "api",
125
+ "isAutoFixable": false,
126
+ "migration_command": null,
127
+ "risk_level": "high",
128
+ "code_description": "// BREAKING CHANGE: NgZone observables ne fonctionnent plus en zoneless\n\n// Avant:\nthis.ngZone.onStable.subscribe(() => {\n // Code apres stabilisation\n});\n\n// Apres - Utiliser afterNextRender (une fois):\nafterNextRender(() => {\n // Code apres le prochain rendu\n});\n\n// Ou afterEveryRender (a chaque cycle):\nafterEveryRender(() => {\n // Code apres chaque rendu\n});",
129
+ "doc_url": "https://angular.dev/guide/zoneless"
130
+ },
131
+ {
132
+ "key": "remove_interpolation_property",
133
+ "summary": "Supprimer la propriete 'interpolation' des @Component",
134
+ "description": "BREAKING CHANGE: La propriete 'interpolation' du decorateur @Component est supprimee. Seuls les marqueurs d'interpolation par defaut {{ }} sont maintenant supportes. Les marqueurs personnalises ne fonctionnent plus.",
135
+ "estimated_time_per_occurrence": 15,
136
+ "onFile": null,
137
+ "fileTypes": ["*.ts"],
138
+ "regex": "interpolation\\s*:\\s*\\[",
139
+ "astPattern": {
140
+ "nodeType": "Decorator",
141
+ "name": "Component",
142
+ "properties": {
143
+ "interpolation": {
144
+ "exists": true
145
+ }
146
+ },
147
+ "excludeContext": ["StringLiteral", "Comment"]
148
+ },
149
+ "category": "component",
150
+ "isAutoFixable": false,
151
+ "migration_command": null,
152
+ "risk_level": "high",
153
+ "code_description": "// BREAKING CHANGE: Propriete interpolation supprimee\n\n// Avant:\n@Component({\n selector: 'app-custom',\n template: `<% name %>`,\n interpolation: ['<%', '%>']\n})\n\n// Apres - Seuls {{ }} sont supportes:\n@Component({\n selector: 'app-custom',\n template: `{{ name }}`\n})\n\n// ATTENTION: Tous les templates doivent etre mis a jour!",
154
+ "doc_url": "https://angular.dev/reference/migrations"
155
+ },
156
+ {
157
+ "key": "remove_moduleId_property",
158
+ "summary": "Supprimer la propriete 'moduleId' des @Component",
159
+ "description": "BREAKING CHANGE: La propriete 'moduleId' du decorateur @Component est supprimee. Cette propriete etait utilisee pour les anciens systemes de modules (CommonJS) et n'est plus necessaire avec les build tools modernes.",
160
+ "estimated_time_per_occurrence": 2,
161
+ "onFile": null,
162
+ "fileTypes": ["*.ts"],
163
+ "regex": "moduleId\\s*:",
164
+ "astPattern": {
165
+ "nodeType": "Decorator",
166
+ "name": "Component",
167
+ "properties": {
168
+ "moduleId": {
169
+ "exists": true
170
+ }
171
+ },
172
+ "excludeContext": ["StringLiteral", "Comment"]
173
+ },
174
+ "category": "component",
175
+ "isAutoFixable": true,
176
+ "migration_command": null,
177
+ "risk_level": "low",
178
+ "code_description": "// BREAKING CHANGE: Propriete moduleId supprimee\n\n// Avant:\n@Component({\n selector: 'app-legacy',\n moduleId: module.id,\n templateUrl: './legacy.component.html'\n})\n\n// Apres - Supprimer moduleId:\n@Component({\n selector: 'app-legacy',\n templateUrl: './legacy.component.html'\n})",
179
+ "doc_url": "https://angular.dev/reference/migrations"
180
+ },
181
+ {
182
+ "key": "ngComponentOutletContent_type_changed",
183
+ "summary": "ngComponentOutletContent: type any[][] → Node[][]",
184
+ "description": "BREAKING CHANGE: Le type de ngComponentOutletContent change de any[][] vers Node[][] | undefined. Le code utilisant des strings ou autres types doit etre mis a jour pour utiliser des Nodes DOM (document.createTextNode, etc.).",
185
+ "estimated_time_per_occurrence": 8,
186
+ "onFile": null,
187
+ "fileTypes": ["*.ts"],
188
+ "regex": "ngComponentOutletContent|\\[ngComponentOutletContent\\]",
189
+ "astPattern": {
190
+ "nodeType": "PropertyDeclaration",
191
+ "name": ["content", "myContent", "projectedContent"],
192
+ "type": {
193
+ "containsAny": ["any[][]", "string[][]"]
194
+ },
195
+ "excludeContext": ["StringLiteral", "Comment"]
196
+ },
197
+ "category": "template",
198
+ "isAutoFixable": false,
199
+ "migration_command": null,
200
+ "risk_level": "medium",
201
+ "code_description": "// BREAKING CHANGE: Type change de any[][] vers Node[][]\n\n// Avant:\nmyContent: any[][] = [['texte']];\n\n// Apres:\nmyContent: Node[][] | undefined = [\n [document.createTextNode('texte')]\n];\n\n// Template:\n<ng-container *ngComponentOutlet=\"comp; content: myContent\">",
202
+ "doc_url": "https://angular.dev/api/common/NgComponentOutlet"
203
+ },
204
+ {
205
+ "key": "host_binding_type_checking_enabled",
206
+ "summary": "Host Binding Type Checking active par defaut",
207
+ "description": "BREAKING CHANGE: Le type checking des host bindings est maintenant active par defaut (typeCheckHostBindings: true). Cela peut reveler des erreurs de typage cachees dans vos composants. Corriger les types ou desactiver temporairement via tsconfig.",
208
+ "estimated_time_per_occurrence": 5,
209
+ "onFile": null,
210
+ "fileTypes": ["*.ts"],
211
+ "regex": "host\\s*:\\s*\\{[^}]*\\[",
212
+ "astPattern": {
213
+ "nodeType": "Decorator",
214
+ "name": ["Component", "Directive"],
215
+ "properties": {
216
+ "host": {
217
+ "exists": true
218
+ }
219
+ },
220
+ "excludeContext": ["StringLiteral", "Comment"]
221
+ },
222
+ "category": "component",
223
+ "isAutoFixable": false,
224
+ "migration_command": null,
225
+ "risk_level": "high",
226
+ "code_description": "// BREAKING CHANGE: Host Binding Type Checking active par defaut\n\n// Erreur revelee:\n@Component({\n host: {\n '[class.active]': 'isActve' // Typo! Erreur detectee maintenant\n }\n})\n\n// Corriger les types:\n@Component({\n host: {\n '[class.active]': 'isActive' // Correct\n }\n})\nexport class MyComponent {\n isActive = true; // boolean requis pour class binding\n}\n\n// Ou desactiver temporairement dans tsconfig.json:\n// \"angularCompilerOptions\": { \"typeCheckHostBindings\": false }",
227
+ "doc_url": "https://angular.dev/reference/migrations"
228
+ },
229
+ {
230
+ "key": "applicationConfig_import_from_core",
231
+ "summary": "ApplicationConfig: import depuis @angular/core",
232
+ "description": "BREAKING CHANGE: ApplicationConfig doit maintenant etre importe depuis @angular/core au lieu de @angular/platform-browser. L'ancien export est supprime.",
233
+ "estimated_time_per_occurrence": 2,
234
+ "onFile": null,
235
+ "fileTypes": ["*.ts"],
236
+ "regex": "import\\s*\\{[^}]*ApplicationConfig[^}]*\\}\\s*from\\s*['\"]@angular/platform-browser['\"]",
237
+ "astPattern": {
238
+ "nodeType": "ImportDeclaration",
239
+ "moduleSpecifier": "@angular/platform-browser",
240
+ "namedImports": {
241
+ "contains": "ApplicationConfig"
242
+ },
243
+ "excludeContext": ["StringLiteral", "Comment"]
244
+ },
245
+ "category": "imports",
246
+ "isAutoFixable": true,
247
+ "migration_command": null,
248
+ "risk_level": "low",
249
+ "code_description": "// BREAKING CHANGE: Import ApplicationConfig change\n\n// Avant:\nimport { ApplicationConfig } from '@angular/platform-browser';\n\n// Apres:\nimport { ApplicationConfig } from '@angular/core';",
250
+ "doc_url": "https://angular.dev/reference/migrations"
251
+ },
252
+ {
253
+ "key": "remove_ignoreChangesOutsideZone",
254
+ "summary": "Supprimer l'option ignoreChangesOutsideZone",
255
+ "description": "BREAKING CHANGE: L'option ignoreChangesOutsideZone n'est plus disponible pour provideZoneChangeDetection(). Si vous aviez besoin de ce comportement, migrez vers zoneless.",
256
+ "estimated_time_per_occurrence": 10,
257
+ "onFile": null,
258
+ "fileTypes": ["*.ts"],
259
+ "regex": "ignoreChangesOutsideZone|__zone_symbol__ignoreChangesOutsideZone",
260
+ "astPattern": {
261
+ "nodeType": "PropertyAssignment",
262
+ "name": "ignoreChangesOutsideZone",
263
+ "excludeContext": ["Comment"]
264
+ },
265
+ "category": "environment",
266
+ "isAutoFixable": false,
267
+ "migration_command": null,
268
+ "risk_level": "medium",
269
+ "code_description": "// BREAKING CHANGE: Option supprimee\n\n// Avant:\nprovideZoneChangeDetection({ ignoreChangesOutsideZone: true })\n\n// Ou dans polyfills:\n(window as any).__zone_symbol__ignoreChangesOutsideZone = true;\n\n// Apres - Supprimer ou migrer vers zoneless:\nprovideZonelessChangeDetection()",
270
+ "doc_url": "https://angular.dev/guide/zoneless"
271
+ },
272
+ {
273
+ "key": "testBed_fakePlatformLocation",
274
+ "summary": "TestBed: FakePlatformLocation par defaut (remplace Mock)",
275
+ "description": "BREAKING CHANGE: TestBed utilise FakePlatformLocation par defaut avec support Navigation API. Les tests accedant directement a window.history peuvent echouer. Utiliser provideLocationMocks() ou MockPlatformLocation pour restaurer l'ancien comportement.",
276
+ "estimated_time_per_occurrence": 5,
277
+ "onFile": null,
278
+ "fileTypes": ["*.spec.ts"],
279
+ "regex": "window\\.history|BrowserPlatformLocation",
280
+ "astPattern": {
281
+ "nodeType": "PropertyAccessExpression",
282
+ "expression": {
283
+ "nodeType": "Identifier",
284
+ "name": "window"
285
+ },
286
+ "propertyName": "history",
287
+ "excludeContext": ["StringLiteral", "Comment"]
288
+ },
289
+ "category": "test",
290
+ "isAutoFixable": false,
291
+ "migration_command": null,
292
+ "risk_level": "medium",
293
+ "code_description": "// BREAKING CHANGE: FakePlatformLocation par defaut\n\n// Si vos tests utilisent window.history directement:\nwindow.history.pushState({}, '', '/test'); // Peut echouer\n\n// Solution 1: provideLocationMocks():\nTestBed.configureTestingModule({\n providers: [provideLocationMocks()]\n});\n\n// Solution 2: MockPlatformLocation explicite:\nimport { MockPlatformLocation } from '@angular/common/testing';\nTestBed.configureTestingModule({\n providers: [\n { provide: PlatformLocation, useClass: MockPlatformLocation }\n ]\n});",
294
+ "doc_url": "https://angular.dev/reference/migrations"
295
+ },
296
+ {
297
+ "key": "upgradeAdapter_removed",
298
+ "summary": "UpgradeAdapter supprime → @angular/upgrade/static",
299
+ "description": "BREAKING CHANGE: UpgradeAdapter de @angular/upgrade est completement supprime (deprecie depuis v5). Utiliser UpgradeModule et les APIs statiques de @angular/upgrade/static.",
300
+ "estimated_time_per_occurrence": 30,
301
+ "onFile": null,
302
+ "fileTypes": ["*.ts"],
303
+ "regex": "UpgradeAdapter|from\\s*['\"]@angular/upgrade['\"](?!/static)",
304
+ "astPattern": {
305
+ "nodeType": "ImportDeclaration",
306
+ "moduleSpecifier": "@angular/upgrade",
307
+ "namedImports": {
308
+ "contains": "UpgradeAdapter"
309
+ },
310
+ "excludeContext": ["StringLiteral", "Comment"]
311
+ },
312
+ "category": "imports",
313
+ "isAutoFixable": false,
314
+ "migration_command": null,
315
+ "risk_level": "high",
316
+ "code_description": "// BREAKING CHANGE: UpgradeAdapter supprime\n\n// Avant:\nimport { UpgradeAdapter } from '@angular/upgrade';\nconst adapter = new UpgradeAdapter(AppModule);\n\n// Apres:\nimport { UpgradeModule } from '@angular/upgrade/static';\n\n@NgModule({ imports: [UpgradeModule] })\nexport class AppModule {\n constructor(private upgrade: UpgradeModule) {}\n ngDoBootstrap() {\n this.upgrade.bootstrap(document.body, ['myApp']);\n }\n}",
317
+ "doc_url": "https://angular.dev/api/upgrade/UpgradeAdapter"
318
+ },
319
+ {
320
+ "key": "formArray_directive_conflict",
321
+ "summary": "FormArrayDirective: conflit avec directives custom [formArray]",
322
+ "description": "BREAKING CHANGE: Angular 21 ajoute FormArrayDirective pour les formulaires classiques. Les directives custom utilisant le selecteur [formArray] ou un input formArray entreront en conflit. Renommer vos directives.",
323
+ "estimated_time_per_occurrence": 10,
324
+ "onFile": null,
325
+ "fileTypes": ["*.ts"],
326
+ "regex": "selector\\s*:\\s*['\"]\\[formArray\\]['\"]|@Input\\(['\"]?formArray['\"]?\\)",
327
+ "astPattern": {
328
+ "nodeType": "Decorator",
329
+ "name": "Directive",
330
+ "properties": {
331
+ "selector": "[formArray]"
332
+ },
333
+ "excludeContext": ["StringLiteral", "Comment"]
334
+ },
335
+ "category": "forms",
336
+ "isAutoFixable": false,
337
+ "migration_command": null,
338
+ "risk_level": "medium",
339
+ "code_description": "// BREAKING CHANGE: Conflit avec nouvelle FormArrayDirective\n\n// Avant - Votre directive custom:\n@Directive({\n selector: '[formArray]'\n})\nexport class MyFormArrayDirective {}\n\n// Apres - Renommer:\n@Directive({\n selector: '[customFormArray]'\n})\nexport class MyFormArrayDirective {}",
340
+ "doc_url": "https://blog.ninja-squad.com/2025/11/20/what-is-new-angular-21.0"
341
+ },
342
+ {
343
+ "key": "ngModuleFactory_removed",
344
+ "summary": "NgModuleFactory supprime → utiliser NgModule/standalone",
345
+ "description": "BREAKING CHANGE: NgModuleFactory est completement supprime. Utiliser createNgModule() pour le chargement dynamique ou migrer vers les composants standalone avec import() direct.",
346
+ "estimated_time_per_occurrence": 15,
347
+ "onFile": null,
348
+ "fileTypes": ["*.ts"],
349
+ "regex": "NgModuleFactory|compileModuleAsync|compileModuleSync",
350
+ "astPattern": {
351
+ "nodeType": "Identifier",
352
+ "name": ["NgModuleFactory"],
353
+ "excludeContext": ["StringLiteral", "Comment", "ImportSpecifier"]
354
+ },
355
+ "category": "api",
356
+ "isAutoFixable": false,
357
+ "migration_command": null,
358
+ "risk_level": "high",
359
+ "code_description": "// BREAKING CHANGE: NgModuleFactory supprime\n\n// Avant:\nimport { NgModuleFactory, Compiler } from '@angular/core';\nconst factory = await compiler.compileModuleAsync(LazyModule);\n\n// Apres - createNgModule:\nimport { createNgModule } from '@angular/core';\nconst moduleRef = createNgModule(LazyModule, this.injector);\n\n// Ou standalone (recommande):\nconst comp = await import('./lazy.component').then(m => m.LazyComponent);",
360
+ "doc_url": "https://angular.dev/reference/migrations"
361
+ },
362
+ {
363
+ "key": "emitDeclarationOnly_not_supported",
364
+ "summary": "emitDeclarationOnly non supporte dans tsconfig",
365
+ "description": "BREAKING CHANGE: L'option emitDeclarationOnly du tsconfig n'est plus supportee par le compilateur Angular. Supprimer ou desactiver cette option.",
366
+ "estimated_time_per_occurrence": 5,
367
+ "onFile": "tsconfig.json",
368
+ "fileTypes": ["tsconfig.json", "tsconfig.*.json"],
369
+ "regex": "\"emitDeclarationOnly\"\\s*:\\s*true",
370
+ "category": "config",
371
+ "isAutoFixable": true,
372
+ "migration_command": null,
373
+ "risk_level": "medium",
374
+ "code_description": "// BREAKING CHANGE: emitDeclarationOnly non supporte\n\n// Avant - tsconfig.json:\n{\n \"compilerOptions\": {\n \"emitDeclarationOnly\": true\n }\n}\n\n// Apres - Supprimer ou false:\n{\n \"compilerOptions\": {\n // \"emitDeclarationOnly\": false - ou supprimer\n }\n}",
375
+ "doc_url": "https://angular.dev/reference/migrations"
376
+ },
377
+ {
378
+ "key": "lastSuccessfulNavigation_is_signal",
379
+ "summary": "Router.lastSuccessfulNavigation est un Signal",
380
+ "description": "BREAKING CHANGE: lastSuccessfulNavigation est maintenant un Signal. Appeler comme une fonction: router.lastSuccessfulNavigation(). Migration automatique via ng update.",
381
+ "estimated_time_per_occurrence": 3,
382
+ "onFile": null,
383
+ "fileTypes": ["*.ts"],
384
+ "regex": "\\.lastSuccessfulNavigation(?!\\s*\\()",
385
+ "astPattern": {
386
+ "nodeType": "PropertyAccessExpression",
387
+ "propertyName": "lastSuccessfulNavigation",
388
+ "parent": {
389
+ "notNodeType": "CallExpression"
390
+ },
391
+ "excludeContext": ["StringLiteral", "Comment"]
392
+ },
393
+ "category": "routing",
394
+ "isAutoFixable": true,
395
+ "migration_command": null,
396
+ "risk_level": "medium",
397
+ "code_description": "// BREAKING CHANGE: lastSuccessfulNavigation est un Signal\n\n// Avant:\nconst nav = this.router.lastSuccessfulNavigation;\nconst state = nav?.extras.state;\n\n// Apres:\nconst nav = this.router.lastSuccessfulNavigation();\nconst state = nav?.extras.state;\n\n// Dans template:\n{{ router.lastSuccessfulNavigation()?.extractedUrl }}",
398
+ "doc_url": "https://github.com/angular/angular/pull/63057"
399
+ },
400
+ {
401
+ "key": "currentNavigation_is_signal",
402
+ "summary": "Router.currentNavigation est un Signal (deprecie getCurrentNavigation)",
403
+ "description": "Router.getCurrentNavigation() est deprecie en faveur du signal currentNavigation(). Le signal permet une approche reactive pour tracker l'etat de navigation.",
404
+ "estimated_time_per_occurrence": 3,
405
+ "onFile": null,
406
+ "fileTypes": ["*.ts"],
407
+ "regex": "\\.getCurrentNavigation\\s*\\(",
408
+ "astPattern": {
409
+ "nodeType": "CallExpression",
410
+ "expression": {
411
+ "nodeType": "PropertyAccessExpression",
412
+ "propertyName": "getCurrentNavigation"
413
+ },
414
+ "excludeContext": ["StringLiteral", "Comment"]
415
+ },
416
+ "category": "routing",
417
+ "isAutoFixable": true,
418
+ "migration_command": null,
419
+ "risk_level": "low",
420
+ "code_description": "// getCurrentNavigation() deprecie\n\n// Avant:\nconst nav = this.router.getCurrentNavigation();\n\n// Apres - Signal:\nconst nav = this.router.currentNavigation();",
421
+ "doc_url": "https://angular.dev/api/router/Router"
422
+ }
423
+ ]