'use strict';
/**
 * @license Angular v21.2.1
 * (c) 2010-2026 Google LLC. https://angular.dev/
 * License: MIT
 */
'use strict';

require('@angular-devkit/core');
require('node:path/posix');
var project_paths = require('./project_paths-D2V-Uh2L.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-CxA_YYgB.cjs');
var property_name = require('./property_name-BCpALNpZ.cjs');
var imports = require('./imports-CVmcbVA9.cjs');
var symbol = require('./symbol-DZeHSR-V.cjs');
require('@angular-devkit/schematics');
require('./project_tsconfig_paths-DkkMibv-.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;
