blob: ad102492dc3955d39cccca8c7efafadec6eeca8a [file] [log] [blame]
"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