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

var compilerCli = require('@angular/compiler-cli');
var schematics = require('@angular-devkit/schematics');
var core = require('@angular-devkit/core');
var posixPath = require('node:path/posix');
var migrations = require('@angular/compiler-cli/private/migrations');
var ts = require('typescript');
var path = require('node:path');
var project_tsconfig_paths = require('./project_tsconfig_paths-DkkMibv-.cjs');

function _interopNamespaceDefault(e) {
    var n = Object.create(null);
    if (e) {
        Object.keys(e).forEach(function (k) {
            if (k !== 'default') {
                var d = Object.getOwnPropertyDescriptor(e, k);
                Object.defineProperty(n, k, d.get ? d : {
                    enumerable: true,
                    get: function () { return e[k]; }
                });
            }
        });
    }
    n.default = e;
    return Object.freeze(n);
}

var posixPath__namespace = /*#__PURE__*/_interopNamespaceDefault(posixPath);
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);

/**
 * Angular compiler file system implementation that leverages an
 * CLI schematic virtual file tree.
 */
class DevkitMigrationFilesystem {
    tree;
    constructor(tree) {
        this.tree = tree;
    }
    extname(path) {
        return core.extname(path);
    }
    isRoot(path) {
        return path === core.normalize('/');
    }
    isRooted(path) {
        return this.normalize(path).startsWith('/');
    }
    dirname(file) {
        return this.normalize(core.dirname(file));
    }
    join(basePath, ...paths) {
        return this.normalize(core.join(basePath, ...paths));
    }
    relative(from, to) {
        return this.normalize(core.relative(from, to));
    }
    basename(filePath, extension) {
        return posixPath__namespace.basename(filePath, extension);
    }
    normalize(path) {
        return core.normalize(path);
    }
    resolve(...paths) {
        const normalizedPaths = paths.map((p) => core.normalize(p));
        // In dev-kit, the NodeJS working directory should never be
        // considered, so `/` is the last resort over `cwd`.
        return this.normalize(posixPath__namespace.resolve(core.normalize('/'), ...normalizedPaths));
    }
    pwd() {
        return '/';
    }
    isCaseSensitive() {
        return true;
    }
    exists(path) {
        return statPath(this.tree, path) !== null;
    }
    readFile(path) {
        return this.tree.readText(path);
    }
    readFileBuffer(path) {
        const buffer = this.tree.read(path);
        if (buffer === null) {
            throw new Error(`File does not exist: ${path}`);
        }
        return buffer;
    }
    readdir(path) {
        const dir = this.tree.getDir(path);
        return [
            ...dir.subdirs,
            ...dir.subfiles,
        ];
    }
    lstat(path) {
        const stat = statPath(this.tree, path);
        if (stat === null) {
            throw new Error(`File does not exist for "lstat": ${path}`);
        }
        return stat;
    }
    stat(path) {
        const stat = statPath(this.tree, path);
        if (stat === null) {
            throw new Error(`File does not exist for "stat": ${path}`);
        }
        return stat;
    }
    realpath(filePath) {
        return filePath;
    }
    getDefaultLibLocation() {
        return 'node_modules/typescript/lib';
    }
    ensureDir(path) {
        // Migrations should compute replacements and not write directly.
        throw new Error('DevkitFilesystem#ensureDir is not supported.');
    }
    writeFile(path, data) {
        // Migrations should compute replacements and not write directly.
        throw new Error('DevkitFilesystem#writeFile is not supported.');
    }
    removeFile(path) {
        // Migrations should compute replacements and not write directly.
        throw new Error('DevkitFilesystem#removeFile is not supported.');
    }
    copyFile(from, to) {
        // Migrations should compute replacements and not write directly.
        throw new Error('DevkitFilesystem#copyFile is not supported.');
    }
    moveFile(from, to) {
        // Migrations should compute replacements and not write directly.
        throw new Error('DevkitFilesystem#moveFile is not supported.');
    }
    removeDeep(path) {
        // Migrations should compute replacements and not write directly.
        throw new Error('DevkitFilesystem#removeDeep is not supported.');
    }
    chdir(_path) {
        throw new Error('FileSystem#chdir is not supported.');
    }
    symlink() {
        throw new Error('FileSystem#symlink is not supported.');
    }
}
/** Stats the given path in the virtual tree. */
function statPath(tree, path) {
    let fileInfo = null;
    let dirInfo = null;
    try {
        fileInfo = tree.get(path);
    }
    catch (e) {
        if (e.constructor.name === 'PathIsDirectoryException') {
            dirInfo = tree.getDir(path);
        }
        else {
            throw e;
        }
    }
    if (fileInfo !== null || dirInfo !== null) {
        return {
            isDirectory: () => dirInfo !== null,
            isFile: () => fileInfo !== null,
            isSymbolicLink: () => false,
        };
    }
    return null;
}

/**
 * Groups the given replacements per project relative
 * file path.
 *
 * This allows for simple execution of the replacements
 * against a given file. E.g. via {@link applyTextUpdates}.
 */
function groupReplacementsByFile(replacements) {
    const result = new Map();
    for (const { projectFile, update } of replacements) {
        if (!result.has(projectFile.rootRelativePath)) {
            result.set(projectFile.rootRelativePath, []);
        }
        result.get(projectFile.rootRelativePath).push(update);
    }
    return result;
}

/**
 * Synchronously combines unit data for the given migration.
 *
 * Note: This helper is useful for testing and execution of
 * Tsurge migrations in non-batchable environments. In general,
 * prefer parallel execution of combining via e.g. Beam combiners.
 */
async function synchronouslyCombineUnitData(migration, unitDatas) {
    if (unitDatas.length === 0) {
        return null;
    }
    if (unitDatas.length === 1) {
        return unitDatas[0];
    }
    let combined = unitDatas[0];
    for (let i = 1; i < unitDatas.length; i++) {
        const other = unitDatas[i];
        combined = await migration.combine(combined, other);
    }
    return combined;
}

/** Whether we are executing inside Google */
function isGoogle3() {
    return process.env['GOOGLE3_TSURGE'] === '1';
}
/**
 * By default, Tsurge will always create an Angular compiler program
 * for projects analyzed and migrated. This works perfectly fine in
 * third-party where Tsurge migrations run in Angular CLI projects.
 *
 * In first party, when running against full Google3, creating an Angular
 * program for e.g. plain `ts_library` targets is overly expensive and
 * can result in out of memory issues for large TS targets. In 1P we can
 * reliably distinguish between TS and Angular targets via the `angularCompilerOptions`.
 */
function google3UsePlainTsProgramIfNoKnownAngularOption() {
    return process.env['GOOGLE3_TSURGE'] === '1';
}

/** Options that are good defaults for Tsurge migrations. */
const defaultMigrationTsOptions = {
    // Avoid checking libraries to speed up migrations.
    skipLibCheck: true,
    skipDefaultLibCheck: true,
    noEmit: true,
    // Does not apply to g3 and externally is enforced when the app is built by the compiler.
    disableTypeScriptVersionCheck: true,
};
/**
 * Creates an instance of a TypeScript program for the given project.
 */
function createPlainTsProgram(tsHost, tsconfig, optionOverrides) {
    const program = ts.createProgram({
        rootNames: tsconfig.rootNames,
        options: {
            ...tsconfig.options,
            ...defaultMigrationTsOptions,
            ...optionOverrides,
        },
    });
    return {
        ngCompiler: null,
        program,
        userOptions: tsconfig.options,
        __programAbsoluteRootFileNames: tsconfig.rootNames,
        host: tsHost,
    };
}

/**
 * Parses the configuration of the given TypeScript project and creates
 * an instance of the Angular compiler for the project.
 */
function createNgtscProgram(tsHost, tsconfig, optionOverrides) {
    const ngtscProgram = new compilerCli.NgtscProgram(tsconfig.rootNames, {
        ...tsconfig.options,
        ...defaultMigrationTsOptions,
        ...optionOverrides,
    }, tsHost);
    // Expose an easy way to debug-print ng semantic diagnostics.
    if (process.env['DEBUG_NG_SEMANTIC_DIAGNOSTICS'] === '1') {
        console.error(ts.formatDiagnosticsWithColorAndContext(ngtscProgram.getNgSemanticDiagnostics(), tsHost));
    }
    return {
        ngCompiler: ngtscProgram.compiler,
        program: ngtscProgram.getTsProgram(),
        userOptions: tsconfig.options,
        __programAbsoluteRootFileNames: tsconfig.rootNames,
        host: tsHost,
    };
}

/** Code of the error raised by TypeScript when a tsconfig doesn't match any files. */
const NO_INPUTS_ERROR_CODE = 18003;
/** Parses the given tsconfig file, supporting Angular compiler options. */
function parseTsconfigOrDie(absoluteTsconfigPath, fs) {
    const tsconfig = compilerCli.readConfiguration(absoluteTsconfigPath, {}, fs);
    // Skip the "No inputs found..." error since we don't want to interrupt the migration if a
    // tsconfig doesn't match a file. This will result in an empty `Program` which is still valid.
    const errors = tsconfig.errors.filter((diag) => diag.code !== NO_INPUTS_ERROR_CODE);
    if (errors.length) {
        throw new Error(`Tsconfig could not be parsed or is invalid:\n\n` + `${errors.map((e) => e.messageText)}`);
    }
    return tsconfig;
}

// Note: Try to keep mostly in sync with
// //depot/google3/javascript/angular2/tools/ngc_wrapped/tsc_plugin.ts
// TODO: Consider moving this logic into the 1P launcher.
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
function fileNameToModuleNameFactory(rootDirs, workspaceName) {
    return (importedFilePath) => {
        let relativePath = '';
        for (const rootDir of rootDirs) {
            const rel = path__namespace.posix.relative(rootDir, importedFilePath);
            if (!rel.startsWith('.')) {
                relativePath = rel;
                break;
            }
        }
        if (relativePath) {
            return `${workspaceName}/${relativePath.replace(EXT, '')}`;
        }
        else {
            return importedFilePath.replace(EXT, '');
        }
    };
}

/** Creates the base program info for the given tsconfig path. */
function createBaseProgramInfo(absoluteTsconfigPath, fs, optionOverrides = {}) {
    // Make sure the FS becomes globally available. Some code paths
    // of the Angular compiler, or tsconfig parsing aren't leveraging
    // the specified file system.
    compilerCli.setFileSystem(fs);
    const tsconfig = parseTsconfigOrDie(absoluteTsconfigPath, fs);
    const tsHost = new compilerCli.NgtscCompilerHost(fs, tsconfig.options);
    // When enabled, use a plain TS program if we are sure it's not
    // an Angular project based on the `tsconfig.json`.
    if (google3UsePlainTsProgramIfNoKnownAngularOption() &&
        tsconfig.options['_useHostForImportGeneration'] === undefined) {
        return createPlainTsProgram(tsHost, tsconfig, optionOverrides);
    }
    // The Angular program may try to emit references during analysis or migration.
    // To replicate the Google3 import emission here, ensure the unified module resolution
    // can be enabled by the compiler.
    if (isGoogle3() && tsconfig.options.rootDirs) {
        tsHost.fileNameToModuleName = fileNameToModuleNameFactory(tsconfig.options.rootDirs, 
        /* workspaceName*/ 'google3');
    }
    return createNgtscProgram(tsHost, tsconfig, optionOverrides);
}
/**
 * Creates the {@link ProgramInfo} from the given base information.
 *
 * This function purely exists to support custom programs that are
 * intended to be injected into Tsurge migrations. e.g. for language
 * service refactorings.
 */
function getProgramInfoFromBaseInfo(baseInfo) {
    const fullProgramSourceFiles = [...baseInfo.program.getSourceFiles()];
    const sourceFiles = fullProgramSourceFiles.filter((f) => !f.isDeclarationFile &&
        // Note `isShim` will work for the initial program, but for TCB programs, the shims are no longer annotated.
        !migrations.isShim(f) &&
        !f.fileName.endsWith('.ngtypecheck.ts'));
    // Sort it by length in reverse order (longest first). This speeds up lookups,
    // since there's no need to keep going through the array once a match is found.
    const sortedRootDirs = migrations.getRootDirs(baseInfo.host, baseInfo.userOptions).sort((a, b) => b.length - a.length);
    // TODO: Consider also following TS's logic here, finding the common source root.
    // See: Program#getCommonSourceDirectory.
    const primaryRoot = compilerCli.absoluteFrom(baseInfo.userOptions.rootDir ?? sortedRootDirs.at(-1) ?? baseInfo.program.getCurrentDirectory());
    return {
        ...baseInfo,
        sourceFiles,
        fullProgramSourceFiles,
        sortedRootDirs,
        projectRoot: primaryRoot,
    };
}

/**
 * @private
 *
 * Base class for the possible Tsurge migration variants.
 *
 * For example, this class exposes methods to conveniently create
 * TypeScript programs, while also allowing migration authors to override.
 */
class TsurgeBaseMigration {
    /**
     * Creates the TypeScript program for a given compilation unit.
     *
     * By default:
     *  - In 3P: Ngtsc programs are being created.
     *  - In 1P: Ngtsc or TS programs are created based on the Blaze target.
     */
    createProgram(tsconfigAbsPath, fs, optionsOverride) {
        return getProgramInfoFromBaseInfo(createBaseProgramInfo(tsconfigAbsPath, fs, optionsOverride));
    }
}

/**
 * A simpler variant of a {@link TsurgeComplexMigration} that does not
 * fan-out into multiple workers per compilation unit to compute
 * the final migration replacements.
 *
 * This is faster and less resource intensive as workers and TS programs
 * are only ever created once.
 *
 * This is commonly the case when migrations are refactored to eagerly
 * compute replacements in the analyze stage, and then leverage the
 * global unit data to filter replacements that turned out to be "invalid".
 */
class TsurgeFunnelMigration extends TsurgeBaseMigration {
}
/**
 * Complex variant of a `Tsurge` migration.
 *
 * For example, every analyze worker may contribute to a list of TS
 * references that are later combined. The migrate phase can then compute actual
 * file updates for all individual compilation units, leveraging the global metadata
 * to e.g. see if there are any references from other compilation units that may be
 * problematic and prevent migration of a given file.
 */
class TsurgeComplexMigration extends TsurgeBaseMigration {
}

exports.MigrationStage = void 0;
(function (MigrationStage) {
    /** The migration is analyzing an entrypoint */
    MigrationStage[MigrationStage["Analysis"] = 0] = "Analysis";
    /** The migration is about to migrate an entrypoint */
    MigrationStage[MigrationStage["Migrate"] = 1] = "Migrate";
})(exports.MigrationStage || (exports.MigrationStage = {}));
/** Runs a Tsurge within an Angular Devkit context. */
async function runMigrationInDevkit(config) {
    const { buildPaths, testPaths } = await project_tsconfig_paths.getProjectTsConfigPaths(config.tree);
    if (!buildPaths.length && !testPaths.length) {
        throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run the migration.');
    }
    const tsconfigPaths = [...buildPaths, ...testPaths];
    const fs = new DevkitMigrationFilesystem(config.tree);
    compilerCli.setFileSystem(fs);
    const migration = config.getMigration(fs);
    const unitResults = [];
    const isFunnelMigration = migration instanceof TsurgeFunnelMigration;
    const compilationUnitAssignments = new Map();
    for (const tsconfigPath of tsconfigPaths) {
        config.beforeProgramCreation?.(tsconfigPath, exports.MigrationStage.Analysis);
        const info = migration.createProgram(tsconfigPath, fs);
        modifyProgramInfoToEnsureNonOverlappingFiles(tsconfigPath, info, compilationUnitAssignments);
        config.afterProgramCreation?.(info, fs, exports.MigrationStage.Analysis);
        config.beforeUnitAnalysis?.(tsconfigPath);
        unitResults.push(await migration.analyze(info));
    }
    config.afterAllAnalyzed?.();
    const combined = await synchronouslyCombineUnitData(migration, unitResults);
    if (combined === null) {
        config.afterAnalysisFailure?.();
        return;
    }
    const globalMeta = await migration.globalMeta(combined);
    let replacements;
    if (isFunnelMigration) {
        replacements = (await migration.migrate(globalMeta)).replacements;
    }
    else {
        replacements = [];
        for (const tsconfigPath of tsconfigPaths) {
            config.beforeProgramCreation?.(tsconfigPath, exports.MigrationStage.Migrate);
            const info = migration.createProgram(tsconfigPath, fs);
            modifyProgramInfoToEnsureNonOverlappingFiles(tsconfigPath, info, compilationUnitAssignments);
            config.afterProgramCreation?.(info, fs, exports.MigrationStage.Migrate);
            const result = await migration.migrate(globalMeta, info);
            replacements.push(...result.replacements);
        }
    }
    const replacementsPerFile = new Map();
    const changesPerFile = groupReplacementsByFile(replacements);
    for (const [file, changes] of changesPerFile) {
        if (!replacementsPerFile.has(file)) {
            replacementsPerFile.set(file, changes);
        }
    }
    for (const [file, changes] of replacementsPerFile) {
        const recorder = config.tree.beginUpdate(file);
        for (const c of changes) {
            recorder
                .remove(c.data.position, c.data.end - c.data.position)
                .insertRight(c.data.position, c.data.toInsert);
        }
        config.tree.commitUpdate(recorder);
    }
    config.whenDone?.(await migration.stats(globalMeta));
}
/**
 * Special logic for devkit migrations. In the Angular CLI, or in 3P precisely,
 * projects can have tsconfigs with overlapping source files. i.e. two tsconfigs
 * like e.g. build or test include the same `ts.SourceFile` (`.ts`). Migrations
 * should never have 2+ compilation units with overlapping source files as this
 * can result in duplicated replacements or analysis— hence we only ever assign a
 * source file to a compilation unit *once*.
 *
 * Note that this is fine as we expect Tsurge migrations to work together as
 * isolated compilation units— so it shouldn't matter if worst case a `.ts`
 * file ends up in the e.g. test program.
 */
function modifyProgramInfoToEnsureNonOverlappingFiles(tsconfigPath, info, compilationUnitAssignments) {
    const sourceFiles = [];
    for (const sf of info.sourceFiles) {
        const assignment = compilationUnitAssignments.get(sf.fileName);
        // File is already assigned to a different compilation unit.
        if (assignment !== undefined && assignment !== tsconfigPath) {
            continue;
        }
        compilationUnitAssignments.set(sf.fileName, tsconfigPath);
        sourceFiles.push(sf);
    }
    info.sourceFiles = sourceFiles;
}

/** A text replacement for the given file. */
class Replacement {
    projectFile;
    update;
    constructor(projectFile, update) {
        this.projectFile = projectFile;
        this.update = update;
    }
}
/** An isolated text update that may be applied to a file. */
class TextUpdate {
    data;
    constructor(data) {
        this.data = data;
    }
}

/** Confirms that the given data `T` is serializable. */
function confirmAsSerializable(data) {
    return data;
}

/**
 * Gets a project file instance for the given file.
 *
 * Use this helper for dealing with project paths throughout your
 * migration. The return type is serializable.
 *
 * See {@link ProjectFile}.
 */
function projectFile(file, { sortedRootDirs, projectRoot }) {
    const fs = compilerCli.getFileSystem();
    const filePath = fs.resolve(typeof file === 'string' ? file : file.fileName);
    // Sorted root directories are sorted longest to shortest. First match
    // is the appropriate root directory for ID computation.
    for (const rootDir of sortedRootDirs) {
        if (!isWithinBasePath(fs, rootDir, filePath)) {
            continue;
        }
        return {
            id: fs.relative(rootDir, filePath),
            rootRelativePath: fs.relative(projectRoot, filePath),
        };
    }
    // E.g. project directory may be `src/`, but files may be looked up
    // from `node_modules/`. This is fine, but in those cases, no root
    // directory matches.
    const rootRelativePath = fs.relative(projectRoot, filePath);
    return {
        id: rootRelativePath,
        rootRelativePath: rootRelativePath,
    };
}
/**
 * Whether `path` is a descendant of the `base`?
 * E.g. `a/b/c` is within `a/b` but not within `a/x`.
 */
function isWithinBasePath(fs, base, path) {
    return compilerCli.isLocalRelativePath(fs.relative(base, path));
}

exports.Replacement = Replacement;
exports.TextUpdate = TextUpdate;
exports.TsurgeComplexMigration = TsurgeComplexMigration;
exports.TsurgeFunnelMigration = TsurgeFunnelMigration;
exports.confirmAsSerializable = confirmAsSerializable;
exports.projectFile = projectFile;
exports.runMigrationInDevkit = runMigrationInDevkit;
