"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isTypedFunctionExpression = exports.doesImmediatelyReturnFunctionExpression = exports.checkFunctionReturnType = exports.checkFunctionExpressionReturnType = void 0;
const experimental_utils_1 = require("@typescript-eslint/experimental-utils");
const astUtils_1 = require("./astUtils");
const nullThrows_1 = require("./nullThrows");
/**
 * Creates a report location for the given function.
 * The location only encompasses the "start" of the function, and not the body
 *
 * eg.
 * function foo(args) {}
 * ^^^^^^^^^^^^^^^^^^
 *
 * get y(args) {}
 * ^^^^^^^^^^^
 *
 * const x = (args) => {}
 *           ^^^^^^^^^
 */
function getReporLoc(node, sourceCode) {
    /**
     * Returns start column position
     * @param node
     */
    function getLocStart() {
        /* highlight method name */
        const parent = node.parent;
        if (parent &&
            (parent.type === experimental_utils_1.AST_NODE_TYPES.MethodDefinition ||
                (parent.type === experimental_utils_1.AST_NODE_TYPES.Property && parent.method))) {
            return parent.loc.start;
        }
        return node.loc.start;
    }
    /**
     * Returns end column position
     * @param node
     */
    function getLocEnd() {
        /* highlight `=>` */
        if (node.type === experimental_utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
            return sourceCode.getTokenBefore(node.body, token => token.type === experimental_utils_1.AST_TOKEN_TYPES.Punctuator && token.value === '=>').loc.end;
        }
        return sourceCode.getTokenBefore(node.body).loc.end;
    }
    return {
        start: getLocStart(),
        end: getLocEnd(),
    };
}
/**
 * Checks if a node is a variable declarator with a type annotation.
 * ```
 * const x: Foo = ...
 * ```
 */
function isVariableDeclaratorWithTypeAnnotation(node) {
    return (node.type === experimental_utils_1.AST_NODE_TYPES.VariableDeclarator && !!node.id.typeAnnotation);
}
/**
 * Checks if a node is a class property with a type annotation.
 * ```
 * public x: Foo = ...
 * ```
 */
function isClassPropertyWithTypeAnnotation(node) {
    return node.type === experimental_utils_1.AST_NODE_TYPES.ClassProperty && !!node.typeAnnotation;
}
/**
 * Checks if a node belongs to:
 * ```
 * new Foo(() => {})
 *         ^^^^^^^^
 * ```
 */
function isConstructorArgument(node) {
    return node.type === experimental_utils_1.AST_NODE_TYPES.NewExpression;
}
/**
 * Checks if a node belongs to:
 * ```
 * const x: Foo = { prop: () => {} }
 * const x = { prop: () => {} } as Foo
 * const x = <Foo>{ prop: () => {} }
 * ```
 */
function isPropertyOfObjectWithType(property) {
    if (!property || property.type !== experimental_utils_1.AST_NODE_TYPES.Property) {
        return false;
    }
    const objectExpr = property.parent; // this shouldn't happen, checking just in case
    /* istanbul ignore if */ if (!objectExpr ||
        objectExpr.type !== experimental_utils_1.AST_NODE_TYPES.ObjectExpression) {
        return false;
    }
    const parent = objectExpr.parent; // this shouldn't happen, checking just in case
    /* istanbul ignore if */ if (!parent) {
        return false;
    }
    return (astUtils_1.isTypeAssertion(parent) ||
        isClassPropertyWithTypeAnnotation(parent) ||
        isVariableDeclaratorWithTypeAnnotation(parent) ||
        isFunctionArgument(parent));
}
/**
 * Checks if a function belongs to:
 * ```
 * () => () => ...
 * () => function () { ... }
 * () => { return () => ... }
 * () => { return function () { ... } }
 * function fn() { return () => ... }
 * function fn() { return function() { ... } }
 * ```
 */
function doesImmediatelyReturnFunctionExpression({ body, }) {
    // Should always have a body; really checking just in case
    /* istanbul ignore if */ if (!body) {
        return false;
    }
    // Check if body is a block with a single statement
    if (body.type === experimental_utils_1.AST_NODE_TYPES.BlockStatement && body.body.length === 1) {
        const [statement] = body.body;
        // Check if that statement is a return statement with an argument
        if (statement.type === experimental_utils_1.AST_NODE_TYPES.ReturnStatement &&
            !!statement.argument) {
            // If so, check that returned argument as body
            body = statement.argument;
        }
    }
    // Check if the body being returned is a function expression
    return (body.type === experimental_utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
        body.type === experimental_utils_1.AST_NODE_TYPES.FunctionExpression);
}
exports.doesImmediatelyReturnFunctionExpression = doesImmediatelyReturnFunctionExpression;
/**
 * Checks if a node belongs to:
 * ```
 * foo(() => 1)
 * ```
 */
function isFunctionArgument(parent, callee) {
    return (parent.type === experimental_utils_1.AST_NODE_TYPES.CallExpression &&
        // make sure this isn't an IIFE
        parent.callee !== callee);
}
/**
 * Checks if a function belongs to:
 * ```
 * () => ({ action: 'xxx' } as const)
 * ```
 */
function returnsConstAssertionDirectly(node) {
    const { body } = node;
    if (astUtils_1.isTypeAssertion(body)) {
        const { typeAnnotation } = body;
        if (typeAnnotation.type === experimental_utils_1.AST_NODE_TYPES.TSTypeReference) {
            const { typeName } = typeAnnotation;
            if (typeName.type === experimental_utils_1.AST_NODE_TYPES.Identifier &&
                typeName.name === 'const') {
                return true;
            }
        }
    }
    return false;
}
/**
 * True when the provided function expression is typed.
 */
function isTypedFunctionExpression(node, options) {
    const parent = nullThrows_1.nullThrows(node.parent, nullThrows_1.NullThrowsReasons.MissingParent);
    if (!options.allowTypedFunctionExpressions) {
        return false;
    }
    return (astUtils_1.isTypeAssertion(parent) ||
        isVariableDeclaratorWithTypeAnnotation(parent) ||
        isClassPropertyWithTypeAnnotation(parent) ||
        isPropertyOfObjectWithType(parent) ||
        isFunctionArgument(parent, node) ||
        isConstructorArgument(parent));
}
exports.isTypedFunctionExpression = isTypedFunctionExpression;
/**
 * Check whether the function expression return type is either typed or valid
 * with the provided options.
 */
function isValidFunctionExpressionReturnType(node, options) {
    if (isTypedFunctionExpression(node, options)) {
        return true;
    }
    const parent = nullThrows_1.nullThrows(node.parent, nullThrows_1.NullThrowsReasons.MissingParent);
    if (options.allowExpressions &&
        parent.type !== experimental_utils_1.AST_NODE_TYPES.VariableDeclarator &&
        parent.type !== experimental_utils_1.AST_NODE_TYPES.MethodDefinition &&
        parent.type !== experimental_utils_1.AST_NODE_TYPES.ExportDefaultDeclaration &&
        parent.type !== experimental_utils_1.AST_NODE_TYPES.ClassProperty) {
        return true;
    }
    // https://github.com/typescript-eslint/typescript-eslint/issues/653
    if (options.allowDirectConstAssertionInArrowFunctions &&
        node.type === experimental_utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
        returnsConstAssertionDirectly(node)) {
        return true;
    }
    return false;
}
/**
 * Check that the function expression or declaration is valid.
 */
function isValidFunctionReturnType(node, options) {
    if (options.allowHigherOrderFunctions &&
        doesImmediatelyReturnFunctionExpression(node)) {
        return true;
    }
    if (node.returnType || astUtils_1.isConstructor(node.parent) || astUtils_1.isSetter(node.parent)) {
        return true;
    }
    return false;
}
/**
 * Checks if a function declaration/expression has a return type.
 */
function checkFunctionReturnType(node, options, sourceCode, report) {
    if (isValidFunctionReturnType(node, options)) {
        return;
    }
    report(getReporLoc(node, sourceCode));
}
exports.checkFunctionReturnType = checkFunctionReturnType;
/**
 * Checks if a function declaration/expression has a return type.
 */
function checkFunctionExpressionReturnType(node, options, sourceCode, report) {
    if (isValidFunctionExpressionReturnType(node, options)) {
        return;
    }
    checkFunctionReturnType(node, options, sourceCode, report);
}
exports.checkFunctionExpressionReturnType = checkFunctionExpressionReturnType;
//# sourceMappingURL=explicitReturnTypeUtils.js.map