@openrewrite/recipes-angular 1.1.3 → 1.1.5

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 (124) hide show
  1. package/dist/index.d.ts +18 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +76 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/libraries/primeng/18/add-primeng-provider.d.ts +45 -0
  6. package/dist/libraries/primeng/18/add-primeng-provider.d.ts.map +1 -0
  7. package/dist/libraries/primeng/18/add-primeng-provider.js +354 -0
  8. package/dist/libraries/primeng/18/add-primeng-provider.js.map +1 -0
  9. package/dist/libraries/primeng/18/index.d.ts +19 -0
  10. package/dist/libraries/primeng/18/index.d.ts.map +1 -0
  11. package/dist/libraries/primeng/18/index.js +45 -0
  12. package/dist/libraries/primeng/18/index.js.map +1 -0
  13. package/dist/libraries/primeng/18/manual-migration-steps.d.ts +19 -0
  14. package/dist/libraries/primeng/18/manual-migration-steps.d.ts.map +1 -0
  15. package/dist/libraries/primeng/18/manual-migration-steps.js +45 -0
  16. package/dist/libraries/primeng/18/manual-migration-steps.js.map +1 -0
  17. package/dist/libraries/primeng/18/mark-deprecated-components.d.ts +9 -0
  18. package/dist/libraries/primeng/18/mark-deprecated-components.d.ts.map +1 -0
  19. package/dist/libraries/primeng/18/mark-deprecated-components.js +125 -0
  20. package/dist/libraries/primeng/18/mark-deprecated-components.js.map +1 -0
  21. package/dist/libraries/primeng/18/mark-deprecated-css-classes.d.ts +15 -0
  22. package/dist/libraries/primeng/18/mark-deprecated-css-classes.d.ts.map +1 -0
  23. package/dist/libraries/primeng/18/mark-deprecated-css-classes.js +108 -0
  24. package/dist/libraries/primeng/18/mark-deprecated-css-classes.js.map +1 -0
  25. package/dist/libraries/primeng/18/mark-drawer-size.d.ts +16 -0
  26. package/dist/libraries/primeng/18/mark-drawer-size.d.ts.map +1 -0
  27. package/dist/libraries/primeng/18/mark-drawer-size.js +90 -0
  28. package/dist/libraries/primeng/18/mark-drawer-size.js.map +1 -0
  29. package/dist/libraries/primeng/18/mark-removed-primeng-modules.d.ts +9 -0
  30. package/dist/libraries/primeng/18/mark-removed-primeng-modules.d.ts.map +1 -0
  31. package/dist/libraries/primeng/18/mark-removed-primeng-modules.js +186 -0
  32. package/dist/libraries/primeng/18/mark-removed-primeng-modules.js.map +1 -0
  33. package/dist/libraries/primeng/18/migrate-messages-to-message-loop.d.ts +15 -0
  34. package/dist/libraries/primeng/18/migrate-messages-to-message-loop.d.ts.map +1 -0
  35. package/dist/libraries/primeng/18/migrate-messages-to-message-loop.js +78 -0
  36. package/dist/libraries/primeng/18/migrate-messages-to-message-loop.js.map +1 -0
  37. package/dist/libraries/primeng/18/migrate-p-fluid-to-wrapper.d.ts +27 -0
  38. package/dist/libraries/primeng/18/migrate-p-fluid-to-wrapper.d.ts.map +1 -0
  39. package/dist/libraries/primeng/18/migrate-p-fluid-to-wrapper.js +495 -0
  40. package/dist/libraries/primeng/18/migrate-p-fluid-to-wrapper.js.map +1 -0
  41. package/dist/libraries/primeng/18/migrate-primeng-config.d.ts +8 -0
  42. package/dist/libraries/primeng/18/migrate-primeng-config.d.ts.map +1 -0
  43. package/dist/libraries/primeng/18/migrate-primeng-config.js +154 -0
  44. package/dist/libraries/primeng/18/migrate-primeng-config.js.map +1 -0
  45. package/dist/libraries/primeng/18/migrate-primeng-signal-assignments.d.ts +12 -0
  46. package/dist/libraries/primeng/18/migrate-primeng-signal-assignments.d.ts.map +1 -0
  47. package/dist/libraries/primeng/18/migrate-primeng-signal-assignments.js +112 -0
  48. package/dist/libraries/primeng/18/migrate-primeng-signal-assignments.js.map +1 -0
  49. package/dist/libraries/primeng/18/rename-calendar-to-datepicker.d.ts +8 -0
  50. package/dist/libraries/primeng/18/rename-calendar-to-datepicker.d.ts.map +1 -0
  51. package/dist/libraries/primeng/18/rename-calendar-to-datepicker.js +114 -0
  52. package/dist/libraries/primeng/18/rename-calendar-to-datepicker.js.map +1 -0
  53. package/dist/libraries/primeng/18/rename-dropdown-to-select.d.ts +8 -0
  54. package/dist/libraries/primeng/18/rename-dropdown-to-select.d.ts.map +1 -0
  55. package/dist/libraries/primeng/18/rename-dropdown-to-select.js +114 -0
  56. package/dist/libraries/primeng/18/rename-dropdown-to-select.js.map +1 -0
  57. package/dist/libraries/primeng/18/rename-inputswitch-to-toggleswitch.d.ts +8 -0
  58. package/dist/libraries/primeng/18/rename-inputswitch-to-toggleswitch.d.ts.map +1 -0
  59. package/dist/libraries/primeng/18/rename-inputswitch-to-toggleswitch.js +114 -0
  60. package/dist/libraries/primeng/18/rename-inputswitch-to-toggleswitch.js.map +1 -0
  61. package/dist/libraries/primeng/18/rename-message-interface.d.ts +8 -0
  62. package/dist/libraries/primeng/18/rename-message-interface.d.ts.map +1 -0
  63. package/dist/libraries/primeng/18/rename-message-interface.js +107 -0
  64. package/dist/libraries/primeng/18/rename-message-interface.js.map +1 -0
  65. package/dist/libraries/primeng/18/rename-overlaypanel-to-popover.d.ts +8 -0
  66. package/dist/libraries/primeng/18/rename-overlaypanel-to-popover.d.ts.map +1 -0
  67. package/dist/libraries/primeng/18/rename-overlaypanel-to-popover.js +114 -0
  68. package/dist/libraries/primeng/18/rename-overlaypanel-to-popover.js.map +1 -0
  69. package/dist/libraries/primeng/18/rename-sidebar-to-drawer.d.ts +8 -0
  70. package/dist/libraries/primeng/18/rename-sidebar-to-drawer.d.ts.map +1 -0
  71. package/dist/libraries/primeng/18/rename-sidebar-to-drawer.js +114 -0
  72. package/dist/libraries/primeng/18/rename-sidebar-to-drawer.js.map +1 -0
  73. package/dist/libraries/primeng/18/rename-template-selectors.d.ts +12 -0
  74. package/dist/libraries/primeng/18/rename-template-selectors.d.ts.map +1 -0
  75. package/dist/libraries/primeng/18/rename-template-selectors.js +79 -0
  76. package/dist/libraries/primeng/18/rename-template-selectors.js.map +1 -0
  77. package/dist/libraries/primeng/18/upgrade-components-to-18.d.ts +8 -0
  78. package/dist/libraries/primeng/18/upgrade-components-to-18.d.ts.map +1 -0
  79. package/dist/libraries/primeng/18/upgrade-components-to-18.js +73 -0
  80. package/dist/libraries/primeng/18/upgrade-components-to-18.js.map +1 -0
  81. package/dist/libraries/primeng/18/upgrade-to-primeng-18.d.ts +8 -0
  82. package/dist/libraries/primeng/18/upgrade-to-primeng-18.d.ts.map +1 -0
  83. package/dist/libraries/primeng/18/upgrade-to-primeng-18.js +68 -0
  84. package/dist/libraries/primeng/18/upgrade-to-primeng-18.js.map +1 -0
  85. package/dist/migration/explicit-standalone-flag.d.ts.map +1 -1
  86. package/dist/migration/explicit-standalone-flag.js +12 -5
  87. package/dist/migration/explicit-standalone-flag.js.map +1 -1
  88. package/dist/migration/replace-untyped-forms.d.ts +14 -2
  89. package/dist/migration/replace-untyped-forms.d.ts.map +1 -1
  90. package/dist/migration/replace-untyped-forms.js +108 -36
  91. package/dist/migration/replace-untyped-forms.js.map +1 -1
  92. package/dist/migration/upgrade-angular-peer-libs-to-18.d.ts +17 -0
  93. package/dist/migration/upgrade-angular-peer-libs-to-18.d.ts.map +1 -0
  94. package/dist/migration/upgrade-angular-peer-libs-to-18.js +49 -0
  95. package/dist/migration/upgrade-angular-peer-libs-to-18.js.map +1 -0
  96. package/dist/migration/upgrade-to-angular-18.d.ts.map +1 -1
  97. package/dist/migration/upgrade-to-angular-18.js +16 -7
  98. package/dist/migration/upgrade-to-angular-18.js.map +1 -1
  99. package/package.json +1 -1
  100. package/src/index.ts +57 -0
  101. package/src/libraries/primeng/18/add-primeng-provider.ts +359 -0
  102. package/src/libraries/primeng/18/index.ts +24 -0
  103. package/src/libraries/primeng/18/manual-migration-steps.ts +37 -0
  104. package/src/libraries/primeng/18/mark-deprecated-components.ts +128 -0
  105. package/src/libraries/primeng/18/mark-deprecated-css-classes.ts +109 -0
  106. package/src/libraries/primeng/18/mark-drawer-size.ts +85 -0
  107. package/src/libraries/primeng/18/mark-removed-primeng-modules.ts +198 -0
  108. package/src/libraries/primeng/18/migrate-messages-to-message-loop.ts +72 -0
  109. package/src/libraries/primeng/18/migrate-p-fluid-to-wrapper.ts +439 -0
  110. package/src/libraries/primeng/18/migrate-primeng-config.ts +112 -0
  111. package/src/libraries/primeng/18/migrate-primeng-signal-assignments.ts +83 -0
  112. package/src/libraries/primeng/18/rename-calendar-to-datepicker.ts +81 -0
  113. package/src/libraries/primeng/18/rename-dropdown-to-select.ts +81 -0
  114. package/src/libraries/primeng/18/rename-inputswitch-to-toggleswitch.ts +81 -0
  115. package/src/libraries/primeng/18/rename-message-interface.ts +78 -0
  116. package/src/libraries/primeng/18/rename-overlaypanel-to-popover.ts +81 -0
  117. package/src/libraries/primeng/18/rename-sidebar-to-drawer.ts +81 -0
  118. package/src/libraries/primeng/18/rename-template-selectors.ts +63 -0
  119. package/src/libraries/primeng/18/upgrade-components-to-18.ts +57 -0
  120. package/src/libraries/primeng/18/upgrade-to-primeng-18.ts +52 -0
  121. package/src/migration/explicit-standalone-flag.ts +11 -5
  122. package/src/migration/replace-untyped-forms.ts +111 -39
  123. package/src/migration/upgrade-angular-peer-libs-to-18.ts +33 -0
  124. package/src/migration/upgrade-to-angular-18.ts +16 -7
package/src/index.ts CHANGED
@@ -107,11 +107,30 @@ import {MigrateInputToSignal} from './migration/migrate-input-to-signal';
107
107
  import {MigrateOutputToSignal} from './migration/migrate-output-to-signal';
108
108
  import {MigrateQueryToSignal} from './migration/migrate-query-to-signal';
109
109
  import {AddLocalizePolyfill} from './migration/add-localize-polyfill';
110
+ import {UpgradeToPrimeNG18} from './libraries/primeng/18/upgrade-to-primeng-18';
111
+ import {UpgradeComponentsTo18} from './libraries/primeng/18/upgrade-components-to-18';
112
+ import {MigratePrimeNgConfigToPrimeNG} from './libraries/primeng/18/migrate-primeng-config';
113
+ import {MigratePrimeNGSignalAssignments} from './libraries/primeng/18/migrate-primeng-signal-assignments';
114
+ import {MigrateMessagesToMessageLoop} from './libraries/primeng/18/migrate-messages-to-message-loop';
115
+ import {MigratePFluidToWrapper} from './libraries/primeng/18/migrate-p-fluid-to-wrapper';
116
+ import {RenameCalendarToDatePicker} from './libraries/primeng/18/rename-calendar-to-datepicker';
117
+ import {RenameDropdownToSelect} from './libraries/primeng/18/rename-dropdown-to-select';
118
+ import {RenameInputSwitchToToggleSwitch} from './libraries/primeng/18/rename-inputswitch-to-toggleswitch';
119
+ import {RenameOverlayPanelToPopover} from './libraries/primeng/18/rename-overlaypanel-to-popover';
120
+ import {RenameSidebarToDrawer} from './libraries/primeng/18/rename-sidebar-to-drawer';
121
+ import {RenameMessageInterface} from './libraries/primeng/18/rename-message-interface';
122
+ import {RenameTemplateSelectors} from './libraries/primeng/18/rename-template-selectors';
123
+ import {MarkRemovedPrimengModules} from './libraries/primeng/18/mark-removed-primeng-modules';
124
+ import {MarkDeprecatedPrimengComponents} from './libraries/primeng/18/mark-deprecated-components';
125
+ import {MarkDeprecatedPrimengCssClasses} from './libraries/primeng/18/mark-deprecated-css-classes';
126
+ import {MarkDrawerSize} from './libraries/primeng/18/mark-drawer-size';
127
+ import {AddPrimengProvider} from './libraries/primeng/18/add-primeng-provider';
110
128
  import {CategoryDescriptor, JavaScript, RecipeMarketplace} from "@openrewrite/rewrite";
111
129
 
112
130
  const Angular: CategoryDescriptor[] = [...JavaScript, {displayName: "Angular"}];
113
131
  const AngularMigration: CategoryDescriptor[] = [...Angular, {displayName: "Migration"}];
114
132
  const AngularSearch: CategoryDescriptor[] = [...Angular, {displayName: "Search"}];
133
+ const PrimeNG: CategoryDescriptor[] = [...JavaScript, {displayName: "PrimeNG"}];
115
134
 
116
135
  export async function activate(marketplace: RecipeMarketplace) {
117
136
  await marketplace.install(RenameFile, AngularMigration);
@@ -218,6 +237,25 @@ export async function activate(marketplace: RecipeMarketplace) {
218
237
  await marketplace.install(MigrateOutputToSignal, AngularMigration);
219
238
  await marketplace.install(MigrateQueryToSignal, AngularMigration);
220
239
  await marketplace.install(AddLocalizePolyfill, AngularMigration);
240
+ // PrimeNG
241
+ await marketplace.install(UpgradeToPrimeNG18, PrimeNG);
242
+ await marketplace.install(UpgradeComponentsTo18, PrimeNG);
243
+ await marketplace.install(MigratePrimeNgConfigToPrimeNG, PrimeNG);
244
+ await marketplace.install(MigratePrimeNGSignalAssignments, PrimeNG);
245
+ await marketplace.install(MigrateMessagesToMessageLoop, PrimeNG);
246
+ await marketplace.install(MigratePFluidToWrapper, PrimeNG);
247
+ await marketplace.install(RenameCalendarToDatePicker, PrimeNG);
248
+ await marketplace.install(RenameDropdownToSelect, PrimeNG);
249
+ await marketplace.install(RenameInputSwitchToToggleSwitch, PrimeNG);
250
+ await marketplace.install(RenameOverlayPanelToPopover, PrimeNG);
251
+ await marketplace.install(RenameSidebarToDrawer, PrimeNG);
252
+ await marketplace.install(RenameMessageInterface, PrimeNG);
253
+ await marketplace.install(RenameTemplateSelectors, PrimeNG);
254
+ await marketplace.install(MarkRemovedPrimengModules, PrimeNG);
255
+ await marketplace.install(MarkDeprecatedPrimengComponents, PrimeNG);
256
+ await marketplace.install(MarkDeprecatedPrimengCssClasses, PrimeNG);
257
+ await marketplace.install(MarkDrawerSize, PrimeNG);
258
+ await marketplace.install(AddPrimengProvider, PrimeNG);
221
259
  }
222
260
 
223
261
  export {RenameFile} from './migration/rename-file';
@@ -324,3 +362,22 @@ export {MigrateInputToSignal} from './migration/migrate-input-to-signal';
324
362
  export {MigrateOutputToSignal} from './migration/migrate-output-to-signal';
325
363
  export {MigrateQueryToSignal} from './migration/migrate-query-to-signal';
326
364
  export {AddLocalizePolyfill} from './migration/add-localize-polyfill';
365
+ // PrimeNG
366
+ export {UpgradeToPrimeNG18} from './libraries/primeng/18/upgrade-to-primeng-18';
367
+ export {UpgradeComponentsTo18} from './libraries/primeng/18/upgrade-components-to-18';
368
+ export {MigratePrimeNgConfigToPrimeNG} from './libraries/primeng/18/migrate-primeng-config';
369
+ export {MigratePrimeNGSignalAssignments} from './libraries/primeng/18/migrate-primeng-signal-assignments';
370
+ export {MigrateMessagesToMessageLoop} from './libraries/primeng/18/migrate-messages-to-message-loop';
371
+ export {MigratePFluidToWrapper} from './libraries/primeng/18/migrate-p-fluid-to-wrapper';
372
+ export {RenameCalendarToDatePicker} from './libraries/primeng/18/rename-calendar-to-datepicker';
373
+ export {RenameDropdownToSelect} from './libraries/primeng/18/rename-dropdown-to-select';
374
+ export {RenameInputSwitchToToggleSwitch} from './libraries/primeng/18/rename-inputswitch-to-toggleswitch';
375
+ export {RenameOverlayPanelToPopover} from './libraries/primeng/18/rename-overlaypanel-to-popover';
376
+ export {RenameSidebarToDrawer} from './libraries/primeng/18/rename-sidebar-to-drawer';
377
+ export {RenameMessageInterface} from './libraries/primeng/18/rename-message-interface';
378
+ export {RenameTemplateSelectors} from './libraries/primeng/18/rename-template-selectors';
379
+ export {MarkRemovedPrimengModules} from './libraries/primeng/18/mark-removed-primeng-modules';
380
+ export {MarkDeprecatedPrimengComponents} from './libraries/primeng/18/mark-deprecated-components';
381
+ export {MarkDeprecatedPrimengCssClasses} from './libraries/primeng/18/mark-deprecated-css-classes';
382
+ export {MarkDrawerSize} from './libraries/primeng/18/mark-drawer-size';
383
+ export {AddPrimengProvider} from './libraries/primeng/18/add-primeng-provider';
@@ -0,0 +1,359 @@
1
+ /*
2
+ * Copyright 2026 the original author or authors.
3
+ *
4
+ * Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
5
+ */
6
+
7
+ import {ExecutionContext, Recipe, ScanningRecipe, TreeVisitor} from "@openrewrite/rewrite";
8
+ import {JavaScriptVisitor, JS, Template, maybeAddImport} from "@openrewrite/rewrite/javascript";
9
+ import {JsonVisitor, Json, getMemberKeyName, isArray, isLiteral} from "@openrewrite/rewrite/json";
10
+ import {J, isIdentifier, emptySpace, singleSpace} from "@openrewrite/rewrite/java";
11
+
12
+ type Preset = 'Aura' | 'Lara' | 'Nora' | 'Material';
13
+
14
+ /**
15
+ * Tri-state: 'system' is the v18 default (follow OS preference), 'disabled' opts the app
16
+ * out of dark mode entirely (equivalent to `darkModeSelector: false`), 'unset' means the
17
+ * recipe shouldn't emit a `darkModeSelector` option (lets v18 default apply).
18
+ */
19
+ type DarkMode = 'system' | 'disabled' | 'unset';
20
+
21
+ interface AddPrimengProviderAcc {
22
+ /** v18 design-token preset to use, derived from the v17 theme found in angular.json. */
23
+ preset: Preset;
24
+ /** Whether to emit `darkModeSelector` and what value, derived from the v17 theme name. */
25
+ darkMode: DarkMode;
26
+ /** Whether the scanner saw any v17 theme path (used only for diagnostics / future hooks). */
27
+ detectedFromAngularJson: boolean;
28
+ }
29
+
30
+ /**
31
+ * Map a v17 theme name (e.g. `lara-light-blue`, `md-dark-indigo`, `mira`) to the closest
32
+ * v18 design-token preset. v17 had dozens of themes; v18 ships four presets:
33
+ * Aura, Lara, Nora, Material. The mapping below picks the closest visual match — for
34
+ * the long tail of v17 themes that have no clear analogue (mira, nova, saga, vela,
35
+ * soho, fluent, viva, rhea, tailwind, bootstrap4, arya, luna, …) we fall back to Aura
36
+ * since it's the default v18 preset.
37
+ */
38
+ function mapThemeToPreset(themeName: string): Preset {
39
+ const lower = themeName.toLowerCase();
40
+ if (lower.startsWith('lara-')) return 'Lara';
41
+ if (lower.startsWith('md-') || lower.startsWith('mdc-')) return 'Material';
42
+ if (lower.startsWith('nora') || lower === 'nano') return 'Nora';
43
+ return 'Aura';
44
+ }
45
+
46
+ /**
47
+ * Map a v17 theme name to a v18 `darkModeSelector` policy. v17 themes were single-mode (the
48
+ * theme file itself was light or dark); v18 presets are mode-agnostic and the app opts in to
49
+ * dark mode via `darkModeSelector` (defaults to `'system'`, i.e. follow OS preference).
50
+ *
51
+ * To preserve the v17 author's intent we default to `'disabled'` (= `false`, keep the app
52
+ * light-only) for everything that wasn't explicitly authored against a dark theme. Most v17
53
+ * themes (mira, nova, saga, vela, soho, fluent, viva, rhea, tailwind, bootstrap4, lara-light,
54
+ * md-light, mdc-light, …) were visually light, and the surrounding app CSS was tested only
55
+ * against that — letting v18 silently flip to OS-dark usually breaks layouts and contrast.
56
+ *
57
+ * Only `*-dark-*` v17 themes (e.g. `lara-dark-blue`, `md-dark-indigo`, `bootstrap4-dark-…`)
58
+ * map to `'system'`: those apps already worked in dark, and `'system'` is the closest v18
59
+ * analogue to "this app was dark" since v18 has no permanent-dark flag.
60
+ */
61
+ function mapThemeToDarkMode(themeName: string): DarkMode {
62
+ const lower = themeName.toLowerCase();
63
+ if (lower.includes('-dark-') || lower.endsWith('-dark')) return 'system';
64
+ return 'disabled';
65
+ }
66
+
67
+ /**
68
+ * Internal sub-recipe that strips any `primeng/resources` entry from the `styles`
69
+ * array in `angular.json`. Folded into `AddPrimengProvider`'s recipeList: now that
70
+ * the parent recipe wires in the v18 replacement (`providePrimeNG({ theme: { preset } })`),
71
+ * the v17 paths can be deleted cleanly — no TODO comments, no manual-migration row.
72
+ */
73
+ class RemovePrimengResourcesFromAngularJson extends Recipe {
74
+ readonly name = "org.openrewrite.primeng.AddPrimengProvider.remove-primeng-resources";
75
+ readonly displayName = "Remove `primeng/resources` style references from `angular.json`";
76
+ readonly description = "Filters every `styles[]` entry containing `primeng/resources` out of " +
77
+ "`angular.json`. Safe to delete because `AddPrimengProvider` adds the v18 equivalent " +
78
+ "(`providePrimeNG({ theme: { preset } })`) alongside this step.";
79
+
80
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
81
+ return new class extends JsonVisitor<ExecutionContext> {
82
+ protected async visitDocument(doc: Json.Document, p: ExecutionContext): Promise<Json | undefined> {
83
+ if (!doc.sourcePath.endsWith('angular.json')) return doc;
84
+ return super.visitDocument(doc, p);
85
+ }
86
+
87
+ protected async visitMember(member: Json.Member, p: ExecutionContext): Promise<Json | undefined> {
88
+ const m = await super.visitMember(member, p) as Json.Member;
89
+ if (!m) return m;
90
+
91
+ if (getMemberKeyName(m) !== 'styles') return m;
92
+ if (!isArray(m.value)) return m;
93
+
94
+ const arr = m.value as Json.Array;
95
+ const filtered = arr.values.filter(rp => {
96
+ const elem = rp.element;
97
+ if (!isLiteral(elem)) return true;
98
+ const val = elem.value as string;
99
+ return !val?.includes('primeng/resources');
100
+ });
101
+
102
+ if (filtered.length === arr.values.length) return m;
103
+
104
+ // Preserve the leading whitespace of the original first element on whichever
105
+ // value is now first, so the array keeps its formatting.
106
+ const newValues = [...filtered];
107
+ if (newValues.length > 0 && arr.values.length > 0) {
108
+ const originalFirstPrefix = arr.values[0].element.prefix;
109
+ const newFirst = newValues[0];
110
+ if (newFirst.element.prefix !== originalFirstPrefix) {
111
+ newValues[0] = {
112
+ ...newFirst,
113
+ element: {...newFirst.element, prefix: originalFirstPrefix},
114
+ };
115
+ }
116
+ }
117
+
118
+ return {
119
+ ...m,
120
+ value: {
121
+ ...arr,
122
+ values: newValues,
123
+ } as Json.Array,
124
+ } as Json.Member;
125
+ }
126
+ }();
127
+ }
128
+ }
129
+
130
+ /**
131
+ * v18 ships with no default styling — the old SASS theme tree under `primeng/resources` is
132
+ * gone. Instead, you opt in to a design-token preset via `providePrimeNG({ theme: { preset } })`
133
+ * in your application providers.
134
+ *
135
+ * Scanner phase: looks at `angular.json`'s `styles` array for any `primeng/resources/themes/<X>`
136
+ * reference and maps `<X>` to the closest v18 preset (Aura, Lara, Nora, or Material).
137
+ *
138
+ * Editor phase: in the root `@NgModule` (the one with a `bootstrap:` field), appends
139
+ * `providePrimeNG({ theme: { preset: <Preset> } })` to its `providers` array and adds
140
+ * `import { providePrimeNG } from 'primeng/config';`
141
+ * `import <Preset> from '@primeng/themes/<preset>';`
142
+ *
143
+ * Sub-recipe: a follow-up `RemovePrimengResourcesFromAngularJson` deletes the v17 theme
144
+ * paths from `angular.json` since the new provider supersedes them. The two changes go
145
+ * together — adding the provider without removing the broken paths, or vice versa, leaves
146
+ * the project unbuildable.
147
+ */
148
+ export class AddPrimengProvider extends ScanningRecipe<AddPrimengProviderAcc> {
149
+ readonly name = "org.openrewrite.primeng.AddPrimengProvider";
150
+ readonly displayName = "Add `providePrimeNG` with a detected theme preset to the root NgModule";
151
+ readonly description = "Wires the v18 styled mode into an NgModule-based app by adding " +
152
+ "`providePrimeNG({ theme: { preset: <Preset> } })` to the root `@NgModule`'s providers array " +
153
+ "(detected by the presence of a `bootstrap:` field). The preset is chosen by scanning " +
154
+ "`angular.json` for a `primeng/resources/themes/<themeName>/theme.css` entry: `lara-*` maps " +
155
+ "to Lara, `md-*`/`mdc-*` to Material, `nora`/`nano` to Nora, and any other v17 theme " +
156
+ "(mira, nova, saga, vela, soho, fluent, viva, rhea, tailwind, bootstrap4, arya, luna, ...) " +
157
+ "falls back to Aura. The matching imports for `providePrimeNG` and the chosen preset are " +
158
+ "added automatically. Also deletes the now-defunct `primeng/resources` style entries from " +
159
+ "`angular.json` so the build doesn't try to load missing files. Idempotent: skips files that " +
160
+ "already call `providePrimeNG`.";
161
+
162
+ initialValue(_ctx: ExecutionContext): AddPrimengProviderAcc {
163
+ return {preset: 'Aura', darkMode: 'unset', detectedFromAngularJson: false};
164
+ }
165
+
166
+ async scanner(acc: AddPrimengProviderAcc): Promise<TreeVisitor<any, ExecutionContext>> {
167
+ return new class extends JsonVisitor<ExecutionContext> {
168
+ protected async visitDocument(doc: Json.Document, p: ExecutionContext): Promise<Json | undefined> {
169
+ if (!doc.sourcePath.endsWith('angular.json')) return doc;
170
+ return super.visitDocument(doc, p);
171
+ }
172
+
173
+ protected async visitLiteral(literal: Json.Literal, p: ExecutionContext): Promise<Json | undefined> {
174
+ const l = await super.visitLiteral(literal, p) as Json.Literal;
175
+ if (!l || typeof l.value !== 'string') return l;
176
+
177
+ // Match e.g. `node_modules/primeng/resources/themes/mira/theme.css`
178
+ const match = (l.value as string).match(/primeng\/resources\/themes\/([^/]+)\/theme\.css/);
179
+ if (!match) return l;
180
+
181
+ acc.preset = mapThemeToPreset(match[1]);
182
+ acc.darkMode = mapThemeToDarkMode(match[1]);
183
+ acc.detectedFromAngularJson = true;
184
+ return l;
185
+ }
186
+ }();
187
+ }
188
+
189
+ async editorWithData(acc: AddPrimengProviderAcc): Promise<TreeVisitor<any, ExecutionContext>> {
190
+ const preset = acc.preset;
191
+ const presetModule = preset.toLowerCase();
192
+
193
+ // Build the `theme: {...}` body. v18's `darkModeSelector` defaults to 'system' (follow
194
+ // OS preference). For `*-light-*` v17 themes we emit `darkModeSelector: false` to keep
195
+ // the app light-only (preserves v17 author's intent). For `*-dark-*` we leave the
196
+ // default since v18 has no permanent-dark mode and 'system' is the closest analogue.
197
+ const themeBody = acc.darkMode === 'disabled'
198
+ ? `{ preset: ${preset}, options: { darkModeSelector: false } }`
199
+ : `{ preset: ${preset} }`;
200
+
201
+ return new class extends JavaScriptVisitor<ExecutionContext> {
202
+ // Wires the imports + appends `providePrimeNG({...})` to a `providers: [...]`
203
+ // array inside an object literal (J.NewClass body of PropertyAssignments).
204
+ // Returns the updated NewClass, or `undefined` to skip (no providers array
205
+ // or providePrimeNG already present).
206
+ private async addToObjectLiteral(newClass: J.NewClass): Promise<J.NewClass | undefined> {
207
+ if (!newClass.body) return undefined;
208
+
209
+ let providersIndex = -1;
210
+ for (let i = 0; i < newClass.body.statements.length; i++) {
211
+ const stmt = newClass.body.statements[i].element;
212
+ if (stmt.kind !== JS.Kind.PropertyAssignment) continue;
213
+ const prop = stmt as JS.PropertyAssignment;
214
+ const nameExpr = prop.name.element;
215
+ if (!isIdentifier(nameExpr)) continue;
216
+ if (nameExpr.simpleName === 'providers') {
217
+ providersIndex = i;
218
+ break;
219
+ }
220
+ }
221
+ if (providersIndex === -1) return undefined;
222
+
223
+ const provStmt = newClass.body.statements[providersIndex].element as any;
224
+ const arr = provStmt.initializer;
225
+ const container = arr?.initializer;
226
+ const existingElements = container?.elements ?? [];
227
+ for (const el of existingElements) {
228
+ const expr = el.element;
229
+ if (expr?.kind === J.Kind.MethodInvocation
230
+ && isIdentifier(expr.name)
231
+ && (expr.name as J.Identifier).simpleName === 'providePrimeNG') {
232
+ return undefined;
233
+ }
234
+ }
235
+
236
+ const provideCall = await Template.builder()
237
+ .code(`providePrimeNG({ theme: ${themeBody} })`)
238
+ .build()
239
+ .apply(newClass, this.cursor) as any;
240
+
241
+ const newProviderRP = {
242
+ kind: J.Kind.RightPadded,
243
+ element: {...provideCall, prefix: existingElements.length > 0 ? singleSpace : emptySpace},
244
+ after: emptySpace,
245
+ markers: {kind: 'org.openrewrite.marker.Markers', id: newClass.id, markers: []},
246
+ };
247
+
248
+ const updatedProvStmt = {
249
+ ...provStmt,
250
+ initializer: {
251
+ ...arr,
252
+ initializer: {
253
+ ...container,
254
+ elements: [...existingElements, newProviderRP],
255
+ },
256
+ },
257
+ };
258
+
259
+ const newStatements = [...newClass.body.statements];
260
+ newStatements[providersIndex] = {
261
+ ...newStatements[providersIndex],
262
+ element: updatedProvStmt,
263
+ };
264
+
265
+ maybeAddImport(this, {
266
+ module: 'primeng/config',
267
+ member: 'providePrimeNG',
268
+ onlyIfReferenced: false,
269
+ });
270
+ maybeAddImport(this, {
271
+ module: `@primeng/themes/${presetModule}`,
272
+ member: 'default',
273
+ alias: preset,
274
+ onlyIfReferenced: false,
275
+ });
276
+
277
+ return {...newClass, body: {...newClass.body, statements: newStatements}};
278
+ }
279
+
280
+ override async visitAnnotation(annotation: J.Annotation, p: ExecutionContext): Promise<J | undefined> {
281
+ let a = await super.visitAnnotation(annotation, p) as J.Annotation;
282
+ if (!a) return a;
283
+
284
+ const annotType = a.annotationType;
285
+ if (!isIdentifier(annotType)) return a;
286
+ if (annotType.simpleName !== 'NgModule') return a;
287
+
288
+ if (!a.arguments?.elements?.length) return a;
289
+ const firstArg = a.arguments.elements[0].element;
290
+ if (firstArg.kind !== J.Kind.NewClass) return a;
291
+
292
+ const newClass = firstArg as J.NewClass;
293
+ // Only target the root NgModule (the one with bootstrap:).
294
+ const hasBootstrap = newClass.body?.statements.some(s => {
295
+ const stmt = s.element;
296
+ if (stmt.kind !== JS.Kind.PropertyAssignment) return false;
297
+ const nameExpr = (stmt as JS.PropertyAssignment).name.element;
298
+ return isIdentifier(nameExpr) && (nameExpr as J.Identifier).simpleName === 'bootstrap';
299
+ });
300
+ if (!hasBootstrap) return a;
301
+
302
+ const updated = await this.addToObjectLiteral(newClass);
303
+ if (!updated) return a;
304
+
305
+ return {
306
+ ...a,
307
+ arguments: {
308
+ ...a.arguments!,
309
+ elements: [{
310
+ ...a.arguments!.elements[0],
311
+ element: updated,
312
+ }],
313
+ },
314
+ } as J.Annotation;
315
+ }
316
+
317
+ // Standalone apps: `export const appConfig: ApplicationConfig = { providers: [...] }`.
318
+ // Detected by the type annotation on the variable declaration; the variable name
319
+ // is irrelevant. The `: ApplicationConfig` annotation parses as JS.TypeInfo wrapping
320
+ // the actual identifier, so unwrap one level before checking the simple name.
321
+ override async visitVariableDeclarations(varDecls: J.VariableDeclarations, p: ExecutionContext): Promise<J | undefined> {
322
+ const v = await super.visitVariableDeclarations(varDecls, p) as J.VariableDeclarations;
323
+ if (!v) return v;
324
+
325
+ let typeExpr: any = v.typeExpression;
326
+ if (typeExpr?.kind === JS.Kind.TypeInfo) typeExpr = (typeExpr as JS.TypeInfo).typeIdentifier;
327
+ if (!typeExpr || !isIdentifier(typeExpr)) return v;
328
+ if ((typeExpr as J.Identifier).simpleName !== 'ApplicationConfig') return v;
329
+ if (!v.variables.length) return v;
330
+
331
+ const variableRP = v.variables[0];
332
+ const namedVar = variableRP.element as J.VariableDeclarations.NamedVariable;
333
+ const initializer = namedVar.initializer?.element;
334
+ if (!initializer || initializer.kind !== J.Kind.NewClass) return v;
335
+
336
+ const updated = await this.addToObjectLiteral(initializer as J.NewClass);
337
+ if (!updated) return v;
338
+
339
+ return {
340
+ ...v,
341
+ variables: [{
342
+ ...variableRP,
343
+ element: {
344
+ ...namedVar,
345
+ initializer: {
346
+ ...namedVar.initializer!,
347
+ element: updated,
348
+ },
349
+ },
350
+ }, ...v.variables.slice(1)],
351
+ } as J.VariableDeclarations;
352
+ }
353
+ };
354
+ }
355
+
356
+ async recipeList(): Promise<Recipe[]> {
357
+ return [new RemovePrimengResourcesFromAngularJson()];
358
+ }
359
+ }
@@ -0,0 +1,24 @@
1
+ /*
2
+ * Copyright 2026 the original author or authors.
3
+ *
4
+ * Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
5
+ */
6
+
7
+ export {UpgradeToPrimeNG18} from './upgrade-to-primeng-18';
8
+ export {UpgradeComponentsTo18} from './upgrade-components-to-18';
9
+ export {MigratePrimeNgConfigToPrimeNG} from './migrate-primeng-config';
10
+ export {MigratePrimeNGSignalAssignments} from './migrate-primeng-signal-assignments';
11
+ export {MigrateMessagesToMessageLoop} from './migrate-messages-to-message-loop';
12
+ export {RenameCalendarToDatePicker} from './rename-calendar-to-datepicker';
13
+ export {RenameDropdownToSelect} from './rename-dropdown-to-select';
14
+ export {RenameInputSwitchToToggleSwitch} from './rename-inputswitch-to-toggleswitch';
15
+ export {RenameOverlayPanelToPopover} from './rename-overlaypanel-to-popover';
16
+ export {RenameSidebarToDrawer} from './rename-sidebar-to-drawer';
17
+ export {RenameMessageInterface} from './rename-message-interface';
18
+ export {RenameTemplateSelectors} from './rename-template-selectors';
19
+ export {MarkRemovedPrimengModules} from './mark-removed-primeng-modules';
20
+ export {MarkDeprecatedPrimengComponents} from './mark-deprecated-components';
21
+ export {MarkDeprecatedPrimengCssClasses} from './mark-deprecated-css-classes';
22
+ export {MarkDrawerSize} from './mark-drawer-size';
23
+ export {AddPrimengProvider} from './add-primeng-provider';
24
+ export {ManualMigrationStep} from './manual-migration-steps';
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Copyright 2026 the original author or authors.
3
+ *
4
+ * Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
5
+ */
6
+
7
+ import {Column} from "@openrewrite/rewrite";
8
+
9
+ /**
10
+ * Row schema shared by every PrimeNG 18 recipe that leaves a manual-migration step (TODO
11
+ * comment, structural rewrite that needs review, removed-but-unmigratable reference).
12
+ *
13
+ * Each recipe instantiates its own `new DataTable<ManualMigrationStep>(MANUAL_MIGRATION_STEPS_NAME, …)`
14
+ * — the framework groups rows by table name when writing CSVs, so multiple recipes
15
+ * naturally end up in the same `org.openrewrite.primeng.ManualMigrationSteps.csv`.
16
+ */
17
+ export class ManualMigrationStep {
18
+ @Column({displayName: "Source path", description: "Path of the file containing the migration site."})
19
+ sourcePath!: string;
20
+
21
+ @Column({displayName: "Recipe", description: "Recipe that flagged this step."})
22
+ recipe!: string;
23
+
24
+ @Column({displayName: "Subject", description: "Identifier or selector that needs migration (e.g. `ChipsModule`, `<p-chips>`)."})
25
+ subject!: string;
26
+
27
+ @Column({displayName: "Reason", description: "Why this needs manual migration (component removed, structural change, etc.)."})
28
+ reason!: string;
29
+
30
+ @Column({displayName: "Action", description: "Concrete instruction for the agent or developer to follow."})
31
+ action!: string;
32
+ }
33
+
34
+ export const MANUAL_MIGRATION_STEPS_NAME = "org.openrewrite.primeng.ManualMigrationSteps";
35
+ export const MANUAL_MIGRATION_STEPS_DISPLAY_NAME = "PrimeNG 18 manual migration steps";
36
+ export const MANUAL_MIGRATION_STEPS_DESCRIPTION =
37
+ "Migration steps the automated PrimeNG 18 recipes could not complete and that need a human or AI agent to finish.";
@@ -0,0 +1,128 @@
1
+ /*
2
+ * Copyright 2026 the original author or authors.
3
+ *
4
+ * Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
5
+ */
6
+
7
+ import {DataTable, ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
8
+ import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
9
+ import {J, isIdentifier} from "@openrewrite/rewrite/java";
10
+ import {
11
+ ManualMigrationStep,
12
+ MANUAL_MIGRATION_STEPS_DESCRIPTION,
13
+ MANUAL_MIGRATION_STEPS_DISPLAY_NAME,
14
+ MANUAL_MIGRATION_STEPS_NAME,
15
+ } from "./manual-migration-steps";
16
+
17
+ interface DeprecatedModule {
18
+ members: string[];
19
+ todo: string;
20
+ }
21
+
22
+ // Modules that still exist in PrimeNG 18 but are deprecated. Their replacement components
23
+ // have different APIs / template structures and cannot be migrated automatically, so we
24
+ // only mark them: leading TODO comment on the import + a row in `ManualMigrationSteps`.
25
+ // (Chips, TriStateCheckbox, Messages, DataViewLayoutOptions, pAnimate are *removed*, not
26
+ // deprecated — they're handled by `MarkRemovedPrimengModules`.)
27
+ const DEPRECATED_MODULES: Record<string, DeprecatedModule> = {
28
+ 'primeng/tabmenu': {
29
+ members: ['TabMenu', 'TabMenuModule'],
30
+ todo: "`TabMenu` is deprecated in PrimeNG 18. Use `Tabs` without panels (import from `primeng/tabs`).",
31
+ },
32
+ 'primeng/steps': {
33
+ members: ['Steps', 'StepsModule'],
34
+ todo: "`Steps` is deprecated in PrimeNG 18. Use `Stepper` without panels (import from `primeng/stepper`).",
35
+ },
36
+ 'primeng/inlinemessage': {
37
+ members: ['InlineMessage', 'InlineMessageModule'],
38
+ todo: "`InlineMessage` is deprecated in PrimeNG 18. Use the `Message` component (import from `primeng/message`).",
39
+ },
40
+ 'primeng/tabview': {
41
+ members: ['TabView', 'TabViewModule', 'TabPanel'],
42
+ todo: "`TabView`/`TabPanel` are deprecated in PrimeNG 18. Use the new `Tabs`+`TabList`+`Tab`+`TabPanels`+`TabPanel` set (import from `primeng/tabs`).",
43
+ },
44
+ 'primeng/defer': {
45
+ members: ['DeferModule'],
46
+ todo: "`pDefer` is deprecated in PrimeNG 18. Use Angular's `@defer` control flow instead.",
47
+ },
48
+ };
49
+
50
+ export class MarkDeprecatedPrimengComponents extends Recipe {
51
+ readonly name = "org.openrewrite.primeng.MarkDeprecatedPrimengComponents";
52
+ readonly displayName = "Mark deprecated PrimeNG components with TODO comments";
53
+ readonly description = "For every TS file that imports a component / module deprecated in PrimeNG 18 " +
54
+ "(`TabMenu`, `Steps`, `InlineMessage`, `TabView`, `pDefer`), prepends a TODO comment to the import " +
55
+ "describing the recommended v18 replacement and writes a row to the `ManualMigrationSteps` data " +
56
+ "table. The import itself is left intact — these modules still exist in v18 but their replacements " +
57
+ "have different APIs that require manual migration.";
58
+
59
+ private dataTable = new DataTable<ManualMigrationStep>(
60
+ MANUAL_MIGRATION_STEPS_NAME,
61
+ MANUAL_MIGRATION_STEPS_DISPLAY_NAME,
62
+ MANUAL_MIGRATION_STEPS_DESCRIPTION,
63
+ ManualMigrationStep,
64
+ );
65
+
66
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
67
+ const recipeName = this.name;
68
+ const dataTable = this.dataTable;
69
+ return new class extends JavaScriptVisitor<ExecutionContext> {
70
+ protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
71
+ const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
72
+ if (!imp?.moduleSpecifier) return imp;
73
+
74
+ const moduleSpec = imp.moduleSpecifier.element;
75
+ if (moduleSpec.kind !== J.Kind.Literal) return imp;
76
+ const moduleName = (moduleSpec as J.Literal).value as string;
77
+ const deprecation = DEPRECATED_MODULES[moduleName];
78
+ if (!deprecation) return imp;
79
+
80
+ // Idempotence: skip if our TODO marker is already attached.
81
+ const existingComments = imp.prefix?.comments ?? [];
82
+ if (existingComments.some(c => (c as any).text?.includes('TODO: PrimeNG 18'))) {
83
+ return imp;
84
+ }
85
+
86
+ const importedNames: string[] = [];
87
+ const namedBindings = imp.importClause?.namedBindings;
88
+ if (namedBindings?.kind === JS.Kind.NamedImports) {
89
+ for (const specifier of (namedBindings as JS.NamedImports).elements.elements) {
90
+ const spec = specifier.element as JS.ImportSpecifier;
91
+ if (isIdentifier(spec.specifier)) {
92
+ importedNames.push((spec.specifier as J.Identifier).simpleName);
93
+ }
94
+ }
95
+ }
96
+ if (importedNames.length === 0) return imp;
97
+
98
+ const sourcePath = (this.cursor.firstEnclosing<any>((o: any): o is any => !!o?.sourcePath)?.sourcePath) ?? '';
99
+ for (const name of importedNames) {
100
+ dataTable.insertRow(p, {
101
+ sourcePath,
102
+ recipe: recipeName,
103
+ subject: `${name} (${moduleName})`,
104
+ reason: "Component deprecated in PrimeNG 18; replacement has a different API.",
105
+ action: deprecation.todo,
106
+ });
107
+ }
108
+
109
+ const todoComment = {
110
+ kind: J.Kind.TextComment,
111
+ multiline: true,
112
+ text: ` TODO: PrimeNG 18 — ${deprecation.todo} (import: \`{ ${importedNames.join(', ')} } from '${moduleName}'\`) `,
113
+ suffix: '\n',
114
+ markers: {kind: 'org.openrewrite.marker.Markers', id: imp.id, markers: []},
115
+ };
116
+
117
+ const originalPrefix = imp.prefix ?? {kind: J.Kind.Space, whitespace: '', comments: []};
118
+ return {
119
+ ...imp,
120
+ prefix: {
121
+ ...originalPrefix,
122
+ comments: [...existingComments, todoComment],
123
+ },
124
+ } as JS.Import;
125
+ }
126
+ };
127
+ }
128
+ }