"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.serializeNodes = exports.flatten = exports.getSourceOffset = exports.getAttribute = exports.hasAttribute = exports.findElementNodes = exports.parseFile = exports.FileChange = exports.tryUninstallPackage = exports.tryInstallPackage = exports.getPackageVersion = exports.canResolvePackage = exports.getPackageManager = exports.supports = exports.escapeRegExp = exports.getProjects = exports.getWorkspace = exports.getWorkspacePath = exports.getProjectPaths = void 0;
const core_1 = require("@angular-devkit/core");
const path = require("path");
const child_process_1 = require("child_process");
const compiler_1 = require("@angular/compiler");
const tsUtils_1 = require("./tsUtils");
const configPaths = ['/.angular.json', '/angular.json'];
const getProjectPaths = (config) => {
    const sourceDirs = [];
    const projects = exports.getProjects(config);
    for (const proj of projects) {
        const sourcePath = path.join('/', proj.sourceRoot);
        sourceDirs.push(core_1.normalize(sourcePath));
    }
    return sourceDirs;
};
exports.getProjectPaths = getProjectPaths;
const getWorkspacePath = (host) => configPaths.find(x => host.exists(x));
exports.getWorkspacePath = getWorkspacePath;
const getWorkspace = (host) => {
    const configPath = exports.getWorkspacePath(host);
    if (configPath) {
        return JSON.parse(host.read(configPath).toString());
    }
    return null;
};
exports.getWorkspace = getWorkspace;
const getProjects = (config) => {
    const projects = [];
    for (const projName of Object.keys(config.projects)) {
        const proj = config.projects[projName];
        if ((proj.architect && proj.architect.e2e && !proj.architect.build)) {
            // skip old style e2e-only projects
            continue;
        }
        projects.push(proj);
    }
    return projects;
};
exports.getProjects = getProjects;
const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
exports.escapeRegExp = escapeRegExp;
const supports = (name) => {
    try {
        child_process_1.execSync(`${name} --version`, { stdio: 'ignore' });
        return true;
    }
    catch (_a) {
        return false;
    }
};
exports.supports = supports;
const getPackageManager = (host) => {
    const hasYarn = exports.supports('yarn');
    const hasYarnLock = host.exists('yarn.lock');
    if (hasYarn && hasYarnLock) {
        return 'yarn';
    }
    return 'npm';
};
exports.getPackageManager = getPackageManager;
const canResolvePackage = (pkg) => {
    try {
        // attempt resolve in child process to keep result out of package.json cache
        // otherwise resolve will not read the json again (after install) and won't load the main correctly
        // https://stackoverflow.com/questions/59865584/how-to-invalidate-cached-require-resolve-results
        child_process_1.execSync(`node -e "require.resolve('${pkg}');"`, { stdio: 'ignore' });
        return true;
    }
    catch (_a) {
        return false;
    }
};
exports.canResolvePackage = canResolvePackage;
const getPackageVersion = (pkg) => {
    var _a;
    let version = null;
    try {
        version = (_a = require(path.posix.join(pkg, 'package.json'))) === null || _a === void 0 ? void 0 : _a.version;
    }
    catch (_b) { }
    return version;
};
exports.getPackageVersion = getPackageVersion;
const tryInstallPackage = (context, packageManager, pkg) => {
    try {
        context.logger.debug(`Installing ${pkg} via ${packageManager}.`);
        switch (packageManager) {
            case 'yarn':
                child_process_1.execSync(`${packageManager} add ${pkg} --no-lock-file`, { stdio: 'ignore' });
                break;
            case 'npm':
                child_process_1.execSync(`${packageManager} i ${pkg} --no-save`, { stdio: 'ignore' });
                break;
        }
        context.logger.debug(`${pkg} installed successfully.`);
    }
    catch (e) {
        context.logger.warn(`Could not install ${pkg}.`, JSON.parse(e));
    }
};
exports.tryInstallPackage = tryInstallPackage;
const tryUninstallPackage = (context, packageManager, pkg) => {
    try {
        context.logger.debug(`Uninstalling ${pkg} via ${packageManager}`);
        switch (packageManager) {
            case 'yarn':
                child_process_1.execSync(`${packageManager} remove ${pkg}`, { stdio: 'ignore' });
                break;
            case 'npm':
                child_process_1.execSync(`${packageManager} uninstall ${pkg} --no-save`, { stdio: 'ignore' });
                break;
        }
        context.logger.debug(`${pkg} uninstalled successfully.`);
    }
    catch (e) {
        context.logger
            .warn(`Could not uninstall ${pkg}, you may want to uninstall it manually.`, JSON.parse(e));
    }
};
exports.tryUninstallPackage = tryUninstallPackage;
class FileChange {
    constructor(position = 0, text = '', replaceText = '', type = 'insert') {
        this.position = position;
        this.text = text;
        this.replaceText = replaceText;
        this.type = type;
    }
    apply(content) {
        if (this.type === 'insert') {
            return `${content.substring(0, this.position)}${this.text}${content.substring(this.position)}`;
        }
        return tsUtils_1.replaceMatch(content, this.replaceText, this.text, this.position);
    }
}
exports.FileChange = FileChange;
/**
 * Parses an Angular template file/content and returns an array of the root nodes of the file.
 *
 * @param host
 * @param filePath
 * @param encoding
 */
const parseFile = (host, filePath, encoding = 'utf8') => new compiler_1.HtmlParser().parse(host.read(filePath).toString(encoding), filePath).rootNodes;
exports.parseFile = parseFile;
const findElementNodes = (root, tag) => {
    const tags = new Set(Array.isArray(tag) ? tag : [tag]);
    return exports.flatten(Array.isArray(root) ? root : [root])
        .filter((node) => tags.has(node.name));
};
exports.findElementNodes = findElementNodes;
const hasAttribute = (root, attribute) => {
    const attrs = Array.isArray(attribute) ? attribute : [attribute];
    return !!root.attrs.find(a => attrs.includes(a.name));
};
exports.hasAttribute = hasAttribute;
const getAttribute = (root, attribute) => {
    const attrs = Array.isArray(attribute) ? attribute : [attribute];
    return root.attrs.filter(a => attrs.includes(a.name));
};
exports.getAttribute = getAttribute;
const getSourceOffset = (element) => {
    const { startSourceSpan, endSourceSpan } = element;
    return {
        startTag: { start: startSourceSpan.start.offset, end: startSourceSpan.end.offset },
        // V.S. May 11th, 2021: Tag could be self-closing
        endTag: { start: endSourceSpan === null || endSourceSpan === void 0 ? void 0 : endSourceSpan.start.offset, end: endSourceSpan === null || endSourceSpan === void 0 ? void 0 : endSourceSpan.end.offset },
        file: {
            content: startSourceSpan.start.file.content,
            url: startSourceSpan.start.file.url
        },
        node: element
    };
};
exports.getSourceOffset = getSourceOffset;
const isElement = (node) => node.children !== undefined;
/**
 * Given an array of `Node` objects, flattens the ast tree to a single array.
 * De facto only `Element` type objects have children.
 *
 * @param list
 */
const flatten = (list) => {
    let r = [];
    for (const node of list) {
        r.push(node);
        if (isElement(node)) {
            r = r.concat(exports.flatten(node.children));
        }
    }
    return r;
};
exports.flatten = flatten;
/**
 * https://github.com/angular/angular/blob/master/packages/compiler/test/ml_parser/util/util.ts
 *
 * May be useful for validating the output of our own migrations,
 */
class SerializerVisitor {
    visitElement(element, context) {
        if (compiler_1.getHtmlTagDefinition(element.name).isVoid) {
            return `<${element.name}${this._visitAll(element.attrs, ' ')}/>`;
        }
        return `<${element.name}${this._visitAll(element.attrs, ' ')}>${this._visitAll(element.children)}</${element.name}>`;
    }
    visitAttribute(attribute, context) {
        return attribute.value === '' ? `${attribute.name}` : `${attribute.name}="${attribute.value}"`;
    }
    visitText(text, context) {
        return text.value;
    }
    visitComment(comment, context) {
        return `<!--${comment.value}-->`;
    }
    visitExpansion(expansion, context) {
        return `{${expansion.switchValue}, ${expansion.type},${this._visitAll(expansion.cases)}}`;
    }
    visitExpansionCase(expansionCase, context) {
        return ` ${expansionCase.value} {${this._visitAll(expansionCase.expression)}}`;
    }
    _visitAll(nodes, join = '') {
        if (nodes.length === 0) {
            return '';
        }
        return join + nodes.map(a => a.visit(this, null)).join(join);
    }
}
const serializeNodes = (nodes) => nodes.map(node => node.visit(new SerializerVisitor(), null));
exports.serializeNodes = serializeNodes;
