@openrewrite/recipes-angular 1.1.4 → 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.
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +76 -1
- package/dist/index.js.map +1 -1
- package/dist/libraries/primeng/18/add-primeng-provider.d.ts +45 -0
- package/dist/libraries/primeng/18/add-primeng-provider.d.ts.map +1 -0
- package/dist/libraries/primeng/18/add-primeng-provider.js +354 -0
- package/dist/libraries/primeng/18/add-primeng-provider.js.map +1 -0
- package/dist/libraries/primeng/18/index.d.ts +19 -0
- package/dist/libraries/primeng/18/index.d.ts.map +1 -0
- package/dist/libraries/primeng/18/index.js +45 -0
- package/dist/libraries/primeng/18/index.js.map +1 -0
- package/dist/libraries/primeng/18/manual-migration-steps.d.ts +19 -0
- package/dist/libraries/primeng/18/manual-migration-steps.d.ts.map +1 -0
- package/dist/libraries/primeng/18/manual-migration-steps.js +45 -0
- package/dist/libraries/primeng/18/manual-migration-steps.js.map +1 -0
- package/dist/libraries/primeng/18/mark-deprecated-components.d.ts +9 -0
- package/dist/libraries/primeng/18/mark-deprecated-components.d.ts.map +1 -0
- package/dist/libraries/primeng/18/mark-deprecated-components.js +125 -0
- package/dist/libraries/primeng/18/mark-deprecated-components.js.map +1 -0
- package/dist/libraries/primeng/18/mark-deprecated-css-classes.d.ts +15 -0
- package/dist/libraries/primeng/18/mark-deprecated-css-classes.d.ts.map +1 -0
- package/dist/libraries/primeng/18/mark-deprecated-css-classes.js +108 -0
- package/dist/libraries/primeng/18/mark-deprecated-css-classes.js.map +1 -0
- package/dist/libraries/primeng/18/mark-drawer-size.d.ts +16 -0
- package/dist/libraries/primeng/18/mark-drawer-size.d.ts.map +1 -0
- package/dist/libraries/primeng/18/mark-drawer-size.js +90 -0
- package/dist/libraries/primeng/18/mark-drawer-size.js.map +1 -0
- package/dist/libraries/primeng/18/mark-removed-primeng-modules.d.ts +9 -0
- package/dist/libraries/primeng/18/mark-removed-primeng-modules.d.ts.map +1 -0
- package/dist/libraries/primeng/18/mark-removed-primeng-modules.js +186 -0
- package/dist/libraries/primeng/18/mark-removed-primeng-modules.js.map +1 -0
- package/dist/libraries/primeng/18/migrate-messages-to-message-loop.d.ts +15 -0
- package/dist/libraries/primeng/18/migrate-messages-to-message-loop.d.ts.map +1 -0
- package/dist/libraries/primeng/18/migrate-messages-to-message-loop.js +78 -0
- package/dist/libraries/primeng/18/migrate-messages-to-message-loop.js.map +1 -0
- package/dist/libraries/primeng/18/migrate-p-fluid-to-wrapper.d.ts +27 -0
- package/dist/libraries/primeng/18/migrate-p-fluid-to-wrapper.d.ts.map +1 -0
- package/dist/libraries/primeng/18/migrate-p-fluid-to-wrapper.js +495 -0
- package/dist/libraries/primeng/18/migrate-p-fluid-to-wrapper.js.map +1 -0
- package/dist/libraries/primeng/18/migrate-primeng-config.d.ts +8 -0
- package/dist/libraries/primeng/18/migrate-primeng-config.d.ts.map +1 -0
- package/dist/libraries/primeng/18/migrate-primeng-config.js +154 -0
- package/dist/libraries/primeng/18/migrate-primeng-config.js.map +1 -0
- package/dist/libraries/primeng/18/migrate-primeng-signal-assignments.d.ts +12 -0
- package/dist/libraries/primeng/18/migrate-primeng-signal-assignments.d.ts.map +1 -0
- package/dist/libraries/primeng/18/migrate-primeng-signal-assignments.js +112 -0
- package/dist/libraries/primeng/18/migrate-primeng-signal-assignments.js.map +1 -0
- package/dist/libraries/primeng/18/rename-calendar-to-datepicker.d.ts +8 -0
- package/dist/libraries/primeng/18/rename-calendar-to-datepicker.d.ts.map +1 -0
- package/dist/libraries/primeng/18/rename-calendar-to-datepicker.js +114 -0
- package/dist/libraries/primeng/18/rename-calendar-to-datepicker.js.map +1 -0
- package/dist/libraries/primeng/18/rename-dropdown-to-select.d.ts +8 -0
- package/dist/libraries/primeng/18/rename-dropdown-to-select.d.ts.map +1 -0
- package/dist/libraries/primeng/18/rename-dropdown-to-select.js +114 -0
- package/dist/libraries/primeng/18/rename-dropdown-to-select.js.map +1 -0
- package/dist/libraries/primeng/18/rename-inputswitch-to-toggleswitch.d.ts +8 -0
- package/dist/libraries/primeng/18/rename-inputswitch-to-toggleswitch.d.ts.map +1 -0
- package/dist/libraries/primeng/18/rename-inputswitch-to-toggleswitch.js +114 -0
- package/dist/libraries/primeng/18/rename-inputswitch-to-toggleswitch.js.map +1 -0
- package/dist/libraries/primeng/18/rename-message-interface.d.ts +8 -0
- package/dist/libraries/primeng/18/rename-message-interface.d.ts.map +1 -0
- package/dist/libraries/primeng/18/rename-message-interface.js +107 -0
- package/dist/libraries/primeng/18/rename-message-interface.js.map +1 -0
- package/dist/libraries/primeng/18/rename-overlaypanel-to-popover.d.ts +8 -0
- package/dist/libraries/primeng/18/rename-overlaypanel-to-popover.d.ts.map +1 -0
- package/dist/libraries/primeng/18/rename-overlaypanel-to-popover.js +114 -0
- package/dist/libraries/primeng/18/rename-overlaypanel-to-popover.js.map +1 -0
- package/dist/libraries/primeng/18/rename-sidebar-to-drawer.d.ts +8 -0
- package/dist/libraries/primeng/18/rename-sidebar-to-drawer.d.ts.map +1 -0
- package/dist/libraries/primeng/18/rename-sidebar-to-drawer.js +114 -0
- package/dist/libraries/primeng/18/rename-sidebar-to-drawer.js.map +1 -0
- package/dist/libraries/primeng/18/rename-template-selectors.d.ts +12 -0
- package/dist/libraries/primeng/18/rename-template-selectors.d.ts.map +1 -0
- package/dist/libraries/primeng/18/rename-template-selectors.js +79 -0
- package/dist/libraries/primeng/18/rename-template-selectors.js.map +1 -0
- package/dist/libraries/primeng/18/upgrade-components-to-18.d.ts +8 -0
- package/dist/libraries/primeng/18/upgrade-components-to-18.d.ts.map +1 -0
- package/dist/libraries/primeng/18/upgrade-components-to-18.js +73 -0
- package/dist/libraries/primeng/18/upgrade-components-to-18.js.map +1 -0
- package/dist/libraries/primeng/18/upgrade-to-primeng-18.d.ts +8 -0
- package/dist/libraries/primeng/18/upgrade-to-primeng-18.d.ts.map +1 -0
- package/dist/libraries/primeng/18/upgrade-to-primeng-18.js +68 -0
- package/dist/libraries/primeng/18/upgrade-to-primeng-18.js.map +1 -0
- package/dist/migration/replace-untyped-forms.d.ts +14 -2
- package/dist/migration/replace-untyped-forms.d.ts.map +1 -1
- package/dist/migration/replace-untyped-forms.js +108 -36
- package/dist/migration/replace-untyped-forms.js.map +1 -1
- package/dist/migration/upgrade-angular-peer-libs-to-18.d.ts +17 -0
- package/dist/migration/upgrade-angular-peer-libs-to-18.d.ts.map +1 -0
- package/dist/migration/upgrade-angular-peer-libs-to-18.js +49 -0
- package/dist/migration/upgrade-angular-peer-libs-to-18.js.map +1 -0
- package/dist/migration/upgrade-to-angular-18.d.ts.map +1 -1
- package/dist/migration/upgrade-to-angular-18.js +16 -7
- package/dist/migration/upgrade-to-angular-18.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +57 -0
- package/src/libraries/primeng/18/add-primeng-provider.ts +359 -0
- package/src/libraries/primeng/18/index.ts +24 -0
- package/src/libraries/primeng/18/manual-migration-steps.ts +37 -0
- package/src/libraries/primeng/18/mark-deprecated-components.ts +128 -0
- package/src/libraries/primeng/18/mark-deprecated-css-classes.ts +109 -0
- package/src/libraries/primeng/18/mark-drawer-size.ts +85 -0
- package/src/libraries/primeng/18/mark-removed-primeng-modules.ts +198 -0
- package/src/libraries/primeng/18/migrate-messages-to-message-loop.ts +72 -0
- package/src/libraries/primeng/18/migrate-p-fluid-to-wrapper.ts +439 -0
- package/src/libraries/primeng/18/migrate-primeng-config.ts +112 -0
- package/src/libraries/primeng/18/migrate-primeng-signal-assignments.ts +83 -0
- package/src/libraries/primeng/18/rename-calendar-to-datepicker.ts +81 -0
- package/src/libraries/primeng/18/rename-dropdown-to-select.ts +81 -0
- package/src/libraries/primeng/18/rename-inputswitch-to-toggleswitch.ts +81 -0
- package/src/libraries/primeng/18/rename-message-interface.ts +78 -0
- package/src/libraries/primeng/18/rename-overlaypanel-to-popover.ts +81 -0
- package/src/libraries/primeng/18/rename-sidebar-to-drawer.ts +81 -0
- package/src/libraries/primeng/18/rename-template-selectors.ts +63 -0
- package/src/libraries/primeng/18/upgrade-components-to-18.ts +57 -0
- package/src/libraries/primeng/18/upgrade-to-primeng-18.ts +52 -0
- package/src/migration/replace-untyped-forms.ts +111 -39
- package/src/migration/upgrade-angular-peer-libs-to-18.ts +33 -0
- package/src/migration/upgrade-to-angular-18.ts +16 -7
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
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 {PlainText, PlainTextVisitor} from "@openrewrite/rewrite/text";
|
|
9
|
+
import {
|
|
10
|
+
ManualMigrationStep,
|
|
11
|
+
MANUAL_MIGRATION_STEPS_DESCRIPTION,
|
|
12
|
+
MANUAL_MIGRATION_STEPS_DISPLAY_NAME,
|
|
13
|
+
MANUAL_MIGRATION_STEPS_NAME,
|
|
14
|
+
} from "./manual-migration-steps";
|
|
15
|
+
|
|
16
|
+
interface DeprecatedClass {
|
|
17
|
+
className: string;
|
|
18
|
+
todo: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DEPRECATED_CLASSES: DeprecatedClass[] = [
|
|
22
|
+
{
|
|
23
|
+
className: 'p-link',
|
|
24
|
+
todo: "`.p-link` CSS class is removed in PrimeNG 18. Replace `<a class=\"p-link\">` with a `<p-button [link]=\"true\">` (or a `pButton` with `[link]=\"true\"`).",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
className: 'p-highlight',
|
|
28
|
+
todo: "`.p-highlight` CSS class is removed in PrimeNG 18. Each component now has its own class — replace with the component-specific selector (e.g. `.p-select-option-selected`, `.p-datatable-row-selected`, `.p-tree-node-selected`).",
|
|
29
|
+
},
|
|
30
|
+
// Note: `.p-fluid` is auto-migrated by `MigratePFluidToWrapper` (rewrites `<div class="…p-fluid…">`
|
|
31
|
+
// to `<p-fluid class="…">` and adds the FluidModule import). It's intentionally not flagged here.
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Mark every occurrence of a removed PrimeNG v17 CSS class (`p-link`, `p-highlight`,
|
|
36
|
+
* `p-fluid`) in HTML templates with an HTML TODO comment, and write a row to the
|
|
37
|
+
* `ManualMigrationSteps` data table. The class itself is left in place — replacements
|
|
38
|
+
* are component-specific and need a human/AI agent to choose the right substitute.
|
|
39
|
+
*/
|
|
40
|
+
export class MarkDeprecatedPrimengCssClasses extends Recipe {
|
|
41
|
+
readonly name = "org.openrewrite.primeng.MarkDeprecatedPrimengCssClasses";
|
|
42
|
+
readonly displayName = "Mark deprecated PrimeNG CSS classes with TODO comments";
|
|
43
|
+
readonly description = "For every HTML template that references a CSS class removed in PrimeNG 18 " +
|
|
44
|
+
"(`.p-link`, `.p-highlight`, `.p-fluid`), inserts a `<!-- TODO: ... -->` comment immediately before " +
|
|
45
|
+
"the offending element and writes a row to the `ManualMigrationSteps` data table. The class itself " +
|
|
46
|
+
"is left in place — the replacements are context-dependent (component-specific selectors, the new " +
|
|
47
|
+
"`fluid` input, etc.) and need a human or AI agent to apply.";
|
|
48
|
+
|
|
49
|
+
private dataTable = new DataTable<ManualMigrationStep>(
|
|
50
|
+
MANUAL_MIGRATION_STEPS_NAME,
|
|
51
|
+
MANUAL_MIGRATION_STEPS_DISPLAY_NAME,
|
|
52
|
+
MANUAL_MIGRATION_STEPS_DESCRIPTION,
|
|
53
|
+
ManualMigrationStep,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
57
|
+
const recipeName = this.name;
|
|
58
|
+
const dataTable = this.dataTable;
|
|
59
|
+
return new class extends PlainTextVisitor<ExecutionContext> {
|
|
60
|
+
override async visitText(text: PlainText, p: ExecutionContext): Promise<PlainText | undefined> {
|
|
61
|
+
const t = await super.visitText(text, p) as PlainText;
|
|
62
|
+
if (!t) return t;
|
|
63
|
+
if (!t.sourcePath.endsWith('.html')) return t;
|
|
64
|
+
|
|
65
|
+
let updated = t.text;
|
|
66
|
+
let modified = false;
|
|
67
|
+
|
|
68
|
+
for (const {className, todo} of DEPRECATED_CLASSES) {
|
|
69
|
+
// Match an opening tag whose attributes contain the deprecated class name
|
|
70
|
+
// either as a bare class token (`class="… p-link …"`) or via a binding
|
|
71
|
+
// (`[class.p-link]="…"` / `[ngClass]="… p-link …"`). We don't try to be
|
|
72
|
+
// perfect about parsing HTML — false positives are tolerable for a
|
|
73
|
+
// search-style recipe; missed matches are not.
|
|
74
|
+
const tagRe = new RegExp(`(^|\\n)([ \\t]*)(<[a-zA-Z][\\w-]*(?:\\s+[^>]*?)?\\b(?:class\\s*=\\s*"[^"]*\\b${className}\\b[^"]*"|class\\s*=\\s*'[^']*\\b${className}\\b[^']*')[^>]*>)`, 'g');
|
|
75
|
+
const todoMarker = `<!-- TODO: PrimeNG 18 — ${todo} -->`;
|
|
76
|
+
|
|
77
|
+
let alreadyMarked = false;
|
|
78
|
+
updated = updated.replace(tagRe, (match, lead, indent, tag) => {
|
|
79
|
+
// Idempotence: skip if a previous run already inserted this exact TODO above.
|
|
80
|
+
const before = match.indexOf(tag);
|
|
81
|
+
const precedingChunk = updated.slice(0, updated.indexOf(match) + before);
|
|
82
|
+
if (precedingChunk.endsWith(todoMarker + '\n' + indent)
|
|
83
|
+
|| precedingChunk.endsWith(todoMarker + indent)) {
|
|
84
|
+
alreadyMarked = true;
|
|
85
|
+
return match;
|
|
86
|
+
}
|
|
87
|
+
modified = true;
|
|
88
|
+
dataTable.insertRow(p, {
|
|
89
|
+
sourcePath: t.sourcePath,
|
|
90
|
+
recipe: recipeName,
|
|
91
|
+
subject: `class .${className}`,
|
|
92
|
+
reason: "PrimeNG 18 removed this CSS class; replacement is component-specific.",
|
|
93
|
+
action: todo,
|
|
94
|
+
});
|
|
95
|
+
return `${lead}${indent}${todoMarker}\n${indent}${tag}`;
|
|
96
|
+
});
|
|
97
|
+
void alreadyMarked;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!modified) return t;
|
|
101
|
+
return {
|
|
102
|
+
...t,
|
|
103
|
+
text: updated,
|
|
104
|
+
snippets: [],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}();
|
|
108
|
+
}
|
|
109
|
+
}
|