@silvestv/migration-planificator 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/LICENSE +96 -0
  2. package/README.fr.md +359 -0
  3. package/README.md +360 -0
  4. package/SECURITY.md +187 -0
  5. package/dist/client.bundle.js +357 -0
  6. package/dist/src/core/app-analyzer.js +134 -0
  7. package/dist/src/core/ast/matchers/html/html-attribute-matcher.js +86 -0
  8. package/dist/src/core/ast/matchers/html/html-component-matcher.js +40 -0
  9. package/dist/src/core/ast/matchers/html/html-element-matcher.js +54 -0
  10. package/dist/src/core/ast/matchers/html/html-parser.js +58 -0
  11. package/dist/src/core/ast/matchers/html/html-pipe-matcher.js +95 -0
  12. package/dist/src/core/ast/matchers/html/html-text-matcher.js +53 -0
  13. package/dist/src/core/ast/matchers/html/index.js +118 -0
  14. package/dist/src/core/ast/matchers/index.js +377 -0
  15. package/dist/src/core/ast/matchers/ts/collection-matcher.js +51 -0
  16. package/dist/src/core/ast/matchers/ts/context-matcher.js +275 -0
  17. package/dist/src/core/ast/matchers/ts/decorator-matcher.js +465 -0
  18. package/dist/src/core/ast/matchers/ts/expression-matcher.js +237 -0
  19. package/dist/src/core/ast/matchers/ts/file-matcher.js +97 -0
  20. package/dist/src/core/ast/matchers/ts/hierarchy-matcher.js +172 -0
  21. package/dist/src/core/ast/matchers/ts/import-matcher.js +39 -0
  22. package/dist/src/core/ast/matchers/ts/index.js +53 -0
  23. package/dist/src/core/ast/matchers/ts/node-matcher.js +156 -0
  24. package/dist/src/core/ast/matchers/ts/symbol-matcher.js +281 -0
  25. package/dist/src/core/ast/matchers/ts/type-matcher.js +207 -0
  26. package/dist/src/core/ast/matchers/utils/matcher-helpers.js +37 -0
  27. package/dist/src/core/ast/scanner-ast.js +444 -0
  28. package/dist/src/core/project-detector.js +196 -0
  29. package/dist/src/core/project-strategy/index.js +9 -0
  30. package/dist/src/core/project-strategy/nx-strategy.js +130 -0
  31. package/dist/src/core/project-strategy/project-strategy.interface.js +2 -0
  32. package/dist/src/core/project-strategy/standalone-strategy.js +74 -0
  33. package/dist/src/core/project-strategy/strategy-factory.js +15 -0
  34. package/dist/src/core/rules-loader.js +89 -0
  35. package/dist/src/core/scan-reporter.js +316 -0
  36. package/dist/src/core/scanner-delta.js +339 -0
  37. package/dist/src/core/scanner-orchestrator.js +266 -0
  38. package/dist/src/core/scanner-regex.js +298 -0
  39. package/dist/src/core/workload/calculator.js +82 -0
  40. package/dist/src/core/workload/constants.js +15 -0
  41. package/dist/src/core/workload/grouping.js +18 -0
  42. package/dist/src/core/workload/hierarchy-calculator.js +127 -0
  43. package/dist/src/core/workload/index.js +11 -0
  44. package/dist/src/core/workload/metadata.js +20 -0
  45. package/dist/src/core/workload/special-workload.js +101 -0
  46. package/dist/src/core/workload/target-resolver.js +34 -0
  47. package/dist/src/data/angular-migration-rules.json +2337 -0
  48. package/dist/src/data/markdown/angular-migration-17-18.md +408 -0
  49. package/dist/src/data/markdown/angular-migration-18-19.md +600 -0
  50. package/dist/src/data/markdown/angular-migration-19-20.md +521 -0
  51. package/dist/src/data/rules/rearchitecture/rearchitecture-rules.json +66 -0
  52. package/dist/src/data/rules/to18/rules-18-obligatoire.json +374 -0
  53. package/dist/src/data/rules/to18/rules-18-optionnelle.json +188 -0
  54. package/dist/src/data/rules/to18/rules-18-recommande.json +218 -0
  55. package/dist/src/data/rules/to19/rules-19-obligatoire.json +348 -0
  56. package/dist/src/data/rules/to19/rules-19-optionnelle.json +223 -0
  57. package/dist/src/data/rules/to19/rules-19-recommande.json +200 -0
  58. package/dist/src/data/rules/to20/rules-20-obligatoire.json +556 -0
  59. package/dist/src/data/rules/to20/rules-20-optionnelle.json +190 -0
  60. package/dist/src/data/rules/to20/rules-20-recommande.json +151 -0
  61. package/dist/src/index.js +161 -0
  62. package/dist/src/models/chip-config.js +45 -0
  63. package/dist/src/models/interfaces/app-details.interface.js +2 -0
  64. package/dist/src/models/interfaces/ast-interfaces.js +5 -0
  65. package/dist/src/models/interfaces/ast-pattern.interface.js +2 -0
  66. package/dist/src/models/interfaces/client-interfaces.js +6 -0
  67. package/dist/src/models/interfaces/detection-stats.interface.js +2 -0
  68. package/dist/src/models/interfaces/html-match.interface.js +2 -0
  69. package/dist/src/models/interfaces/html-report-data.interface.js +2 -0
  70. package/dist/src/models/interfaces/lib-details.interface.js +2 -0
  71. package/dist/src/models/interfaces/migration-rules.interface.js +2 -0
  72. package/dist/src/models/interfaces/parsed-args.interface.js +2 -0
  73. package/dist/src/models/interfaces/project-info.interface.js +2 -0
  74. package/dist/src/models/interfaces/project-overview-data.interface.js +2 -0
  75. package/dist/src/models/interfaces/rule-match.interface.js +2 -0
  76. package/dist/src/models/interfaces/rule.interface.js +2 -0
  77. package/dist/src/models/interfaces/rules-by-priority.interface.js +2 -0
  78. package/dist/src/models/interfaces/scanner-comparison.interface.js +2 -0
  79. package/dist/src/models/interfaces/special-workload.interface.js +2 -0
  80. package/dist/src/models/interfaces/workload-report.interface.js +2 -0
  81. package/dist/src/models/types/build-block-blob.type.js +2 -0
  82. package/dist/src/models/types/migration-version.type.js +2 -0
  83. package/dist/src/models/types/project-type.type.js +2 -0
  84. package/dist/src/models/types/risk-level.type.js +2 -0
  85. package/dist/src/models/types/rule-category.type.js +2 -0
  86. package/dist/src/models/types/rule-priority.type.js +2 -0
  87. package/dist/src/models/types/rule-workload-type.type.js +2 -0
  88. package/dist/src/templates/landing/applications-analyzed.template.js +18 -0
  89. package/dist/src/templates/landing/card-app-info.template.js +63 -0
  90. package/dist/src/templates/landing/card-lib-info.template.js +67 -0
  91. package/dist/src/templates/landing/libs-analyzed.template.js +22 -0
  92. package/dist/src/templates/landing/nx-summary.template.js +115 -0
  93. package/dist/src/templates/landing/project-overview.template.js +27 -0
  94. package/dist/src/templates/page/index-page.template.js +95 -0
  95. package/dist/src/templates/page/main.template.js +83 -0
  96. package/dist/src/templates/page/migration-guide.template.js +175 -0
  97. package/dist/src/templates/page/workload-report.template.js +53 -0
  98. package/dist/src/templates/workload/dashboard.template.js +184 -0
  99. package/dist/src/templates/workload/filters-panel.template.js +215 -0
  100. package/dist/src/templates/workload/guide-rule-card.template.js +107 -0
  101. package/dist/src/templates/workload/hierarchy-nx.template.js +104 -0
  102. package/dist/src/templates/workload/hierarchy-shared.js +163 -0
  103. package/dist/src/templates/workload/hierarchy-standalone.template.js +36 -0
  104. package/dist/src/templates/workload/hierarchy.template.js +35 -0
  105. package/dist/src/templates/workload/rule-modal.template.js +280 -0
  106. package/dist/src/utils/core/args-parser.js +123 -0
  107. package/dist/src/utils/core/array-helpers.js +18 -0
  108. package/dist/src/utils/core/ast-helpers.js +99 -0
  109. package/dist/src/utils/core/file-helpers.js +109 -0
  110. package/dist/src/utils/core/html-helpers.js +36 -0
  111. package/dist/src/utils/core/index.js +28 -0
  112. package/dist/src/utils/core/logger.js +38 -0
  113. package/dist/src/utils/core/rule-helpers.js +15 -0
  114. package/dist/src/utils/core/workload-formatter.js +6 -0
  115. package/dist/src/utils/shared/array-helpers.js +25 -0
  116. package/dist/src/utils/shared/date-helpers.js +109 -0
  117. package/dist/src/utils/shared/html-helpers.js +37 -0
  118. package/dist/src/utils/shared/index.js +25 -0
  119. package/dist/src/utils/shared/rule-helpers.js +20 -0
  120. package/dist/src/utils/shared/time-formatters.js +76 -0
  121. package/dist/styles.css +2 -0
  122. package/package.json +107 -0
@@ -0,0 +1,2337 @@
1
+ {
2
+ "to18": {
3
+ "obligatoire": [
4
+ {
5
+ "key": "node_version_18",
6
+ "summary": "Mise à jour Node.js vers 18.19.1+",
7
+ "description": "Angular 18 nécessite Node.js version 18.19.1 ou supérieure pour supporter les nouvelles fonctionnalités TypeScript 5.4 et les optimisations du compilateur. Cette mise à jour est critique car les versions antérieures peuvent causer des erreurs de build.",
8
+ "estimated_time_per_occurrence": 15,
9
+ "onFile": "package.json",
10
+ "fileTypes": [
11
+ "package.json"
12
+ ],
13
+ "regex": null,
14
+ "category": "environment",
15
+ "auto_fixable": false,
16
+ "migration_command": null,
17
+ "risk_level": "high",
18
+ "code_description": "// Vérifier et mettre à jour Node.js\n// Avant: Node v16.x\n// Après: Node v18.19.1+\n// Commande: node --version",
19
+ "doc_url": "https://angular.dev/reference/migrations"
20
+ },
21
+ {
22
+ "key": "typescript_5_4",
23
+ "summary": "Mise à jour TypeScript vers 5.4+",
24
+ "description": "TypeScript 5.4 apporte des améliorations de performance et de nouveaux contrôles de type requis par Angular 18. La migration est automatisable via npm et nécessite une vérification de compatibilité des types existants.",
25
+ "estimated_time_per_occurrence": 10,
26
+ "onFile": "package.json",
27
+ "fileTypes": [
28
+ "package.json"
29
+ ],
30
+ "regex": null,
31
+ "category": "environment",
32
+ "auto_fixable": true,
33
+ "migration_command": "npm install typescript@~5.4.0",
34
+ "risk_level": "medium",
35
+ "code_description": "// package.json\n// Avant: \"typescript\": \"~4.9.0\"\n// Après: \"typescript\": \"~5.4.0\"",
36
+ "doc_url": "https://angular.dev/reference/migrations"
37
+ },
38
+ {
39
+ "key": "import_async_to_waitForAsync",
40
+ "summary": "Renommer import { async } en { waitForAsync }",
41
+ "description": "L'import 'async' de @angular/core est renommé en 'waitForAsync' pour éviter les conflits avec le mot-clé async/await natif de JavaScript. Tous les tests utilisant async() doivent être mis à jour.",
42
+ "estimated_time_per_occurrence": 2,
43
+ "onFile": null,
44
+ "fileTypes": [
45
+ "*.ts"
46
+ ],
47
+ "regex": "import\\s*\\{[^}]*\\basync\\b[^}]*\\}\\s*from\\s*['\"]@angular/core['\"]",
48
+ "astPattern": {
49
+ "nodeType": "ImportDeclaration",
50
+ "moduleSpecifier": "@angular/core",
51
+ "namedImports": {
52
+ "contains": "async"
53
+ },
54
+ "excludeContext": [
55
+ "StringLiteral",
56
+ "Comment"
57
+ ]
58
+ },
59
+ "category": "imports",
60
+ "auto_fixable": true,
61
+ "migration_command": null,
62
+ "risk_level": "low",
63
+ "code_description": "// Avant:\nimport { async } from '@angular/core';\n\n// Après:\nimport { waitForAsync } from '@angular/core';",
64
+ "doc_url": "https://angular.dev/reference/migrations"
65
+ },
66
+ {
67
+ "key": "import_statekey_transferstate_core",
68
+ "summary": "Déplacer StateKey/TransferState vers @angular/core",
69
+ "description": "StateKey et TransferState sont déplacés de @angular/platform-browser vers @angular/core pour une meilleure cohérence architecturale. Cela affecte principalement le code SSR et le transfert d'état.",
70
+ "estimated_time_per_occurrence": 2,
71
+ "onFile": null,
72
+ "fileTypes": [
73
+ "*.ts"
74
+ ],
75
+ "regex": "import\\s*\\{[^}]*(StateKey|TransferState)[^}]*\\}\\s*from\\s*['\"]@angular/platform-browser['\"]",
76
+ "astPattern": {
77
+ "nodeType": "ImportDeclaration",
78
+ "moduleSpecifier": "@angular/platform-browser",
79
+ "namedImports": {
80
+ "containsAny": [
81
+ "StateKey",
82
+ "TransferState"
83
+ ]
84
+ },
85
+ "excludeContext": [
86
+ "StringLiteral",
87
+ "Comment"
88
+ ]
89
+ },
90
+ "category": "imports",
91
+ "auto_fixable": true,
92
+ "migration_command": null,
93
+ "risk_level": "low",
94
+ "code_description": "// Avant:\nimport { StateKey, TransferState } from '@angular/platform-browser';\n\n// Après:\nimport { StateKey, TransferState } from '@angular/core';",
95
+ "doc_url": "https://angular.dev/reference/migrations"
96
+ },
97
+ {
98
+ "key": "platformDynamicServer_to_platformServer",
99
+ "summary": "Remplacer platformDynamicServer par platformServer",
100
+ "description": "platformDynamicServer est obsolète et remplacé par platformServer qui nécessite un import explicite du compilateur Angular. Cette migration affecte la configuration SSR et nécessite l'ajout de @angular/compiler.",
101
+ "estimated_time_per_occurrence": 5,
102
+ "onFile": null,
103
+ "fileTypes": [
104
+ "*.ts"
105
+ ],
106
+ "regex": "(?<![\"\\'])\\bplatformDynamicServer\\b(?![\"\\'])",
107
+ "astPattern": {
108
+ "nodeType": "Identifier",
109
+ "name": "platformDynamicServer",
110
+ "excludeContext": [
111
+ "Comment",
112
+ "StringLiteral",
113
+ "ImportSpecifier"
114
+ ]
115
+ },
116
+ "category": "api",
117
+ "auto_fixable": true,
118
+ "migration_command": null,
119
+ "risk_level": "medium",
120
+ "code_description": "// Avant:\nimport { platformDynamicServer } from '@angular/platform-server';\n\n// Après:\nimport { platformServer } from '@angular/platform-server';\nimport '@angular/compiler';",
121
+ "doc_url": "https://angular.dev/reference/migrations"
122
+ },
123
+ {
124
+ "key": "remove_ServerTransferStateModule",
125
+ "summary": "Supprimer ServerTransferStateModule (obsolète)",
126
+ "description": "Supprime le module `ServerTransferStateModule`, supprimé en Angular 18. Il est désormais remplacé par le mécanisme `TransferState` intégré côté client.",
127
+ "estimated_time_per_occurrence": 3,
128
+ "onFile": null,
129
+ "fileTypes": [
130
+ "*.ts"
131
+ ],
132
+ "regex": "\\bServerTransferStateModule\\b",
133
+ "astPattern": {
134
+ "nodeType": "Identifier",
135
+ "name": "ServerTransferStateModule",
136
+ "excludeContext": [
137
+ "Comment",
138
+ "StringLiteral",
139
+ "ImportSpecifier"
140
+ ],
141
+ "context": [
142
+ "ArrayLiteralExpression",
143
+ "ImportDeclaration"
144
+ ]
145
+ },
146
+ "category": "imports",
147
+ "auto_fixable": true,
148
+ "migration_command": null,
149
+ "risk_level": "low",
150
+ "code_description": "// Avant:\nimport { ServerTransferStateModule } from '@angular/platform-server';\n@NgModule({ imports: [ServerTransferStateModule] })\n\n// Après:\n// Supprimer complètement, plus nécessaire",
151
+ "doc_url": "https://angular.dev/update-guide"
152
+ },
153
+ {
154
+ "key": "remove_matchesElement",
155
+ "summary": "Supprimer animationDriver.matchesElement()",
156
+ "description": "La méthode matchesElement() de l'animation driver est supprimée sans remplacement direct. Il faut refactoriser le code pour utiliser des approches alternatives de sélection d'éléments comme querySelector ou des directives Angular.",
157
+ "estimated_time_per_occurrence": 10,
158
+ "onFile": null,
159
+ "fileTypes": [
160
+ "*.ts"
161
+ ],
162
+ "regex": "\\.matchesElement\\s*\\(",
163
+ "astPattern": {
164
+ "nodeType": "CallExpression",
165
+ "expression": {
166
+ "nodeType": "PropertyAccessExpression",
167
+ "propertyName": "matchesElement"
168
+ },
169
+ "excludeContext": [
170
+ "StringLiteral",
171
+ "Comment"
172
+ ]
173
+ },
174
+ "category": "api",
175
+ "auto_fixable": false,
176
+ "migration_command": null,
177
+ "risk_level": "medium",
178
+ "code_description": "// Avant:\nanimationDriver.matchesElement(element, selector);\n\n// Après:\n// Supprimer - utiliser une autre approche\n// (pas de remplacement direct)",
179
+ "doc_url": "https://angular.dev/reference/migrations"
180
+ },
181
+ {
182
+ "key": "remove_isPlatformWorkerUi",
183
+ "summary": "Supprimer isPlatformWorkerUi()",
184
+ "description": "La fonction isPlatformWorkerUi() n'est plus supportée car Web Workers UI ne sont plus maintenus dans Angular. Supprimer tout le code dépendant de cette détection de plateforme.",
185
+ "estimated_time_per_occurrence": 5,
186
+ "onFile": null,
187
+ "fileTypes": [
188
+ "*.ts"
189
+ ],
190
+ "regex": "(?<![\"\\'])\\bisPlatformWorkerUi\\s*\\(",
191
+ "astPattern": {
192
+ "nodeType": "CallExpression",
193
+ "functionName": "isPlatformWorkerUi",
194
+ "excludeContext": [
195
+ "StringLiteral",
196
+ "Comment"
197
+ ]
198
+ },
199
+ "category": "api",
200
+ "auto_fixable": false,
201
+ "migration_command": null,
202
+ "risk_level": "low",
203
+ "code_description": "// Avant:\nif (isPlatformWorkerUi(platformId)) { }\n\n// Après:\n// Supprimer - plus supporté",
204
+ "doc_url": "https://angular.dev/reference/migrations"
205
+ },
206
+ {
207
+ "key": "remove_testability_methods",
208
+ "summary": "Supprimer méthodes Testability.increasePendingRequestCount()",
209
+ "description": "Les méthodes increasePendingRequestCount et decreasePendingRequestCount de Testability sont supprimées car la gestion des requêtes en attente est maintenant automatique via ZoneJS. Retirer tous les appels manuels.",
210
+ "estimated_time_per_occurrence": 5,
211
+ "onFile": null,
212
+ "fileTypes": [
213
+ "*.ts"
214
+ ],
215
+ "regex": "testability\\.(increasePendingRequestCount|decreasePendingRequestCount)\\s*\\(",
216
+ "category": "api",
217
+ "auto_fixable": false,
218
+ "migration_command": null,
219
+ "risk_level": "low",
220
+ "code_description": "// Avant:\ntestability.increasePendingRequestCount();\n\n// Après:\n// Supprimer - géré automatiquement par ZoneJS",
221
+ "astPattern": {
222
+ "nodeType": "CallExpression",
223
+ "expression": {
224
+ "nodeType": "PropertyAccessExpression",
225
+ "propertyName": [
226
+ "increasePendingRequestCount",
227
+ "decreasePendingRequestCount"
228
+ ]
229
+ },
230
+ "excludeContext": [
231
+ "StringLiteral",
232
+ "Comment"
233
+ ]
234
+ },
235
+ "doc_url": "https://angular.dev/reference/migrations"
236
+ },
237
+ {
238
+ "key": "ngModel_no_assignment",
239
+ "summary": "Pas d'assignation dans [(ngModel)]",
240
+ "description": "Angular 18 interdit les expressions d'assignation directement dans la liaison [(ngModel)] pour éviter les effets de bord imprévus. Utiliser (ngModelChange) pour gérer les transformations de valeur.",
241
+ "estimated_time_per_occurrence": 5,
242
+ "onFile": null,
243
+ "fileTypes": [
244
+ "*.html"
245
+ ],
246
+ "regex": "\\[\\(ngModel\\)\\]\\s*=\\s*[\"'][^\"']*=",
247
+ "category": "template",
248
+ "auto_fixable": false,
249
+ "migration_command": null,
250
+ "risk_level": "medium",
251
+ "code_description": "// Avant:\n<input [(ngModel)]=\"user.name = process()\">\n\n// Après:\n<input [(ngModel)]=\"user.name\" (ngModelChange)=\"onChange($event)\">",
252
+ "doc_url": "https://angular.dev/reference/migrations"
253
+ },
254
+ {
255
+ "key": "providers_router_outlet",
256
+ "summary": "Déplacer providers de composant avec <router-outlet>",
257
+ "description": "Les providers définis dans un composant contenant un router-outlet ne sont plus disponibles dans les composants routés. Déplacer ces providers vers le bootstrap de l'application ou la configuration des routes pour assurer leur disponibilité.",
258
+ "estimated_time_per_occurrence": 10,
259
+ "onFile": null,
260
+ "fileTypes": [
261
+ "*.html"
262
+ ],
263
+ "regex": "<router-outlet[^>]*>",
264
+ "category": "architecture",
265
+ "auto_fixable": false,
266
+ "migration_command": null,
267
+ "risk_level": "high",
268
+ "code_description": "// Avant:\n@Component({\n template: `<router-outlet></router-outlet>`,\n providers: [MyService]\n})\n\n// Après:\nbootstrapApplication(App, {\n providers: [MyService]\n});",
269
+ "astPattern": {
270
+ "nodeType": "Decorator",
271
+ "name": "Component",
272
+ "properties": {
273
+ "providers": {
274
+ "exists": true
275
+ }
276
+ },
277
+ "template": {
278
+ "contains": "router-outlet"
279
+ }
280
+ },
281
+ "doc_url": "https://angular.dev/reference/migrations"
282
+ },
283
+ {
284
+ "key": "onpush_host_bindings",
285
+ "summary": "OnPush + host bindings nécessitent markForCheck()",
286
+ "description": "Avec OnPush change detection, les host bindings ne sont plus automatiquement mis à jour. Appeler manuellement markForCheck() après modification des propriétés utilisées dans les host bindings pour forcer la détection de changement.",
287
+ "estimated_time_per_occurrence": 8,
288
+ "onFile": null,
289
+ "fileTypes": [
290
+ "*.ts"
291
+ ],
292
+ "regex": "@Component[\\s\\S]*?(?:host\\s*:\\s*\\{[\\s\\S]*?[\\[\\(]|changeDetection\\s*:\\s*ChangeDetectionStrategy\\.OnPush)[\\s\\S]*?(?:host\\s*:\\s*\\{[\\s\\S]*?[\\[\\(]|changeDetection\\s*:\\s*ChangeDetectionStrategy\\.OnPush)",
293
+ "category": "component",
294
+ "auto_fixable": false,
295
+ "migration_command": null,
296
+ "risk_level": "high",
297
+ "code_description": "// Avant:\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { '[class.active]': 'isActive' }\n})\ntoggle() { this.isActive = true; }\n\n// Après:\ntoggle() {\n this.isActive = true;\n this.cdr.markForCheck();\n}",
298
+ "astPattern": {
299
+ "nodeType": "Decorator",
300
+ "name": "Component",
301
+ "properties": {
302
+ "changeDetection": "ChangeDetectionStrategy.OnPush",
303
+ "host": {
304
+ "containsAny": [
305
+ {
306
+ "nodeType": "StringLiteral",
307
+ "valueMatches": "\\[.*\\]"
308
+ },
309
+ {
310
+ "nodeType": "StringLiteral",
311
+ "valueMatches": "\\(.*\\)"
312
+ }
313
+ ]
314
+ }
315
+ },
316
+ "excludeContext": [
317
+ "StringLiteral",
318
+ "Comment"
319
+ ]
320
+ },
321
+ "doc_url": "https://angular.dev/reference/migrations"
322
+ },
323
+ {
324
+ "key": "test_ignoreChangesOutsideZone",
325
+ "summary": "Tests: ajouter ignoreChangesOutsideZone si problèmes",
326
+ "description": "Si les tests avec fixture.autoDetect rencontrent des problèmes d'ordre ou de timing, ajouter ignoreChangesOutsideZone: true dans la configuration de ZoneChangeDetection pour ignorer les changements déclenchés hors de la zone Angular.",
327
+ "estimated_time_per_occurrence": 5,
328
+ "onFile": null,
329
+ "fileTypes": [
330
+ "*.spec.ts"
331
+ ],
332
+ "regex": "fixture\\.autoDetect\\s*=\\s*true",
333
+ "category": "test",
334
+ "auto_fixable": false,
335
+ "migration_command": null,
336
+ "risk_level": "medium",
337
+ "code_description": "// Si tests échouent avec auto-detect:\nTestBed.configureTestingModule({\n providers: [\n provideZoneChangeDetection({\n ignoreChangesOutsideZone: true\n })\n ]\n});",
338
+ "astPattern": {
339
+ "nodeType": "BinaryExpression",
340
+ "left": {
341
+ "nodeType": "PropertyAccessExpression",
342
+ "object": "fixture",
343
+ "property": "autoDetect"
344
+ },
345
+ "right": {
346
+ "value": true
347
+ },
348
+ "excludeContext": [
349
+ "StringLiteral",
350
+ "Comment"
351
+ ],
352
+ "operatorKind": "EqualsToken"
353
+ },
354
+ "doc_url": "https://angular.dev/reference/migrations"
355
+ }
356
+ ],
357
+ "recommande": [
358
+ {
359
+ "key": "redirectTo_function",
360
+ "summary": "redirectTo peut maintenant être une fonction",
361
+ "description": "La propriété redirectTo dans les routes Angular peut maintenant accepter une fonction en plus des strings, permettant des redirections dynamiques basées sur l'état de l'application. Cette fonctionnalité est optionnelle mais recommandée pour plus de flexibilité.",
362
+ "estimated_time_per_occurrence": 5,
363
+ "onFile": null,
364
+ "fileTypes": [
365
+ "*.ts"
366
+ ],
367
+ "regex": "redirectTo\\s*:\\s*['\"]",
368
+ "category": "routing",
369
+ "auto_fixable": false,
370
+ "migration_command": null,
371
+ "risk_level": "low",
372
+ "code_description": "// Avant:\nredirectTo: '/new'\n\n// Après (optionnel):\nredirectTo: () => '/new'",
373
+ "astPattern": {
374
+ "nodeType": "PropertyAssignment",
375
+ "name": "redirectTo",
376
+ "initializer": {
377
+ "nodeType": "StringLiteral"
378
+ },
379
+ "excludeContext": [
380
+ "StringLiteral",
381
+ "Comment"
382
+ ]
383
+ },
384
+ "doc_url": "https://angular.dev/reference/migrations"
385
+ },
386
+ {
387
+ "key": "zoneless_ts_provider",
388
+ "summary": "Activer le mode zoneless via provider (main.ts)",
389
+ "description": "Active le mode Zoneless en ajoutant le provider `provideZonelessChangeDetection()` dans le bootstrap (`main.ts`). Cette approche supprime la dépendance à ZoneJS et améliore la performance de détection de changements.",
390
+ "estimated_time_per_occurrence": 30,
391
+ "onFile": null,
392
+ "fileTypes": [
393
+ "*.ts"
394
+ ],
395
+ "regex": "\\bbootstrapApplication\\s*\\(",
396
+ "category": "architecture",
397
+ "auto_fixable": false,
398
+ "migration_command": null,
399
+ "risk_level": "high",
400
+ "code_description": "// Avant (exemple):\nbootstrapApplication(AppComponent, { providers: [] });\n\n// Après:\nbootstrapApplication(AppComponent, {\n providers: [provideZonelessChangeDetection()]\n});",
401
+ "astPattern": {
402
+ "nodeType": "CallExpression",
403
+ "functionName": "bootstrapApplication",
404
+ "excludeContext": [
405
+ "StringLiteral",
406
+ "Comment"
407
+ ],
408
+ "arguments": {
409
+ "containsAny": [
410
+ {
411
+ "nodeType": "ObjectLiteralExpression",
412
+ "properties": {
413
+ "providers": {
414
+ "exists": true
415
+ }
416
+ }
417
+ },
418
+ {
419
+ "nodeType": "ObjectLiteralExpression",
420
+ "properties": {}
421
+ }
422
+ ]
423
+ }
424
+ },
425
+ "doc_url": "https://angular.dev/guide/zoneless"
426
+ },
427
+ {
428
+ "key": "zoneless_remove_zone_polyfills",
429
+ "summary": "Zoneless: retirer zone.js des polyfills (build & test)",
430
+ "description": "Supprime `zone.js` et `zone.js/testing` des polyfills définis dans `angular.json`. Cette étape est nécessaire pour les applications utilisant le mode Zoneless (Angular 20).",
431
+ "estimated_time_per_occurrence": 30,
432
+ "onFile": null,
433
+ "fileTypes": [
434
+ "angular.json"
435
+ ],
436
+ "regex": null,
437
+ "category": "architecture",
438
+ "auto_fixable": false,
439
+ "migration_command": null,
440
+ "risk_level": "high",
441
+ "code_description": "// Avant (angular.json):\n\"polyfills\": [\"zone.js\"],\n\"polyfills\": [\"zone.js/testing\"],\n\n// Après:\n\"polyfills\": [],\n// (ou supprimer les entrées liées à zone.js / zone.js/testing)",
442
+ "doc_url": "https://angular.dev/guide/zoneless"
443
+ },
444
+ {
445
+ "key": "event_coalescing",
446
+ "summary": "Activer event coalescing pour optimiser détection",
447
+ "description": "Event coalescing regroupe plusieurs événements déclenchés simultanément en un seul cycle de détection de changement, réduisant ainsi le nombre d'exécutions de change detection et améliorant les performances lors d'interactions utilisateur intensives.",
448
+ "estimated_time_per_occurrence": 5,
449
+ "onFile": null,
450
+ "fileTypes": [
451
+ "*.ts"
452
+ ],
453
+ "regex": "provideZoneChangeDetection\\s*\\(",
454
+ "category": "performance",
455
+ "auto_fixable": false,
456
+ "migration_command": null,
457
+ "risk_level": "low",
458
+ "code_description": "bootstrapApplication(App, {\n providers: [\n provideZoneChangeDetection({ eventCoalescing: true })\n ]\n});",
459
+ "astPattern": {
460
+ "nodeType": "CallExpression",
461
+ "functionName": "provideZoneChangeDetection",
462
+ "arguments": {
463
+ "properties": {
464
+ "eventCoalescing": {
465
+ "missing": true
466
+ }
467
+ }
468
+ },
469
+ "excludeContext": [
470
+ "StringLiteral",
471
+ "Comment"
472
+ ]
473
+ },
474
+ "doc_url": "https://angular.dev/reference/migrations"
475
+ },
476
+ {
477
+ "key": "adopt_signals",
478
+ "summary": "Adopter les signaux pour réactivité optimisée",
479
+ "description": "Remplace les propriétés réactives classiques par l’API `signal()` d’Angular 18. Cette migration améliore la granularité de la réactivité et réduit la dépendance à la détection de changements globale. Commencer par les composants OnPush.",
480
+ "estimated_time_per_occurrence": 10,
481
+ "onFile": null,
482
+ "fileTypes": [
483
+ "*.ts"
484
+ ],
485
+ "regex": "(?<!signal\\s*\\()\\b(?:public|private|protected|readonly|static\\s+)*#?[a-zA-Z_]\\w*\\s*(?:!|\\?)?\\s*(?::[^=;]+)?=\\s*(\\d+|['\"][^'\"]*['\"]|\\[[\\s\\S]*?\\])",
486
+ "category": "architecture",
487
+ "auto_fixable": false,
488
+ "migration_command": null,
489
+ "risk_level": "high",
490
+ "code_description": "// Avant:\ncount: number = 0;\nincrement() { this.count++; }\n\n// Après:\ncount = signal(0);\nincrement() { this.count.update(v => v + 1); }",
491
+ "astPattern": {
492
+ "nodeType": "PropertyDeclaration",
493
+ "initializer": {
494
+ "nodeType": [
495
+ "NumericLiteral",
496
+ "StringLiteral",
497
+ "ArrayLiteralExpression"
498
+ ],
499
+ "notWrappedIn": "signal"
500
+ },
501
+ "inClass": {
502
+ "hasDecorator": [
503
+ "Component",
504
+ "Directive"
505
+ ]
506
+ },
507
+ "notInClass": {
508
+ "hasDecorator": [
509
+ "Injectable"
510
+ ]
511
+ },
512
+ "excludeContext": [
513
+ "StringLiteral",
514
+ "Comment"
515
+ ]
516
+ },
517
+ "doc_url": "https://angular.dev/guide/signals"
518
+ },
519
+ {
520
+ "key": "event_replay_ssr",
521
+ "summary": "Activer Event Replay pour SSR",
522
+ "description": "Ajoute `provideClientHydration(withEventReplay())` pour activer la relecture des événements utilisateur après la réhydratation du DOM côté client (SSR Angular 19).",
523
+ "estimated_time_per_occurrence": 10,
524
+ "onFile": null,
525
+ "fileTypes": [
526
+ "*.ts"
527
+ ],
528
+ "regex": "(?<![\"\\'])\\bprovideClientHydration\\s*\\(",
529
+ "category": "ssr",
530
+ "auto_fixable": false,
531
+ "migration_command": null,
532
+ "risk_level": "medium",
533
+ "code_description": "// main.server.ts\nbootstrapApplication(App, {\n providers: [\n provideClientHydration(withEventReplay())\n ]\n});",
534
+ "astPattern": {
535
+ "nodeType": "CallExpression",
536
+ "functionName": "provideClientHydration",
537
+ "arguments": {
538
+ "missing": "withEventReplay"
539
+ },
540
+ "excludeContext": [
541
+ "StringLiteral",
542
+ "Comment"
543
+ ]
544
+ },
545
+ "doc_url": "https://angular.dev/guide/ssr#hydration"
546
+ },
547
+ {
548
+ "key": "forms_events_unified",
549
+ "summary": "Utiliser control.events unifié",
550
+ "description": "L'API control.events unifie valueChanges et statusChanges en un seul stream d'événements typés, simplifiant la gestion des changements de formulaires et offrant plus de types d'événements (PristineChangeEvent, TouchedChangeEvent, etc.).",
551
+ "estimated_time_per_occurrence": 5,
552
+ "onFile": null,
553
+ "fileTypes": [
554
+ "*.ts"
555
+ ],
556
+ "regex": "\\.(valueChanges|statusChanges)[!?]?\\s*(?:\\.[\\s\\S]*?pipe\\([^)]*\\))?\\s*[\\n\\r\\s]*\\.?subscribe",
557
+ "category": "forms",
558
+ "auto_fixable": false,
559
+ "migration_command": null,
560
+ "risk_level": "low",
561
+ "code_description": "// Avant:\ncontrol.valueChanges.subscribe(v => {});\ncontrol.statusChanges.subscribe(s => {});\n\n// Après:\ncontrol.events.subscribe(event => {\n if (event instanceof ValueChangeEvent) { }\n if (event instanceof StatusChangeEvent) { }\n});",
562
+ "astPattern": {
563
+ "nodeType": "PropertyAccessExpression",
564
+ "propertyName": [
565
+ "valueChanges",
566
+ "statusChanges"
567
+ ],
568
+ "excludeContext": [
569
+ "StringLiteral",
570
+ "Comment"
571
+ ]
572
+ },
573
+ "doc_url": "https://angular.dev/reference/migrations"
574
+ }
575
+ ],
576
+ "optionnelle": [
577
+ {
578
+ "key": "guards_RedirectCommand",
579
+ "summary": "Guards peuvent retourner RedirectCommand",
580
+ "description": "Les guards de routage peuvent maintenant retourner RedirectCommand pour des redirections plus explicites et typées, offrant un meilleur contrôle que les simples UrlTree. Mettre à jour les signatures de type des guards.",
581
+ "estimated_time_per_occurrence": 8,
582
+ "onFile": null,
583
+ "fileTypes": [
584
+ "*.ts"
585
+ ],
586
+ "regex": "canActivate.*:.*boolean\\s*\\|\\s*UrlTree",
587
+ "category": "routing",
588
+ "auto_fixable": false,
589
+ "migration_command": null,
590
+ "risk_level": "medium",
591
+ "code_description": "// Avant:\ncanActivate(): boolean | UrlTree\n\n// Après:\ncanActivate(): boolean | UrlTree | RedirectCommand {\n return new RedirectCommand('/login');\n}",
592
+ "astPattern": {
593
+ "nodeType": "MethodDeclaration",
594
+ "name": [
595
+ "canActivate",
596
+ "canActivateChild",
597
+ "canDeactivate",
598
+ "canMatch"
599
+ ],
600
+ "returnType": {
601
+ "union": {
602
+ "contains": [
603
+ "boolean",
604
+ "UrlTree"
605
+ ],
606
+ "missing": "RedirectCommand"
607
+ }
608
+ },
609
+ "excludeContext": [
610
+ "StringLiteral",
611
+ "Comment"
612
+ ]
613
+ },
614
+ "doc_url": "https://angular.dev/reference/migrations"
615
+ },
616
+ {
617
+ "key": "guards_RedirectCommand_fn",
618
+ "summary": "Guards fonctionnels peuvent retourner RedirectCommand",
619
+ "description": "Les guards fonctionnels (CanActivateFn, CanMatchFn...) peuvent maintenant retourner RedirectCommand pour une meilleure expressivité des redirections.",
620
+ "estimated_time_per_occurrence": 6,
621
+ "fileTypes": [
622
+ "*.ts"
623
+ ],
624
+ "regex": "\\bCan(?:Activate|Match|Deactivate|Load)Fn\\b[\\s\\S]*?boolean\\s*\\|\\s*UrlTree(?![\\s\\S]*RedirectCommand)",
625
+ "category": "routing",
626
+ "auto_fixable": false,
627
+ "risk_level": "medium",
628
+ "code_description": "// Avant:\nexport const authGuard: CanActivateFn = (route, state) => {\n return Authservice.isAuth() ? true : inject(Router).createUrlTree(['/login']);\n};\n\n// Après:\nexport const authGuard: CanActivateFn = (route, state) => {\n return Authservice.isAuth() ? true : new RedirectCommand('/login');\n};",
629
+ "astPattern": {
630
+ "nodeType": "VariableDeclaration",
631
+ "type": {
632
+ "containsAny": [
633
+ "CanActivateFn",
634
+ "CanMatchFn",
635
+ "CanDeactivateFn",
636
+ "CanLoadFn"
637
+ ]
638
+ },
639
+ "initializer": {
640
+ "nodeType": "ArrowFunction"
641
+ },
642
+ "returnType": {
643
+ "union": {
644
+ "contains": [
645
+ "boolean",
646
+ "UrlTree"
647
+ ],
648
+ "missing": "RedirectCommand"
649
+ }
650
+ }
651
+ },
652
+ "doc_url": "https://angular.dev/guide/router#redirectcommand"
653
+ },
654
+ {
655
+ "key": "ng_content_fallback",
656
+ "summary": "Utiliser fallback content dans ng-content",
657
+ "description": "Angular 18 permet de définir un contenu par défaut à l'intérieur de ng-content qui sera affiché si aucun contenu n'est projeté. Utile pour créer des composants avec des valeurs par défaut sans logique conditionnelle complexe.",
658
+ "estimated_time_per_occurrence": 3,
659
+ "onFile": null,
660
+ "fileTypes": [
661
+ "*.html",
662
+ "*.ts"
663
+ ],
664
+ "regex": "<ng-content[^>]*></ng-content>",
665
+ "category": "template",
666
+ "auto_fixable": false,
667
+ "migration_command": null,
668
+ "risk_level": "low",
669
+ "code_description": "// Avant:\n<ng-content></ng-content>\n\n// Après:\n<ng-content>\n <div class=\"default\">Contenu par défaut</div>\n</ng-content>",
670
+ "doc_url": "https://angular.dev/reference/migrations"
671
+ },
672
+ {
673
+ "key": "http_cache_auth",
674
+ "summary": "Cache HTTP avec headers auth",
675
+ "description": "Par défaut, le cache HTTP de transfert d'état exclut les requêtes avec headers d'authentification. Activer includeRequestsWithAuthHeaders: true si votre application nécessite le cache de ces requêtes.",
676
+ "estimated_time_per_occurrence": 5,
677
+ "fileTypes": [
678
+ "*.ts"
679
+ ],
680
+ "category": "http",
681
+ "risk_level": "low",
682
+ "auto_fixable": false,
683
+ "regex": "provideHttpClient\\s*\\([^)]*withHttpTransferCache\\s*\\([^)]*\\)",
684
+ "code_description": "provideHttpClient(\n withHttpTransferCache({ \n includeRequestsWithAuthHeaders: true \n })\n)",
685
+ "astPattern": {
686
+ "nodeType": "CallExpression",
687
+ "functionName": "provideHttpClient",
688
+ "arguments": {
689
+ "containsCall": {
690
+ "functionName": "withHttpTransferCache",
691
+ "object": {
692
+ "properties": {
693
+ "includeRequestsWithAuthHeaders": {
694
+ "missing": true
695
+ }
696
+ }
697
+ }
698
+ }
699
+ }
700
+ },
701
+ "doc_url": "https://angular.dev/reference/migrations"
702
+ },
703
+ {
704
+ "key": "partial_hydration_defer",
705
+ "summary": "Hydratation partielle avec @defer SSR",
706
+ "description": "L'hydratation partielle avec @defer permet de rendre des composants côté serveur mais de différer leur hydratation côté client, réduisant le JavaScript initial et améliorant le TTI.",
707
+ "estimated_time_per_occurrence": 15,
708
+ "fileTypes": [
709
+ "*.html"
710
+ ],
711
+ "category": "ssr",
712
+ "risk_level": "medium",
713
+ "auto_fixable": false,
714
+ "regex": "(?<!<!--[^>]*?)@defer\\s*\\(",
715
+ "code_description": "@defer (render on server; on viewport) {\n <heavy-component />\n} @placeholder {\n <div>Chargement...</div>\n}",
716
+ "doc_url": "https://angular.dev/reference/migrations"
717
+ }
718
+ ]
719
+ },
720
+ "to19": {
721
+ "obligatoire": [
722
+ {
723
+ "key": "typescript_5_5",
724
+ "summary": "Mise à jour TypeScript vers 5.5+",
725
+ "description": "TypeScript 5.5 est requis pour Angular 19 car il inclut des améliorations critiques pour les inferred type predicates et le narrowing de types nécessaires aux nouvelles APIs de signaux. TypeScript 5.4 n'est plus supporté.",
726
+ "estimated_time_per_occurrence": 10,
727
+ "onFile": "package.json",
728
+ "fileTypes": [
729
+ "package.json"
730
+ ],
731
+ "regex": null,
732
+ "category": "environment",
733
+ "auto_fixable": true,
734
+ "migration_command": "npm install typescript@~5.5.0",
735
+ "risk_level": "medium",
736
+ "code_description": "// package.json\n// Avant: \"typescript\": \"~5.4.0\"\n// Après: \"typescript\": \"~5.5.0\"",
737
+ "doc_url": "https://angular.dev/reference/migrations"
738
+ },
739
+ {
740
+ "key": "standalone_default",
741
+ "summary": "Angular 19 : standalone est maintenant true par défaut – ajoutez standalone: false ou migrez",
742
+ "description": "BREAKING CHANGE MAJEUR : Angular 19 définit `standalone: true` comme valeur par défaut. Les composants hérités doivent explicitement ajouter `standalone: false` ou être migrés via les trois schematics officiels Angular.",
743
+ "estimated_time_per_occurrence": 120,
744
+ "fileTypes": [
745
+ "*.ts"
746
+ ],
747
+ "regex": "@(Component|Directive|Pipe)\\s*\\(",
748
+ "category": "architecture",
749
+ "auto_fixable": false,
750
+ "migration_command": "ng g @angular/core:convert-to-standalone && ng g @angular/core:remove-ng-modules && ng g @angular/core:bootstrap-standalone",
751
+ "risk_level": "critical",
752
+ "code_description": "// ⚠️ BREAKING : standalone est true par défaut à partir de v19\n\n// Option 1 : Ajout explicite (fix temporaire)\n@Component({\n standalone: false,\n ...\n})\n\n// Option 2 : Migration complète (recommandée)\n// ng g @angular/core:convert-to-standalone\n// ng g @angular/core:remove-ng-modules\n// ng g @angular/core:bootstrap-standalone",
753
+ "astPattern": {
754
+ "nodeType": "Decorator",
755
+ "name": [
756
+ "Component",
757
+ "Directive",
758
+ "Pipe"
759
+ ],
760
+ "inClass": {
761
+ "nodeType": "ClassDeclaration"
762
+ },
763
+ "properties": {
764
+ "standalone": {
765
+ "missing": true
766
+ }
767
+ },
768
+ "excludeContext": [
769
+ "StringLiteral",
770
+ "Comment"
771
+ ]
772
+ },
773
+ "doc_url": "https://angular.dev/reference/migrations/standalone"
774
+ },
775
+ {
776
+ "key": "template_refs_no_this",
777
+ "summary": "Supprimer 'this.' devant les template refs",
778
+ "description": "Angular 19 interdit l'utilisation du préfixe 'this.' devant les références de template (#ref) car elles ne sont pas des propriétés de classe mais des variables de template locales. Cette restriction améliore la clarté et évite les confusions.",
779
+ "estimated_time_per_occurrence": 2,
780
+ "onFile": null,
781
+ "fileTypes": [
782
+ "*.html"
783
+ ],
784
+ "regex": "\\{\\{\\s*this\\.#\\w+",
785
+ "category": "template",
786
+ "auto_fixable": true,
787
+ "migration_command": null,
788
+ "risk_level": "medium",
789
+ "code_description": "// Avant:\n<div #myDiv></div>\n{{ this.myDiv.textContent }}\n\n// Après:\n<div #myDiv></div>\n{{ myDiv.textContent }}",
790
+ "doc_url": "https://angular.dev/reference/migrations"
791
+ },
792
+ {
793
+ "key": "browserModule_withServerTransition",
794
+ "summary": "BrowserModule.withServerTransition → APP_ID token",
795
+ "description": "BrowserModule.withServerTransition est obsolète dans l'architecture standalone. Utiliser le token APP_ID directement dans les providers du bootstrap pour identifier l'application dans un contexte SSR multi-apps.",
796
+ "estimated_time_per_occurrence": 10,
797
+ "onFile": null,
798
+ "fileTypes": [
799
+ "*.ts"
800
+ ],
801
+ "regex": "BrowserModule\\.withServerTransition",
802
+ "category": "imports",
803
+ "auto_fixable": false,
804
+ "migration_command": null,
805
+ "risk_level": "medium",
806
+ "code_description": "// Avant:\nBrowserModule.withServerTransition({ appId: 'my-app' })\n\n// Après:\nimport { APP_ID } from '@angular/core';\nbootstrapApplication(App, {\n providers: [{ provide: APP_ID, useValue: 'my-app' }]\n});",
807
+ "astPattern": {
808
+ "nodeType": "CallExpression",
809
+ "expression": {
810
+ "nodeType": "PropertyAccessExpression",
811
+ "expression": {
812
+ "nodeType": "Identifier",
813
+ "name": "BrowserModule"
814
+ },
815
+ "name": "withServerTransition"
816
+ },
817
+ "excludeContext": [
818
+ "StringLiteral",
819
+ "Comment"
820
+ ]
821
+ },
822
+ "doc_url": "https://angular.dev/reference/migrations"
823
+ },
824
+ {
825
+ "key": "keyValueDiffers_factories",
826
+ "summary": "KeyValueDiffers.factories propriété supprimée",
827
+ "description": "La propriété factories de KeyValueDiffers est supprimée car elle exposait des détails d'implémentation internes. Utiliser directement les méthodes find() et create() qui offrent une API plus simple et plus stable.",
828
+ "estimated_time_per_occurrence": 5,
829
+ "onFile": null,
830
+ "fileTypes": [
831
+ "*.ts"
832
+ ],
833
+ "regex": "\\.factories\\b",
834
+ "category": "api",
835
+ "auto_fixable": false,
836
+ "migration_command": null,
837
+ "risk_level": "low",
838
+ "code_description": "// Avant:\nconst factories = differs.factories;\n\n// Après:\n// Utiliser directement find() et create()\nconst differ = differs.find({}).create();",
839
+ "astPattern": {
840
+ "nodeType": "PropertyAccessExpression",
841
+ "propertyName": "factories",
842
+ "excludeContext": [
843
+ "StringLiteral",
844
+ "Comment"
845
+ ]
846
+ },
847
+ "doc_url": "https://angular.dev/reference/migrations"
848
+ },
849
+ {
850
+ "key": "angular_json_localize_name_to_project",
851
+ "summary": "angular.json localize: 'name' → 'project'",
852
+ "description": "Le builder @angular/localize utilise maintenant 'project' au lieu de 'name' pour plus de cohérence avec les autres builders Angular. Mise à jour automatisable via ng update mais nécessite une vérification manuelle des configurations personnalisées.",
853
+ "estimated_time_per_occurrence": 5,
854
+ "onFile": "angular.json",
855
+ "fileTypes": [
856
+ "angular.json"
857
+ ],
858
+ "regex": null,
859
+ "category": "config",
860
+ "auto_fixable": true,
861
+ "migration_command": null,
862
+ "risk_level": "low",
863
+ "code_description": "// angular.json\n// Avant:\n\"options\": { \"name\": \"my-project\" }\n\n// Après:\n\"options\": { \"project\": \"my-project\" }",
864
+ "doc_url": "https://angular.dev/reference/migrations"
865
+ },
866
+ {
867
+ "key": "experimentalPendingTasks_to_pendingTasks",
868
+ "summary": "ExperimentalPendingTasks → PendingTasks",
869
+ "description": "L'API PendingTasks est maintenant stable et sort de sa phase expérimentale. Remplacer tous les imports et usages de ExperimentalPendingTasks par PendingTasks, l'API et le comportement restent identiques.",
870
+ "estimated_time_per_occurrence": 3,
871
+ "onFile": null,
872
+ "fileTypes": [
873
+ "*.ts"
874
+ ],
875
+ "regex": "\\bExperimentalPendingTasks\\b",
876
+ "category": "api",
877
+ "auto_fixable": true,
878
+ "migration_command": null,
879
+ "risk_level": "low",
880
+ "code_description": "// Avant:\nimport { ExperimentalPendingTasks } from '@angular/core';\n\n// Après:\nimport { PendingTasks } from '@angular/core';",
881
+ "astPattern": {
882
+ "nodeType": "Identifier",
883
+ "name": "ExperimentalPendingTasks",
884
+ "excludeContext": [
885
+ "Comment",
886
+ "StringLiteral"
887
+ ]
888
+ },
889
+ "doc_url": "https://angular.dev/reference/migrations"
890
+ },
891
+ {
892
+ "key": "router_errorHandler_migration",
893
+ "summary": "Router.errorHandler → withNavigationErrorHandler",
894
+ "description": "L'assignation directe de router.errorHandler est dépréciée au profit de withNavigationErrorHandler dans la configuration du router, offrant une meilleure intégration avec l'architecture standalone et permettant un tree-shaking plus efficace.",
895
+ "estimated_time_per_occurrence": 10,
896
+ "onFile": null,
897
+ "fileTypes": [
898
+ "*.ts"
899
+ ],
900
+ "regex": "router\\.errorHandler\\s*=",
901
+ "category": "routing",
902
+ "auto_fixable": false,
903
+ "migration_command": null,
904
+ "risk_level": "medium",
905
+ "code_description": "// Avant:\nthis.router.errorHandler = (error) => { };\n\n// Après:\nprovideRouter(routes,\n withNavigationErrorHandler((error) => { })\n)",
906
+ "astPattern": {
907
+ "nodeType": "BinaryExpression",
908
+ "left": {
909
+ "nodeType": "PropertyAccessExpression",
910
+ "propertyName": "errorHandler"
911
+ },
912
+ "excludeContext": [
913
+ "StringLiteral",
914
+ "Comment"
915
+ ],
916
+ "operatorKind": "EqualsToken"
917
+ },
918
+ "doc_url": "https://angular.dev/reference/migrations"
919
+ },
920
+ {
921
+ "key": "resolve_redirectCommand",
922
+ "summary": "Resolve peut retourner RedirectCommand",
923
+ "description": "Les resolvers peuvent maintenant retourner RedirectCommand pour gérer les redirections de manière plus explicite et typée. Mettre à jour les signatures de type pour inclure RedirectCommand dans les valeurs de retour possibles des resolvers.",
924
+ "estimated_time_per_occurrence": 8,
925
+ "onFile": null,
926
+ "fileTypes": [
927
+ "*.ts"
928
+ ],
929
+ "regex": "implements\\s+Resolve<(?!.*RedirectCommand)",
930
+ "category": "routing",
931
+ "auto_fixable": false,
932
+ "migration_command": null,
933
+ "risk_level": "medium",
934
+ "code_description": "// Avant:\nResolve<User>\n\n// Après:\nResolve<User | RedirectCommand> {\n return this.service.get().pipe(\n catchError(() => of(new RedirectCommand('/error')))\n );\n}",
935
+ "astPattern": {
936
+ "nodeType": "ClassDeclaration",
937
+ "implements": "Resolve",
938
+ "implementsGeneric": {
939
+ "missing": "RedirectCommand"
940
+ },
941
+ "excludeContext": [
942
+ "StringLiteral",
943
+ "Comment"
944
+ ]
945
+ },
946
+ "doc_url": "https://angular.dev/reference/migrations"
947
+ },
948
+ {
949
+ "key": "tests_effects_timing",
950
+ "summary": "Tests effects: ajouter await fixture.whenStable()",
951
+ "description": "Les effects Angular 19 s'exécutent de manière asynchrone et ne sont plus automatiquement flush dans les tests. Ajouter await fixture.whenStable() ou fixture.detectChanges() après avoir déclenché un effect pour s'assurer que toutes les mises à jour sont appliquées avant les assertions.",
952
+ "estimated_time_per_occurrence": 3,
953
+ "onFile": null,
954
+ "fileTypes": [
955
+ "*.spec.ts"
956
+ ],
957
+ "regex": "triggerEffect|effect\\(\\s*\\(\\)",
958
+ "category": "test",
959
+ "auto_fixable": false,
960
+ "migration_command": null,
961
+ "risk_level": "medium",
962
+ "code_description": "// Avant:\ncomponent.triggerEffect();\nexpect(value).toBe('updated');\n\n// Après:\ncomponent.triggerEffect();\nawait fixture.whenStable();\nexpect(value).toBe('updated');",
963
+ "astPattern": {
964
+ "nodeType": "CallExpression",
965
+ "functionName": [
966
+ "effect",
967
+ "triggerEffect"
968
+ ],
969
+ "inFile": "*.spec.ts",
970
+ "nextStatement": {
971
+ "notContains": [
972
+ "fixture.whenStable",
973
+ "fixture.detectChanges"
974
+ ]
975
+ },
976
+ "excludeContext": [
977
+ "StringLiteral",
978
+ "Comment"
979
+ ]
980
+ },
981
+ "doc_url": "https://angular.dev/reference/migrations"
982
+ },
983
+ {
984
+ "key": "fakeAsync_flush_default",
985
+ "summary": "fakeAsync flush automatique par défaut",
986
+ "description": "En Angular 19, fakeAsync flush automatiquement tous les timers en attente à la fin du test par défaut. Si l'ancien comportement est nécessaire (pas de flush automatique), passer l'option {flush: false} au wrapper fakeAsync.",
987
+ "estimated_time_per_occurrence": 5,
988
+ "onFile": null,
989
+ "fileTypes": [
990
+ "*.spec.ts"
991
+ ],
992
+ "regex": "fakeAsync\\s*\\(",
993
+ "category": "test",
994
+ "auto_fixable": false,
995
+ "migration_command": null,
996
+ "risk_level": "low",
997
+ "code_description": "// En v19: flush automatique des timers\n// Pour ancien comportement:\nfakeAsync(() => {\n // ...\n}, { flush: false })",
998
+ "astPattern": {
999
+ "nodeType": "CallExpression",
1000
+ "functionName": "fakeAsync",
1001
+ "arguments": {
1002
+ "count": 1,
1003
+ "secondArgument": {
1004
+ "missing": true
1005
+ }
1006
+ },
1007
+ "excludeContext": [
1008
+ "StringLiteral",
1009
+ "Comment"
1010
+ ]
1011
+ },
1012
+ "doc_url": "https://angular.dev/reference/migrations"
1013
+ },
1014
+ {
1015
+ "key": "createComponent_projectableNodes",
1016
+ "summary": "createComponent: passer projectableNodes vide pour éviter fallback",
1017
+ "description": "createComponent affiche maintenant le contenu fallback par défaut si aucun projectableNodes n'est fourni. Pour éviter ce comportement et garder ng-content vide, passer explicitement projectableNodes avec un tableau contenant un nœud texte vide.",
1018
+ "estimated_time_per_occurrence": 3,
1019
+ "onFile": null,
1020
+ "fileTypes": [
1021
+ "*.ts"
1022
+ ],
1023
+ "regex": "createComponent\\s*\\(",
1024
+ "category": "api",
1025
+ "auto_fixable": false,
1026
+ "migration_command": null,
1027
+ "risk_level": "low",
1028
+ "code_description": "// Pour éviter fallback par défaut:\ncreateComponent(MyComponent, {\n projectableNodes: [[document.createTextNode('')]]\n});",
1029
+ "astPattern": {
1030
+ "nodeType": "CallExpression",
1031
+ "functionName": "createComponent",
1032
+ "arguments": {
1033
+ "properties": {
1034
+ "projectableNodes": {
1035
+ "missing": true
1036
+ }
1037
+ }
1038
+ },
1039
+ "excludeContext": [
1040
+ "StringLiteral",
1041
+ "Comment"
1042
+ ]
1043
+ },
1044
+ "doc_url": "https://angular.dev/reference/migrations"
1045
+ }
1046
+ ],
1047
+ "recommande": [
1048
+ {
1049
+ "key": "signal_input_migration",
1050
+ "summary": "Migration @Input → input()",
1051
+ "description": "Migrer les décorateurs @Input vers la nouvelle API input() basée sur les signaux pour bénéficier d'une réactivité fine-grained et d'un meilleur tree-shaking. La migration automatique via schematic gère la plupart des cas, nécessite une revue manuelle pour les inputs complexes.",
1052
+ "estimated_time_per_occurrence": 3,
1053
+ "onFile": null,
1054
+ "fileTypes": [
1055
+ "*.ts"
1056
+ ],
1057
+ "regex": "@Input\\s*\\(\\)",
1058
+ "category": "signals",
1059
+ "auto_fixable": false,
1060
+ "migration_command": "ng generate @angular/core:signal-input-migration",
1061
+ "risk_level": "medium",
1062
+ "code_description": "// Avant:\n@Input() name: string;\n@Input({ required: true }) age: number;\n\n// Après:\nreadonly name = input<string>();\nreadonly age = input.required<number>();",
1063
+ "astPattern": {
1064
+ "nodeType": "Decorator",
1065
+ "name": "Input",
1066
+ "parent": {
1067
+ "nodeType": "PropertyDeclaration"
1068
+ },
1069
+ "excludeContext": [
1070
+ "StringLiteral",
1071
+ "Comment"
1072
+ ]
1073
+ },
1074
+ "doc_url": "https://angular.dev/reference/migrations"
1075
+ },
1076
+ {
1077
+ "key": "signal_output_migration",
1078
+ "summary": "Migration @Output → output()",
1079
+ "description": "Remplacer @Output et EventEmitter par la nouvelle fonction output() qui retourne un OutputEmitterRef typé et interopérable avec les signaux. Plus performant et offre une meilleure intégration avec l'écosystème signals.",
1080
+ "estimated_time_per_occurrence": 3,
1081
+ "onFile": null,
1082
+ "fileTypes": [
1083
+ "*.ts"
1084
+ ],
1085
+ "regex": "@Output\\s*\\(\\)",
1086
+ "category": "signals",
1087
+ "auto_fixable": false,
1088
+ "migration_command": "ng generate @angular/core:output-migration",
1089
+ "risk_level": "low",
1090
+ "code_description": "// Avant:\n@Output() userChange = new EventEmitter<User>();\n\n// Après:\nreadonly userChange = output<User>();",
1091
+ "astPattern": {
1092
+ "nodeType": "Decorator",
1093
+ "name": "Output",
1094
+ "parent": {
1095
+ "nodeType": "PropertyDeclaration",
1096
+ "initializer": {
1097
+ "nodeType": "NewExpression",
1098
+ "expression": "EventEmitter"
1099
+ }
1100
+ },
1101
+ "excludeContext": [
1102
+ "StringLiteral",
1103
+ "Comment"
1104
+ ]
1105
+ },
1106
+ "doc_url": "https://angular.dev/reference/migrations"
1107
+ },
1108
+ {
1109
+ "key": "signal_queries_migration",
1110
+ "summary": "Migration @ViewChild/@ContentChild → viewChild()/contentChild()",
1111
+ "description": "Migrer les query decorators vers les fonctions signal-based viewChild, viewChildren, contentChild et contentChildren. Ces nouvelles APIs retournent des signaux qui se mettent à jour automatiquement quand la vue change, éliminant le besoin de ngAfterViewInit dans beaucoup de cas.",
1112
+ "estimated_time_per_occurrence": 3,
1113
+ "onFile": null,
1114
+ "fileTypes": [
1115
+ "*.ts"
1116
+ ],
1117
+ "regex": "@(ViewChild|ViewChildren|ContentChild|ContentChildren)\\s*\\(",
1118
+ "category": "signals",
1119
+ "auto_fixable": false,
1120
+ "migration_command": "ng generate @angular/core:signal-queries-migration",
1121
+ "risk_level": "medium",
1122
+ "code_description": "// Avant:\n@ViewChild('ref') ref: ElementRef;\n\n// Après:\nreadonly ref = viewChild<ElementRef>('ref');",
1123
+ "astPattern": {
1124
+ "nodeType": "Decorator",
1125
+ "name": [
1126
+ "ViewChild",
1127
+ "ViewChildren",
1128
+ "ContentChild",
1129
+ "ContentChildren"
1130
+ ],
1131
+ "parent": {
1132
+ "nodeType": "PropertyDeclaration"
1133
+ },
1134
+ "excludeContext": [
1135
+ "StringLiteral",
1136
+ "Comment"
1137
+ ]
1138
+ },
1139
+ "doc_url": "https://angular.dev/reference/migrations"
1140
+ },
1141
+ {
1142
+ "key": "linkedSignal_state",
1143
+ "summary": "Utiliser linkedSignal pour état dépendant",
1144
+ "description": "linkedSignal remplace les patterns effect + set pour gérer l'état dérivé avec reset automatique. Quand le signal source change, le signal lié est automatiquement recalculé avec la fonction computation, éliminant la logique manuelle dans effects.",
1145
+ "estimated_time_per_occurrence": 10,
1146
+ "onFile": null,
1147
+ "fileTypes": [
1148
+ "*.ts",
1149
+ "*.spec.ts"
1150
+ ],
1151
+ "regex": "effect\\s*\\(\\s*\\(\\)\\s*=>\\s*\\{[^}]*\\.set\\(",
1152
+ "category": "signals",
1153
+ "auto_fixable": false,
1154
+ "migration_command": null,
1155
+ "risk_level": "low",
1156
+ "code_description": "// Avant:\nngOnInit() {\n effect(() => {\n if (this.category() !== 'all') {\n this.itemsPerPage.set(10);\n }\n });\n}\n\n// Après:\nitemsPerPage = linkedSignal({\n source: this.category,\n computation: () => 10\n});",
1157
+ "astPattern": {
1158
+ "nodeType": "CallExpression",
1159
+ "functionName": "effect",
1160
+ "arguments": {
1161
+ "contains": {
1162
+ "nodeType": "ArrowFunction",
1163
+ "body": {
1164
+ "contains": {
1165
+ "nodeType": "CallExpression",
1166
+ "expression": {
1167
+ "nodeType": "PropertyAccessExpression",
1168
+ "propertyName": [
1169
+ "set",
1170
+ "update"
1171
+ ]
1172
+ }
1173
+ }
1174
+ }
1175
+ }
1176
+ },
1177
+ "excludeContext": [
1178
+ "StringLiteral",
1179
+ "Comment"
1180
+ ]
1181
+ },
1182
+ "doc_url": "https://angular.dev/reference/migrations"
1183
+ },
1184
+ {
1185
+ "key": "provideAppInitializer",
1186
+ "summary": "Utiliser provideAppInitializer au lieu de APP_INITIALIZER",
1187
+ "description": "La fonction provideAppInitializer offre une API plus simple et plus typée que le token APP_INITIALIZER multi-provider. Elle supporte automatiquement inject() et élimine le besoin de deps array, simplifiant la configuration d'initialisation.",
1188
+ "estimated_time_per_occurrence": 5,
1189
+ "onFile": null,
1190
+ "fileTypes": [
1191
+ "*.ts"
1192
+ ],
1193
+ "regex": "provide:\\s*APP_INITIALIZER",
1194
+ "category": "architecture",
1195
+ "auto_fixable": false,
1196
+ "migration_command": null,
1197
+ "risk_level": "low",
1198
+ "code_description": "// Avant:\n{ provide: APP_INITIALIZER, useFactory: ... }\n\n// Après:\nprovideAppInitializer(() => {\n return inject(ConfigService).load();\n})",
1199
+ "astPattern": {
1200
+ "nodeType": "PropertyAssignment",
1201
+ "name": "provide",
1202
+ "initializer": {
1203
+ "nodeType": "Identifier",
1204
+ "name": "APP_INITIALIZER"
1205
+ },
1206
+ "parent": {
1207
+ "nodeType": "ObjectLiteralExpression",
1208
+ "properties": {
1209
+ "useFactory": {
1210
+ "exists": true
1211
+ }
1212
+ }
1213
+ },
1214
+ "excludeContext": [
1215
+ "StringLiteral",
1216
+ "Comment"
1217
+ ]
1218
+ },
1219
+ "doc_url": "https://angular.dev/reference/migrations"
1220
+ },
1221
+ {
1222
+ "key": "routerOutletData",
1223
+ "summary": "Partager données via routerOutletData",
1224
+ "description": "routerOutletData permet de passer des données réactives du composant parent aux composants routés via le router-outlet, évitant le prop drilling ou l'utilisation de services partagés. Les données sont accessibles via le token ROUTER_OUTLET_DATA dans les composants enfants.",
1225
+ "estimated_time_per_occurrence": 10,
1226
+ "onFile": null,
1227
+ "fileTypes": [
1228
+ "*.html"
1229
+ ],
1230
+ "regex": "<router-outlet[^>]*>",
1231
+ "category": "routing",
1232
+ "auto_fixable": false,
1233
+ "migration_command": null,
1234
+ "risk_level": "low",
1235
+ "code_description": "// Parent:\n<router-outlet [routerOutletData]=\"sharedData\"></router-outlet>\n\n// Enfant:\ndata = inject(ROUTER_OUTLET_DATA);",
1236
+ "astPattern": {
1237
+ "nodeType": "Decorator",
1238
+ "name": "Component",
1239
+ "template": {
1240
+ "contains": "router-outlet",
1241
+ "notContains": "routerOutletData"
1242
+ }
1243
+ },
1244
+ "doc_url": "https://angular.dev/reference/migrations"
1245
+ }
1246
+ ],
1247
+ "optionnelle": [
1248
+ {
1249
+ "key": "resource_api_http",
1250
+ "summary": "Utiliser resource API pour gestion HTTP réactive",
1251
+ "description": "L'API resource simplifie la gestion d'état des requêtes HTTP avec status tracking automatique (loading, resolved, error), retry intégré et invalidation réactive. Alternative moderne aux patterns Observable + AsyncPipe, particulièrement utile avec les signaux.",
1252
+ "estimated_time_per_occurrence": 15,
1253
+ "onFile": null,
1254
+ "fileTypes": [
1255
+ "*.ts"
1256
+ ],
1257
+ "regex": "httpClient\\.get\\s*\\(",
1258
+ "category": "reactive",
1259
+ "auto_fixable": false,
1260
+ "migration_command": null,
1261
+ "risk_level": "medium",
1262
+ "code_description": "import { resource } from '@angular/core';\n\nuserResource = resource({\n loader: () => fetch('/api/user').then(r => r.json())\n});\n\n// Template:\n@switch (userResource.status()) {\n @case ('loading') { <p>Chargement...</p> }\n @case ('resolved') { <p>{{ userResource.value() }}</p> }\n}",
1263
+ "astPattern": {
1264
+ "nodeType": "CallExpression",
1265
+ "expression": {
1266
+ "nodeType": "PropertyAccessExpression",
1267
+ "expression": {
1268
+ "name": [
1269
+ "httpClient",
1270
+ "http",
1271
+ "this.httpClient",
1272
+ "this.http"
1273
+ ]
1274
+ },
1275
+ "name": [
1276
+ "get",
1277
+ "post",
1278
+ "put",
1279
+ "delete",
1280
+ "patch"
1281
+ ]
1282
+ },
1283
+ "parent": {
1284
+ "notNodeType": "CallExpression",
1285
+ "notFunctionName": "resource"
1286
+ },
1287
+ "excludeContext": [
1288
+ "StringLiteral",
1289
+ "Comment"
1290
+ ]
1291
+ },
1292
+ "doc_url": "https://angular.dev/reference/migrations"
1293
+ },
1294
+ {
1295
+ "key": "incremental_hydration",
1296
+ "summary": "Hydratation incrémentale expérimentale",
1297
+ "description": "L'hydratation incrémentale permet d'hydrater progressivement l'application Angular côté client, en priorisant les parties visibles et interactives. Combine @defer avec hydrate triggers (viewport, interaction, idle) pour optimiser TTI et réduire le JavaScript initial.",
1298
+ "estimated_time_per_occurrence": 20,
1299
+ "onFile": null,
1300
+ "fileTypes": [
1301
+ "*.ts",
1302
+ "*.html"
1303
+ ],
1304
+ "regex": "(?<![\"\\'])\\bprovideClientHydration\\s*\\(",
1305
+ "category": "ssr",
1306
+ "auto_fixable": false,
1307
+ "migration_command": null,
1308
+ "risk_level": "high",
1309
+ "code_description": "bootstrapApplication(App, {\n providers: [\n provideClientHydration(withIncrementalHydration())\n ]\n});\n\n// Template:\n@defer (render on server; hydrate on interaction) {\n <heavy-component />\n}",
1310
+ "astPattern": {
1311
+ "nodeType": "CallExpression",
1312
+ "functionName": "provideClientHydration",
1313
+ "arguments": {
1314
+ "missing": "withIncrementalHydration"
1315
+ },
1316
+ "excludeContext": [
1317
+ "StringLiteral",
1318
+ "Comment"
1319
+ ]
1320
+ },
1321
+ "doc_url": "https://angular.dev/reference/migrations"
1322
+ },
1323
+ {
1324
+ "key": "service_worker_enhanced",
1325
+ "summary": "Service Worker avec refreshAhead et applicationMaxAge",
1326
+ "description": "Angular 19 ajoute refreshAhead pour pré-charger les assets avant expiration du cache et applicationMaxAge pour définir la durée de vie globale de l'app dans le cache. Améliore l'expérience offline et réduit les latences perçues.",
1327
+ "estimated_time_per_occurrence": 10,
1328
+ "onFile": "ngsw-config.json",
1329
+ "fileTypes": [
1330
+ "ngsw-config.json"
1331
+ ],
1332
+ "regex": null,
1333
+ "category": "pwa",
1334
+ "auto_fixable": false,
1335
+ "migration_command": null,
1336
+ "risk_level": "low",
1337
+ "code_description": "// ngsw-config.json\n{\n \"applicationMaxAge\": \"7d\",\n \"dataGroups\": [{\n \"cacheConfig\": {\n \"maxAge\": \"1h\",\n \"refreshAhead\": \"10m\"\n }\n }]\n}",
1338
+ "doc_url": "https://angular.dev/reference/migrations"
1339
+ },
1340
+ {
1341
+ "key": "hybrid_rendering",
1342
+ "summary": "Rendu hybride par route (SSR/SSG/CSR)",
1343
+ "description": "Le rendu hybride permet de définir une stratégie de rendu différente par route: Prerender pour les pages statiques, Server pour le contenu dynamique, Client pour les parties authentifiées. getPrerenderParams génère les paramètres pour le SSG dynamique.",
1344
+ "estimated_time_per_occurrence": 30,
1345
+ "onFile": null,
1346
+ "fileTypes": [
1347
+ "*.ts"
1348
+ ],
1349
+ "regex": "export\\s+const\\s+routes\\s*:\\s*Routes",
1350
+ "category": "ssr",
1351
+ "auto_fixable": false,
1352
+ "migration_command": null,
1353
+ "risk_level": "medium",
1354
+ "code_description": "// app.routes.server.ts\nimport { RenderMode } from '@angular/ssr';\n\nexport const serverRoutes = [\n { path: '', renderMode: RenderMode.Prerender },\n { path: 'blog/**', renderMode: RenderMode.Server },\n { path: 'admin/**', renderMode: RenderMode.Client }\n];",
1355
+ "astPattern": {
1356
+ "nodeType": "VariableStatement",
1357
+ "modifiers": [
1358
+ "export"
1359
+ ],
1360
+ "declarations": {
1361
+ "name": [
1362
+ "routes",
1363
+ "serverRoutes"
1364
+ ],
1365
+ "initializer": {
1366
+ "nodeType": "ArrayLiteralExpression",
1367
+ "elements": {
1368
+ "nodeType": "ObjectLiteralExpression",
1369
+ "properties": {
1370
+ "path": {
1371
+ "exists": true
1372
+ },
1373
+ "renderMode": {
1374
+ "missing": true
1375
+ }
1376
+ }
1377
+ }
1378
+ }
1379
+ },
1380
+ "excludeContext": [
1381
+ "StringLiteral",
1382
+ "Comment"
1383
+ ]
1384
+ },
1385
+ "doc_url": "https://angular.dev/reference/migrations"
1386
+ },
1387
+ {
1388
+ "key": "control_flow_migration",
1389
+ "summary": "Migration vers Control Flow (@if, @for, @switch)",
1390
+ "description": "Le nouveau Control Flow syntax remplace les directives structurelles *ngIf/*ngFor/*ngSwitch par une syntaxe @if/@for/@switch plus performante et plus lisible. Permet un meilleur tree-shaking et type checking, et élimine le besoin d'importer CommonModule.",
1391
+ "estimated_time_per_occurrence": 3,
1392
+ "onFile": null,
1393
+ "fileTypes": [
1394
+ "*.html",
1395
+ "*.ts"
1396
+ ],
1397
+ "regex": "\\*ng(If|For|Switch)",
1398
+ "category": "template",
1399
+ "auto_fixable": false,
1400
+ "migration_command": "ng generate @angular/core:control-flow",
1401
+ "risk_level": "low",
1402
+ "code_description": "// Avant:\n<div *ngIf=\"show\">Content</div>\n<div *ngFor=\"let item of items\">{{ item }}</div>\n\n// Après:\n@if (show) {\n <div>Content</div>\n}\n@for (item of items; track item.id) {\n <div>{{ item }}</div>\n}",
1403
+ "doc_url": "https://angular.dev/reference/migrations"
1404
+ },
1405
+ {
1406
+ "key": "route_lazy_loading",
1407
+ "summary": "Migration automatique vers lazy loading",
1408
+ "description": "Le schematic route-lazy-loading convertit automatiquement les routes avec component eager loading en loadComponent avec imports dynamiques, réduisant le bundle initial. Analyse le code pour identifier les opportunités de lazy loading et applique les transformations.",
1409
+ "estimated_time_per_occurrence": 5,
1410
+ "onFile": null,
1411
+ "fileTypes": [
1412
+ "*.ts"
1413
+ ],
1414
+ "regex": "\\bcomponent\\s*:\\s*[A-Z]\\w+Component\\b",
1415
+ "category": "routing",
1416
+ "auto_fixable": false,
1417
+ "migration_command": "ng generate @angular/core:route-lazy-loading",
1418
+ "risk_level": "low",
1419
+ "code_description": "// Avant:\n{ path: 'users', component: UsersComponent }\n\n// Après:\n{\n path: 'users',\n loadComponent: () => import('./users.component').then(m => m.UsersComponent)\n}",
1420
+ "astPattern": {
1421
+ "nodeType": "ObjectLiteralExpression",
1422
+ "properties": {
1423
+ "path": {
1424
+ "exists": true
1425
+ },
1426
+ "component": {
1427
+ "exists": true
1428
+ },
1429
+ "loadComponent": {
1430
+ "missing": true
1431
+ }
1432
+ },
1433
+ "parent": {
1434
+ "nodeType": [
1435
+ "VariableDeclaration",
1436
+ "VariableStatement"
1437
+ ],
1438
+ "name": [
1439
+ "routes",
1440
+ "serverRoutes",
1441
+ "appRoutes"
1442
+ ]
1443
+ },
1444
+ "excludeContext": [
1445
+ "StringLiteral",
1446
+ "Comment"
1447
+ ]
1448
+ },
1449
+ "doc_url": "https://angular.dev/reference/migrations"
1450
+ },
1451
+ {
1452
+ "key": "inject_function_migration",
1453
+ "summary": "Migration constructor → inject()",
1454
+ "description": "La fonction inject() moderne remplace l'injection par constructeur avec une syntaxie plus concise et fonctionnelle. Permet l'injection dans les propriétés de classe, élimine le besoin de déclarer les dépendances deux fois (paramètres + propriétés) et facilite le tree-shaking.",
1455
+ "estimated_time_per_occurrence": 5,
1456
+ "onFile": null,
1457
+ "fileTypes": [
1458
+ "*.ts"
1459
+ ],
1460
+ "regex": "constructor\\s*\\([^)]*private\\s+\\w+\\s*:\\s*\\w+",
1461
+ "category": "di",
1462
+ "auto_fixable": false,
1463
+ "migration_command": "ng generate @angular/core:inject",
1464
+ "risk_level": "low",
1465
+ "code_description": "// Avant:\nconstructor(private http: HttpClient) {}\n\n// Après:\nprivate http = inject(HttpClient);",
1466
+ "astPattern": {
1467
+ "nodeType": "Constructor",
1468
+ "parameters": {
1469
+ "hasModifier": [
1470
+ "private",
1471
+ "public",
1472
+ "protected"
1473
+ ],
1474
+ "hasType": true
1475
+ },
1476
+ "excludeContext": [
1477
+ "StringLiteral",
1478
+ "Comment"
1479
+ ]
1480
+ },
1481
+ "doc_url": "https://angular.dev/reference/migrations"
1482
+ }
1483
+ ]
1484
+ },
1485
+ "to20": {
1486
+ "obligatoire": [
1487
+ {
1488
+ "key": "node_version_20",
1489
+ "summary": "Mise à jour Node.js vers 20.11.1+ (PAS v18, PAS v22.0-22.10)",
1490
+ "description": "Angular 20 requiert Node.js 20.11.1 minimum car cette version inclut des correctifs critiques pour les ESM et la stabilité. ATTENTION: Node.js v18 n'est plus supporté et les versions v22.0 à v22.10 ont des bugs connus incompatibles avec Angular.",
1491
+ "estimated_time_per_occurrence": 15,
1492
+ "onFile": "package.json",
1493
+ "fileTypes": [
1494
+ "package.json"
1495
+ ],
1496
+ "regex": null,
1497
+ "category": "environment",
1498
+ "auto_fixable": false,
1499
+ "migration_command": null,
1500
+ "risk_level": "critical",
1501
+ "code_description": "// Vérifier et mettre à jour Node.js\n// Avant: Node v18.x ou v19.x\n// Après: Node v20.11.1+ REQUIS\n// ⚠️ PAS v22.0 à v22.10\n// Commande: node --version",
1502
+ "doc_url": "https://angular.dev/reference/migrations"
1503
+ },
1504
+ {
1505
+ "key": "typescript_5_8",
1506
+ "summary": "Mise à jour TypeScript vers 5.8+",
1507
+ "description": "TypeScript 5.8 est requis pour Angular 20 car il apporte des améliorations importantes pour les inferred types, const type parameters et path mapping nécessaires aux nouvelles fonctionnalités du compilateur Angular.",
1508
+ "estimated_time_per_occurrence": 10,
1509
+ "onFile": "package.json",
1510
+ "fileTypes": [
1511
+ "package.json"
1512
+ ],
1513
+ "regex": null,
1514
+ "category": "environment",
1515
+ "auto_fixable": true,
1516
+ "migration_command": "npm install typescript@~5.8.0",
1517
+ "risk_level": "medium",
1518
+ "code_description": "// package.json\n// Avant: \"typescript\": \"~5.5.0\"\n// Après: \"typescript\": \"~5.8.0\"",
1519
+ "doc_url": "https://angular.dev/reference/migrations"
1520
+ },
1521
+ {
1522
+ "key": "afterRender_to_afterEveryRender",
1523
+ "summary": "afterRender() → afterEveryRender()",
1524
+ "description": "Le renommage de afterRender en afterEveryRender clarifie que ce hook s'exécute après chaque cycle de rendu, pas une seule fois. Ce changement améliore la compréhension du comportement et évite les bugs liés à une mauvaise interprétation de la sémantique.",
1525
+ "estimated_time_per_occurrence": 3,
1526
+ "onFile": null,
1527
+ "fileTypes": [
1528
+ "*.ts"
1529
+ ],
1530
+ "regex": "\\bafterRender\\s*\\(",
1531
+ "category": "api",
1532
+ "auto_fixable": true,
1533
+ "migration_command": null,
1534
+ "risk_level": "low",
1535
+ "code_description": "// Avant:\nafterRender(() => { });\n\n// Après:\nafterEveryRender(() => { });",
1536
+ "astPattern": {
1537
+ "nodeType": "CallExpression",
1538
+ "functionName": "afterRender",
1539
+ "excludeContext": [
1540
+ "Comment",
1541
+ "StringLiteral"
1542
+ ]
1543
+ },
1544
+ "doc_url": "https://angular.dev/reference/migrations"
1545
+ },
1546
+ {
1547
+ "key": "provideExperimentalCheckNoChanges_to_provideCheckNoChangesConfig",
1548
+ "summary": "provideExperimentalCheckNoChangesForDebug() → provideCheckNoChangesConfig()",
1549
+ "description": "L'API de vérification des changements sort de sa phase expérimentale et devient stable. La propriété useNgZoneOnStable est supprimée car obsolète avec le mode zoneless. Simplement renommer la fonction provider.",
1550
+ "estimated_time_per_occurrence": 3,
1551
+ "onFile": null,
1552
+ "fileTypes": [
1553
+ "*.ts"
1554
+ ],
1555
+ "regex": "provideExperimentalCheckNoChangesForDebug",
1556
+ "category": "api",
1557
+ "auto_fixable": true,
1558
+ "migration_command": null,
1559
+ "risk_level": "low",
1560
+ "code_description": "// Avant:\nprovideExperimentalCheckNoChangesForDebug()\n\n// Après:\nprovideCheckNoChangesConfig()\n// Note: useNgZoneOnStable supprimé",
1561
+ "astPattern": {
1562
+ "nodeType": "CallExpression",
1563
+ "functionName": "provideExperimentalCheckNoChangesForDebug",
1564
+ "excludeContext": [
1565
+ "StringLiteral",
1566
+ "Comment"
1567
+ ]
1568
+ },
1569
+ "doc_url": "https://angular.dev/reference/migrations"
1570
+ },
1571
+ {
1572
+ "key": "provideExperimentalZoneless_to_provideZoneless",
1573
+ "summary": "provideExperimentalZonelessChangeDetection() → provideZonelessChangeDetection()",
1574
+ "description": "Remplace l’API expérimentale `provideExperimentalZonelessChangeDetection` par l’API stable `provideZonelessChangeDetection()` introduite en Angular 20.",
1575
+ "estimated_time_per_occurrence": 3,
1576
+ "onFile": null,
1577
+ "fileTypes": [
1578
+ "*.ts"
1579
+ ],
1580
+ "regex": "\\bprovideExperimentalZonelessChangeDetection\\b",
1581
+ "category": "api",
1582
+ "auto_fixable": true,
1583
+ "migration_command": null,
1584
+ "risk_level": "medium",
1585
+ "code_description": "// Avant:\nprovideExperimentalZonelessChangeDetection()\n\n// Après:\nprovideZonelessChangeDetection()",
1586
+ "astPattern": {
1587
+ "nodeType": "CallExpression",
1588
+ "functionName": "provideExperimentalZonelessChangeDetection",
1589
+ "excludeContext": [
1590
+ "StringLiteral",
1591
+ "Comment"
1592
+ ]
1593
+ },
1594
+ "doc_url": "https://angular.dev/api/core/provideZonelessChangeDetection"
1595
+ },
1596
+ {
1597
+ "key": "resource_request_to_params",
1598
+ "summary": "resource(): 'request' → 'params'",
1599
+ "description": "La propriété 'request' de l'API resource est renommée en 'params' pour mieux refléter son usage comme paramètres de requête réactifs. Le paramètre 'request' dans le loader reste inchangé pour la rétrocompatibilité.",
1600
+ "estimated_time_per_occurrence": 3,
1601
+ "onFile": null,
1602
+ "fileTypes": [
1603
+ "*.ts"
1604
+ ],
1605
+ "regex": "\\bresource\\s*\\(\\s*\\{[^}]*request\\s*:",
1606
+ "category": "api",
1607
+ "auto_fixable": true,
1608
+ "migration_command": null,
1609
+ "risk_level": "medium",
1610
+ "code_description": "// Avant:\nresource({\n request: () => ({ id: userId() }),\n loader: ({ request }) => fetch(`/users/${request.id}`)\n})\n\n// Après:\nresource({\n params: () => ({ id: userId() }),\n loader: ({ request }) => fetch(`/users/${request.id}`)\n})",
1611
+ "astPattern": {
1612
+ "nodeType": "CallExpression",
1613
+ "functionName": "resource",
1614
+ "arguments": {
1615
+ "properties": {
1616
+ "request": {
1617
+ "exists": true
1618
+ }
1619
+ }
1620
+ },
1621
+ "excludeContext": [
1622
+ "StringLiteral",
1623
+ "Comment"
1624
+ ]
1625
+ },
1626
+ "doc_url": "https://angular.dev/reference/migrations"
1627
+ },
1628
+ {
1629
+ "key": "rxResource_loader_to_stream",
1630
+ "summary": "rxResource(): 'loader' → 'stream'",
1631
+ "description": "rxResource renomme 'loader' en 'stream' pour clarifier qu'il retourne un Observable stream plutôt qu'une Promise. Ce changement aligne la terminologie avec les conventions RxJS et améliore la compréhension du comportement asynchrone.",
1632
+ "estimated_time_per_occurrence": 3,
1633
+ "onFile": null,
1634
+ "fileTypes": [
1635
+ "*.ts"
1636
+ ],
1637
+ "regex": "\\brxResource\\s*\\(\\s*\\{[^}]*loader\\s*:",
1638
+ "category": "api",
1639
+ "auto_fixable": true,
1640
+ "migration_command": null,
1641
+ "risk_level": "medium",
1642
+ "code_description": "// Avant:\nrxResource({\n loader: () => httpClient.get('/data')\n})\n\n// Après:\nrxResource({\n stream: () => httpClient.get('/data')\n})",
1643
+ "astPattern": {
1644
+ "nodeType": "CallExpression",
1645
+ "functionName": "rxResource",
1646
+ "arguments": {
1647
+ "properties": {
1648
+ "loader": {
1649
+ "exists": true
1650
+ }
1651
+ }
1652
+ },
1653
+ "excludeContext": [
1654
+ "StringLiteral",
1655
+ "Comment"
1656
+ ]
1657
+ },
1658
+ "doc_url": "https://angular.dev/reference/migrations"
1659
+ },
1660
+ {
1661
+ "key": "resourceStatus_enum_to_string",
1662
+ "summary": "ResourceStatus enum → strings littéraux",
1663
+ "description": "ResourceStatus n'est plus un enum TypeScript mais un union type de strings littéraux pour un meilleur tree-shaking et des bundles plus petits. Remplacer ResourceStatus.Loading par 'loading', etc. Valeurs: 'idle', 'loading', 'resolved', 'error', 'reloading', 'local'.",
1664
+ "estimated_time_per_occurrence": 2,
1665
+ "onFile": null,
1666
+ "fileTypes": [
1667
+ "*.ts"
1668
+ ],
1669
+ "regex": "ResourceStatus\\.(Loading|Idle|Resolved|Error|Reloading|Local)",
1670
+ "category": "api",
1671
+ "auto_fixable": true,
1672
+ "migration_command": null,
1673
+ "risk_level": "medium",
1674
+ "code_description": "// Avant:\nimport { ResourceStatus } from '@angular/core';\nif (resource.status() === ResourceStatus.Loading) { }\n\n// Après:\nif (resource.status() === 'loading') { }\n// Valeurs: 'idle' | 'loading' | 'resolved' | 'error' | 'reloading' | 'local'",
1675
+ "astPattern": {
1676
+ "nodeType": "PropertyAccessExpression",
1677
+ "expression": {
1678
+ "nodeType": "Identifier",
1679
+ "name": "ResourceStatus"
1680
+ },
1681
+ "name": [
1682
+ "Loading",
1683
+ "Idle",
1684
+ "Resolved",
1685
+ "Error",
1686
+ "Reloading",
1687
+ "Local"
1688
+ ],
1689
+ "excludeContext": [
1690
+ "StringLiteral",
1691
+ "Comment"
1692
+ ]
1693
+ },
1694
+ "doc_url": "https://angular.dev/reference/migrations"
1695
+ },
1696
+ {
1697
+ "key": "template_operators_in_void",
1698
+ "summary": "Opérateurs 'in' et 'void' réservés dans templates",
1699
+ "description": "Les mots-clés JavaScript 'in' et 'void' sont maintenant réservés dans les templates Angular et ne peuvent plus être utilisés comme noms de propriétés sans qualification. Utiliser this.in/this.void ou renommer les propriétés pour éviter les conflits.",
1700
+ "estimated_time_per_occurrence": 5,
1701
+ "onFile": null,
1702
+ "fileTypes": [
1703
+ "*.html",
1704
+ "*.ts"
1705
+ ],
1706
+ "regex": "\\{\\{\\s*(in|void)\\s*\\}\\}",
1707
+ "category": "template",
1708
+ "auto_fixable": false,
1709
+ "migration_command": null,
1710
+ "risk_level": "high",
1711
+ "code_description": "// Avant:\n@Component({ template: `{{ in }} {{ void }}` })\nclass MyComponent {\n in = 'value';\n void = 'data';\n}\n\n// Après:\n// Option 1: utiliser this\n{{ this.in }} {{ this.void }}\n\n// Option 2: renommer\ninValue = 'value';\nvoidData = 'data';",
1712
+ "doc_url": "https://angular.dev/reference/migrations"
1713
+ },
1714
+ {
1715
+ "key": "testBed_flushEffects_removed",
1716
+ "summary": "TestBed.flushEffects() supprimé → TestBed.tick()",
1717
+ "description": "TestBed.flushEffects() est supprimé car redondant avec TestBed.tick() qui flush maintenant automatiquement les effects en plus des microtasks et macrotasks. Remplacer tous les appels par TestBed.tick().",
1718
+ "estimated_time_per_occurrence": 3,
1719
+ "onFile": null,
1720
+ "fileTypes": [
1721
+ "*.spec.ts"
1722
+ ],
1723
+ "regex": "TestBed\\.flushEffects\\s*\\(",
1724
+ "category": "test",
1725
+ "auto_fixable": true,
1726
+ "migration_command": null,
1727
+ "risk_level": "medium",
1728
+ "code_description": "// Avant:\nTestBed.flushEffects();\n\n// Après:\nTestBed.tick();",
1729
+ "astPattern": {
1730
+ "nodeType": "CallExpression",
1731
+ "expression": {
1732
+ "nodeType": "PropertyAccessExpression",
1733
+ "object": "TestBed",
1734
+ "property": "flushEffects"
1735
+ },
1736
+ "excludeContext": [
1737
+ "StringLiteral",
1738
+ "Comment"
1739
+ ]
1740
+ },
1741
+ "doc_url": "https://angular.dev/reference/migrations"
1742
+ },
1743
+ {
1744
+ "key": "ng_reflect_attributes_removed",
1745
+ "summary": "Attributs ng-reflect-* supprimés",
1746
+ "description": "Les attributs ng-reflect-* utilisés pour le debugging sont supprimés par défaut pour réduire la taille du DOM. Si nécessaire temporairement, utiliser provideNgReflectAttributes() en dev mode uniquement, mais refactoriser le code pour ne plus dépendre de ces attributs.",
1747
+ "estimated_time_per_occurrence": 10,
1748
+ "onFile": null,
1749
+ "fileTypes": [
1750
+ "*.ts",
1751
+ "*.spec.ts"
1752
+ ],
1753
+ "regex": "getAttribute\\s*\\(\\s*['\"]ng-reflect-",
1754
+ "category": "test",
1755
+ "auto_fixable": false,
1756
+ "migration_command": null,
1757
+ "risk_level": "high",
1758
+ "code_description": "// Avant:\nconst value = element.getAttribute('ng-reflect-disabled');\n\n// Après:\n// Option 1: Provider temporaire (dev only)\nprovideNgReflectAttributes()\n\n// Option 2: Refactoriser le code (recommandé)",
1759
+ "astPattern": {
1760
+ "nodeType": "CallExpression",
1761
+ "functionName": "getAttribute",
1762
+ "arguments": {
1763
+ "contains": {
1764
+ "nodeType": "StringLiteral",
1765
+ "text": {
1766
+ "startsWith": "ng-reflect-"
1767
+ }
1768
+ }
1769
+ },
1770
+ "excludeContext": [
1771
+ "StringLiteral",
1772
+ "Comment"
1773
+ ]
1774
+ },
1775
+ "doc_url": "https://angular.dev/reference/migrations"
1776
+ },
1777
+ {
1778
+ "key": "redirectFn_async_support",
1779
+ "summary": "RedirectFn peut retourner Observable/Promise",
1780
+ "description": "RedirectFn supporte maintenant les valeurs asynchrones via Observable ou Promise en plus des strings/UrlTree synchrones. Permet des redirections conditionnelles basées sur des appels API ou l'état asynchrone de l'application.",
1781
+ "estimated_time_per_occurrence": 5,
1782
+ "onFile": null,
1783
+ "fileTypes": [
1784
+ "*.ts",
1785
+ "*.spec.ts"
1786
+ ],
1787
+ "regex": ":\\s*RedirectFn\\s*=",
1788
+ "category": "routing",
1789
+ "auto_fixable": false,
1790
+ "migration_command": null,
1791
+ "risk_level": "low",
1792
+ "code_description": "// Avant:\nconst redirectFn: RedirectFn = () => '/login';\n\n// Après:\nconst redirectFn: RedirectFn = () => {\n return '/login'; // string\n // OU return of('/login'); // Observable\n // OU return Promise.resolve('/login'); // Promise\n};",
1793
+ "astPattern": {
1794
+ "nodeType": "VariableDeclaration",
1795
+ "type": "RedirectFn",
1796
+ "initializer": {
1797
+ "nodeType": [
1798
+ "ArrowFunction",
1799
+ "FunctionExpression"
1800
+ ],
1801
+ "returnType": {
1802
+ "missing": [
1803
+ "Observable",
1804
+ "Promise"
1805
+ ]
1806
+ }
1807
+ },
1808
+ "excludeContext": [
1809
+ "StringLiteral",
1810
+ "Comment"
1811
+ ]
1812
+ },
1813
+ "doc_url": "https://angular.dev/reference/migrations"
1814
+ },
1815
+ {
1816
+ "key": "route_guards_no_strings",
1817
+ "summary": "Guards: plus de strings dans canActivate",
1818
+ "description": "L'utilisation de strings pour référencer des guards dans canActivate/canDeactivate est dépréciée et supprimée. Utiliser directement les classes de guards ou les fonctions guard, permettant un meilleur type checking et tree-shaking.",
1819
+ "estimated_time_per_occurrence": 5,
1820
+ "onFile": null,
1821
+ "fileTypes": [
1822
+ "*.ts"
1823
+ ],
1824
+ "regex": "canActivate\\s*:\\s*\\[\\s*['\"]",
1825
+ "category": "routing",
1826
+ "auto_fixable": false,
1827
+ "migration_command": null,
1828
+ "risk_level": "medium",
1829
+ "code_description": "// Avant:\ncanActivate: ['authGuard']\n\n// Après:\ncanActivate: [AuthGuard] // ProviderToken ou fonction",
1830
+ "astPattern": {
1831
+ "nodeType": "PropertyAssignment",
1832
+ "name": [
1833
+ "canActivate",
1834
+ "canActivateChild",
1835
+ "canDeactivate",
1836
+ "canMatch"
1837
+ ],
1838
+ "initializer": {
1839
+ "nodeType": "ArrayLiteralExpression",
1840
+ "elements": {
1841
+ "contains": {
1842
+ "nodeType": "StringLiteral"
1843
+ }
1844
+ }
1845
+ },
1846
+ "excludeContext": [
1847
+ "StringLiteral",
1848
+ "Comment"
1849
+ ]
1850
+ },
1851
+ "doc_url": "https://angular.dev/reference/migrations"
1852
+ },
1853
+ {
1854
+ "key": "template_parentheses_strict",
1855
+ "summary": "Parenthèses respectées strictement (comportement JS natif)",
1856
+ "description": "Angular 20 respecte maintenant strictement les règles de parenthèses JavaScript. Les expressions comme (foo?.bar).baz qui fonctionnaient par accident vont maintenant lancer une erreur si foo est null. Utiliser le chaînage optionnel correct: foo?.bar?.baz.",
1857
+ "estimated_time_per_occurrence": 5,
1858
+ "onFile": null,
1859
+ "fileTypes": [
1860
+ "*.html",
1861
+ "*.ts"
1862
+ ],
1863
+ "regex": "\\(\\s*\\w+\\?\\.\\w+\\s*\\)\\.\\w+",
1864
+ "category": "template",
1865
+ "auto_fixable": false,
1866
+ "migration_command": null,
1867
+ "risk_level": "high",
1868
+ "code_description": "// Avant (fonctionnait par accident):\n{{ (foo?.bar).baz }}\n\n// Après:\n{{ foo?.bar?.baz }}\n// (foo?.bar).baz lancera erreur si foo est null",
1869
+ "doc_url": "https://angular.dev/reference/migrations"
1870
+ },
1871
+ {
1872
+ "key": "testBed_get_removed",
1873
+ "summary": "TestBed.get() complètement supprimé",
1874
+ "description": "TestBed.get() déprécié depuis Angular 9 est maintenant complètement supprimé. Utiliser TestBed.inject() qui offre un meilleur typage TypeScript et une API cohérente avec la fonction inject() du framework.",
1875
+ "estimated_time_per_occurrence": 2,
1876
+ "onFile": null,
1877
+ "fileTypes": [
1878
+ "*.spec.ts"
1879
+ ],
1880
+ "regex": "TestBed\\.get\\s*\\(",
1881
+ "category": "test",
1882
+ "auto_fixable": true,
1883
+ "migration_command": null,
1884
+ "risk_level": "medium",
1885
+ "code_description": "// Avant:\nconst service = TestBed.get(MyService);\n\n// Après:\nconst service = TestBed.inject(MyService);",
1886
+ "astPattern": {
1887
+ "nodeType": "CallExpression",
1888
+ "expression": {
1889
+ "nodeType": "PropertyAccessExpression",
1890
+ "object": "TestBed",
1891
+ "property": "get"
1892
+ },
1893
+ "excludeContext": [
1894
+ "StringLiteral",
1895
+ "Comment"
1896
+ ]
1897
+ },
1898
+ "doc_url": "https://angular.dev/reference/migrations"
1899
+ },
1900
+ {
1901
+ "key": "injectFlags_enum_removed",
1902
+ "summary": "InjectFlags enum supprimé",
1903
+ "description": "L'enum InjectFlags est supprimé au profit d'un objet d'options plus explicite et extensible. Remplacer InjectFlags.Optional par { optional: true }, InjectFlags.Self par { self: true }, etc. Meilleure lisibilité et typage.",
1904
+ "estimated_time_per_occurrence": 3,
1905
+ "onFile": null,
1906
+ "fileTypes": [
1907
+ "*.ts"
1908
+ ],
1909
+ "regex": "InjectFlags\\.(Optional|Self|SkipSelf|Host)",
1910
+ "category": "api",
1911
+ "auto_fixable": true,
1912
+ "migration_command": null,
1913
+ "risk_level": "medium",
1914
+ "code_description": "// Avant:\nimport { InjectFlags } from '@angular/core';\ninject(MyService, InjectFlags.Optional);\n\n// Après:\ninject(MyService, { optional: true });",
1915
+ "astPattern": {
1916
+ "nodeType": "PropertyAccessExpression",
1917
+ "expression": {
1918
+ "nodeType": "Identifier",
1919
+ "name": "InjectFlags"
1920
+ },
1921
+ "name": [
1922
+ "Optional",
1923
+ "Self",
1924
+ "SkipSelf",
1925
+ "Host"
1926
+ ],
1927
+ "excludeContext": [
1928
+ "StringLiteral",
1929
+ "Comment"
1930
+ ]
1931
+ },
1932
+ "doc_url": "https://angular.dev/reference/migrations"
1933
+ },
1934
+ {
1935
+ "key": "pendingTasks_run_changed",
1936
+ "summary": "PendingTasks.run() changé → add() + remove()",
1937
+ "description": "PendingTasks.run() ne retourne plus la valeur de la fonction passée en paramètre. Utiliser add() pour ajouter une tâche, exécuter le code asynchrone, puis remove() dans un finally pour nettoyer. Offre plus de contrôle sur le cycle de vie des tâches.",
1938
+ "estimated_time_per_occurrence": 8,
1939
+ "onFile": null,
1940
+ "fileTypes": [
1941
+ "*.ts"
1942
+ ],
1943
+ "regex": "pendingTasks\\.run\\s*\\(",
1944
+ "category": "api",
1945
+ "auto_fixable": false,
1946
+ "migration_command": null,
1947
+ "risk_level": "medium",
1948
+ "code_description": "// Avant:\nconst result = await pendingTasks.run(() => promise);\n\n// Après:\nconst taskId = pendingTasks.add();\ntry {\n const result = await promise;\n} finally {\n pendingTasks.remove(taskId);\n}",
1949
+ "astPattern": {
1950
+ "nodeType": "CallExpression",
1951
+ "expression": {
1952
+ "nodeType": "PropertyAccessExpression",
1953
+ "object": "pendingTasks",
1954
+ "property": "run"
1955
+ },
1956
+ "excludeContext": [
1957
+ "StringLiteral",
1958
+ "Comment"
1959
+ ]
1960
+ },
1961
+ "doc_url": "https://angular.dev/reference/migrations"
1962
+ },
1963
+ {
1964
+ "key": "asyncPipe_errors_to_errorHandler",
1965
+ "summary": "AsyncPipe erreurs reportées à ErrorHandler",
1966
+ "description": "Les erreurs dans les Observables utilisés avec AsyncPipe sont maintenant reportées au ErrorHandler global au lieu d'être silencieuses. Dans les tests, mocker ErrorHandler pour capturer et valider ces erreurs. Améliore le debugging des erreurs asynchrones.",
1967
+ "estimated_time_per_occurrence": 5,
1968
+ "onFile": null,
1969
+ "fileTypes": [
1970
+ "*.html",
1971
+ "*.ts",
1972
+ "*.spec.ts"
1973
+ ],
1974
+ "regex": "\\|\\s*async",
1975
+ "category": "template",
1976
+ "auto_fixable": false,
1977
+ "migration_command": null,
1978
+ "risk_level": "medium",
1979
+ "code_description": "// En v20: erreurs AsyncPipe → ErrorHandler\n\n// Dans les tests:\nTestBed.configureTestingModule({\n providers: [{\n provide: ErrorHandler,\n useValue: { handleError: jasmine.createSpy('handleError') }\n }]\n});",
1980
+ "astPattern": {
1981
+ "nodeType": "Decorator",
1982
+ "name": "Component",
1983
+ "template": {
1984
+ "contains": "| async"
1985
+ },
1986
+ "excludeContext": [
1987
+ "StringLiteral",
1988
+ "Comment"
1989
+ ]
1990
+ },
1991
+ "doc_url": "https://angular.dev/reference/migrations"
1992
+ }
1993
+ ],
1994
+ "recommande": [
1995
+ {
1996
+ "key": "control_flow_deprecation",
1997
+ "summary": "⚠️ DÉPRÉCIÉ: *ngIf/*ngFor/*ngSwitch (retrait v22)",
1998
+ "description": "Les directives structurelles *ngIf, *ngFor et *ngSwitch sont officiellement dépréciées en Angular 20 et seront complètement retirées en v22. Migrer dès maintenant vers la syntaxe Control Flow (@if, @for, @switch) pour éviter les breaking changes futurs.",
1999
+ "estimated_time_per_occurrence": 3,
2000
+ "onFile": null,
2001
+ "fileTypes": [
2002
+ "*.html",
2003
+ "*.ts"
2004
+ ],
2005
+ "regex": "\\*ng(If|For|Switch)",
2006
+ "category": "template",
2007
+ "auto_fixable": false,
2008
+ "migration_command": "ng generate @angular/core:control-flow",
2009
+ "risk_level": "high",
2010
+ "code_description": "// ⚠️ DÉPRÉCIÉ - Retrait prévu en v22\n<div *ngIf=\"condition\">Content</div>\n<div *ngFor=\"let item of items\">{{ item }}</div>\n\n// Migrer vers Control Flow:\n@if (condition) {\n <div>Content</div>\n}\n@for (item of items; track item.id) {\n <div>{{ item }}</div>\n}",
2011
+ "astPattern": {
2012
+ "nodeType": "Attribute",
2013
+ "name": [
2014
+ "*ngIf",
2015
+ "*ngFor",
2016
+ "*ngSwitch",
2017
+ "*ngSwitchCase",
2018
+ "*ngSwitchDefault"
2019
+ ],
2020
+ "excludeContext": [
2021
+ "StringLiteral",
2022
+ "Comment"
2023
+ ]
2024
+ },
2025
+ "doc_url": "https://angular.dev/reference/migrations"
2026
+ },
2027
+ {
2028
+ "key": "signals_stable",
2029
+ "summary": "APIs Signals stabilisées (effect, linkedSignal, toSignal)",
2030
+ "description": "Toutes les APIs de signaux sortent de leur phase expérimentale et deviennent stables en Angular 20: effect, linkedSignal, toSignal, computed. Adopter ces APIs maintenant garantit une stabilité à long terme et des performances optimales.",
2031
+ "estimated_time_per_occurrence": 5,
2032
+ "onFile": null,
2033
+ "fileTypes": [
2034
+ "*.ts"
2035
+ ],
2036
+ "regex": "\\b(effect|linkedSignal|toSignal)\\s*\\(",
2037
+ "category": "signals",
2038
+ "auto_fixable": false,
2039
+ "migration_command": null,
2040
+ "risk_level": "low",
2041
+ "code_description": "// ✅ Maintenant stables (plus 'experimental')\n\neffect(() => {\n console.log('Value:', mySignal());\n});\n\nconst linked = linkedSignal({\n source: sourceSignal,\n computation: () => 'default'\n});\n\nconst fromObs = toSignal(observable$, {\n initialValue: 'default'\n});",
2042
+ "astPattern": {
2043
+ "nodeType": "CallExpression",
2044
+ "functionName": [
2045
+ "effect",
2046
+ "linkedSignal",
2047
+ "toSignal",
2048
+ "computed"
2049
+ ],
2050
+ "excludeContext": [
2051
+ "StringLiteral",
2052
+ "Comment"
2053
+ ]
2054
+ },
2055
+ "doc_url": "https://angular.dev/reference/migrations"
2056
+ },
2057
+ {
2058
+ "key": "incremental_hydration_stable",
2059
+ "summary": "Hydratation incrémentale stable",
2060
+ "description": "L'hydratation incrémentale passe de expérimental à stable, permettant d'hydrater progressivement les composants Angular côté client. Combiné avec @defer et les triggers d'hydratation, réduit drastiquement le TTI et le JavaScript initial sur mobile.",
2061
+ "estimated_time_per_occurrence": 15,
2062
+ "onFile": null,
2063
+ "fileTypes": [
2064
+ "*.ts",
2065
+ "*.html"
2066
+ ],
2067
+ "regex": "(?<![\"\\'])\\bprovideClientHydration\\s*\\(",
2068
+ "category": "ssr",
2069
+ "auto_fixable": false,
2070
+ "migration_command": null,
2071
+ "risk_level": "medium",
2072
+ "code_description": "// ✅ API stable en v20\n\nbootstrapApplication(App, {\n providers: [\n provideClientHydration(withIncrementalHydration())\n ]\n});\n\n// Template:\n@defer (hydrate on viewport) {\n <heavy-component />\n}",
2073
+ "astPattern": {
2074
+ "nodeType": "CallExpression",
2075
+ "functionName": "provideClientHydration",
2076
+ "arguments": {
2077
+ "containsCall": {
2078
+ "functionName": "withIncrementalHydration"
2079
+ }
2080
+ },
2081
+ "excludeContext": [
2082
+ "StringLiteral",
2083
+ "Comment"
2084
+ ]
2085
+ },
2086
+ "doc_url": "https://angular.dev/reference/migrations"
2087
+ },
2088
+ {
2089
+ "key": "server_routes_stable",
2090
+ "summary": "Configuration rendu par route stable",
2091
+ "description": "L'API de configuration du rendu par route devient stable, permettant de définir précisément la stratégie (Prerender/Server/Client) pour chaque route. getPrerenderParams génère dynamiquement les paramètres pour le SSG, idéal pour les blogs et e-commerce.",
2092
+ "estimated_time_per_occurrence": 20,
2093
+ "onFile": null,
2094
+ "fileTypes": [
2095
+ "*.ts"
2096
+ ],
2097
+ "regex": "export\\s+const\\s+serverRoutes",
2098
+ "category": "ssr",
2099
+ "auto_fixable": false,
2100
+ "migration_command": null,
2101
+ "risk_level": "medium",
2102
+ "code_description": "// app.routes.server.ts - ✅ API stable\n\nexport const serverRoutes: ServerRoute[] = [\n { path: '/', renderMode: RenderMode.Prerender },\n { path: '/api/**', renderMode: RenderMode.Server },\n { path: '/admin/**', renderMode: RenderMode.Client },\n {\n path: '/product/:id',\n renderMode: RenderMode.Prerender,\n async getPrerenderParams() {\n return ids.map(id => ({ id }));\n }\n }\n];",
2103
+ "astPattern": {
2104
+ "nodeType": "VariableDeclaration",
2105
+ "name": "serverRoutes",
2106
+ "type": "ServerRoute[]",
2107
+ "exported": true,
2108
+ "excludeContext": [
2109
+ "StringLiteral",
2110
+ "Comment"
2111
+ ]
2112
+ },
2113
+ "doc_url": "https://angular.dev/reference/migrations"
2114
+ },
2115
+ {
2116
+ "key": "zoneless_preview",
2117
+ "summary": "Zoneless en preview développeur avec error handling",
2118
+ "description": "Le mode zoneless entre en preview développeur avec provideBrowserGlobalErrorListeners pour capturer les erreurs non gérées. Élimine Zone.js (~50KB), améliore les performances de 35% (cf YouTube), mais nécessite des tests approfondis avant production.",
2119
+ "estimated_time_per_occurrence": 30,
2120
+ "onFile": null,
2121
+ "fileTypes": [
2122
+ "*.ts",
2123
+ "angular.json"
2124
+ ],
2125
+ "regex": "\\bprovideZonelessChangeDetection\\b\\s*\\(",
2126
+ "category": "architecture",
2127
+ "auto_fixable": false,
2128
+ "migration_command": null,
2129
+ "risk_level": "high",
2130
+ "code_description": "// ✅ Preview développeur\n\nbootstrapApplication(App, {\n providers: [\n provideZonelessChangeDetection(),\n provideBrowserGlobalErrorListeners() // Nouveau\n ]\n});\n\n// angular.json: retirer \"zone.js\"",
2131
+ "astPattern": {
2132
+ "nodeType": "CallExpression",
2133
+ "functionName": "provideZonelessChangeDetection",
2134
+ "nextProvider": {
2135
+ "missing": "provideBrowserGlobalErrorListeners"
2136
+ },
2137
+ "excludeContext": [
2138
+ "StringLiteral",
2139
+ "Comment"
2140
+ ]
2141
+ },
2142
+ "doc_url": "https://angular.dev/reference/migrations"
2143
+ }
2144
+ ],
2145
+ "optionnelle": [
2146
+ {
2147
+ "key": "httpResource_api",
2148
+ "summary": "httpResource pour requêtes HTTP réactives",
2149
+ "description": "La nouvelle API httpResource expérimentale simplifie les requêtes HTTP réactives avec tracking automatique du status et des erreurs. Alternative moderne aux patterns HttpClient + AsyncPipe, s'intègre nativement avec les signaux pour une réactivité fine-grained.",
2150
+ "estimated_time_per_occurrence": 15,
2151
+ "onFile": null,
2152
+ "fileTypes": [
2153
+ "*.ts"
2154
+ ],
2155
+ "regex": "httpClient\\.get\\s*\\(",
2156
+ "category": "reactive",
2157
+ "auto_fixable": false,
2158
+ "migration_command": null,
2159
+ "risk_level": "medium",
2160
+ "code_description": "// Nouvelle API expérimentale\n\nconst userResource = httpResource<User>(() => \n `https://api.example.com/users/${userId()}`\n);",
2161
+ "astPattern": {
2162
+ "nodeType": "CallExpression",
2163
+ "functionName": "httpResource",
2164
+ "excludeContext": [
2165
+ "StringLiteral",
2166
+ "Comment"
2167
+ ]
2168
+ },
2169
+ "doc_url": "https://angular.dev/reference/migrations"
2170
+ },
2171
+ {
2172
+ "key": "resource_streaming",
2173
+ "summary": "Resource streaming pour websockets",
2174
+ "description": "L'API resource streaming permet de créer des ressources qui se mettent à jour en continu via WebSocket ou Server-Sent Events. Le signal retourné se met à jour automatiquement à chaque message, idéal pour les données temps réel (chat, dashboards).",
2175
+ "estimated_time_per_occurrence": 30,
2176
+ "onFile": null,
2177
+ "fileTypes": [
2178
+ "*.ts"
2179
+ ],
2180
+ "regex": "new\\s+WebSocket\\s*\\(",
2181
+ "category": "reactive",
2182
+ "auto_fixable": false,
2183
+ "migration_command": null,
2184
+ "risk_level": "high",
2185
+ "code_description": "const streamResource = resource({\n stream: () => new Promise<Signal<ResourceStreamItem>>((resolve) => {\n const result = signal<{ value: Data }>({ value: initialData });\n \n websocket.onmessage = (event) => {\n result.update(current => ({\n value: [...current.value, event.data]\n }));\n };\n \n resolve(result);\n })\n});",
2186
+ "astPattern": {
2187
+ "nodeType": "CallExpression",
2188
+ "functionName": "resource",
2189
+ "arguments": {
2190
+ "properties": {
2191
+ "stream": {
2192
+ "exists": true
2193
+ }
2194
+ }
2195
+ },
2196
+ "excludeContext": [
2197
+ "StringLiteral",
2198
+ "Comment"
2199
+ ]
2200
+ },
2201
+ "doc_url": "https://angular.dev/reference/migrations"
2202
+ },
2203
+ {
2204
+ "key": "createComponent_bindings",
2205
+ "summary": "createComponent avec bindings dynamiques",
2206
+ "description": "createComponent supporte maintenant les bindings dynamiques (input, output, two-way) et l'application de directives au runtime. Permet de créer des composants programmatiquement avec la même flexibilité que les templates, idéal pour les dialogs et overlays.",
2207
+ "estimated_time_per_occurrence": 15,
2208
+ "onFile": null,
2209
+ "fileTypes": [
2210
+ "*.ts"
2211
+ ],
2212
+ "regex": "createComponent\\s*\\(",
2213
+ "category": "api",
2214
+ "auto_fixable": false,
2215
+ "migration_command": null,
2216
+ "risk_level": "medium",
2217
+ "code_description": "createComponent(MyDialog, {\n bindings: [\n inputBinding('title', titleSignal),\n outputBinding('onClose', (result) => console.log(result)),\n twoWayBinding('value', valueSignal)\n ],\n directives: [\n FocusTrap,\n {\n type: HasColor,\n bindings: [inputBinding('color', () => 'primary')]\n }\n ]\n});",
2218
+ "astPattern": {
2219
+ "nodeType": "CallExpression",
2220
+ "functionName": "createComponent",
2221
+ "arguments": {
2222
+ "properties": {
2223
+ "bindings": {
2224
+ "exists": true
2225
+ }
2226
+ }
2227
+ },
2228
+ "excludeContext": [
2229
+ "StringLiteral",
2230
+ "Comment"
2231
+ ]
2232
+ },
2233
+ "doc_url": "https://angular.dev/reference/migrations"
2234
+ },
2235
+ {
2236
+ "key": "vitest_support",
2237
+ "summary": "Support Vitest expérimental",
2238
+ "description": "Angular 20 ajoute le support expérimental de Vitest comme test runner alternatif à Karma/Jasmine. Vitest offre un démarrage instantané, un watch mode performant et une compatibilité Jest. Configuration via angular.json avec le builder @angular/build:unit-test.",
2239
+ "estimated_time_per_occurrence": 60,
2240
+ "onFile": "angular.json",
2241
+ "fileTypes": [
2242
+ "angular.json",
2243
+ "package.json"
2244
+ ],
2245
+ "regex": null,
2246
+ "category": "test",
2247
+ "auto_fixable": false,
2248
+ "migration_command": "npm i vitest jsdom --save-dev",
2249
+ "risk_level": "high",
2250
+ "code_description": "// angular.json\n\"test\": {\n \"builder\": \"@angular/build:unit-test\",\n \"options\": {\n \"runner\": \"vitest\"\n }\n}\n\n// Installation:\n// npm i vitest jsdom --save-dev",
2251
+ "astPattern": {
2252
+ "nodeType": "PropertyAssignment",
2253
+ "name": "runner",
2254
+ "initializer": {
2255
+ "nodeType": "StringLiteral",
2256
+ "text": "vitest"
2257
+ },
2258
+ "excludeContext": [
2259
+ "StringLiteral",
2260
+ "Comment"
2261
+ ]
2262
+ },
2263
+ "doc_url": "https://angular.dev/reference/migrations"
2264
+ },
2265
+ {
2266
+ "key": "style_guide_no_suffix",
2267
+ "summary": "Guide de style sans suffixes",
2268
+ "description": "Angular 20 adopte un nouveau guide de style par défaut sans suffixes (.component, .service, etc.) pour les fichiers générés. Pour les projets existants, ng update ajoute automatiquement la config de schematics pour garder les suffixes. Nouveau style: user.ts au lieu de user.component.ts.",
2269
+ "estimated_time_per_occurrence": 10,
2270
+ "onFile": "angular.json",
2271
+ "fileTypes": [
2272
+ "angular.json"
2273
+ ],
2274
+ "regex": null,
2275
+ "category": "style",
2276
+ "auto_fixable": false,
2277
+ "migration_command": null,
2278
+ "risk_level": "low",
2279
+ "code_description": "// Par défaut en v20:\n// ng generate component user\n// Crée: user.ts (pas user.component.ts)\n\n// Pour garder suffixes (projets existants):\n// angular.json:\n{\n \"schematics\": {\n \"@schematics/angular:component\": { \"type\": \"component\" }\n }\n}",
2280
+ "doc_url": "https://angular.dev/reference/migrations"
2281
+ },
2282
+ {
2283
+ "key": "host_bindings_type_checking",
2284
+ "summary": "Type checking pour host bindings",
2285
+ "description": "Le compilateur Angular 20 active par défaut le type checking strict pour les host bindings en v21 avec typeCheckHostBindings: true. Permet de détecter les erreurs de typage dans les bindings de classe, styles et événements définis dans le décorateur host.",
2286
+ "estimated_time_per_occurrence": 5,
2287
+ "onFile": "tsconfig.json",
2288
+ "fileTypes": [
2289
+ "tsconfig.json"
2290
+ ],
2291
+ "regex": null,
2292
+ "category": "config",
2293
+ "auto_fixable": false,
2294
+ "migration_command": null,
2295
+ "risk_level": "low",
2296
+ "code_description": "// tsconfig.json\n{\n \"angularCompilerOptions\": {\n \"typeCheckHostBindings\": true // Activé par défaut en v21\n }\n}\n\n// Maintenant avec vérification:\n@Component({\n host: {\n '[class.active]': 'isActive()',\n '(click)': 'onClick($event)'\n }\n})",
2297
+ "astPattern": {
2298
+ "nodeType": "Decorator",
2299
+ "name": "Component",
2300
+ "properties": {
2301
+ "host": {
2302
+ "exists": true
2303
+ }
2304
+ },
2305
+ "inFile": {
2306
+ "tsconfig": {
2307
+ "angularCompilerOptions": {
2308
+ "typeCheckHostBindings": {
2309
+ "missing": true
2310
+ }
2311
+ }
2312
+ }
2313
+ }
2314
+ },
2315
+ "doc_url": "https://angular.dev/reference/migrations"
2316
+ },
2317
+ {
2318
+ "key": "datePipe_warnings",
2319
+ "summary": "DatePipe warnings pour format Y sans w",
2320
+ "description": "Le DatePipe émet maintenant un warning si le format 'Y' (année ISO) est utilisé sans 'w' (semaine). Ce pattern est suspect car l'année ISO n'a de sens qu'avec la semaine ISO. Utiliser 'y' pour l'année standard ou 'Y w' ensemble.",
2321
+ "estimated_time_per_occurrence": 3,
2322
+ "onFile": null,
2323
+ "fileTypes": [
2324
+ "*.html",
2325
+ "*.ts"
2326
+ ],
2327
+ "regex": "\\|\\s*date\\s*:\\s*['\"]Y['\"]",
2328
+ "category": "template",
2329
+ "auto_fixable": false,
2330
+ "migration_command": null,
2331
+ "risk_level": "low",
2332
+ "code_description": "// ⚠️ Avertissement:\n{{ date | date:'Y' }} // Suspect: année sans semaine\n\n// ✅ Correct:\n{{ date | date:'y' }} // Année standard\n{{ date | date:'Y w' }} // Année et semaine ensemble",
2333
+ "doc_url": "https://angular.dev/reference/migrations"
2334
+ }
2335
+ ]
2336
+ }
2337
+ }