"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getWrappingFixer = void 0;
const utils_1 = require("@typescript-eslint/utils");
/**
 * Wraps node with some code. Adds parenthesis as necessary.
 * @returns Fixer which adds the specified code and parens if necessary.
 */
function getWrappingFixer(params) {
    const { sourceCode, node, innerNode = node, wrap } = params;
    const innerNodes = Array.isArray(innerNode) ? innerNode : [innerNode];
    return (fixer) => {
        const innerCodes = innerNodes.map(innerNode => {
            let code = sourceCode.getText(innerNode);
            // check the inner expression's precedence
            if (!isStrongPrecedenceNode(innerNode)) {
                // the code we are adding might have stronger precedence than our wrapped node
                // let's wrap our node in parens in case it has a weaker precedence than the code we are wrapping it in
                code = `(${code})`;
            }
            return code;
        });
        // do the wrapping
        let code = wrap(...innerCodes);
        // check the outer expression's precedence
        if (isWeakPrecedenceParent(node)) {
            // we wrapped the node in some expression which very likely has a different precedence than original wrapped node
            // let's wrap the whole expression in parens just in case
            if (!utils_1.ASTUtils.isParenthesized(node, sourceCode)) {
                code = `(${code})`;
            }
        }
        // check if we need to insert semicolon
        if (/^[`([]/.exec(code) && isMissingSemicolonBefore(node, sourceCode)) {
            code = `;${code}`;
        }
        return fixer.replaceText(node, code);
    };
}
exports.getWrappingFixer = getWrappingFixer;
/**
 * Check if a node will always have the same precedence if it's parent changes.
 */
function isStrongPrecedenceNode(innerNode) {
    return (innerNode.type === utils_1.AST_NODE_TYPES.Literal ||
        innerNode.type === utils_1.AST_NODE_TYPES.Identifier ||
        innerNode.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
        innerNode.type === utils_1.AST_NODE_TYPES.ObjectExpression ||
        innerNode.type === utils_1.AST_NODE_TYPES.MemberExpression ||
        innerNode.type === utils_1.AST_NODE_TYPES.CallExpression ||
        innerNode.type === utils_1.AST_NODE_TYPES.NewExpression ||
        innerNode.type === utils_1.AST_NODE_TYPES.TaggedTemplateExpression);
}
/**
 * Check if a node's parent could have different precedence if the node changes.
 */
function isWeakPrecedenceParent(node) {
    const parent = node.parent;
    if (parent.type === utils_1.AST_NODE_TYPES.UpdateExpression ||
        parent.type === utils_1.AST_NODE_TYPES.UnaryExpression ||
        parent.type === utils_1.AST_NODE_TYPES.BinaryExpression ||
        parent.type === utils_1.AST_NODE_TYPES.LogicalExpression ||
        parent.type === utils_1.AST_NODE_TYPES.ConditionalExpression ||
        parent.type === utils_1.AST_NODE_TYPES.AwaitExpression) {
        return true;
    }
    if (parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
        parent.object === node) {
        return true;
    }
    if ((parent.type === utils_1.AST_NODE_TYPES.CallExpression ||
        parent.type === utils_1.AST_NODE_TYPES.NewExpression) &&
        parent.callee === node) {
        return true;
    }
    if (parent.type === utils_1.AST_NODE_TYPES.TaggedTemplateExpression &&
        parent.tag === node) {
        return true;
    }
    return false;
}
/**
 * Returns true if a node is at the beginning of expression statement and the statement above doesn't end with semicolon.
 * Doesn't check if the node begins with `(`, `[` or `` ` ``.
 */
function isMissingSemicolonBefore(node, sourceCode) {
    for (;;) {
        const parent = node.parent;
        if (parent.type === utils_1.AST_NODE_TYPES.ExpressionStatement) {
            const block = parent.parent;
            if (block.type === utils_1.AST_NODE_TYPES.Program ||
                block.type === utils_1.AST_NODE_TYPES.BlockStatement) {
                // parent is an expression statement in a block
                const statementIndex = block.body.indexOf(parent);
                const previousStatement = block.body[statementIndex - 1];
                if (statementIndex > 0 &&
                    sourceCode.getLastToken(previousStatement).value !== ';') {
                    return true;
                }
            }
        }
        if (!isLeftHandSide(node)) {
            return false;
        }
        node = parent;
    }
}
/**
 * Checks if a node is LHS of an operator.
 */
function isLeftHandSide(node) {
    const parent = node.parent;
    // a++
    if (parent.type === utils_1.AST_NODE_TYPES.UpdateExpression) {
        return true;
    }
    // a + b
    if ((parent.type === utils_1.AST_NODE_TYPES.BinaryExpression ||
        parent.type === utils_1.AST_NODE_TYPES.LogicalExpression ||
        parent.type === utils_1.AST_NODE_TYPES.AssignmentExpression) &&
        node === parent.left) {
        return true;
    }
    // a ? b : c
    if (parent.type === utils_1.AST_NODE_TYPES.ConditionalExpression &&
        node === parent.test) {
        return true;
    }
    // a(b)
    if (parent.type === utils_1.AST_NODE_TYPES.CallExpression && node === parent.callee) {
        return true;
    }
    // a`b`
    if (parent.type === utils_1.AST_NODE_TYPES.TaggedTemplateExpression &&
        node === parent.tag) {
        return true;
    }
    return false;
}
//# sourceMappingURL=getWrappingFixer.js.map