Skip to content

Commit

Permalink
Merge branch 'main' into issue-4428
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaStevens committed Jan 17, 2022
2 parents 18e4fb9 + d053cde commit dc21aa0
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 66 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -83,7 +83,7 @@
"cross-env": "^7.0.3",
"cspell": "^5.12.3",
"cz-conventional-changelog": "^3.3.0",
"downlevel-dts": "^0.7.0",
"downlevel-dts": "^0.8.0",
"enhanced-resolve": "^5.8.3",
"eslint": "^8.1.0",
"eslint-plugin-eslint-comments": "^3.2.0",
Expand Down
17 changes: 15 additions & 2 deletions packages/eslint-plugin/src/rules/explicit-function-return-type.ts
Expand Up @@ -2,7 +2,8 @@ import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';
import * as util from '../util';
import {
checkFunctionReturnType,
checkFunctionExpressionReturnType,
isValidFunctionExpressionReturnType,
ancestorHasReturnType,
} from '../util/explicitReturnTypeUtils';

type Options = [
Expand Down Expand Up @@ -78,7 +79,15 @@ export default util.createRule<Options, MessageIds>({
return;
}

checkFunctionExpressionReturnType(node, options, sourceCode, loc =>
if (
options.allowTypedFunctionExpressions &&
(isValidFunctionExpressionReturnType(node, options) ||
ancestorHasReturnType(node))
) {
return;
}

checkFunctionReturnType(node, options, sourceCode, loc =>
context.report({
node,
loc,
Expand All @@ -87,6 +96,10 @@ export default util.createRule<Options, MessageIds>({
);
},
FunctionDeclaration(node): void {
if (options.allowTypedFunctionExpressions && node.returnType) {
return;
}

checkFunctionReturnType(node, options, sourceCode, loc =>
context.report({
node,
Expand Down
Expand Up @@ -8,6 +8,7 @@ import {
FunctionExpression,
FunctionNode,
isTypedFunctionExpression,
ancestorHasReturnType,
} from '../util/explicitReturnTypeUtils';

type Options = [
Expand Down Expand Up @@ -387,55 +388,6 @@ export default util.createRule<Options, MessageIds>({
}
}

/**
* Check whether any ancestor of the provided function has a valid return type.
* This function assumes that the function either:
* - belongs to an exported function chain validated by isExportedHigherOrderFunction
* - is directly exported itself
*/
function ancestorHasReturnType(node: FunctionNode): boolean {
let ancestor = node.parent;

if (ancestor?.type === AST_NODE_TYPES.Property) {
ancestor = ancestor.value;
}

// if the ancestor is not a return, then this function was not returned at all, so we can exit early
const isReturnStatement =
ancestor?.type === AST_NODE_TYPES.ReturnStatement;
const isBodylessArrow =
ancestor?.type === AST_NODE_TYPES.ArrowFunctionExpression &&
ancestor.body.type !== AST_NODE_TYPES.BlockStatement;
if (!isReturnStatement && !isBodylessArrow) {
return false;
}

while (ancestor) {
switch (ancestor.type) {
case AST_NODE_TYPES.ArrowFunctionExpression:
case AST_NODE_TYPES.FunctionExpression:
case AST_NODE_TYPES.FunctionDeclaration:
if (ancestor.returnType) {
return true;
}
// assume
break;

// const x: Foo = () => {};
// Assume that a typed variable types the function expression
case AST_NODE_TYPES.VariableDeclarator:
if (ancestor.id.typeAnnotation) {
return true;
}
break;
}

ancestor = ancestor.parent;
}

return false;
}

function checkEmptyBodyFunctionExpression(
node: TSESTree.TSEmptyBodyFunctionExpression,
): void {
Expand Down
46 changes: 46 additions & 0 deletions packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts
Expand Up @@ -293,11 +293,57 @@ function checkFunctionExpressionReturnType(
checkFunctionReturnType(node, options, sourceCode, report);
}

/**
* Check whether any ancestor of the provided function has a valid return type.
*/
function ancestorHasReturnType(node: FunctionNode): boolean {
let ancestor = node.parent;

if (ancestor?.type === AST_NODE_TYPES.Property) {
ancestor = ancestor.value;
}

// if the ancestor is not a return, then this function was not returned at all, so we can exit early
const isReturnStatement = ancestor?.type === AST_NODE_TYPES.ReturnStatement;
const isBodylessArrow =
ancestor?.type === AST_NODE_TYPES.ArrowFunctionExpression &&
ancestor.body.type !== AST_NODE_TYPES.BlockStatement;
if (!isReturnStatement && !isBodylessArrow) {
return false;
}

while (ancestor) {
switch (ancestor.type) {
case AST_NODE_TYPES.ArrowFunctionExpression:
case AST_NODE_TYPES.FunctionExpression:
case AST_NODE_TYPES.FunctionDeclaration:
if (ancestor.returnType) {
return true;
}
break;

// const x: Foo = () => {};
// Assume that a typed variable types the function expression
case AST_NODE_TYPES.VariableDeclarator:
if (ancestor.id.typeAnnotation) {
return true;
}
break;
}

ancestor = ancestor.parent;
}

return false;
}

export {
checkFunctionExpressionReturnType,
checkFunctionReturnType,
doesImmediatelyReturnFunctionExpression,
FunctionExpression,
FunctionNode,
isTypedFunctionExpression,
isValidFunctionExpressionReturnType,
ancestorHasReturnType,
};
Expand Up @@ -400,6 +400,68 @@ new Foo(1, () => {});
code: 'const log = (message: string) => void console.log(message);',
options: [{ allowConciseArrowFunctionExpressionsStartingWithVoid: true }],
},
{
filename: 'test.ts',
code: `
type HigherOrderType = () => (arg1: string) => (arg2: number) => string;
const x: HigherOrderType = () => arg1 => arg2 => 'foo';
`,
options: [
{
allowTypedFunctionExpressions: true,
allowHigherOrderFunctions: true,
},
],
},
{
filename: 'test.ts',
code: `
type HigherOrderType = () => (arg1: string) => (arg2: number) => string;
const x: HigherOrderType = () => arg1 => arg2 => 'foo';
`,
options: [
{
allowTypedFunctionExpressions: true,
allowHigherOrderFunctions: false,
},
],
},
{
filename: 'test.ts',
code: `
interface Foo {
foo: string;
arrowFn: () => string;
}
function foo(): Foo {
return {
foo: 'foo',
arrowFn: () => 'test',
};
}
`,
options: [
{
allowTypedFunctionExpressions: true,
allowHigherOrderFunctions: true,
},
],
},
{
filename: 'test.ts',
code: `
type Foo = (arg1: string) => string;
type Bar<T> = (arg2: string) => T;
const x: Bar<Foo> = arg1 => arg2 => arg1 + arg2;
`,
options: [
{
allowTypedFunctionExpressions: true,
allowHigherOrderFunctions: true,
},
],
},
],
invalid: [
{
Expand Down Expand Up @@ -1027,6 +1089,64 @@ foo({
{
filename: 'test.ts',
code: `
type HigherOrderType = () => (arg1: string) => (arg2: number) => string;
const x: HigherOrderType = () => arg1 => arg2 => 'foo';
`,
options: [
{
allowTypedFunctionExpressions: false,
allowHigherOrderFunctions: true,
},
],
errors: [
{
messageId: 'missingReturnType',
line: 3,
endLine: 3,
column: 42,
endColumn: 49,
},
],
},
{
filename: 'test.ts',
code: `
type HigherOrderType = () => (arg1: string) => (arg2: number) => string;
const x: HigherOrderType = () => arg1 => arg2 => 'foo';
`,
options: [
{
allowTypedFunctionExpressions: false,
allowHigherOrderFunctions: false,
},
],
errors: [
{
messageId: 'missingReturnType',
line: 3,
endLine: 3,
column: 28,
endColumn: 33,
},
{
messageId: 'missingReturnType',
line: 3,
endLine: 3,
column: 34,
endColumn: 41,
},
{
messageId: 'missingReturnType',
line: 3,
endLine: 3,
column: 42,
endColumn: 49,
},
],
},
{
filename: 'test.ts',
code: `
const func = (value: number) => ({ type: 'X', value } as any);
const func = (value: number) => ({ type: 'X', value } as Action);
`,
Expand Down
2 changes: 1 addition & 1 deletion packages/type-utils/tests/isTypeReadonly.test.ts
@@ -1,5 +1,5 @@
import * as ts from 'typescript';
import { TSESTree } from '@typescript-eslint/experimental-utils';
import { TSESTree } from '@typescript-eslint/utils';
import { parseForESLint } from '@typescript-eslint/parser';
import {
isTypeReadonly,
Expand Down
26 changes: 13 additions & 13 deletions yarn.lock
Expand Up @@ -4006,9 +4006,9 @@
integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==

"@types/prettier@*", "@types/prettier@^2.1.5", "@types/prettier@^2.4.2":
version "2.4.2"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.2.tgz#4c62fae93eb479660c3bd93f9d24d561597a8281"
integrity sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA==
version "2.4.3"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.3.tgz#a3c65525b91fca7da00ab1a3ac2b5a2a4afbffbf"
integrity sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==

"@types/prop-types@*":
version "15.7.4"
Expand Down Expand Up @@ -6588,10 +6588,10 @@ dotenv@~10.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==

downlevel-dts@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/downlevel-dts/-/downlevel-dts-0.7.0.tgz#155c24310dad8a4820ad077e64b15a8c461aa932"
integrity sha512-tcjGqElN0/oad/LJBlaxmZ3zOYEQOBbGuirKzif8s1jeRiWBdNX9H6OBFlRjOQkosXgV+qSjs4osuGCivyZ4Jw==
downlevel-dts@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/downlevel-dts/-/downlevel-dts-0.8.0.tgz#bed86768357824b1a04f60df79d8b0867b83a382"
integrity sha512-wBy+Q0Ya/1XRz9MMaj3BXH95E8aSckY3lppmUnf8Qv7dUg0wbWm3szDiVL4PdAvwcS7JbBBDPhCXeAGNT3ttFQ==
dependencies:
semver "^7.3.2"
shelljs "^0.8.3"
Expand Down Expand Up @@ -6967,9 +6967,9 @@ eslint-visitor-keys@^2.0.0:
integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==

eslint-visitor-keys@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2"
integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==
version "3.2.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1"
integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==

eslint@*, eslint@^8.1.0:
version "8.1.0"
Expand Down Expand Up @@ -13136,9 +13136,9 @@ rimraf@^2.6.3:
glob "^7.1.3"

rollup@^2.59.0:
version "2.63.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.63.0.tgz#fe2f7fec2133f3fab9e022b9ac245628d817c6bb"
integrity sha512-nps0idjmD+NXl6OREfyYXMn/dar3WGcyKn+KBzPdaLecub3x/LrId0wUcthcr8oZUAcZAR8NKcfGGFlNgGL1kQ==
version "2.64.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.64.0.tgz#f0f59774e21fbb56de438a37d06a2189632b207a"
integrity sha512-+c+lbw1lexBKSMb1yxGDVfJ+vchJH3qLbmavR+awDinTDA2C5Ug9u7lkOzj62SCu0PKUExsW36tpgW7Fmpn3yQ==
optionalDependencies:
fsevents "~2.3.2"

Expand Down

0 comments on commit dc21aa0

Please sign in to comment.