rules.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. "use strict";
  2. /**
  3. * @license
  4. * Copyright Google LLC All Rights Reserved.
  5. *
  6. * Use of this source code is governed by an MIT-style license that can be
  7. * found in the LICENSE file at https://angular.dev/license
  8. */
  9. Object.defineProperty(exports, "__esModule", { value: true });
  10. exports.addRootImport = addRootImport;
  11. exports.addRootProvider = addRootProvider;
  12. const core_1 = require("@angular-devkit/core");
  13. const schematics_1 = require("@angular-devkit/schematics");
  14. const ast_utils_1 = require("../ast-utils");
  15. const change_1 = require("../change");
  16. const ng_ast_utils_1 = require("../ng-ast-utils");
  17. const app_config_1 = require("./app_config");
  18. const code_block_1 = require("./code_block");
  19. const util_1 = require("./util");
  20. /**
  21. * Adds an import to the root of the project.
  22. * @param project Name of the project to which to add the import.
  23. * @param callback Function that generates the code block which should be inserted.
  24. * @example
  25. *
  26. * ```ts
  27. * import { Rule } from '@angular-devkit/schematics';
  28. * import { addRootImport } from '@schematics/angular/utility';
  29. *
  30. * export default function(): Rule {
  31. * return addRootImport('default', ({code, external}) => {
  32. * return code`${external('MyModule', '@my/module')}.forRoot({})`;
  33. * });
  34. * }
  35. * ```
  36. */
  37. function addRootImport(project, callback) {
  38. return getRootInsertionRule(project, callback, 'imports', {
  39. name: 'importProvidersFrom',
  40. module: '@angular/core',
  41. });
  42. }
  43. /**
  44. * Adds a provider to the root of the project.
  45. * @param project Name of the project to which to add the import.
  46. * @param callback Function that generates the code block which should be inserted.
  47. * @example
  48. *
  49. * ```ts
  50. * import { Rule } from '@angular-devkit/schematics';
  51. * import { addRootProvider } from '@schematics/angular/utility';
  52. *
  53. * export default function(): Rule {
  54. * return addRootProvider('default', ({code, external}) => {
  55. * return code`${external('provideLibrary', '@my/library')}({})`;
  56. * });
  57. * }
  58. * ```
  59. */
  60. function addRootProvider(project, callback) {
  61. return getRootInsertionRule(project, callback, 'providers');
  62. }
  63. /**
  64. * Creates a rule that inserts code at the root of either a standalone or NgModule-based project.
  65. * @param project Name of the project into which to inser tthe code.
  66. * @param callback Function that generates the code block which should be inserted.
  67. * @param ngModuleField Field of the root NgModule into which the code should be inserted, if the
  68. * app is based on NgModule
  69. * @param standaloneWrapperFunction Function with which to wrap the code if the app is standalone.
  70. */
  71. function getRootInsertionRule(project, callback, ngModuleField, standaloneWrapperFunction) {
  72. return async (host) => {
  73. const mainFilePath = await (0, util_1.getMainFilePath)(host, project);
  74. const codeBlock = new code_block_1.CodeBlock();
  75. if ((0, ng_ast_utils_1.isStandaloneApp)(host, mainFilePath)) {
  76. return (tree) => addProviderToStandaloneBootstrap(tree, callback(codeBlock), mainFilePath, standaloneWrapperFunction);
  77. }
  78. const modulePath = (0, ng_ast_utils_1.getAppModulePath)(host, mainFilePath);
  79. const pendingCode = code_block_1.CodeBlock.transformPendingCode(callback(codeBlock), modulePath);
  80. return (0, schematics_1.chain)([
  81. ...pendingCode.rules,
  82. (tree) => {
  83. const changes = (0, ast_utils_1.addSymbolToNgModuleMetadata)((0, util_1.getSourceFile)(tree, modulePath), modulePath, ngModuleField, pendingCode.code.expression,
  84. // Explicitly set the import path to null since we deal with imports here separately.
  85. null);
  86. (0, util_1.applyChangesToFile)(tree, modulePath, changes);
  87. },
  88. ]);
  89. };
  90. }
  91. /**
  92. * Adds a provider to the root of a standalone project.
  93. * @param host Tree of the root rule.
  94. * @param pendingCode Code that should be inserted.
  95. * @param mainFilePath Path to the project's main file.
  96. * @param wrapperFunction Optional function with which to wrap the provider.
  97. */
  98. function addProviderToStandaloneBootstrap(host, pendingCode, mainFilePath, wrapperFunction) {
  99. const bootstrapCall = (0, util_1.findBootstrapApplicationCall)(host, mainFilePath);
  100. const fileToEdit = (0, app_config_1.findAppConfig)(bootstrapCall, host, mainFilePath)?.filePath || mainFilePath;
  101. const { code, rules } = code_block_1.CodeBlock.transformPendingCode(pendingCode, fileToEdit);
  102. return (0, schematics_1.chain)([
  103. ...rules,
  104. () => {
  105. let wrapped;
  106. let additionalRules;
  107. if (wrapperFunction) {
  108. const block = new code_block_1.CodeBlock();
  109. const result = code_block_1.CodeBlock.transformPendingCode(block.code `${block.external(wrapperFunction.name, wrapperFunction.module)}(${code.expression})`, fileToEdit);
  110. wrapped = result.code;
  111. additionalRules = result.rules;
  112. }
  113. else {
  114. wrapped = code;
  115. additionalRules = [];
  116. }
  117. return (0, schematics_1.chain)([
  118. ...additionalRules,
  119. (tree) => insertStandaloneRootProvider(tree, mainFilePath, wrapped.expression),
  120. ]);
  121. },
  122. ]);
  123. }
  124. /**
  125. * Inserts a string expression into the root of a standalone project.
  126. * @param tree File tree used to modify the project.
  127. * @param mainFilePath Path to the main file of the project.
  128. * @param expression Code expression to be inserted.
  129. */
  130. function insertStandaloneRootProvider(tree, mainFilePath, expression) {
  131. const bootstrapCall = (0, util_1.findBootstrapApplicationCall)(tree, mainFilePath);
  132. const appConfig = (0, app_config_1.findAppConfig)(bootstrapCall, tree, mainFilePath);
  133. if (bootstrapCall.arguments.length === 0) {
  134. throw new schematics_1.SchematicsException(`Cannot add provider to invalid bootstrapApplication call in ${bootstrapCall.getSourceFile().fileName}`);
  135. }
  136. if (appConfig) {
  137. addProvidersExpressionToAppConfig(tree, appConfig, expression);
  138. return;
  139. }
  140. const newAppConfig = `, {\n${core_1.tags.indentBy(2) `providers: [${expression}]`}\n}`;
  141. let targetCall;
  142. if (bootstrapCall.arguments.length === 1) {
  143. targetCall = bootstrapCall;
  144. }
  145. else if ((0, util_1.isMergeAppConfigCall)(bootstrapCall.arguments[1])) {
  146. targetCall = bootstrapCall.arguments[1];
  147. }
  148. else {
  149. throw new schematics_1.SchematicsException(`Cannot statically analyze bootstrapApplication call in ${bootstrapCall.getSourceFile().fileName}`);
  150. }
  151. (0, util_1.applyChangesToFile)(tree, mainFilePath, [
  152. (0, ast_utils_1.insertAfterLastOccurrence)(targetCall.arguments, newAppConfig, mainFilePath, targetCall.getEnd() - 1),
  153. ]);
  154. }
  155. /**
  156. * Adds a string expression to an app config object.
  157. * @param tree File tree used to modify the project.
  158. * @param appConfig Resolved configuration object of the project.
  159. * @param expression Code expression to be inserted.
  160. */
  161. function addProvidersExpressionToAppConfig(tree, appConfig, expression) {
  162. const { node, filePath } = appConfig;
  163. const configProps = node.properties;
  164. const providersLiteral = (0, util_1.findProvidersLiteral)(node);
  165. // If there's a `providers` property, we can add the provider
  166. // to it, otherwise we need to declare it ourselves.
  167. if (providersLiteral) {
  168. (0, util_1.applyChangesToFile)(tree, filePath, [
  169. (0, ast_utils_1.insertAfterLastOccurrence)(providersLiteral.elements, (providersLiteral.elements.length === 0 ? '' : ', ') + expression, filePath, providersLiteral.getStart() + 1),
  170. ]);
  171. }
  172. else {
  173. const prop = core_1.tags.indentBy(2) `providers: [${expression}]`;
  174. let toInsert;
  175. let insertPosition;
  176. if (configProps.length === 0) {
  177. toInsert = '\n' + prop + '\n';
  178. insertPosition = node.getEnd() - 1;
  179. }
  180. else {
  181. const hasTrailingComma = configProps.hasTrailingComma;
  182. toInsert = (hasTrailingComma ? '' : ',') + '\n' + prop;
  183. insertPosition = configProps[configProps.length - 1].getEnd() + (hasTrailingComma ? 1 : 0);
  184. }
  185. (0, util_1.applyChangesToFile)(tree, filePath, [new change_1.InsertChange(filePath, insertPosition, toInsert)]);
  186. }
  187. }