@openrewrite/recipes-angular 1.17.0 → 1.18.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.
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +83 -1
- package/dist/index.js.map +1 -1
- package/dist/libraries/cdk/find-cdk-signature-changes.d.ts +48 -0
- package/dist/libraries/cdk/find-cdk-signature-changes.d.ts.map +1 -0
- package/dist/libraries/cdk/find-cdk-signature-changes.js +323 -0
- package/dist/libraries/cdk/find-cdk-signature-changes.js.map +1 -0
- package/dist/libraries/cdk/find-removed-cdk-apis.d.ts +32 -0
- package/dist/libraries/cdk/find-removed-cdk-apis.d.ts.map +1 -0
- package/dist/libraries/cdk/find-removed-cdk-apis.js +75 -0
- package/dist/libraries/cdk/find-removed-cdk-apis.js.map +1 -0
- package/dist/libraries/cdk/migrate-cdk-sass-imports.d.ts +17 -0
- package/dist/libraries/cdk/migrate-cdk-sass-imports.d.ts.map +1 -0
- package/dist/libraries/cdk/migrate-cdk-sass-imports.js +177 -0
- package/dist/libraries/cdk/migrate-cdk-sass-imports.js.map +1 -0
- package/dist/libraries/cdk/migrate-cdk-stepper.d.ts +8 -0
- package/dist/libraries/cdk/migrate-cdk-stepper.d.ts.map +1 -0
- package/dist/libraries/cdk/migrate-cdk-stepper.js +92 -0
- package/dist/libraries/cdk/migrate-cdk-stepper.js.map +1 -0
- package/dist/libraries/cdk/migrate-deprecated-overlay-sass.d.ts +9 -0
- package/dist/libraries/cdk/migrate-deprecated-overlay-sass.d.ts.map +1 -0
- package/dist/libraries/cdk/migrate-deprecated-overlay-sass.js +57 -0
- package/dist/libraries/cdk/migrate-deprecated-overlay-sass.js.map +1 -0
- package/dist/libraries/cdk/migrate-drag-drop-registry.d.ts +8 -0
- package/dist/libraries/cdk/migrate-drag-drop-registry.d.ts.map +1 -0
- package/dist/libraries/cdk/migrate-drag-drop-registry.js +118 -0
- package/dist/libraries/cdk/migrate-drag-drop-registry.js.map +1 -0
- package/dist/libraries/cdk/migrate-focus-trap-factory-create.d.ts +8 -0
- package/dist/libraries/cdk/migrate-focus-trap-factory-create.d.ts.map +1 -0
- package/dist/libraries/cdk/migrate-focus-trap-factory-create.js +74 -0
- package/dist/libraries/cdk/migrate-focus-trap-factory-create.js.map +1 -0
- package/dist/libraries/cdk/remove-cdk-super-arguments.d.ts +8 -0
- package/dist/libraries/cdk/remove-cdk-super-arguments.d.ts.map +1 -0
- package/dist/libraries/cdk/remove-cdk-super-arguments.js +111 -0
- package/dist/libraries/cdk/remove-cdk-super-arguments.js.map +1 -0
- package/dist/libraries/cdk/remove-portal-factory-resolver-arguments.d.ts +8 -0
- package/dist/libraries/cdk/remove-portal-factory-resolver-arguments.d.ts.map +1 -0
- package/dist/libraries/cdk/remove-portal-factory-resolver-arguments.js +108 -0
- package/dist/libraries/cdk/remove-portal-factory-resolver-arguments.js.map +1 -0
- package/dist/libraries/cdk/remove-tilde-from-stylesheet-imports.d.ts +8 -0
- package/dist/libraries/cdk/remove-tilde-from-stylesheet-imports.d.ts.map +1 -0
- package/dist/libraries/cdk/remove-tilde-from-stylesheet-imports.js +27 -0
- package/dist/libraries/cdk/remove-tilde-from-stylesheet-imports.js.map +1 -0
- package/dist/libraries/cdk/rename-portal-template-selectors.d.ts +27 -0
- package/dist/libraries/cdk/rename-portal-template-selectors.d.ts.map +1 -0
- package/dist/libraries/cdk/rename-portal-template-selectors.js +75 -0
- package/dist/libraries/cdk/rename-portal-template-selectors.js.map +1 -0
- package/dist/libraries/cdk/upgrade-to-cdk.d.ts +38 -0
- package/dist/libraries/cdk/upgrade-to-cdk.d.ts.map +1 -0
- package/dist/libraries/cdk/upgrade-to-cdk.js +173 -0
- package/dist/libraries/cdk/upgrade-to-cdk.js.map +1 -0
- package/dist/libraries/components/lst.d.ts +9 -0
- package/dist/libraries/components/lst.d.ts.map +1 -0
- package/dist/libraries/components/lst.js +37 -0
- package/dist/libraries/components/lst.js.map +1 -0
- package/dist/libraries/components/stylesheets.d.ts +13 -0
- package/dist/libraries/components/stylesheets.d.ts.map +1 -0
- package/dist/libraries/components/stylesheets.js +71 -0
- package/dist/libraries/components/stylesheets.js.map +1 -0
- package/dist/libraries/components/symbols.d.ts +27 -0
- package/dist/libraries/components/symbols.d.ts.map +1 -0
- package/dist/libraries/components/symbols.js +120 -0
- package/dist/libraries/components/symbols.js.map +1 -0
- package/dist/libraries/components/templates.d.ts +24 -0
- package/dist/libraries/components/templates.d.ts.map +1 -0
- package/dist/libraries/components/templates.js +141 -0
- package/dist/libraries/components/templates.js.map +1 -0
- package/dist/libraries/primeng/18/mark-deprecated-components.d.ts.map +1 -1
- package/dist/libraries/primeng/18/mark-deprecated-components.js +9 -5
- package/dist/libraries/primeng/18/mark-deprecated-components.js.map +1 -1
- package/dist/migration/add-trust-proxy-headers.d.ts.map +1 -1
- package/dist/migration/add-trust-proxy-headers.js +2 -1
- package/dist/migration/add-trust-proxy-headers.js.map +1 -1
- package/dist/migration/replace-initial-navigation.d.ts.map +1 -1
- package/dist/migration/replace-initial-navigation.js +2 -2
- package/dist/migration/replace-initial-navigation.js.map +1 -1
- package/dist/migration/update-component-template-url.js +1 -1
- package/dist/migration/update-component-template-url.js.map +1 -1
- package/dist/migration/upgrade-to-angular-13.d.ts.map +1 -1
- package/dist/migration/upgrade-to-angular-13.js +2 -0
- package/dist/migration/upgrade-to-angular-13.js.map +1 -1
- package/dist/migration/upgrade-to-angular-14.d.ts.map +1 -1
- package/dist/migration/upgrade-to-angular-14.js +2 -0
- package/dist/migration/upgrade-to-angular-14.js.map +1 -1
- package/dist/migration/upgrade-to-angular-17.d.ts.map +1 -1
- package/dist/migration/upgrade-to-angular-17.js +2 -0
- package/dist/migration/upgrade-to-angular-17.js.map +1 -1
- package/dist/migration/upgrade-to-angular-20.d.ts.map +1 -1
- package/dist/migration/upgrade-to-angular-20.js +2 -0
- package/dist/migration/upgrade-to-angular-20.js.map +1 -1
- package/dist/migration/upgrade-to-angular-21.d.ts.map +1 -1
- package/dist/migration/upgrade-to-angular-21.js +2 -0
- package/dist/migration/upgrade-to-angular-21.js.map +1 -1
- package/dist/migration/upgrade-to-angular-22.d.ts.map +1 -1
- package/dist/migration/upgrade-to-angular-22.js +2 -0
- package/dist/migration/upgrade-to-angular-22.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +97 -0
- package/src/libraries/cdk/find-cdk-signature-changes.ts +209 -0
- package/src/libraries/cdk/find-removed-cdk-apis.ts +76 -0
- package/src/libraries/cdk/migrate-cdk-sass-imports.ts +174 -0
- package/src/libraries/cdk/migrate-cdk-stepper.ts +58 -0
- package/src/libraries/cdk/migrate-deprecated-overlay-sass.ts +56 -0
- package/src/libraries/cdk/migrate-drag-drop-registry.ts +79 -0
- package/src/libraries/cdk/migrate-focus-trap-factory-create.ts +51 -0
- package/src/libraries/cdk/remove-cdk-super-arguments.ts +83 -0
- package/src/libraries/cdk/remove-portal-factory-resolver-arguments.ts +88 -0
- package/src/libraries/cdk/remove-tilde-from-stylesheet-imports.ts +22 -0
- package/src/libraries/cdk/rename-portal-template-selectors.ts +51 -0
- package/src/libraries/cdk/upgrade-to-cdk.ts +146 -0
- package/src/libraries/components/lst.ts +33 -0
- package/src/libraries/components/stylesheets.ts +55 -0
- package/src/libraries/components/symbols.ts +116 -0
- package/src/libraries/components/templates.ts +126 -0
- package/src/libraries/primeng/18/mark-deprecated-components.ts +8 -4
- package/src/migration/add-trust-proxy-headers.ts +2 -1
- package/src/migration/replace-initial-navigation.ts +3 -1
- package/src/migration/update-component-template-url.ts +1 -1
- package/src/migration/upgrade-to-angular-13.ts +2 -0
- package/src/migration/upgrade-to-angular-14.ts +2 -0
- package/src/migration/upgrade-to-angular-17.ts +2 -0
- package/src/migration/upgrade-to-angular-20.ts +2 -0
- package/src/migration/upgrade-to-angular-21.ts +2 -0
- package/src/migration/upgrade-to-angular-22.ts +2 -0
|
@@ -0,0 +1,209 @@
|
|
|
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, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
import {isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
10
|
+
import {importedNames} from "../components/symbols";
|
|
11
|
+
import {extendedClassName} from "../components/lst";
|
|
12
|
+
|
|
13
|
+
/** Whether a type is the named class of an `@angular/cdk` entry point. */
|
|
14
|
+
export function isCdkClass(type: any, className: string): boolean {
|
|
15
|
+
const fqn: string | undefined = type?.fullyQualifiedName;
|
|
16
|
+
return fqn !== undefined && fqn.startsWith('@angular/cdk') && fqn.endsWith(`.${className}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function argumentCount(container: J.Container<J> | undefined): number {
|
|
20
|
+
const elements = container?.elements ?? [];
|
|
21
|
+
return elements.length === 1 && elements[0].element.kind === J.Kind.Empty ? 0 : elements.length;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class FindOverlayConnectedTo extends Recipe {
|
|
25
|
+
readonly name = "org.openrewrite.angular.cdk.FindOverlayConnectedTo";
|
|
26
|
+
readonly displayName = "Find `OverlayPositionBuilder.connectedTo` calls";
|
|
27
|
+
readonly description = "Finds calls of `connectedTo` on the `OverlayPositionBuilder` that `overlay.position()` returns. " +
|
|
28
|
+
"CDK 13 removed it with `ConnectedPositionStrategy`, so call `flexibleConnectedTo(origin)` and pass the positions " +
|
|
29
|
+
"to `withPositions` instead.";
|
|
30
|
+
|
|
31
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
32
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
33
|
+
protected async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
34
|
+
const m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
35
|
+
if (m.name.simpleName !== 'connectedTo') return m;
|
|
36
|
+
const select = m.select?.element;
|
|
37
|
+
const onPositionBuilder = isCdkClass(m.methodType?.declaringType, 'OverlayPositionBuilder') ||
|
|
38
|
+
(select?.kind === J.Kind.MethodInvocation && (select as J.MethodInvocation).name.simpleName === 'position');
|
|
39
|
+
if (!onPositionBuilder) return m;
|
|
40
|
+
return foundSearchResult(m, "`connectedTo` was removed in @angular/cdk 13. " +
|
|
41
|
+
"Use `flexibleConnectedTo(origin).withPositions([...])` instead.");
|
|
42
|
+
}
|
|
43
|
+
}();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class FindDialogComponentFactoryResolver extends Recipe {
|
|
48
|
+
readonly name = "org.openrewrite.angular.cdk.FindDialogComponentFactoryResolver";
|
|
49
|
+
readonly displayName = "Find `componentFactoryResolver` in CDK dialog configurations";
|
|
50
|
+
readonly description = "Finds `componentFactoryResolver` properties in files that import `@angular/cdk/dialog`. " +
|
|
51
|
+
"CDK 20 removed `DialogConfig.componentFactoryResolver`, since Ivy resolves components without one.";
|
|
52
|
+
|
|
53
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
54
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
55
|
+
protected async visitJsCompilationUnit(cu: JS.CompilationUnit, p: ExecutionContext): Promise<J | undefined> {
|
|
56
|
+
const importsDialog = cu.statements.some(s => s.element.kind === JS.Kind.Import &&
|
|
57
|
+
((s.element as JS.Import).moduleSpecifier?.element as J.Literal | undefined)?.value === '@angular/cdk/dialog');
|
|
58
|
+
return importsDialog ? super.visitJsCompilationUnit(cu, p) : cu;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected async visitPropertyAssignment(property: JS.PropertyAssignment, p: ExecutionContext): Promise<J | undefined> {
|
|
62
|
+
const prop = await super.visitPropertyAssignment(property, p) as JS.PropertyAssignment;
|
|
63
|
+
const name = prop.name.element;
|
|
64
|
+
if (!isIdentifier(name) || name.simpleName !== 'componentFactoryResolver') return prop;
|
|
65
|
+
return foundSearchResult(prop, "`componentFactoryResolver` was removed from `DialogConfig` in " +
|
|
66
|
+
"@angular/cdk 20. Remove it, since components resolve without a factory resolver.");
|
|
67
|
+
}
|
|
68
|
+
}();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class FindTestElementImplementations extends Recipe {
|
|
73
|
+
readonly name = "org.openrewrite.angular.cdk.FindTestElementImplementations";
|
|
74
|
+
readonly displayName = "Find `TestElement` implementations without `setContenteditableValue`";
|
|
75
|
+
readonly description = "Finds classes that implement `TestElement` from `@angular/cdk/testing` without a " +
|
|
76
|
+
"`setContenteditableValue` method, which CDK 21 made required.";
|
|
77
|
+
|
|
78
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
79
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
80
|
+
private testElement = new Map<string, string>();
|
|
81
|
+
|
|
82
|
+
protected async visitJsCompilationUnit(cu: JS.CompilationUnit, p: ExecutionContext): Promise<J | undefined> {
|
|
83
|
+
this.testElement = importedNames(cu, '@angular/cdk/testing', ['TestElement']);
|
|
84
|
+
return this.testElement.size === 0 ? cu : super.visitJsCompilationUnit(cu, p);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
protected async visitClassDeclaration(classDecl: J.ClassDeclaration, p: ExecutionContext): Promise<J | undefined> {
|
|
88
|
+
const c = await super.visitClassDeclaration(classDecl, p) as J.ClassDeclaration;
|
|
89
|
+
const implementsTestElement = (c.implements?.elements ?? [])
|
|
90
|
+
.some(i => isIdentifier(i.element) && this.testElement.has(i.element.simpleName));
|
|
91
|
+
if (!implementsTestElement) return c;
|
|
92
|
+
const hasMethod = c.body.statements.some(s => s.element.kind === J.Kind.MethodDeclaration &&
|
|
93
|
+
(s.element as J.MethodDeclaration).name.simpleName === 'setContenteditableValue');
|
|
94
|
+
if (hasMethod) return c;
|
|
95
|
+
return foundSearchResult(c, "`TestElement.setContenteditableValue` is required as of @angular/cdk 21. " +
|
|
96
|
+
"Implement it to set the text of a `contenteditable` element.");
|
|
97
|
+
}
|
|
98
|
+
}();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Constructor arity at which the `injector` parameter of the CDK 22 focus traps becomes required. */
|
|
103
|
+
const FOCUS_TRAP_ARITY: Record<string, number> = {
|
|
104
|
+
FocusTrap: 6,
|
|
105
|
+
ConfigurableFocusTrap: 8,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export class FindFocusTrapWithoutInjector extends Recipe {
|
|
109
|
+
readonly name = "org.openrewrite.angular.cdk.FindFocusTrapWithoutInjector";
|
|
110
|
+
readonly displayName = "Find focus traps constructed without an `Injector`";
|
|
111
|
+
readonly description = "Finds `new FocusTrap(...)` and `new ConfigurableFocusTrap(...)` calls without the trailing " +
|
|
112
|
+
"`Injector` argument, which CDK 22 made required. Prefer creating focus traps with `FocusTrapFactory` or " +
|
|
113
|
+
"`ConfigurableFocusTrapFactory`, which pass the injector themselves.";
|
|
114
|
+
|
|
115
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
116
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
117
|
+
private focusTraps = new Map<string, string>();
|
|
118
|
+
|
|
119
|
+
protected async visitJsCompilationUnit(cu: JS.CompilationUnit, p: ExecutionContext): Promise<J | undefined> {
|
|
120
|
+
this.focusTraps = importedNames(cu, '@angular/cdk/a11y', Object.keys(FOCUS_TRAP_ARITY));
|
|
121
|
+
return this.focusTraps.size === 0 ? cu : super.visitJsCompilationUnit(cu, p);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected async visitNewClass(newClass: J.NewClass, p: ExecutionContext): Promise<J | undefined> {
|
|
125
|
+
const n = await super.visitNewClass(newClass, p) as J.NewClass;
|
|
126
|
+
const focusTrap = n.class && isIdentifier(n.class) ? this.focusTraps.get(n.class.simpleName) : undefined;
|
|
127
|
+
if (!focusTrap || argumentCount(n.arguments) >= FOCUS_TRAP_ARITY[focusTrap]) return n;
|
|
128
|
+
return foundSearchResult(n, `The \`Injector\` argument of \`${focusTrap}\` is required as of ` +
|
|
129
|
+
"@angular/cdk 22. Pass one, or create the focus trap with its factory.");
|
|
130
|
+
}
|
|
131
|
+
}();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export class FindDropListRefDropWithoutEvent extends Recipe {
|
|
136
|
+
readonly name = "org.openrewrite.angular.cdk.FindDropListRefDropWithoutEvent";
|
|
137
|
+
readonly displayName = "Find `DropListRef.drop` calls without an event";
|
|
138
|
+
readonly description = "Finds `DropListRef.drop(...)` calls without the trailing `event` argument, which CDK 22 made required.";
|
|
139
|
+
|
|
140
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
141
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
142
|
+
protected async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
143
|
+
const m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
144
|
+
if (m.name.simpleName !== 'drop' || !isCdkClass(m.methodType?.declaringType, 'DropListRef')) return m;
|
|
145
|
+
if (argumentCount(m.arguments) >= 8) return m;
|
|
146
|
+
return foundSearchResult(m, "The `event` argument of `DropListRef.drop` is required as of @angular/cdk 22. " +
|
|
147
|
+
"Pass the `MouseEvent` or `TouchEvent` that ended the drag.");
|
|
148
|
+
}
|
|
149
|
+
}();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export class FindComponentPortalFactoryResolver extends Recipe {
|
|
154
|
+
readonly name = "org.openrewrite.angular.cdk.FindComponentPortalFactoryResolver";
|
|
155
|
+
readonly displayName = "Find `ComponentPortal.componentFactoryResolver` uses";
|
|
156
|
+
readonly description = "Finds reads and writes of `ComponentPortal.componentFactoryResolver`, which CDK 20 removed " +
|
|
157
|
+
"since Ivy resolves components without one.";
|
|
158
|
+
|
|
159
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
160
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
161
|
+
protected async visitFieldAccess(fieldAccess: J.FieldAccess, p: ExecutionContext): Promise<J | undefined> {
|
|
162
|
+
const f = await super.visitFieldAccess(fieldAccess, p) as J.FieldAccess;
|
|
163
|
+
const name = f.name.element;
|
|
164
|
+
if (name.simpleName !== 'componentFactoryResolver' || !isCdkClass(name.fieldType?.owner, 'ComponentPortal')) return f;
|
|
165
|
+
return foundSearchResult(f, "`ComponentPortal.componentFactoryResolver` was removed in @angular/cdk 20. " +
|
|
166
|
+
"Remove it, since components resolve without a factory resolver.");
|
|
167
|
+
}
|
|
168
|
+
}();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const SELECTION_METHODS = ['clear', 'deselect', 'select', 'setSelection', 'toggle'];
|
|
173
|
+
|
|
174
|
+
export class FindSelectionModelVoidOverrides extends Recipe {
|
|
175
|
+
readonly name = "org.openrewrite.angular.cdk.FindSelectionModelVoidOverrides";
|
|
176
|
+
readonly displayName = "Find `SelectionModel` overrides that return `void`";
|
|
177
|
+
readonly description = "Finds `select`, `deselect`, `setSelection`, `toggle` and `clear` overrides in subclasses of " +
|
|
178
|
+
"`SelectionModel` that declare a `void` return type. CDK 20 changed these methods to return whether the " +
|
|
179
|
+
"selection changed, so the overrides no longer compile.";
|
|
180
|
+
|
|
181
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
182
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
183
|
+
private selectionModel = new Map<string, string>();
|
|
184
|
+
|
|
185
|
+
protected async visitJsCompilationUnit(cu: JS.CompilationUnit, p: ExecutionContext): Promise<J | undefined> {
|
|
186
|
+
this.selectionModel = importedNames(cu, '@angular/cdk/collections', ['SelectionModel']);
|
|
187
|
+
return this.selectionModel.size === 0 ? cu : super.visitJsCompilationUnit(cu, p);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
protected async visitMethodDeclaration(method: J.MethodDeclaration, p: ExecutionContext): Promise<J | undefined> {
|
|
191
|
+
const m = await super.visitMethodDeclaration(method, p) as J.MethodDeclaration;
|
|
192
|
+
if (!SELECTION_METHODS.includes(m.name.simpleName)) return m;
|
|
193
|
+
const returnType = m.returnTypeExpression;
|
|
194
|
+
const returnsVoid = returnType !== undefined && printsAs(returnType, 'void');
|
|
195
|
+
const classDecl = this.cursor.firstEnclosing((t: any): t is J.ClassDeclaration => t?.kind === J.Kind.ClassDeclaration);
|
|
196
|
+
const extended = classDecl && extendedClassName(classDecl);
|
|
197
|
+
if (!returnsVoid || !extended || !this.selectionModel.has(extended)) return m;
|
|
198
|
+
return foundSearchResult(m, `\`SelectionModel.${m.name.simpleName}\` returns a \`boolean\` as of @angular/cdk 20. ` +
|
|
199
|
+
"Return whether the selection changed, usually the result of the `super` call.");
|
|
200
|
+
}
|
|
201
|
+
}();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function printsAs(typeTree: J, keyword: string): boolean {
|
|
206
|
+
const type = typeTree.kind === JS.Kind.TypeInfo ? (typeTree as JS.TypeInfo).typeIdentifier : typeTree;
|
|
207
|
+
return (type.kind === J.Kind.Primitive && (type as J.Primitive).type?.keyword === keyword) ||
|
|
208
|
+
(isIdentifier(type) && type.simpleName === keyword);
|
|
209
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
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 {FindRemovedSymbols, RemovedSymbol} from "../components/symbols";
|
|
8
|
+
|
|
9
|
+
function removed(module: string, version: number, names: string[], hint: string): RemovedSymbol[] {
|
|
10
|
+
return names.map(name => ({module, name, message: `\`${name}\` was removed in @angular/cdk ${version}. ${hint}`}));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class FindRemovedCdk13Apis extends FindRemovedSymbols {
|
|
14
|
+
readonly name = "org.openrewrite.angular.cdk.FindRemovedCdk13Apis";
|
|
15
|
+
readonly displayName = "Find APIs removed in Angular CDK 13";
|
|
16
|
+
readonly description = "Finds imports of `ConnectedPositionStrategy`, which `@angular/cdk/overlay` 13 removed.";
|
|
17
|
+
|
|
18
|
+
protected readonly symbols = removed('@angular/cdk/overlay', 13, ['ConnectedPositionStrategy'],
|
|
19
|
+
"Use `FlexibleConnectedPositionStrategy`, created with `overlay.position().flexibleConnectedTo(origin)`.");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class FindRemovedCdk14Apis extends FindRemovedSymbols {
|
|
23
|
+
readonly name = "org.openrewrite.angular.cdk.FindRemovedCdk14Apis";
|
|
24
|
+
readonly displayName = "Find APIs removed in Angular CDK 14";
|
|
25
|
+
readonly description = "Finds imports from `@angular/cdk/testing/protractor`, the entry point CDK 14 removed " +
|
|
26
|
+
"together with Protractor support.";
|
|
27
|
+
|
|
28
|
+
protected readonly symbols = removed('@angular/cdk/testing/protractor', 14,
|
|
29
|
+
['ProtractorElement', 'ProtractorHarnessEnvironment', 'ProtractorHarnessEnvironmentOptions'],
|
|
30
|
+
"Move the end-to-end tests to another runner, or use `SeleniumWebDriverHarnessEnvironment` from " +
|
|
31
|
+
"`@angular/cdk/testing/selenium-webdriver`.");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class FindRemovedCdk20Apis extends FindRemovedSymbols {
|
|
35
|
+
readonly name = "org.openrewrite.angular.cdk.FindRemovedCdk20Apis";
|
|
36
|
+
readonly displayName = "Find APIs removed in Angular CDK 20";
|
|
37
|
+
readonly description = "Finds imports of the `@angular/cdk/dialog`, `@angular/cdk/portal` and " +
|
|
38
|
+
"`@angular/cdk/table` APIs that CDK 20 removed without a drop-in replacement.";
|
|
39
|
+
|
|
40
|
+
protected readonly symbols = [
|
|
41
|
+
...removed('@angular/cdk/dialog', 20, ['DIALOG_SCROLL_STRATEGY_PROVIDER', 'DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY'],
|
|
42
|
+
"Provide `DIALOG_SCROLL_STRATEGY` with a factory that returns the scroll strategy instead."),
|
|
43
|
+
...removed('@angular/cdk/portal', 20, ['PortalInjector'],
|
|
44
|
+
"Use `Injector.create({parent, providers})` from `@angular/core` instead."),
|
|
45
|
+
...removed('@angular/cdk/table', 20, ['CanStick', 'CanStickCtor', 'Constructor', 'mixinHasStickyInput'],
|
|
46
|
+
"The sticky mixin is gone, so declare a boolean `sticky` input on the directive instead."),
|
|
47
|
+
...removed('@angular/cdk/table', 20, ['CDK_TABLE_TEMPLATE'],
|
|
48
|
+
"Copy the table template into the component instead of reusing the CDK constant."),
|
|
49
|
+
...removed('@angular/cdk/table', 20,
|
|
50
|
+
['StickyDirection', 'STICKY_DIRECTIONS', 'StickyStyler', '_COALESCED_STYLE_SCHEDULER', '_CoalescedStyleScheduler', '_Schedule'],
|
|
51
|
+
"This was an internal API of `CdkTable` and has no public replacement."),
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class FindRemovedCdk21Apis extends FindRemovedSymbols {
|
|
56
|
+
readonly name = "org.openrewrite.angular.cdk.FindRemovedCdk21Apis";
|
|
57
|
+
readonly displayName = "Find APIs removed in Angular CDK 21";
|
|
58
|
+
readonly description = "Finds imports of the `@angular/cdk/a11y` factory functions that CDK 21 removed.";
|
|
59
|
+
|
|
60
|
+
protected readonly symbols = [
|
|
61
|
+
...removed('@angular/cdk/a11y', 21, ['LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY'],
|
|
62
|
+
"Provide `LIVE_ANNOUNCER_ELEMENT_TOKEN` with `useValue: null` or your own element instead."),
|
|
63
|
+
...removed('@angular/cdk/a11y', 21, ['TREE_KEY_MANAGER_FACTORY', 'TREE_KEY_MANAGER_FACTORY_PROVIDER', 'NOOP_TREE_KEY_MANAGER_FACTORY'],
|
|
64
|
+
"Provide `TREE_KEY_MANAGER` with your own factory, for example `useValue: (items, options) => new TreeKeyManager(items, options)`."),
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class FindRemovedCdk22Apis extends FindRemovedSymbols {
|
|
69
|
+
readonly name = "org.openrewrite.angular.cdk.FindRemovedCdk22Apis";
|
|
70
|
+
readonly displayName = "Find APIs removed in Angular CDK 22";
|
|
71
|
+
readonly description = "Finds imports of the `@angular/cdk/a11y` `AriaDescriber` constants that CDK 22 removed.";
|
|
72
|
+
|
|
73
|
+
protected readonly symbols = removed('@angular/cdk/a11y', 22,
|
|
74
|
+
['CDK_DESCRIBEDBY_HOST_ATTRIBUTE', 'CDK_DESCRIBEDBY_ID_PREFIX', 'MESSAGES_CONTAINER_ID'],
|
|
75
|
+
"These were internals of `AriaDescriber`, so select described elements by their `aria-describedby` attribute instead.");
|
|
76
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
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, findMarker, foundSearchResult, MarkersKind, ScanningRecipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {PlainText, PlainTextVisitor} from "@openrewrite/rewrite/text";
|
|
9
|
+
import {escapeRegExp, maskComments, withoutComments} from "../components/stylesheets";
|
|
10
|
+
|
|
11
|
+
/** `@import` of an entry point whose `_index.import.scss` CDK 17 deleted. */
|
|
12
|
+
const LEGACY_IMPORT = /^[ \t]*@import\s+(['"])~?@angular\/cdk\/(?:overlay|a11y|text-field)(?:\/_?index(?:\.import)?)?(?:\.scss)?\1[ \t]*;?[ \t]*(?:\r?\n|$)/gm;
|
|
13
|
+
|
|
14
|
+
const CDK_USE = /@use\s+(['"])~?@angular\/cdk\1(?:\s+as\s+([\w-]+|\*))?/;
|
|
15
|
+
|
|
16
|
+
/** Mixins the legacy entry points exposed with a `cdk-` prefix, mapped to their `@angular/cdk` module names. */
|
|
17
|
+
const MIXINS: Record<string, string> = {
|
|
18
|
+
'cdk-overlay': 'overlay',
|
|
19
|
+
'cdk-a11y-visually-hidden': 'a11y-visually-hidden',
|
|
20
|
+
'cdk-a11y': 'a11y-visually-hidden',
|
|
21
|
+
'cdk-high-contrast': 'high-contrast',
|
|
22
|
+
'cdk-text-field-autosize': 'text-field-autosize',
|
|
23
|
+
'cdk-text-field-autofill-color': 'text-field-autofill-color',
|
|
24
|
+
'cdk-text-field-autofill': 'text-field-autofill',
|
|
25
|
+
'cdk-text-field': 'text-field',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Variables the legacy entry points exposed with a `cdk-` prefix, including the deprecated aliases. */
|
|
29
|
+
const VARIABLES: Record<string, string> = {
|
|
30
|
+
'cdk-overlay-container-z-index': 'overlay-container-z-index',
|
|
31
|
+
'cdk-overlay-z-index': 'overlay-z-index',
|
|
32
|
+
'cdk-overlay-backdrop-z-index': 'overlay-backdrop-z-index',
|
|
33
|
+
'cdk-overlay-backdrop-color': 'overlay-backdrop-color',
|
|
34
|
+
'cdk-z-index-overlay-container': 'overlay-container-z-index',
|
|
35
|
+
'cdk-z-index-overlay': 'overlay-z-index',
|
|
36
|
+
'cdk-z-index-overlay-backdrop': 'overlay-backdrop-z-index',
|
|
37
|
+
'cdk-dark-backdrop-background': 'overlay-backdrop-color',
|
|
38
|
+
'cdk-overlay-z-index-overlay-container': 'overlay-container-z-index',
|
|
39
|
+
'cdk-overlay-z-index-overlay': 'overlay-z-index',
|
|
40
|
+
'cdk-overlay-z-index-overlay-backdrop': 'overlay-backdrop-z-index',
|
|
41
|
+
'cdk-overlay-dark-backdrop-background': 'overlay-backdrop-color',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
interface Accumulator {
|
|
45
|
+
/** `cdk-` members that stylesheets of the project declare themselves. */
|
|
46
|
+
declared: Set<string>;
|
|
47
|
+
/** Partial names that stylesheets of the project `@import`. */
|
|
48
|
+
imported: Set<string>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class MigrateCdkSassImports extends ScanningRecipe<Accumulator> {
|
|
52
|
+
readonly name = "org.openrewrite.angular.cdk.MigrateCdkSassImports";
|
|
53
|
+
readonly displayName = "Migrate Angular CDK Sass `@import` to `@use`";
|
|
54
|
+
readonly description = "CDK 17 deleted the `@import` entry points of `@angular/cdk/overlay`, `@angular/cdk/a11y` and " +
|
|
55
|
+
"`@angular/cdk/text-field`. Replaces those imports with `@use '@angular/cdk' as cdk` and renames the `cdk-` " +
|
|
56
|
+
"prefixed mixins and variables they exposed, such as `@include cdk-overlay()` and `$cdk-z-index-overlay`, to the " +
|
|
57
|
+
"members of the `cdk` module. Names that a stylesheet of the project declares itself, as a vendored copy of the CDK " +
|
|
58
|
+
"styles does, keep referring to that declaration. A partial that other stylesheets `@import` and that defines " +
|
|
59
|
+
"members next to its own `@import` rules is marked instead, since adding `@use` to it can change which of those " +
|
|
60
|
+
"members its importers see. This ports the CDK part of the theming API migration of `@angular/material` 12.";
|
|
61
|
+
|
|
62
|
+
initialValue(_ctx: ExecutionContext): Accumulator {
|
|
63
|
+
return {declared: new Set(), imported: new Set()};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async scanner(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
67
|
+
return new class extends PlainTextVisitor<ExecutionContext> {
|
|
68
|
+
override async visitText(text: PlainText, p: ExecutionContext): Promise<PlainText | undefined> {
|
|
69
|
+
if (text.sourcePath.endsWith('.scss')) {
|
|
70
|
+
const masked = maskComments(text.text);
|
|
71
|
+
declaredNames(masked).forEach(name => acc.declared.add(name));
|
|
72
|
+
importedPartials(masked).forEach(name => acc.imported.add(name));
|
|
73
|
+
}
|
|
74
|
+
return text;
|
|
75
|
+
}
|
|
76
|
+
}();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async editorWithData(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
80
|
+
return new class extends PlainTextVisitor<ExecutionContext> {
|
|
81
|
+
override async visitText(text: PlainText, p: ExecutionContext): Promise<PlainText | undefined> {
|
|
82
|
+
const t = await super.visitText(text, p) as PlainText;
|
|
83
|
+
if (!t || !t.sourcePath.endsWith('.scss') || !t.text.includes('cdk')) return t;
|
|
84
|
+
const masked = maskComments(t.text);
|
|
85
|
+
if (configuresLegacyImport(masked)) {
|
|
86
|
+
return mark(t, "This stylesheet configures the defaults of a CDK Sass entry point that @angular/cdk 17 " +
|
|
87
|
+
"deleted. Pass them with `@use '@angular/cdk' with ($overlay-z-index: ...)` instead, using the unprefixed names.");
|
|
88
|
+
}
|
|
89
|
+
const updated = withoutComments(t.text, content => migrateSass(content, acc.declared));
|
|
90
|
+
if (updated === t.text) return t;
|
|
91
|
+
if (!CDK_USE.test(masked) && acc.imported.has(partialName(t.sourcePath)) && definesMembersNextToImports(masked)) {
|
|
92
|
+
return mark(t, "This partial uses CDK Sass members that @angular/cdk 17 only provides through " +
|
|
93
|
+
"`@use '@angular/cdk'`. Other stylesheets `@import` it and it defines its own members next to its " +
|
|
94
|
+
"`@import` rules, so adding `@use` here can change which of those members they see. Migrate it by hand.");
|
|
95
|
+
}
|
|
96
|
+
return {...t, text: updated, snippets: []};
|
|
97
|
+
}
|
|
98
|
+
}();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function mark(text: PlainText, message: string): PlainText {
|
|
103
|
+
return findMarker(text, MarkersKind.SearchResult) ? text : foundSearchResult(text, message);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function migrateSass(content: string, declared: Set<string>): string {
|
|
107
|
+
const existingUse = content.match(CDK_USE);
|
|
108
|
+
const namespace = existingUse?.[2] ?? 'cdk';
|
|
109
|
+
const prefix = namespace === '*' ? '' : `${namespace}.`;
|
|
110
|
+
|
|
111
|
+
let migrated = content.replace(LEGACY_IMPORT, '');
|
|
112
|
+
const hadLegacyImport = migrated !== content;
|
|
113
|
+
const beforeSymbols = migrated;
|
|
114
|
+
|
|
115
|
+
for (const name of longestFirst(MIXINS).filter(n => !declared.has(n))) {
|
|
116
|
+
migrated = migrated.replace(new RegExp(`@include(\\s+)${escapeRegExp(name)}(?![\\w-])`, 'g'),
|
|
117
|
+
`@include$1${prefix}${MIXINS[name]}`);
|
|
118
|
+
}
|
|
119
|
+
for (const name of longestFirst(VARIABLES).filter(n => !declared.has(n))) {
|
|
120
|
+
migrated = migrated.replace(new RegExp(`(?<![\\w.-])\\$${escapeRegExp(name)}(?![\\w-])`, 'g'),
|
|
121
|
+
`${prefix}$${VARIABLES[name]}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (migrated === beforeSymbols) {
|
|
125
|
+
return hadLegacyImport ? migrated : content;
|
|
126
|
+
}
|
|
127
|
+
return existingUse ? migrated : insertUse(migrated);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Inserts `@use` after any leading comments and `@charset`, since Sass requires `@use` before other rules. */
|
|
131
|
+
function insertUse(content: string): string {
|
|
132
|
+
const newline = content.includes('\r\n') ? '\r\n' : '\n';
|
|
133
|
+
const header = content.match(/^(?:\s*(?:__ngComment\d+__|@charset[^;]*;))+[ \t]*(?:\r?\n|$)/)?.[0] ?? '';
|
|
134
|
+
const separator = header.length > 0 && !header.endsWith('\n') ? newline : '';
|
|
135
|
+
return header + separator + `@use '@angular/cdk' as cdk;` + newline + content.slice(header.length);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** `cdk-` members a stylesheet declares itself, such as in a vendored copy of the CDK styles. */
|
|
139
|
+
function declaredNames(content: string): Set<string> {
|
|
140
|
+
return new Set([...content.matchAll(/\$(cdk-[\w-]+)\s*:|@mixin\s+(cdk-[\w-]+)/g)].map(m => m[1] ?? m[2]));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Whether a stylesheet sets a `!default` of a legacy entry point before importing it, which `@use ... with` replaces. */
|
|
144
|
+
function configuresLegacyImport(content: string): boolean {
|
|
145
|
+
LEGACY_IMPORT.lastIndex = 0;
|
|
146
|
+
const hasLegacyImport = LEGACY_IMPORT.test(content);
|
|
147
|
+
LEGACY_IMPORT.lastIndex = 0;
|
|
148
|
+
return hasLegacyImport && [...declaredNames(content)].some(name => name in VARIABLES);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Names of the project partials an `@import` rule loads, without directory, leading underscore and extension. */
|
|
152
|
+
function importedPartials(content: string): string[] {
|
|
153
|
+
const names: string[] = [];
|
|
154
|
+
for (const rule of content.matchAll(/@import\s+([^;]+);/g)) {
|
|
155
|
+
for (const path of rule[1].matchAll(/['"]([^'"]+)['"]/g)) {
|
|
156
|
+
if (/^(~|@|https?:|\/\/)/.test(path[1]) || path[1].endsWith('.css')) continue;
|
|
157
|
+
names.push(partialName(path[1]));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return names;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function partialName(path: string): string {
|
|
164
|
+
return path.replace(/^.*\//, '').replace(/^_/, '').replace(/\.scss$/, '');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function definesMembersNextToImports(content: string): boolean {
|
|
168
|
+
const otherImports = content.replace(LEGACY_IMPORT, '');
|
|
169
|
+
return /@import\s/.test(otherImports) && /@mixin\s|@function\s|^\s*\$[\w-]+\s*:/m.test(otherImports);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function longestFirst(mapping: Record<string, string>): string[] {
|
|
173
|
+
return Object.keys(mapping).sort((a, b) => b.length - a.length);
|
|
174
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
import {isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
10
|
+
import {create} from "mutative";
|
|
11
|
+
import {importedNames} from "../components/symbols";
|
|
12
|
+
import {extendedClassName, isSuperCall} from "../components/lst";
|
|
13
|
+
import {argumentCount, isCdkClass} from "./find-cdk-signature-changes";
|
|
14
|
+
|
|
15
|
+
export class MigrateCdkStepper extends Recipe {
|
|
16
|
+
readonly name = "org.openrewrite.angular.cdk.MigrateCdkStepper";
|
|
17
|
+
readonly displayName = "Migrate `CdkStepper` subclasses to CDK 14";
|
|
18
|
+
readonly description = "CDK 14 made `CdkStepper._orientation` private and removed the deprecated `_document` " +
|
|
19
|
+
"constructor parameter. Replaces `_orientation` with the public `orientation` property and removes the fourth " +
|
|
20
|
+
"argument of `super(...)` in classes that extend `CdkStepper`.";
|
|
21
|
+
|
|
22
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
23
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
24
|
+
private stepper = new Map<string, string>();
|
|
25
|
+
|
|
26
|
+
protected async visitJsCompilationUnit(cu: JS.CompilationUnit, p: ExecutionContext): Promise<J | undefined> {
|
|
27
|
+
this.stepper = importedNames(cu, '@angular/cdk/stepper', ['CdkStepper']);
|
|
28
|
+
return super.visitJsCompilationUnit(cu, p);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected async visitFieldAccess(fieldAccess: J.FieldAccess, p: ExecutionContext): Promise<J | undefined> {
|
|
32
|
+
const f = await super.visitFieldAccess(fieldAccess, p) as J.FieldAccess;
|
|
33
|
+
const name = f.name.element;
|
|
34
|
+
if (name.simpleName !== '_orientation') return f;
|
|
35
|
+
const declaredByStepper = isCdkClass(name.fieldType?.owner, 'CdkStepper') ||
|
|
36
|
+
(isIdentifier(f.target) && f.target.simpleName === 'this' && this.insideStepperSubclass());
|
|
37
|
+
if (!declaredByStepper) return f;
|
|
38
|
+
return create(f, draft => {
|
|
39
|
+
draft.name.element.simpleName = 'orientation';
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected async visitFunctionCall(functionCall: JS.FunctionCall, p: ExecutionContext): Promise<J | undefined> {
|
|
44
|
+
const call = await super.visitFunctionCall(functionCall, p) as JS.FunctionCall;
|
|
45
|
+
if (!isSuperCall(call) || argumentCount(call.arguments) !== 4 || !this.insideStepperSubclass()) return call;
|
|
46
|
+
const elements = call.arguments.elements;
|
|
47
|
+
const retained = [...elements.slice(0, 2), {...elements[2], after: elements[3].after}];
|
|
48
|
+
return {...call, arguments: {...call.arguments, elements: retained}} as JS.FunctionCall;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private insideStepperSubclass(): boolean {
|
|
52
|
+
const classDecl = this.cursor.firstEnclosing((t: any): t is J.ClassDeclaration => t?.kind === J.Kind.ClassDeclaration);
|
|
53
|
+
const extended = classDecl && extendedClassName(classDecl);
|
|
54
|
+
return extended !== undefined && this.stepper.has(extended);
|
|
55
|
+
}
|
|
56
|
+
}();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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 {escapeRegExp, StylesheetRecipe, withoutComments} from "../components/stylesheets";
|
|
8
|
+
|
|
9
|
+
const DEPRECATED_USE = /@use\s+(['"])~?@angular\/cdk\/overlay\/_?index-deprecated(?:\.scss)?\1(?:\s+as\s+([\w-]+|\*))?\s*;[ \t]*(\r?\n)?/g;
|
|
10
|
+
|
|
11
|
+
const OVERLAY_USE = /@use\s+(['"])~?@angular\/cdk\/overlay\1(?:\s+as\s+overlay)?\s*;/;
|
|
12
|
+
|
|
13
|
+
/** The variables of `overlay/_index-deprecated.scss`, mapped to the `@angular/cdk/overlay` names they aliased. */
|
|
14
|
+
const VARIABLES: Record<string, string> = {
|
|
15
|
+
'z-index-overlay-container': 'overlay-container-z-index',
|
|
16
|
+
'z-index-overlay-backdrop': 'overlay-backdrop-z-index',
|
|
17
|
+
'z-index-overlay': 'overlay-z-index',
|
|
18
|
+
'dark-backdrop-background': 'overlay-backdrop-color',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export class MigrateDeprecatedOverlaySass extends StylesheetRecipe {
|
|
22
|
+
readonly name = "org.openrewrite.angular.cdk.MigrateDeprecatedOverlaySass";
|
|
23
|
+
readonly displayName = "Migrate the removed CDK overlay Sass variables";
|
|
24
|
+
readonly description = "CDK 21 deleted `@angular/cdk/overlay/index-deprecated` and its `$z-index-overlay-container`, " +
|
|
25
|
+
"`$z-index-overlay`, `$z-index-overlay-backdrop` and `$dark-backdrop-background` variables. Replaces the `@use` " +
|
|
26
|
+
"with `@angular/cdk/overlay` and renames the variables to `$overlay-container-z-index`, `$overlay-z-index`, " +
|
|
27
|
+
"`$overlay-backdrop-z-index` and `$overlay-backdrop-color`.";
|
|
28
|
+
|
|
29
|
+
protected acceptsPath(sourcePath: string): boolean {
|
|
30
|
+
return sourcePath.endsWith('.scss');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected migrate(content: string): string {
|
|
34
|
+
if (!content.includes('index-deprecated')) return content;
|
|
35
|
+
return withoutComments(content, migrateDeprecatedOverlay);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function migrateDeprecatedOverlay(content: string): string {
|
|
40
|
+
const hasOverlayUse = OVERLAY_USE.test(content);
|
|
41
|
+
let migrated = content;
|
|
42
|
+
for (const match of [...content.matchAll(DEPRECATED_USE)]) {
|
|
43
|
+
const [rule, quote, alias, newline = ''] = match;
|
|
44
|
+
const namespace = alias ?? 'index-deprecated';
|
|
45
|
+
const newNamespace = alias ?? 'overlay';
|
|
46
|
+
const replacement = alias !== undefined ? `@use ${quote}@angular/cdk/overlay${quote} as ${alias};${newline}`
|
|
47
|
+
: hasOverlayUse ? '' : `@use ${quote}@angular/cdk/overlay${quote};${newline}`;
|
|
48
|
+
migrated = migrated.replace(rule, replacement);
|
|
49
|
+
for (const [name, newName] of Object.entries(VARIABLES)) {
|
|
50
|
+
const reference = namespace === '*' ? `(?<![\\w.-])\\$${name}` : `(?<![\\w-])${escapeRegExp(namespace)}\\.\\$${name}`;
|
|
51
|
+
const renamed = newNamespace === '*' ? `$${newName}` : `${newNamespace}.$${newName}`;
|
|
52
|
+
migrated = migrated.replace(new RegExp(`${reference}(?![\\w-])`, 'g'), renamed);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return migrated;
|
|
56
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
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 {emptyMarkers, ExecutionContext, foundSearchResult, randomId, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
import {emptySpace, Expression, isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
10
|
+
import {importedNames} from "../components/symbols";
|
|
11
|
+
import {emptyArguments} from "../components/lst";
|
|
12
|
+
import {isCdkClass} from "./find-cdk-signature-changes";
|
|
13
|
+
|
|
14
|
+
/** Observable operators that `scrolled()` supports as well as the `scroll` subject did. */
|
|
15
|
+
const OBSERVABLE_METHODS = ['pipe', 'subscribe'];
|
|
16
|
+
|
|
17
|
+
export class MigrateDragDropRegistry extends Recipe {
|
|
18
|
+
readonly name = "org.openrewrite.angular.cdk.MigrateDragDropRegistry";
|
|
19
|
+
readonly displayName = "Migrate `DragDropRegistry` to CDK 20";
|
|
20
|
+
readonly description = "CDK 20 made `DragDropRegistry` non-generic and removed its `scroll` subject. Removes the type " +
|
|
21
|
+
"arguments of `DragDropRegistry<...>`, rewrites `registry.scroll.pipe(...)` and `registry.scroll.subscribe(...)` " +
|
|
22
|
+
"to the `scrolled()` method, and marks other uses of `scroll`, which relied on it being a `Subject`.";
|
|
23
|
+
|
|
24
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
25
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
26
|
+
private registry = new Map<string, string>();
|
|
27
|
+
|
|
28
|
+
protected async visitJsCompilationUnit(cu: JS.CompilationUnit, p: ExecutionContext): Promise<J | undefined> {
|
|
29
|
+
this.registry = importedNames(cu, '@angular/cdk/drag-drop', ['DragDropRegistry']);
|
|
30
|
+
return super.visitJsCompilationUnit(cu, p);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected async visitParameterizedType(type: J.ParameterizedType, p: ExecutionContext): Promise<J | undefined> {
|
|
34
|
+
const t = await super.visitParameterizedType(type, p) as J.ParameterizedType;
|
|
35
|
+
if (!isIdentifier(t.class) || !this.registry.has(t.class.simpleName)) return t;
|
|
36
|
+
return {...t.class, prefix: t.prefix};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protected async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
40
|
+
const m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
41
|
+
const select = m.select?.element;
|
|
42
|
+
if (!OBSERVABLE_METHODS.includes(m.name.simpleName) || !isRegistryScroll(select)) return m;
|
|
43
|
+
return {...m, select: {...m.select!, element: scrolledCall(select as J.FieldAccess)}} as J.MethodInvocation;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected async visitFieldAccess(fieldAccess: J.FieldAccess, p: ExecutionContext): Promise<J | undefined> {
|
|
47
|
+
const f = await super.visitFieldAccess(fieldAccess, p) as J.FieldAccess;
|
|
48
|
+
if (!isRegistryScroll(f)) return f;
|
|
49
|
+
const parent = this.cursor.parentTree()?.value;
|
|
50
|
+
if (parent?.kind === J.Kind.MethodInvocation && OBSERVABLE_METHODS.includes((parent as J.MethodInvocation).name.simpleName)) {
|
|
51
|
+
return f;
|
|
52
|
+
}
|
|
53
|
+
return foundSearchResult(f, "`DragDropRegistry.scroll` was removed in @angular/cdk 20. " +
|
|
54
|
+
"Use the `scrolled()` method, which returns an `Observable` instead of a `Subject`.");
|
|
55
|
+
}
|
|
56
|
+
}();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isRegistryScroll(expression: J | undefined): boolean {
|
|
61
|
+
if (expression?.kind !== J.Kind.FieldAccess) return false;
|
|
62
|
+
const name = (expression as J.FieldAccess).name.element;
|
|
63
|
+
return name.simpleName === 'scroll' && isCdkClass(name.fieldType?.owner, 'DragDropRegistry');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** `registry.scroll` rebuilt as `registry.scrolled()`, keeping the receiver and its formatting. */
|
|
67
|
+
function scrolledCall(scroll: J.FieldAccess): J.MethodInvocation {
|
|
68
|
+
const name = scroll.name.element;
|
|
69
|
+
return {
|
|
70
|
+
kind: J.Kind.MethodInvocation,
|
|
71
|
+
id: randomId(),
|
|
72
|
+
prefix: scroll.prefix,
|
|
73
|
+
markers: emptyMarkers,
|
|
74
|
+
select: {kind: J.Kind.RightPadded, element: scroll.target, after: scroll.name.before, markers: emptyMarkers} as J.RightPadded<Expression>,
|
|
75
|
+
name: {...name, simpleName: 'scrolled', fieldType: undefined, type: undefined},
|
|
76
|
+
arguments: emptyArguments({kind: J.Kind.Container, before: emptySpace, elements: [], markers: emptyMarkers} as J.Container<Expression>),
|
|
77
|
+
methodType: undefined,
|
|
78
|
+
} as J.MethodInvocation;
|
|
79
|
+
}
|