provide-initializer.cjs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. 'use strict';
  2. /**
  3. * @license Angular v19.2.13
  4. * (c) 2010-2025 Google LLC. https://angular.io/
  5. * License: MIT
  6. */
  7. 'use strict';
  8. var schematics = require('@angular-devkit/schematics');
  9. var p = require('path');
  10. var project_tsconfig_paths = require('./project_tsconfig_paths-CDVxT6Ov.cjs');
  11. var compiler_host = require('./compiler_host-B1Gyeytz.cjs');
  12. var ts = require('typescript');
  13. var imports = require('./imports-CIX-JgAN.cjs');
  14. require('@angular-devkit/core');
  15. require('./checker-5pyJrZ9G.cjs');
  16. require('os');
  17. require('fs');
  18. require('module');
  19. require('url');
  20. function migrateFile(sourceFile, rewriteFn) {
  21. const changeTracker = new compiler_host.ChangeTracker(ts.createPrinter());
  22. const visitNode = (node) => {
  23. const provider = tryParseProviderExpression(node);
  24. if (provider) {
  25. replaceProviderWithNewApi({
  26. sourceFile,
  27. node,
  28. provider,
  29. changeTracker,
  30. });
  31. return;
  32. }
  33. ts.forEachChild(node, visitNode);
  34. };
  35. ts.forEachChild(sourceFile, visitNode);
  36. for (const change of changeTracker.recordChanges().get(sourceFile)?.values() ?? []) {
  37. rewriteFn(change.start, change.removeLength ?? 0, change.text);
  38. }
  39. }
  40. function replaceProviderWithNewApi({ sourceFile, node, provider, changeTracker, }) {
  41. const { initializerCode, importInject, provideInitializerFunctionName, initializerToken } = provider;
  42. const initializerTokenSpecifier = imports.getImportSpecifier(sourceFile, angularCoreModule, initializerToken);
  43. // The token doesn't come from `@angular/core`.
  44. if (!initializerTokenSpecifier) {
  45. return;
  46. }
  47. // Replace the provider with the new provide function.
  48. changeTracker.replaceText(sourceFile, node.getStart(), node.getWidth(), `${provideInitializerFunctionName}(${initializerCode})`);
  49. // Remove the `*_INITIALIZER` token from imports.
  50. changeTracker.removeImport(sourceFile, initializerToken, angularCoreModule);
  51. // Add the `inject` function to imports if needed.
  52. if (importInject) {
  53. changeTracker.addImport(sourceFile, 'inject', angularCoreModule);
  54. }
  55. // Add the `provide*Initializer` function to imports.
  56. changeTracker.addImport(sourceFile, provideInitializerFunctionName, angularCoreModule);
  57. }
  58. function tryParseProviderExpression(node) {
  59. if (!ts.isObjectLiteralExpression(node)) {
  60. return;
  61. }
  62. let deps = [];
  63. let initializerToken;
  64. let useExisting;
  65. let useFactoryCode;
  66. let useValue;
  67. let multi = false;
  68. for (const property of node.properties) {
  69. if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name)) {
  70. switch (property.name.text) {
  71. case 'deps':
  72. if (ts.isArrayLiteralExpression(property.initializer)) {
  73. deps = property.initializer.elements.map((el) => el.getText());
  74. }
  75. break;
  76. case 'provide':
  77. initializerToken = property.initializer.getText();
  78. break;
  79. case 'useExisting':
  80. useExisting = property.initializer;
  81. break;
  82. case 'useFactory':
  83. useFactoryCode = property.initializer.getText();
  84. break;
  85. case 'useValue':
  86. useValue = property.initializer;
  87. break;
  88. case 'multi':
  89. multi = property.initializer.kind === ts.SyntaxKind.TrueKeyword;
  90. break;
  91. }
  92. }
  93. // Handle the `useFactory() {}` shorthand case.
  94. if (ts.isMethodDeclaration(property) && property.name.getText() === 'useFactory') {
  95. const params = property.parameters.map((param) => param.getText()).join(', ');
  96. useFactoryCode = `(${params}) => ${property.body?.getText()}`;
  97. }
  98. }
  99. if (!initializerToken || !multi) {
  100. return;
  101. }
  102. const provideInitializerFunctionName = initializerTokenToFunctionMap.get(initializerToken);
  103. if (!provideInitializerFunctionName) {
  104. return;
  105. }
  106. const info = {
  107. initializerToken,
  108. provideInitializerFunctionName,
  109. importInject: false,
  110. };
  111. if (useExisting) {
  112. return {
  113. ...info,
  114. importInject: true,
  115. initializerCode: `() => inject(${useExisting.getText()})()`,
  116. };
  117. }
  118. if (useFactoryCode) {
  119. const args = deps.map((dep) => `inject(${dep})`);
  120. return {
  121. ...info,
  122. importInject: deps.length > 0,
  123. initializerCode: `() => {
  124. const initializerFn = (${useFactoryCode})(${args.join(', ')});
  125. return initializerFn();
  126. }`,
  127. };
  128. }
  129. if (useValue) {
  130. return { ...info, initializerCode: useValue.getText() };
  131. }
  132. return;
  133. }
  134. const angularCoreModule = '@angular/core';
  135. const initializerTokenToFunctionMap = new Map([
  136. ['APP_INITIALIZER', 'provideAppInitializer'],
  137. ['ENVIRONMENT_INITIALIZER', 'provideEnvironmentInitializer'],
  138. ['PLATFORM_INITIALIZER', 'providePlatformInitializer'],
  139. ]);
  140. function migrate() {
  141. return async (tree) => {
  142. const { buildPaths, testPaths } = await project_tsconfig_paths.getProjectTsConfigPaths(tree);
  143. const basePath = process.cwd();
  144. const allPaths = [...buildPaths, ...testPaths];
  145. if (!allPaths.length) {
  146. throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run the provide initializer migration.');
  147. }
  148. for (const tsconfigPath of allPaths) {
  149. runMigration(tree, tsconfigPath, basePath);
  150. }
  151. };
  152. }
  153. function runMigration(tree, tsconfigPath, basePath) {
  154. const program = compiler_host.createMigrationProgram(tree, tsconfigPath, basePath);
  155. const sourceFiles = program
  156. .getSourceFiles()
  157. .filter((sourceFile) => compiler_host.canMigrateFile(basePath, sourceFile, program));
  158. for (const sourceFile of sourceFiles) {
  159. let update = null;
  160. const rewriter = (startPos, width, text) => {
  161. if (update === null) {
  162. // Lazily initialize update, because most files will not require migration.
  163. update = tree.beginUpdate(p.relative(basePath, sourceFile.fileName));
  164. }
  165. update.remove(startPos, width);
  166. if (text !== null) {
  167. update.insertLeft(startPos, text);
  168. }
  169. };
  170. migrateFile(sourceFile, rewriter);
  171. if (update !== null) {
  172. tree.commitUpdate(update);
  173. }
  174. }
  175. }
  176. exports.migrate = migrate;