189 lines
8.2 KiB
JavaScript
189 lines
8.2 KiB
JavaScript
|
"use strict";
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright Google LLC All Rights Reserved.
|
||
|
*
|
||
|
* Use of this source code is governed by an MIT-style license that can be
|
||
|
* found in the LICENSE file at https://angular.io/license
|
||
|
*/
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.addRootProvider = exports.addRootImport = void 0;
|
||
|
const core_1 = require("@angular-devkit/core");
|
||
|
const schematics_1 = require("@angular-devkit/schematics");
|
||
|
const ast_utils_1 = require("../ast-utils");
|
||
|
const change_1 = require("../change");
|
||
|
const ng_ast_utils_1 = require("../ng-ast-utils");
|
||
|
const app_config_1 = require("./app_config");
|
||
|
const code_block_1 = require("./code_block");
|
||
|
const util_1 = require("./util");
|
||
|
/**
|
||
|
* Adds an import to the root of the project.
|
||
|
* @param project Name of the project to which to add the import.
|
||
|
* @param callback Function that generates the code block which should be inserted.
|
||
|
* @example
|
||
|
*
|
||
|
* ```ts
|
||
|
* import { Rule } from '@angular-devkit/schematics';
|
||
|
* import { addRootImport } from '@schematics/angular/utility';
|
||
|
*
|
||
|
* export default function(): Rule {
|
||
|
* return addRootImport('default', ({code, external}) => {
|
||
|
* return code`${external('MyModule', '@my/module')}.forRoot({})`;
|
||
|
* });
|
||
|
* }
|
||
|
* ```
|
||
|
*/
|
||
|
function addRootImport(project, callback) {
|
||
|
return getRootInsertionRule(project, callback, 'imports', {
|
||
|
name: 'importProvidersFrom',
|
||
|
module: '@angular/core',
|
||
|
});
|
||
|
}
|
||
|
exports.addRootImport = addRootImport;
|
||
|
/**
|
||
|
* Adds a provider to the root of the project.
|
||
|
* @param project Name of the project to which to add the import.
|
||
|
* @param callback Function that generates the code block which should be inserted.
|
||
|
* @example
|
||
|
*
|
||
|
* ```ts
|
||
|
* import { Rule } from '@angular-devkit/schematics';
|
||
|
* import { addRootProvider } from '@schematics/angular/utility';
|
||
|
*
|
||
|
* export default function(): Rule {
|
||
|
* return addRootProvider('default', ({code, external}) => {
|
||
|
* return code`${external('provideLibrary', '@my/library')}({})`;
|
||
|
* });
|
||
|
* }
|
||
|
* ```
|
||
|
*/
|
||
|
function addRootProvider(project, callback) {
|
||
|
return getRootInsertionRule(project, callback, 'providers');
|
||
|
}
|
||
|
exports.addRootProvider = addRootProvider;
|
||
|
/**
|
||
|
* Creates a rule that inserts code at the root of either a standalone or NgModule-based project.
|
||
|
* @param project Name of the project into which to inser tthe code.
|
||
|
* @param callback Function that generates the code block which should be inserted.
|
||
|
* @param ngModuleField Field of the root NgModule into which the code should be inserted, if the
|
||
|
* app is based on NgModule
|
||
|
* @param standaloneWrapperFunction Function with which to wrap the code if the app is standalone.
|
||
|
*/
|
||
|
function getRootInsertionRule(project, callback, ngModuleField, standaloneWrapperFunction) {
|
||
|
return async (host) => {
|
||
|
const mainFilePath = await (0, util_1.getMainFilePath)(host, project);
|
||
|
const codeBlock = new code_block_1.CodeBlock();
|
||
|
if ((0, ng_ast_utils_1.isStandaloneApp)(host, mainFilePath)) {
|
||
|
return (tree) => addProviderToStandaloneBootstrap(tree, callback(codeBlock), mainFilePath, standaloneWrapperFunction);
|
||
|
}
|
||
|
const modulePath = (0, ng_ast_utils_1.getAppModulePath)(host, mainFilePath);
|
||
|
const pendingCode = code_block_1.CodeBlock.transformPendingCode(callback(codeBlock), modulePath);
|
||
|
return (0, schematics_1.chain)([
|
||
|
...pendingCode.rules,
|
||
|
(tree) => {
|
||
|
const changes = (0, ast_utils_1.addSymbolToNgModuleMetadata)((0, util_1.getSourceFile)(tree, modulePath), modulePath, ngModuleField, pendingCode.code.expression,
|
||
|
// Explicitly set the import path to null since we deal with imports here separately.
|
||
|
null);
|
||
|
(0, util_1.applyChangesToFile)(tree, modulePath, changes);
|
||
|
},
|
||
|
]);
|
||
|
};
|
||
|
}
|
||
|
/**
|
||
|
* Adds a provider to the root of a standalone project.
|
||
|
* @param host Tree of the root rule.
|
||
|
* @param pendingCode Code that should be inserted.
|
||
|
* @param mainFilePath Path to the project's main file.
|
||
|
* @param wrapperFunction Optional function with which to wrap the provider.
|
||
|
*/
|
||
|
function addProviderToStandaloneBootstrap(host, pendingCode, mainFilePath, wrapperFunction) {
|
||
|
const bootstrapCall = (0, util_1.findBootstrapApplicationCall)(host, mainFilePath);
|
||
|
const fileToEdit = (0, app_config_1.findAppConfig)(bootstrapCall, host, mainFilePath)?.filePath || mainFilePath;
|
||
|
const { code, rules } = code_block_1.CodeBlock.transformPendingCode(pendingCode, fileToEdit);
|
||
|
return (0, schematics_1.chain)([
|
||
|
...rules,
|
||
|
() => {
|
||
|
let wrapped;
|
||
|
let additionalRules;
|
||
|
if (wrapperFunction) {
|
||
|
const block = new code_block_1.CodeBlock();
|
||
|
const result = code_block_1.CodeBlock.transformPendingCode(block.code `${block.external(wrapperFunction.name, wrapperFunction.module)}(${code.expression})`, fileToEdit);
|
||
|
wrapped = result.code;
|
||
|
additionalRules = result.rules;
|
||
|
}
|
||
|
else {
|
||
|
wrapped = code;
|
||
|
additionalRules = [];
|
||
|
}
|
||
|
return (0, schematics_1.chain)([
|
||
|
...additionalRules,
|
||
|
(tree) => insertStandaloneRootProvider(tree, mainFilePath, wrapped.expression),
|
||
|
]);
|
||
|
},
|
||
|
]);
|
||
|
}
|
||
|
/**
|
||
|
* Inserts a string expression into the root of a standalone project.
|
||
|
* @param tree File tree used to modify the project.
|
||
|
* @param mainFilePath Path to the main file of the project.
|
||
|
* @param expression Code expression to be inserted.
|
||
|
*/
|
||
|
function insertStandaloneRootProvider(tree, mainFilePath, expression) {
|
||
|
const bootstrapCall = (0, util_1.findBootstrapApplicationCall)(tree, mainFilePath);
|
||
|
const appConfig = (0, app_config_1.findAppConfig)(bootstrapCall, tree, mainFilePath);
|
||
|
if (bootstrapCall.arguments.length === 0) {
|
||
|
throw new schematics_1.SchematicsException(`Cannot add provider to invalid bootstrapApplication call in ${bootstrapCall.getSourceFile().fileName}`);
|
||
|
}
|
||
|
if (appConfig) {
|
||
|
addProvidersExpressionToAppConfig(tree, appConfig, expression);
|
||
|
return;
|
||
|
}
|
||
|
const newAppConfig = `, {\n${core_1.tags.indentBy(2) `providers: [${expression}]`}\n}`;
|
||
|
let targetCall;
|
||
|
if (bootstrapCall.arguments.length === 1) {
|
||
|
targetCall = bootstrapCall;
|
||
|
}
|
||
|
else if ((0, util_1.isMergeAppConfigCall)(bootstrapCall.arguments[1])) {
|
||
|
targetCall = bootstrapCall.arguments[1];
|
||
|
}
|
||
|
else {
|
||
|
throw new schematics_1.SchematicsException(`Cannot statically analyze bootstrapApplication call in ${bootstrapCall.getSourceFile().fileName}`);
|
||
|
}
|
||
|
(0, util_1.applyChangesToFile)(tree, mainFilePath, [
|
||
|
(0, ast_utils_1.insertAfterLastOccurrence)(targetCall.arguments, newAppConfig, mainFilePath, targetCall.getEnd() - 1),
|
||
|
]);
|
||
|
}
|
||
|
/**
|
||
|
* Adds a string expression to an app config object.
|
||
|
* @param tree File tree used to modify the project.
|
||
|
* @param appConfig Resolved configuration object of the project.
|
||
|
* @param expression Code expression to be inserted.
|
||
|
*/
|
||
|
function addProvidersExpressionToAppConfig(tree, appConfig, expression) {
|
||
|
const { node, filePath } = appConfig;
|
||
|
const configProps = node.properties;
|
||
|
const providersLiteral = (0, util_1.findProvidersLiteral)(node);
|
||
|
// If there's a `providers` property, we can add the provider
|
||
|
// to it, otherwise we need to declare it ourselves.
|
||
|
if (providersLiteral) {
|
||
|
(0, util_1.applyChangesToFile)(tree, filePath, [
|
||
|
(0, ast_utils_1.insertAfterLastOccurrence)(providersLiteral.elements, (providersLiteral.elements.length === 0 ? '' : ', ') + expression, filePath, providersLiteral.getStart() + 1),
|
||
|
]);
|
||
|
}
|
||
|
else {
|
||
|
const prop = core_1.tags.indentBy(2) `providers: [${expression}]`;
|
||
|
let toInsert;
|
||
|
let insertPosition;
|
||
|
if (configProps.length === 0) {
|
||
|
toInsert = '\n' + prop + '\n';
|
||
|
insertPosition = node.getEnd() - 1;
|
||
|
}
|
||
|
else {
|
||
|
const hasTrailingComma = configProps.hasTrailingComma;
|
||
|
toInsert = (hasTrailingComma ? '' : ',') + '\n' + prop;
|
||
|
insertPosition = configProps[configProps.length - 1].getEnd() + (hasTrailingComma ? 1 : 0);
|
||
|
}
|
||
|
(0, util_1.applyChangesToFile)(tree, filePath, [new change_1.InsertChange(filePath, insertPosition, toInsert)]);
|
||
|
}
|
||
|
}
|