@openrewrite/recipes-angular 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/package.json +37 -0
  2. package/src/index.ts +321 -0
  3. package/src/migration/add-default-configuration.ts +121 -0
  4. package/src/migration/add-localize-polyfill.ts +51 -0
  5. package/src/migration/add-module-with-providers-generic.ts +102 -0
  6. package/src/migration/add-static-false-to-view-queries.ts +92 -0
  7. package/src/migration/add-testbed-teardown.ts +41 -0
  8. package/src/migration/enable-aot-build.ts +132 -0
  9. package/src/migration/explicit-standalone-flag.ts +82 -0
  10. package/src/migration/migrate-constructor-to-inject.ts +172 -0
  11. package/src/migration/migrate-input-to-signal.ts +320 -0
  12. package/src/migration/migrate-output-to-signal.ts +268 -0
  13. package/src/migration/migrate-query-to-signal.ts +276 -0
  14. package/src/migration/migrate-to-solution-style-tsconfig.ts +139 -0
  15. package/src/migration/move-document-to-core.ts +40 -0
  16. package/src/migration/remove-aot-summaries.ts +72 -0
  17. package/src/migration/remove-browser-module-with-server-transition.ts +185 -0
  18. package/src/migration/remove-component-factory-resolver.ts +48 -0
  19. package/src/migration/remove-default-project.ts +52 -0
  20. package/src/migration/remove-empty-ng-on-init.ts +80 -0
  21. package/src/migration/remove-enable-ivy.ts +63 -0
  22. package/src/migration/remove-entry-components.ts +75 -0
  23. package/src/migration/remove-es5-browser-support.ts +59 -0
  24. package/src/migration/remove-extract-css.ts +60 -0
  25. package/src/migration/remove-ie-polyfills.ts +118 -0
  26. package/src/migration/remove-module-id.ts +59 -0
  27. package/src/migration/remove-relative-link-resolution.ts +64 -0
  28. package/src/migration/remove-standalone-true.ts +50 -0
  29. package/src/migration/remove-static-false.ts +71 -0
  30. package/src/migration/remove-zone-js-polyfill.ts +55 -0
  31. package/src/migration/rename-after-render.ts +32 -0
  32. package/src/migration/rename-check-no-changes.ts +29 -0
  33. package/src/migration/rename-file.ts +72 -0
  34. package/src/migration/rename-pending-tasks.ts +30 -0
  35. package/src/migration/rename-zoneless-provider.ts +29 -0
  36. package/src/migration/replace-async-with-wait-for-async.ts +32 -0
  37. package/src/migration/replace-deep-zone-js-imports.ts +118 -0
  38. package/src/migration/replace-http-client-module.ts +276 -0
  39. package/src/migration/replace-initial-navigation.ts +73 -0
  40. package/src/migration/replace-inject-flags.ts +83 -0
  41. package/src/migration/replace-load-children-string.ts +48 -0
  42. package/src/migration/replace-node-sass-with-sass.ts +22 -0
  43. package/src/migration/replace-router-link-with-href.ts +37 -0
  44. package/src/migration/replace-testbed-get-with-inject.ts +33 -0
  45. package/src/migration/replace-untyped-forms.ts +59 -0
  46. package/src/migration/replace-validator-with-validators.ts +41 -0
  47. package/src/migration/replace-view-encapsulation-native.ts +51 -0
  48. package/src/migration/update-component-template-url.ts +186 -0
  49. package/src/migration/update-tsconfig-module.ts +75 -0
  50. package/src/migration/update-tsconfig-target.ts +61 -0
  51. package/src/migration/upgrade-to-angular-10.ts +52 -0
  52. package/src/migration/upgrade-to-angular-11.ts +52 -0
  53. package/src/migration/upgrade-to-angular-12.ts +43 -0
  54. package/src/migration/upgrade-to-angular-13.ts +45 -0
  55. package/src/migration/upgrade-to-angular-14.ts +44 -0
  56. package/src/migration/upgrade-to-angular-15.ts +43 -0
  57. package/src/migration/upgrade-to-angular-16.ts +57 -0
  58. package/src/migration/upgrade-to-angular-17.ts +43 -0
  59. package/src/migration/upgrade-to-angular-18.ts +69 -0
  60. package/src/migration/upgrade-to-angular-19.ts +52 -0
  61. package/src/migration/upgrade-to-angular-20.ts +47 -0
  62. package/src/migration/upgrade-to-angular-21.ts +53 -0
  63. package/src/migration/upgrade-to-angular-8.ts +54 -0
  64. package/src/migration/upgrade-to-angular-9.ts +69 -0
  65. package/src/search/find-analyze-for-entry-components-usage.ts +46 -0
  66. package/src/search/find-angular-decorator.ts +58 -0
  67. package/src/search/find-angular-http-usage.ts +35 -0
  68. package/src/search/find-animation-driver-matches-element.ts +38 -0
  69. package/src/search/find-async-test-helper-usage.ts +45 -0
  70. package/src/search/find-bare-module-with-providers.ts +47 -0
  71. package/src/search/find-browser-transfer-state-module-usage.ts +45 -0
  72. package/src/search/find-common-module-usage.ts +47 -0
  73. package/src/search/find-compiler-factory-usage.ts +51 -0
  74. package/src/search/find-date-pipe-default-timezone-usage.ts +46 -0
  75. package/src/search/find-effect-timing-usage.ts +28 -0
  76. package/src/search/find-empty-projectable-nodes.ts +68 -0
  77. package/src/search/find-fake-async-usage.ts +37 -0
  78. package/src/search/find-hammer-js-usage.ts +48 -0
  79. package/src/search/find-i18n-usage.ts +94 -0
  80. package/src/search/find-karma-usage.ts +47 -0
  81. package/src/search/find-load-children-string-usage.ts +43 -0
  82. package/src/search/find-missing-injectable.ts +75 -0
  83. package/src/search/find-ng-class-usage.ts +45 -0
  84. package/src/search/find-ng-style-usage.ts +45 -0
  85. package/src/search/find-path-match-type-usage.ts +44 -0
  86. package/src/search/find-platform-dynamic-server-usage.ts +38 -0
  87. package/src/search/find-platform-webworker-usage.ts +34 -0
  88. package/src/search/find-platform-worker-usage.ts +39 -0
  89. package/src/search/find-preserve-fragment-usage.ts +32 -0
  90. package/src/search/find-preserve-query-params-usage.ts +32 -0
  91. package/src/search/find-provided-in-deprecated-usage.ts +65 -0
  92. package/src/search/find-reflective-injector-usage.ts +45 -0
  93. package/src/search/find-render-application-usage.ts +47 -0
  94. package/src/search/find-render-component-type-usage.ts +46 -0
  95. package/src/search/find-render-module-factory-usage.ts +45 -0
  96. package/src/search/find-renderer-usage.ts +46 -0
  97. package/src/search/find-resource-cache-provider-usage.ts +38 -0
  98. package/src/search/find-root-renderer-usage.ts +47 -0
  99. package/src/search/find-rxjs-compat-usage.ts +40 -0
  100. package/src/search/find-server-transfer-state-module-usage.ts +38 -0
  101. package/src/search/find-setup-testing-router-usage.ts +45 -0
  102. package/src/search/find-testability-pending-request-usage.ts +38 -0
  103. package/src/search/find-undecorated-angular-class.ts +78 -0
  104. package/src/search/find-with-no-dom-reuse-usage.ts +46 -0
  105. package/src/search/find-wrapped-value-usage.ts +46 -0
  106. package/src/search/find-zone-js-usage.ts +43 -0
@@ -0,0 +1,58 @@
1
+ import {ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
3
+ import {isIdentifier, J} from "@openrewrite/rewrite/java";
4
+
5
+ export class FindAngularDecorator extends Recipe {
6
+ readonly name = "org.openrewrite.angular.search.find-angular-decorator";
7
+ readonly displayName: string = "Find Angular decorators";
8
+ readonly description: string = "Finds all Angular decorators like @Component, @Directive, @Injectable, etc.";
9
+
10
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
11
+ const angularDecorators = new Set([
12
+ 'Component',
13
+ 'Directive',
14
+ 'Injectable',
15
+ 'NgModule',
16
+ 'Pipe',
17
+ 'Input',
18
+ 'Output',
19
+ 'ViewChild',
20
+ 'ViewChildren',
21
+ 'ContentChild',
22
+ 'ContentChildren',
23
+ 'HostBinding',
24
+ 'HostListener'
25
+ ]);
26
+
27
+ return new class extends JavaScriptVisitor<ExecutionContext> {
28
+ override async visitAnnotation(annotation: J.Annotation, p: ExecutionContext): Promise<J | undefined> {
29
+ let a = await super.visitAnnotation(annotation, p) as J.Annotation;
30
+
31
+ // Check if this is an Angular decorator
32
+ let decoratorName: string | undefined;
33
+ const annotType = a.annotationType;
34
+ if (isIdentifier(annotType)) {
35
+ decoratorName = annotType.simpleName;
36
+ } else if (annotType.kind === J.Kind.FieldAccess) {
37
+ const fieldAccess = annotType as J.FieldAccess;
38
+ const nameIdent = fieldAccess.name.element;
39
+ if (isIdentifier(nameIdent)) {
40
+ decoratorName = nameIdent.simpleName;
41
+ }
42
+ } else if (annotType.kind === JS.Kind.ExpressionWithTypeArguments) {
43
+ const exprWithTypeArgs = annotType as JS.ExpressionWithTypeArguments;
44
+ if (isIdentifier(exprWithTypeArgs.clazz)) {
45
+ decoratorName = exprWithTypeArgs.clazz.simpleName;
46
+ }
47
+ }
48
+
49
+ // Mark Angular decorators with SearchResult
50
+ if (decoratorName && angularDecorators.has(decoratorName)) {
51
+ return foundSearchResult(a as J.Annotation);
52
+ }
53
+
54
+ return a;
55
+ }
56
+ }();
57
+ }
58
+ }
@@ -0,0 +1,35 @@
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 {J} from "@openrewrite/rewrite/java";
10
+
11
+ export class FindAngularHttpUsage extends Recipe {
12
+ readonly name = "org.openrewrite.angular.search.find-angular-http-usage";
13
+ readonly displayName: string = "Find removed `@angular/http` usage";
14
+ readonly description: string = "Finds imports from the `@angular/http` module, which was deprecated in Angular 5 and removed in Angular 8. " +
15
+ "Use `@angular/common/http` (`HttpClient`, `HttpClientModule`) instead.";
16
+
17
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
18
+ return new class extends JavaScriptVisitor<ExecutionContext> {
19
+ protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
20
+ const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
21
+ if (!imp.moduleSpecifier) return imp;
22
+
23
+ const moduleSpec = imp.moduleSpecifier.element;
24
+ if (moduleSpec.kind !== J.Kind.Literal) return imp;
25
+
26
+ const moduleName = (moduleSpec as J.Literal).value as string;
27
+ if (moduleName !== '@angular/http') return imp;
28
+
29
+ return foundSearchResult(imp,
30
+ "`@angular/http` was deprecated in Angular 5 and removed in Angular 8. " +
31
+ "Use `@angular/common/http` (`HttpClient`, `HttpClientModule`) instead.");
32
+ }
33
+ };
34
+ }
35
+ }
@@ -0,0 +1,38 @@
1
+ import {ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
3
+ import {J, isIdentifier} from "@openrewrite/rewrite/java";
4
+
5
+ export class FindAnimationDriverMatchesElement extends Recipe {
6
+ readonly name = "org.openrewrite.angular.search.find-animation-driver-matches-element";
7
+ readonly displayName: string = "Find `AnimationDriver.matchesElement` usage";
8
+ readonly description: string = "Finds imports of `AnimationDriver` from `@angular/animations/browser`, which had its `matchesElement` method removed in Angular 18.";
9
+
10
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
11
+ return new class extends JavaScriptVisitor<ExecutionContext> {
12
+ protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
13
+ const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
14
+ if (!imp.moduleSpecifier) return imp;
15
+
16
+ const moduleSpec = imp.moduleSpecifier.element;
17
+ if (moduleSpec.kind !== J.Kind.Literal) return imp;
18
+
19
+ const moduleName = (moduleSpec as J.Literal).value as string;
20
+ if (moduleName !== '@angular/animations/browser') return imp;
21
+
22
+ const namedBindings = imp.importClause?.namedBindings;
23
+ if (namedBindings?.kind === JS.Kind.NamedImports) {
24
+ const named = namedBindings as JS.NamedImports;
25
+ for (const specifier of named.elements.elements) {
26
+ const spec = specifier.element as JS.ImportSpecifier;
27
+ if (isIdentifier(spec.specifier) && spec.specifier.simpleName === 'AnimationDriver') {
28
+ return foundSearchResult(imp,
29
+ "AnimationDriver.matchesElement has been removed in Angular 18.");
30
+ }
31
+ }
32
+ }
33
+
34
+ return imp;
35
+ }
36
+ };
37
+ }
38
+ }
@@ -0,0 +1,45 @@
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 {J, isIdentifier} from "@openrewrite/rewrite/java";
10
+
11
+ export class FindAsyncTestHelperUsage extends Recipe {
12
+ readonly name = "org.openrewrite.angular.search.find-async-test-helper-usage";
13
+ readonly displayName: string = "Find deprecated `async` test helper usage";
14
+ readonly description: string = "Finds usages of the deprecated `async` test helper from `@angular/core/testing`. " +
15
+ "The `async` function was deprecated in Angular 11 and should be replaced with `waitForAsync`.";
16
+
17
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
18
+ return new class extends JavaScriptVisitor<ExecutionContext> {
19
+ protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
20
+ const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
21
+ if (!imp.moduleSpecifier) return imp;
22
+
23
+ const moduleSpec = imp.moduleSpecifier.element;
24
+ if (moduleSpec.kind !== J.Kind.Literal) return imp;
25
+
26
+ const moduleName = (moduleSpec as J.Literal).value as string;
27
+ if (moduleName !== '@angular/core/testing') return imp;
28
+
29
+ const namedBindings = imp.importClause?.namedBindings;
30
+ if (namedBindings?.kind === JS.Kind.NamedImports) {
31
+ const named = namedBindings as JS.NamedImports;
32
+ for (const specifier of named.elements.elements) {
33
+ const spec = specifier.element as JS.ImportSpecifier;
34
+ if (isIdentifier(spec.specifier) && spec.specifier.simpleName === 'async') {
35
+ return foundSearchResult(imp,
36
+ "The `async` test helper is deprecated since Angular 11. Use `waitForAsync` instead.");
37
+ }
38
+ }
39
+ }
40
+
41
+ return imp;
42
+ }
43
+ };
44
+ }
45
+ }
@@ -0,0 +1,47 @@
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 {J, isIdentifier} from "@openrewrite/rewrite/java";
10
+
11
+ export class FindBareModuleWithProviders extends Recipe {
12
+ readonly name = "org.openrewrite.angular.search.find-bare-module-with-providers";
13
+ readonly displayName: string = "Find `ModuleWithProviders` without generic type";
14
+ readonly description: string = "Finds imports of `ModuleWithProviders` from `@angular/core`. " +
15
+ "Starting in Angular 10, `ModuleWithProviders` requires a generic type parameter " +
16
+ "(e.g. `ModuleWithProviders<MyModule>`). Ensure all usages specify the module type.";
17
+
18
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
19
+ return new class extends JavaScriptVisitor<ExecutionContext> {
20
+ protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
21
+ const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
22
+ if (!imp.moduleSpecifier) return imp;
23
+
24
+ const moduleSpec = imp.moduleSpecifier.element;
25
+ if (moduleSpec.kind !== J.Kind.Literal) return imp;
26
+
27
+ const moduleName = (moduleSpec as J.Literal).value as string;
28
+ if (moduleName !== '@angular/core') return imp;
29
+
30
+ const namedBindings = imp.importClause?.namedBindings;
31
+ if (namedBindings?.kind === JS.Kind.NamedImports) {
32
+ const named = namedBindings as JS.NamedImports;
33
+ for (const specifier of named.elements.elements) {
34
+ const spec = specifier.element as JS.ImportSpecifier;
35
+ if (isIdentifier(spec.specifier) && spec.specifier.simpleName === 'ModuleWithProviders') {
36
+ return foundSearchResult(imp,
37
+ "`ModuleWithProviders` requires a generic type parameter since Angular 10. " +
38
+ "Ensure all usages specify the module type: `ModuleWithProviders<MyModule>`.");
39
+ }
40
+ }
41
+ }
42
+
43
+ return imp;
44
+ }
45
+ };
46
+ }
47
+ }
@@ -0,0 +1,45 @@
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 {J, isIdentifier} from "@openrewrite/rewrite/java";
10
+
11
+ export class FindBrowserTransferStateModuleUsage extends Recipe {
12
+ readonly name = "org.openrewrite.angular.search.find-browser-transfer-state-module-usage";
13
+ readonly displayName: string = "Find `BrowserTransferStateModule` usage";
14
+ readonly description: string = "Finds usages of `BrowserTransferStateModule` from `@angular/platform-browser` " +
15
+ "which was removed in Angular 16. `TransferState` can be used directly without this module.";
16
+
17
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
18
+ return new class extends JavaScriptVisitor<ExecutionContext> {
19
+ protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
20
+ const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
21
+ if (!imp.moduleSpecifier) return imp;
22
+
23
+ const moduleSpec = imp.moduleSpecifier.element;
24
+ if (moduleSpec.kind !== J.Kind.Literal) return imp;
25
+
26
+ const moduleName = (moduleSpec as J.Literal).value as string;
27
+ if (moduleName !== '@angular/platform-browser') return imp;
28
+
29
+ const namedBindings = imp.importClause?.namedBindings;
30
+ if (namedBindings?.kind === JS.Kind.NamedImports) {
31
+ const named = namedBindings as JS.NamedImports;
32
+ for (const specifier of named.elements.elements) {
33
+ const spec = specifier.element as JS.ImportSpecifier;
34
+ if (isIdentifier(spec.specifier) && spec.specifier.simpleName === 'BrowserTransferStateModule') {
35
+ return foundSearchResult(imp,
36
+ "BrowserTransferStateModule has been removed in Angular 16. TransferState can be used directly without this module.");
37
+ }
38
+ }
39
+ }
40
+
41
+ return imp;
42
+ }
43
+ };
44
+ }
45
+ }
@@ -0,0 +1,47 @@
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 {J, isIdentifier} from "@openrewrite/rewrite/java";
10
+
11
+ export class FindCommonModuleUsage extends Recipe {
12
+ readonly name = "org.openrewrite.angular.search.find-common-module-usage";
13
+ readonly displayName: string = "Find `CommonModule` usage";
14
+ readonly description: string = "Finds imports of `CommonModule` from `@angular/common`. " +
15
+ "Since Angular 19, standalone components are the default and `CommonModule` is no longer needed " +
16
+ "in component `imports` arrays. Built-in directives and pipes are available automatically.";
17
+
18
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
19
+ return new class extends JavaScriptVisitor<ExecutionContext> {
20
+ protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
21
+ const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
22
+ if (!imp.moduleSpecifier) return imp;
23
+
24
+ const moduleSpec = imp.moduleSpecifier.element;
25
+ if (moduleSpec.kind !== J.Kind.Literal) return imp;
26
+
27
+ const moduleName = (moduleSpec as J.Literal).value as string;
28
+ if (moduleName !== '@angular/common') return imp;
29
+
30
+ const namedBindings = imp.importClause?.namedBindings;
31
+ if (namedBindings?.kind === JS.Kind.NamedImports) {
32
+ const named = namedBindings as JS.NamedImports;
33
+ for (const specifier of named.elements.elements) {
34
+ const spec = specifier.element as JS.ImportSpecifier;
35
+ if (isIdentifier(spec.specifier) && spec.specifier.simpleName === 'CommonModule') {
36
+ return foundSearchResult(imp,
37
+ "CommonModule is unnecessary in standalone components (default since Angular 19). " +
38
+ "Built-in directives and pipes are available automatically. See https://angular.dev/guide/components/importing");
39
+ }
40
+ }
41
+ }
42
+
43
+ return imp;
44
+ }
45
+ };
46
+ }
47
+ }
@@ -0,0 +1,51 @@
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 {J, isIdentifier} from "@openrewrite/rewrite/java";
10
+
11
+ const DEPRECATED_SYMBOLS = [
12
+ 'CompilerFactory', 'Compiler', 'CompilerOptions',
13
+ 'ModuleWithComponentFactories', 'NgModuleFactory', 'NgModuleFactoryLoader'
14
+ ];
15
+
16
+ export class FindCompilerFactoryUsage extends Recipe {
17
+ readonly name = "org.openrewrite.angular.search.find-compiler-factory-usage";
18
+ readonly displayName: string = "Find View Engine API usage";
19
+ readonly description: string = "Finds usages of View Engine APIs from `@angular/core` " +
20
+ "(`CompilerFactory`, `Compiler`, `CompilerOptions`, `ModuleWithComponentFactories`, " +
21
+ "`NgModuleFactory`, `NgModuleFactoryLoader`) which were deprecated in Angular 13.";
22
+
23
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
24
+ return new class extends JavaScriptVisitor<ExecutionContext> {
25
+ protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
26
+ const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
27
+ if (!imp.moduleSpecifier) return imp;
28
+
29
+ const moduleSpec = imp.moduleSpecifier.element;
30
+ if (moduleSpec.kind !== J.Kind.Literal) return imp;
31
+
32
+ const moduleName = (moduleSpec as J.Literal).value as string;
33
+ if (moduleName !== '@angular/core') return imp;
34
+
35
+ const namedBindings = imp.importClause?.namedBindings;
36
+ if (namedBindings?.kind === JS.Kind.NamedImports) {
37
+ const named = namedBindings as JS.NamedImports;
38
+ for (const specifier of named.elements.elements) {
39
+ const spec = specifier.element as JS.ImportSpecifier;
40
+ if (isIdentifier(spec.specifier) && DEPRECATED_SYMBOLS.includes(spec.specifier.simpleName)) {
41
+ return foundSearchResult(imp,
42
+ `${spec.specifier.simpleName} was deprecated in Angular 13 with the removal of the View Engine rendering pipeline.`);
43
+ }
44
+ }
45
+ }
46
+
47
+ return imp;
48
+ }
49
+ };
50
+ }
51
+ }
@@ -0,0 +1,46 @@
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 {J, isIdentifier} from "@openrewrite/rewrite/java";
10
+
11
+ export class FindDatePipeDefaultTimezoneUsage extends Recipe {
12
+ readonly name = "org.openrewrite.angular.search.find-date-pipe-default-timezone-usage";
13
+ readonly displayName: string = "Find `DATE_PIPE_DEFAULT_TIMEZONE` usage";
14
+ readonly description: string = "Finds usages of `DATE_PIPE_DEFAULT_TIMEZONE` which was deprecated in Angular 15. " +
15
+ "Use `DATE_PIPE_DEFAULT_OPTIONS` with a `{timezone: '...'}` object value instead.";
16
+
17
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
18
+ return new class extends JavaScriptVisitor<ExecutionContext> {
19
+ protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
20
+ const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
21
+ if (!imp.moduleSpecifier) return imp;
22
+
23
+ const moduleSpec = imp.moduleSpecifier.element;
24
+ if (moduleSpec.kind !== J.Kind.Literal) return imp;
25
+
26
+ const moduleName = (moduleSpec as J.Literal).value as string;
27
+ if (moduleName !== '@angular/common') return imp;
28
+
29
+ const namedBindings = imp.importClause?.namedBindings;
30
+ if (namedBindings?.kind === JS.Kind.NamedImports) {
31
+ const named = namedBindings as JS.NamedImports;
32
+ for (const specifier of named.elements.elements) {
33
+ const spec = specifier.element as JS.ImportSpecifier;
34
+ if (isIdentifier(spec.specifier) && spec.specifier.simpleName === 'DATE_PIPE_DEFAULT_TIMEZONE') {
35
+ return foundSearchResult(imp,
36
+ "DATE_PIPE_DEFAULT_TIMEZONE has been deprecated in Angular 15. " +
37
+ "Use DATE_PIPE_DEFAULT_OPTIONS with a {timezone: '...'} object value instead.");
38
+ }
39
+ }
40
+ }
41
+
42
+ return imp;
43
+ }
44
+ };
45
+ }
46
+ }
@@ -0,0 +1,28 @@
1
+ import {ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
3
+ import {J, Type} from "@openrewrite/rewrite/java";
4
+
5
+ export class FindEffectTimingUsage extends Recipe {
6
+ readonly name = "org.openrewrite.angular.search.find-effect-timing-usage";
7
+ readonly displayName: string = "Find `effect()` usage affected by Angular 19 timing changes";
8
+ readonly description: string = "Finds `effect()` calls from `@angular/core`. In Angular 19, effects triggered outside change detection now run as part of the change detection process instead of as a microtask, and effects triggered during change detection run earlier, before the component's template.";
9
+
10
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
11
+ return new class extends JavaScriptVisitor<ExecutionContext> {
12
+ protected async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
13
+ const m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
14
+
15
+ if (m.name.simpleName === 'effect') {
16
+ const methodType = m.methodType;
17
+ if (methodType?.declaringType?.kind === Type.Kind.Class
18
+ && (methodType.declaringType as Type.Class).fullyQualifiedName === '@angular/core') {
19
+ return foundSearchResult(m,
20
+ "effect() timing changed in v19. See https://angular.dev/api/core/effect");
21
+ }
22
+ }
23
+
24
+ return m;
25
+ }
26
+ };
27
+ }
28
+ }
@@ -0,0 +1,68 @@
1
+ import {ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
3
+ import {J, isIdentifier, Type} from "@openrewrite/rewrite/java";
4
+
5
+ export class FindEmptyProjectableNodes extends Recipe {
6
+ readonly name = "org.openrewrite.angular.search.find-empty-projectable-nodes";
7
+ readonly displayName: string = "Find `createComponent` calls with empty `projectableNodes`";
8
+ readonly description: string = "Finds `createComponent()` calls that pass empty arrays in `projectableNodes`. In Angular 19, passing an empty array now renders the default `ng-content` fallback content. To suppress fallback content, pass `[document.createTextNode('')]` instead.";
9
+
10
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
11
+ return new class extends JavaScriptVisitor<ExecutionContext> {
12
+ protected async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
13
+ const m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
14
+
15
+ if (m.name.simpleName !== 'createComponent') return m;
16
+
17
+ const methodType = m.methodType;
18
+ if (methodType?.declaringType?.kind !== Type.Kind.Class
19
+ || (methodType.declaringType as Type.Class).fullyQualifiedName !== '@angular/core') {
20
+ return m;
21
+ }
22
+
23
+ const args = m.arguments?.elements;
24
+ if (!args || args.length < 2) return m;
25
+
26
+ // The second argument is the options object { projectableNodes: [...] }
27
+ const optionsArg = args[1].element;
28
+ if (optionsArg.kind !== J.Kind.NewClass) return m;
29
+
30
+ const optionsObj = optionsArg as J.NewClass;
31
+ if (!optionsObj.body) return m;
32
+
33
+ for (const stmt of optionsObj.body.statements) {
34
+ if (stmt.element.kind !== JS.Kind.PropertyAssignment) continue;
35
+ const prop = stmt.element as JS.PropertyAssignment;
36
+ const nameExpr = prop.name.element;
37
+ if (!isIdentifier(nameExpr) || nameExpr.simpleName !== 'projectableNodes') continue;
38
+
39
+ if (prop.initializer && hasEmptyArrayElement(prop.initializer)) {
40
+ return foundSearchResult(m,
41
+ "Empty projectableNodes now render ng-content fallback in v19. See https://angular.dev/api/core/createComponent");
42
+ }
43
+ }
44
+
45
+ return m;
46
+ }
47
+ };
48
+ }
49
+ }
50
+
51
+ function hasEmptyArrayElement(node: any): boolean {
52
+ // projectableNodes: [[], [node]] — look for [] elements in the outer array
53
+ const elements = node.initializer?.elements;
54
+ if (!elements) return false;
55
+
56
+ for (const rp of elements) {
57
+ const elem = rp.element;
58
+ // Empty array: J$NewArray whose only element is J$Empty
59
+ if (elem.kind === J.Kind.NewArray) {
60
+ const innerElements = elem.initializer?.elements;
61
+ if (innerElements && innerElements.length === 1
62
+ && innerElements[0].element.kind === J.Kind.Empty) {
63
+ return true;
64
+ }
65
+ }
66
+ }
67
+ return false;
68
+ }
@@ -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 {ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
8
+ import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
9
+ import {J, Type} from "@openrewrite/rewrite/java";
10
+
11
+ export class FindFakeAsyncUsage extends Recipe {
12
+ readonly name = "org.openrewrite.angular.search.find-fake-async-usage";
13
+ readonly displayName: string = "Find zone.js-dependent test helper usage";
14
+ readonly description: string = "Finds `fakeAsync()`, `tick()`, and `waitForAsync()` calls from `@angular/core/testing`. " +
15
+ "These zone.js-dependent test helpers are incompatible with Vitest, the default test runner in Angular 21. " +
16
+ "Migrate to native async/await patterns instead.";
17
+
18
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
19
+ return new class extends JavaScriptVisitor<ExecutionContext> {
20
+ protected async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
21
+ const m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
22
+
23
+ if (m.name.simpleName === 'fakeAsync' || m.name.simpleName === 'tick' || m.name.simpleName === 'waitForAsync') {
24
+ const methodType = m.methodType;
25
+ if (methodType?.declaringType?.kind === Type.Kind.Class
26
+ && (methodType.declaringType as Type.Class).fullyQualifiedName === '@angular/core/testing') {
27
+ return foundSearchResult(m,
28
+ `${m.name.simpleName}() is a zone.js-dependent test helper incompatible with Vitest. ` +
29
+ "Migrate to native async/await. See https://angular.dev/guide/testing");
30
+ }
31
+ }
32
+
33
+ return m;
34
+ }
35
+ };
36
+ }
37
+ }
@@ -0,0 +1,48 @@
1
+ import {ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
3
+ import {J, Type} from "@openrewrite/rewrite/java";
4
+
5
+ export class FindHammerJsUsage extends Recipe {
6
+ readonly name = "org.openrewrite.angular.search.find-hammer-js-usage";
7
+ readonly displayName: string = "Find HammerJS usage";
8
+ readonly description: string = "Finds `HammerModule` imports and HammerJS references. Angular has deprecated HammerJS support and it will be removed in Angular 21.";
9
+
10
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
11
+ return new class extends JavaScriptVisitor<ExecutionContext> {
12
+ protected async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
13
+ const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
14
+ if (!imp.moduleSpecifier) return imp;
15
+
16
+ const moduleSpec = imp.moduleSpecifier.element;
17
+ if (moduleSpec.kind !== J.Kind.Literal) return imp;
18
+
19
+ const moduleName = (moduleSpec as J.Literal).value as string;
20
+ if (moduleName === 'hammerjs' || moduleName.startsWith('hammerjs/')) {
21
+ return foundSearchResult(imp,
22
+ "HammerJS import. Angular has deprecated HammerJS support; it will be removed in Angular 21.");
23
+ }
24
+
25
+ return imp;
26
+ }
27
+
28
+ override async visitIdentifier(identifier: J.Identifier, p: ExecutionContext): Promise<J | undefined> {
29
+ const id = await super.visitIdentifier(identifier, p) as J.Identifier;
30
+ if (!id) return id;
31
+
32
+ if (id.simpleName === 'HammerModule' && id.type?.kind === Type.Kind.Class
33
+ && (id.type as Type.Class).fullyQualifiedName === 'HammerModule') {
34
+ return foundSearchResult(id,
35
+ "HammerModule usage. Angular has deprecated HammerJS support; it will be removed in Angular 21.");
36
+ }
37
+
38
+ if (id.simpleName === 'HammerGestureConfig' && id.type?.kind === Type.Kind.Class
39
+ && (id.type as Type.Class).fullyQualifiedName === 'HammerGestureConfig') {
40
+ return foundSearchResult(id,
41
+ "HammerGestureConfig usage. Angular has deprecated HammerJS support; it will be removed in Angular 21.");
42
+ }
43
+
44
+ return id;
45
+ }
46
+ };
47
+ }
48
+ }