@angular/core
Version:
Angular - the core framework
645 lines (640 loc) • 30.6 kB
JavaScript
'use strict';
/**
* @license Angular v21.0.5
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
'use strict';
require('@angular-devkit/core');
require('node:path/posix');
var project_paths = require('./project_paths-DvD50ouC.cjs');
require('@angular/compiler-cli');
var migrations = require('@angular/compiler-cli/private/migrations');
var ts = require('typescript');
require('node:path');
var apply_import_manager = require('./apply_import_manager-1Zs_gpB6.cjs');
var property_name = require('./property_name-BBwFuqMe.cjs');
var imports = require('./imports-DP72APSx.cjs');
var symbol = require('./symbol-BObKoqes.cjs');
require('@angular-devkit/schematics');
require('./project_tsconfig_paths-CDVxT6Ov.cjs');
const CORE_PACKAGE = '@angular/core';
const PROVIDE_ZONE_CHANGE_DETECTION = 'provideZoneChangeDetection';
const ZONE_CD_PROVIDER = `${PROVIDE_ZONE_CHANGE_DETECTION}()`;
const SAFE_TO_REMOVE_OPTIONS = [
'ignoreChangesOutsideZone',
'ngZoneRunCoalescing',
'ngZoneEventCoalescing',
];
const BOOTSTRAP_OPTIONS = ['ngZone', ...SAFE_TO_REMOVE_OPTIONS];
class BootstrapOptionsMigration extends project_paths.TsurgeFunnelMigration {
async analyze(info) {
let replacements = [];
const importManager = new migrations.ImportManager();
for (const sourceFile of info.sourceFiles) {
// We need to migration either
// * `bootstrapApplication(App)
// * `platformBrowser().bootstrapModule(AppModule)`
// * `platformBrowserDynamic().bootstrapModule(AppModule)`
// * `TestBed.initTestEnvironment([AppModule], platformBrowserTesting())`
// * `getTestBed.initTestEnvironment([AppModule], platformBrowserTesting())`
const specifiers = getSpecifiers(sourceFile);
// If none of the imports related to bootstraping are present, we can skip the file.
if (specifiers === null)
continue;
const { bootstrapAppSpecifier, testBedSpecifier, createApplicationSpecifier, getTestBedSpecifier, } = specifiers;
const typeChecker = info.program.getTypeChecker();
const isCreateApplicationNode = (node) => {
return (ts.isCallExpression(node) &&
createApplicationSpecifier !== null &&
symbol.isReferenceToImport(typeChecker, node.expression, createApplicationSpecifier));
};
const isBootstrapAppNode = (node) => {
return (ts.isCallExpression(node) &&
bootstrapAppSpecifier !== null &&
symbol.isReferenceToImport(typeChecker, node.expression, bootstrapAppSpecifier));
};
const isBootstrapModuleNode = (node) => {
return (ts.isCallExpression(node) &&
ts.isPropertyAccessExpression(node.expression) &&
node.expression.name.text === 'bootstrapModule' &&
node.arguments.length > 0);
};
const isTestBedInitEnvironmentNode = (node) => {
return (ts.isCallExpression(node) &&
ts.isPropertyAccessExpression(node.expression) &&
node.expression.name.text === 'initTestEnvironment' &&
(symbol.isReferenceToImport(typeChecker, node.expression.expression, testBedSpecifier) ||
symbol.isReferenceToImport(typeChecker, node.expression.expression, getTestBedSpecifier)));
};
const reflector = new migrations.TypeScriptReflectionHost(typeChecker);
const evaluator = new migrations.PartialEvaluator(reflector, typeChecker, null);
const walk = (node) => {
if (isBootstrapAppNode(node)) {
this.analyzeBootstrapApplication(node, sourceFile, info, typeChecker, importManager, replacements);
}
else if (isCreateApplicationNode(node)) {
this.analyzeCreateApplication(node, sourceFile, info, typeChecker, importManager, replacements);
}
else if (isBootstrapModuleNode(node)) {
this.analyzeBootstrapModule(node, sourceFile, reflector, evaluator, info, typeChecker, importManager, replacements);
}
else if (isTestBedInitEnvironmentNode(node)) {
this.analyzeTestBedInitEnvironment(node, sourceFile, info, typeChecker, importManager, replacements);
}
node.forEachChild(walk);
};
sourceFile.forEachChild(walk);
}
apply_import_manager.applyImportManagerChanges(importManager, replacements, info.sourceFiles, info);
return project_paths.confirmAsSerializable({ replacements });
}
async combine(unitA, unitB) {
const combined = [...unitA.replacements, ...unitB.replacements];
return project_paths.confirmAsSerializable({ replacements: combined });
}
async globalMeta(data) {
return project_paths.confirmAsSerializable(data);
}
async stats(data) {
return project_paths.confirmAsSerializable({});
}
async migrate(data) {
return { replacements: data.replacements };
}
analyzeBootstrapApplication(node, sourceFile, info, typeChecker, importManager, replacements) {
const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(node, typeChecker);
if (hasExistingChangeDetectionProvider)
return;
const providerFn = 'provideZoneChangeDetection()';
const optionsNode = node.arguments[1];
const currentProjectFile = project_paths.projectFile(sourceFile, info);
if (optionsNode) {
let optionProjectFile = currentProjectFile;
let optionLiteral;
if (ts.isObjectLiteralExpression(optionsNode)) {
optionLiteral = optionsNode;
addProvidersToBootstrapOption(optionProjectFile, optionLiteral, providerFn, replacements);
}
else if (this.isServerConfigZoneless(optionsNode, typeChecker)) {
// Nothing to migrate for the SSR bootstrap
return;
}
else if (ts.isIdentifier(optionsNode)) {
const text = `{...${optionsNode.getText()}, providers: [${providerFn}, ...${optionsNode.getText()}.providers]}`;
replacements.push(new project_paths.Replacement(currentProjectFile, new project_paths.TextUpdate({
position: optionsNode.getStart(),
end: optionsNode.getEnd(),
toInsert: text,
})));
}
else {
throw new Error('unsupported optionsNode: ' + optionsNode.getText());
}
}
else {
// No options object, add it.
const text = `, {providers: [${providerFn}]}`;
const component = node.arguments[0];
replacements.push(new project_paths.Replacement(currentProjectFile, new project_paths.TextUpdate({ position: component.getEnd(), end: component.getEnd(), toInsert: text })));
}
importManager.addImport({
exportModuleSpecifier: CORE_PACKAGE,
exportSymbolName: 'provideZoneChangeDetection',
requestedFile: sourceFile,
});
}
/**
* The optionsNode can be a appConfig built with mergeApplicationConfig
* In this case we need to analyze if the base config uses provideZonelessChangeDetection
*/
isServerConfigZoneless(optionsNode, typeChecker) {
// Check if optionsNode is a result of mergeApplicationConfig
let symbol = typeChecker.getSymbolAtLocation(optionsNode);
if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
symbol = typeChecker.getAliasedSymbol(symbol);
}
const optionDeclaration = symbol?.getDeclarations()?.[0];
if (!optionDeclaration) {
return false;
}
if (!ts.isVariableDeclaration(optionDeclaration) ||
!optionDeclaration.initializer ||
!ts.isCallExpression(optionDeclaration.initializer) ||
!ts.isIdentifier(optionDeclaration.initializer.expression) ||
optionDeclaration.initializer.expression.text !== 'mergeApplicationConfig') {
// We didn't find a mergeApplicationConfig call, this isn't a server config
return false;
}
let maybeAppConfig = optionDeclaration.initializer.arguments[0];
if (ts.isIdentifier(maybeAppConfig)) {
const resolved = getObjectLiteralFromIdentifier(maybeAppConfig, typeChecker);
if (resolved) {
maybeAppConfig = resolved;
}
}
if (maybeAppConfig && ts.isObjectLiteralExpression(maybeAppConfig)) {
for (const prop of maybeAppConfig.properties) {
if (ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'providers' &&
ts.isArrayLiteralExpression(prop.initializer)) {
for (const el of prop.initializer.elements) {
if (ts.isCallExpression(el) &&
ts.isIdentifier(el.expression) &&
el.expression.text === 'provideZonelessChangeDetection') {
return true;
}
}
}
}
}
return false;
}
analyzeCreateApplication(node, sourceFile, info, typeChecker, importManager, replacements) {
const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(node, typeChecker);
if (hasExistingChangeDetectionProvider)
return;
const providerFn = 'provideZoneChangeDetection()';
const optionsNode = node.arguments[0];
const currentProjectFile = project_paths.projectFile(sourceFile, info);
if (optionsNode) {
let optionProjectFile = currentProjectFile;
let optionLiteral;
if (ts.isObjectLiteralExpression(optionsNode)) {
optionLiteral = optionsNode;
addProvidersToBootstrapOption(optionProjectFile, optionLiteral, providerFn, replacements);
}
else if (ts.isIdentifier(optionsNode) ||
ts.isCallExpression(optionsNode) ||
ts.isPropertyAccessExpression(optionsNode)) {
// This is tricky case to handle, in G3 we're might not be able to resolve the identifier's value
// Our best alternative is to assume there is no CD providers set and add the ZoneChangeDetection provider
// In the cases where it is, we'll just override the zone provider we just set by re-used inthe appConfig providers
// TODO: Should we insert a TODO to clean this up ?
const text = `{...${optionsNode.getText()}, providers: [${providerFn}, ...${optionsNode.getText()}.providers]}`;
replacements.push(new project_paths.Replacement(currentProjectFile, new project_paths.TextUpdate({
position: optionsNode.getStart(),
end: optionsNode.getEnd(),
toInsert: text,
})));
}
else {
throw new Error('unsupported optionsNode: ' + optionsNode.getText());
}
}
else {
// No options object, add it.
const text = `{providers: [${providerFn}]}`;
replacements.push(new project_paths.Replacement(currentProjectFile, new project_paths.TextUpdate({
position: node.expression.getEnd() + 1,
end: node.expression.getEnd() + 1,
toInsert: text,
})));
}
importManager.addImport({
exportModuleSpecifier: CORE_PACKAGE,
exportSymbolName: 'provideZoneChangeDetection',
requestedFile: sourceFile,
});
}
analyzeBootstrapModule(node, sourceFile, reflector, evaluator, info, typeChecker, importManager, replacements) {
const moduleIdentifier = node.arguments[0];
const moduleType = evaluator.evaluate(moduleIdentifier);
if (!(moduleType instanceof migrations.Reference) || !ts.isClassDeclaration(moduleType.node)) {
return;
}
const moduleClass = moduleType.node;
const ngModule = findNgModule(moduleClass, reflector);
if (!ngModule) {
return;
}
const optionsNode = node.arguments[1];
const file = project_paths.projectFile(sourceFile, info);
replacements.push(new project_paths.Replacement(file, new project_paths.TextUpdate({
position: moduleIdentifier.getEnd(),
end: node.getEnd() - 1,
toInsert: '',
})));
const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(ngModule, typeChecker);
if (hasExistingChangeDetectionProvider) {
return;
}
// Let's try to understand the bootstrap options.
let options = optionsNode ? evaluator.evaluate(optionsNode) : null;
let extraOptions = new Map();
let zoneCdProvider = ZONE_CD_PROVIDER;
let zoneInstanceProvider = null;
if (Array.isArray(options)) {
const mergedOptions = options.reduce((acc, item) => {
if (item instanceof Map) {
for (const [k, v] of item) {
acc.set(k, v);
if (!SAFE_TO_REMOVE_OPTIONS.includes(k)) {
extraOptions.set(k, v);
}
}
}
return acc;
}, new Map());
options = mergedOptions;
}
if (options instanceof Map) {
[...options.entries()].forEach(([k, v]) => {
if (!BOOTSTRAP_OPTIONS.includes(k) && typeof v !== 'string') {
extraOptions.set(k, v);
}
});
if (options.has('ngZoneRunCoalescing') || options.has('ngZoneEventCoalescing')) {
const config = [];
if (options.get('ngZoneRunCoalescing')) {
config.push('runCoalescing: true');
}
if (options.get('ngZoneEventCoalescing')) {
config.push('eventCoalescing: true');
}
zoneCdProvider = `${PROVIDE_ZONE_CHANGE_DETECTION}(${config.length > 0 ? `{ ${config.join(', ')} }` : ''})`;
}
const ngZoneOption = options.get('ngZone');
if (ngZoneOption instanceof migrations.Reference) {
const clazz = ngZoneOption.node;
if (ts.isClassDeclaration(clazz) && clazz.name) {
zoneInstanceProvider = `{provide: NgZone, useClass: ${clazz.name.text}}`;
removePropertiesFromLiteral(file, optionsNode, ['ngZone'], replacements);
}
}
else if (typeof ngZoneOption === 'string') {
if (ngZoneOption === 'noop') {
return;
}
}
else if (ngZoneOption && typeof ngZoneOption !== 'string') {
// This is a case where we're not able to migrate automatically
// The migration fails gracefully, keeps the ngZone option and adds a TODO.
let ngZoneValue;
optionsNode.properties.forEach((p) => {
if (ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === 'ngZone') {
ngZoneValue = p.initializer.getText();
}
else if (ts.isShorthandPropertyAssignment(p) && p.name.text === 'ngZone') {
ngZoneValue = p.name.text;
}
});
if (ngZoneValue) {
// We re-add the ngZone option
extraOptions.set('ngZone', ngZoneValue);
}
replacements.push(new project_paths.Replacement(project_paths.projectFile(sourceFile, info), new project_paths.TextUpdate({
position: node.getStart() - 1,
end: node.getStart() - 1,
toInsert: '// TODO: BootstrapOptions are deprecated & ignored. Configure NgZone in the providers array of the application module instead.',
})));
}
}
const providers = [zoneCdProvider];
if (zoneInstanceProvider) {
providers.push(zoneInstanceProvider);
}
importManager.addImport({
exportModuleSpecifier: CORE_PACKAGE,
exportSymbolName: PROVIDE_ZONE_CHANGE_DETECTION,
requestedFile: sourceFile,
});
// if we only use the key, we use the a shorthand asignment
const extraOptionsStr = [...extraOptions.entries()]
.map(([k, v]) => (k != v ? `${k}: ${v},` : `${k},`))
.join(', ');
replacements.push(new project_paths.Replacement(file, new project_paths.TextUpdate({
position: moduleIdentifier.end,
end: moduleIdentifier.end,
toInsert: `, { applicationProviders: [${providers.join(', ')}], ${extraOptionsStr}}`,
})));
}
analyzeTestBedInitEnvironment(callExpr, sourceFile, info, typeChecker, importManager, replacements) {
const hasExistingChangeDetectionProvider = hasChangeDetectionProvider(callExpr, typeChecker);
if (hasExistingChangeDetectionProvider)
return;
const ngModules = callExpr.arguments[0];
const moduleProjectFile = project_paths.projectFile(sourceFile, info);
importManager.addImport({
exportModuleSpecifier: CORE_PACKAGE,
exportSymbolName: PROVIDE_ZONE_CHANGE_DETECTION,
requestedFile: sourceFile,
});
let tmpNode = callExpr;
let insertPosition = 0;
while (tmpNode.parent.kind !== ts.SyntaxKind.SourceFile) {
insertPosition = tmpNode.parent.getStart(sourceFile, true) - 1;
tmpNode = tmpNode.parent;
}
importManager.addImport({
exportModuleSpecifier: CORE_PACKAGE,
exportSymbolName: 'NgModule',
requestedFile: sourceFile,
});
addZoneCDModule(ZONE_CD_PROVIDER, moduleProjectFile, insertPosition, replacements);
insertZoneCDModule(ngModules, moduleProjectFile, replacements, 'ZoneChangeDetectionModule');
}
}
function addZoneCDModule(providersText, projectFile, location, replacements) {
const newModuleText = `\n@NgModule({ providers: [ ${providersText} ] })
export class ZoneChangeDetectionModule {}\n\n`;
if (replacementsHaveZoneCdModule(projectFile.rootRelativePath, replacements, newModuleText)) {
return;
}
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
position: location,
end: location,
toInsert: newModuleText,
})));
}
function insertZoneCDModule(node, projectFile, replacements, importedModule) {
if (ts.isArrayLiteralExpression(node)) {
const literal = node;
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
position: literal.elements[0]?.getStart() ?? literal.getEnd() - 1,
end: literal.elements[0]?.getStart() ?? literal.getEnd() - 1,
toInsert: importedModule + ',',
})));
}
else if (ts.isIdentifier(node)) {
// This should be a good enough heuristic to determine if the identifier is not array
let isArray = !node.text.endsWith('Module');
// Because if it's an array, we need to spread it
const newImports = `[${importedModule}, ${isArray ? '...' : ''}${node.text}]`;
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
position: node.getStart(),
end: node.getEnd(),
toInsert: newImports,
})));
}
else {
throw new Error('unsupported importsNode: ' + node.getText());
}
}
function addProvidersToBootstrapOption(projectFile, optionsNode, providersText, replacements) {
const providersProp = property_name.findLiteralProperty(optionsNode, 'providers');
if (providersProp && ts.isPropertyAssignment(providersProp)) {
// Can be bootstrap(App, {providers: [...]}), bootstrap(App, {providers}), bootstrap(App, {...appConfig, providers}) etc.
if (ts.isArrayLiteralExpression(providersProp.initializer)) {
const initializer = providersProp.initializer;
const text = `${providersText},`;
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
position: initializer.elements[0]?.getStart() ?? initializer.getEnd() - 1,
end: initializer.elements[0]?.getStart() ?? initializer.getEnd() - 1,
toInsert: text,
})));
}
else if (ts.isIdentifier(providersProp.initializer)) {
const newProviders = `[${providersText}, ...${providersProp.initializer.text}]`;
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
position: providersProp.initializer.getStart(),
end: providersProp.initializer.getEnd(),
toInsert: newProviders,
})));
}
else {
const newProviders = `[${providersText}, ...`;
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
position: providersProp.initializer.getStart(),
end: providersProp.initializer.getStart(),
toInsert: newProviders,
})));
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
position: providersProp.initializer.getEnd(),
end: providersProp.initializer.getEnd(),
toInsert: ']',
})));
}
}
else if (providersProp && ts.isShorthandPropertyAssignment(providersProp)) {
const newProviders = `providers: [${providersText}, ...${providersProp.name.text}]`;
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
position: providersProp.getStart(),
end: providersProp.getEnd(),
toInsert: newProviders,
})));
}
else if (optionsNode.properties.length === 1 &&
ts.isSpreadAssignment(optionsNode.properties[0])) {
const spread = optionsNode.properties[0];
const newProviders = `, providers: [${providersText}, ...${spread.expression.getText()}.providers]`;
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
position: spread.getEnd(),
end: spread.getEnd(),
toInsert: newProviders,
})));
}
else {
const text = `providers: [${providersText}]`;
let toInsert;
let position;
if (optionsNode.properties.length > 0) {
const lastProperty = optionsNode.properties[optionsNode.properties.length - 1];
toInsert = `,\n ${text}`;
position = lastProperty.getEnd();
}
else {
toInsert = `\n ${text}\n`;
position = optionsNode.getStart() + 1;
}
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({
position,
end: position,
toInsert,
})));
}
}
function findNgModule(node, reflector) {
const decorators = reflector.getDecoratorsOfDeclaration(node);
if (decorators) {
const ngModuleDecorator = migrations.getAngularDecorators(decorators, ['NgModule'], true)[0];
if (ngModuleDecorator &&
ngModuleDecorator.args &&
ngModuleDecorator.args.length > 0 &&
ts.isObjectLiteralExpression(ngModuleDecorator.args[0])) {
return ngModuleDecorator.args[0];
}
}
return null;
}
function hasChangeDetectionProvider(expression, // either the bootstrapApplication or platformBrowserDynamic().bootstrapModule()
typeChecker) {
let literal;
if (ts.isCallExpression(expression)) {
let optionsNode = expression.arguments[1];
if (!optionsNode &&
symbol.isReferenceToImport(typeChecker, expression.expression, imports.getImportSpecifier(expression.getSourceFile(), '@angular/core', 'createApplication'))) {
optionsNode = expression.arguments[0];
}
if (!optionsNode)
return false;
if (ts.isIdentifier(optionsNode)) {
literal = getObjectLiteralFromIdentifier(optionsNode, typeChecker);
}
else {
literal = optionsNode;
}
}
else {
literal = expression;
}
if (!literal) {
return false;
}
const provideZoneCdSpecifier = imports.getImportSpecifier(literal.getSourceFile(), '@angular/core', 'provideZoneChangeDetection');
const provideZonelessCdSpecifier = imports.getImportSpecifier(literal.getSourceFile(), '@angular/core', 'provideZonelessChangeDetection');
if (provideZoneCdSpecifier === null && provideZonelessCdSpecifier === null) {
return false;
}
const found = ts.forEachChild(literal, function walk(node) {
if (ts.isCallExpression(node)) {
if (provideZonelessCdSpecifier &&
node.getText().includes(provideZonelessCdSpecifier.getText())) {
return true;
}
if (provideZoneCdSpecifier && node.getText().includes(provideZoneCdSpecifier.getText())) {
return true;
}
}
return ts.forEachChild(node, walk);
});
return !!found;
}
function getObjectLiteralFromIdentifier(identifier, typeChecker) {
let symbol = typeChecker.getSymbolAtLocation(identifier);
if (!symbol)
return;
// Follow aliases (for imported symbols)
if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) {
symbol = typeChecker.getAliasedSymbol(symbol);
}
const declarations = symbol.getDeclarations();
if (!declarations)
return;
for (const decl of declarations) {
if (ts.isVariableDeclaration(decl) &&
decl.initializer &&
ts.isObjectLiteralExpression(decl.initializer)) {
return decl.initializer;
}
}
return;
}
/**
* Extracts the import specifiers related to bootstraping from the source file.
* Returns null if no relevant specifiers are found.
*/
function getSpecifiers(sourceFile) {
const createApplicationSpecifier = imports.getImportSpecifier(sourceFile, '@angular/core', 'createApplication');
const bootstrapAppSpecifier = imports.getImportSpecifier(sourceFile, '@angular/platform-browser', 'bootstrapApplication');
const platformBrowserDynamicSpecifier = imports.getImportSpecifier(sourceFile, '@angular/platform-browser-dynamic', 'platformBrowserDynamic');
const platformBrowserSpecifier = imports.getImportSpecifier(sourceFile, '@angular/platform-browser', 'platformBrowser');
const testBedSpecifier = imports.getImportSpecifier(sourceFile, '@angular/core/testing', 'TestBed');
const getTestBedSpecifier = imports.getImportSpecifier(sourceFile, '@angular/core/testing', 'getTestBed');
const ngModuleSpecifier = imports.getImportSpecifier(sourceFile, '@angular/core', 'NgModule');
if (!createApplicationSpecifier &&
!bootstrapAppSpecifier &&
!platformBrowserSpecifier &&
!platformBrowserDynamicSpecifier &&
!testBedSpecifier &&
!ngModuleSpecifier &&
!getTestBedSpecifier) {
return null;
}
return {
createApplicationSpecifier,
bootstrapAppSpecifier,
platformBrowserDynamicSpecifier,
platformBrowserSpecifier,
testBedSpecifier,
ngModuleSpecifier,
getTestBedSpecifier,
};
}
/**
* In the case we're looking to insert a new ZoneChangeDetectionModule, we need to check if we already inserted one.
*
* This function also checks if the existing one has fewer options (shorter text length), which means the previous migration strategy inserted one
* but the following one is more complete and we should still add it (the dedup function will take care of the cleanup).
*/
function replacementsHaveZoneCdModule(rootRelativePath, replacements, text) {
return replacements.some((replacement) => {
const exisitingText = replacement.update.data.toInsert;
const isSameFile = replacement.projectFile.rootRelativePath === rootRelativePath;
return (isSameFile &&
text.includes('ZoneChangeDetectionModule') &&
exisitingText.length >= text.length);
});
}
function removePropertiesFromLiteral(projectFile, literal, propertyNames, replacements) {
const syntaxList = literal.getChildren().find((ch) => ch.kind === ts.SyntaxKind.SyntaxList);
const optionsElements = syntaxList.getChildren();
const optionsToRemove = [];
optionsElements.forEach((node, i, children) => {
if (ts.isPropertyAssignment(node) &&
ts.isIdentifier(node.name) &&
propertyNames.includes(node.name.text)) {
// Look ahead for comma
const next = children[i + 1];
if (next && next.kind === ts.SyntaxKind.CommaToken) {
optionsToRemove.push({ start: node.getStart(), end: next.getEnd() });
}
else {
optionsToRemove.push({ start: node.getStart(), end: node.getEnd() });
}
}
});
optionsToRemove.forEach((toRemove) => {
replacements.push(new project_paths.Replacement(projectFile, new project_paths.TextUpdate({ position: toRemove.start, end: toRemove.end, toInsert: '' })));
});
}
function migrate() {
return async (tree) => {
await project_paths.runMigrationInDevkit({
tree,
getMigration: () => new BootstrapOptionsMigration(),
});
};
}
exports.migrate = migrate;