diff --git a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md index 7e3093ebfaa0..f12cb8a4327d 100644 --- a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md +++ b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md @@ -69,6 +69,8 @@ type Options = { allowTypedFunctionExpressions?: boolean; // if true, functions immediately returning another function expression will not be checked allowHigherOrderFunctions?: boolean; + // if true, functions not directly exported will not be checked + ignoreUnexportedFunctions?: boolean; }; const defaults = { @@ -198,6 +200,28 @@ function fn() { } ``` +### ignoreUnexportedFunctions + +Examples of **incorrect** code for this rule with `{ ignoreUnexportedFunctions: true }`: + +```ts +export default () => () => {}; + +export const arrowFn = () => {}; + +export function fn() { + return function() {}; +} +``` + +Examples of **correct** code for this rule with `{ ignoreUnexportedFunctions: true }`: + +```ts +function viaDoubleVariableReference() {} +const variableRefOne = viaDoubleVariableReference; +export const variableRefTwo = variableRefOne; +``` + ## When Not To Use It If you don't wish to prevent calling code from using function return values in unexpected ways, then diff --git a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts index d7e5fe18b7b6..92e1b2cf8e09 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -11,6 +11,7 @@ type Options = [ allowTypedFunctionExpressions?: boolean; allowHigherOrderFunctions?: boolean; allowDirectConstAssertionInArrowFunctions?: boolean; + ignoreUnexportedFunctions?: boolean; }, ]; type MessageIds = 'missingReturnType'; @@ -44,6 +45,9 @@ export default util.createRule({ allowDirectConstAssertionInArrowFunctions: { type: 'boolean', }, + ignoreUnexportedFunctions: { + type: 'boolean', + }, }, additionalProperties: false, }, @@ -55,6 +59,7 @@ export default util.createRule({ allowTypedFunctionExpressions: true, allowHigherOrderFunctions: true, allowDirectConstAssertionInArrowFunctions: true, + ignoreUnexportedFunctions: false, }, ], create(context, [options]) { @@ -206,6 +211,22 @@ export default util.createRule({ ); } + function isUnexported(node: TSESTree.Node | undefined): boolean { + while (node) { + if ( + node.type === AST_NODE_TYPES.ExportDefaultDeclaration || + node.type === AST_NODE_TYPES.ExportNamedDeclaration || + node.type === AST_NODE_TYPES.ExportSpecifier + ) { + return false; + } + + node = node.parent; + } + + return true; + } + /** * Checks if a function belongs to: * `() => () => ...` @@ -305,6 +326,10 @@ export default util.createRule({ return; } + if (options.ignoreUnexportedFunctions && isUnexported(node.parent)) { + return; + } + if ( node.returnType || isConstructor(node.parent) || diff --git a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts index 3552c718d65a..cea7c81a5a54 100644 --- a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts @@ -334,6 +334,82 @@ new Foo(1, () => {}); }, ], }, + { + filename: 'test.ts', + code: ` +function test( + a: number, + b: number, +) { + return; +} + `, + options: [ + { + ignoreUnexportedFunctions: true, + }, + ], + }, + { + filename: 'test.ts', + code: ` +export var arrowFn = (): string => 'test'; +var fn = function() { + return 1; +}; + `, + options: [ + { + ignoreUnexportedFunctions: true, + }, + ], + }, + { + filename: 'test.ts', + code: ` +class Test { + constructor() {} + get prop() { + return 1; + } + set prop() {} + method() { + return; + } + arrow = () => 'arrow'; + private method() { + return; + } +} + `, + options: [ + { + ignoreUnexportedFunctions: true, + }, + ], + }, + { + filename: 'test.ts', + code: ` +function viaDoubleVariableReference() {} +const variableRefOne = viaDoubleVariableReference; +export const variableRefTwo = variableRefOne; + `, + options: [ + { + ignoreUnexportedFunctions: true, + }, + ], + }, + { + filename: 'test.ts', + code: `export default (): void => {}`, + options: [ + { + ignoreUnexportedFunctions: true, + }, + ], + }, ], invalid: [ { @@ -980,5 +1056,99 @@ const func = (value: number) => ({ type: "X", value } as const); }, ], }, + { + filename: 'test.ts', + code: ` +export function test( + a: number, + b: number, +) { + return; +} + `, + options: [ + { + ignoreUnexportedFunctions: true, + }, + ], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + endLine: 5, + column: 8, + endColumn: 2, + }, + ], + }, + { + filename: 'test.ts', + code: ` +export var fn = function() { + return 1; +}; + `, + options: [ + { + ignoreUnexportedFunctions: false, + }, + ], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + endLine: 2, + column: 17, + endColumn: 27, + }, + ], + }, + { + filename: 'test.ts', + code: ` +export class Test { + constructor() {} + get prop() { + return 1; + } + set prop() {} + method() { + return; + } + arrow = (): string => 'arrow'; + private method() { + return; + } +} + `, + options: [ + { + ignoreUnexportedFunctions: false, + }, + ], + errors: [ + { + messageId: 'missingReturnType', + line: 4, + endLine: 4, + column: 3, + endColumn: 13, + }, + { + messageId: 'missingReturnType', + line: 8, + endLine: 8, + column: 3, + endColumn: 11, + }, + { + messageId: 'missingReturnType', + line: 12, + endLine: 12, + column: 3, + endColumn: 19, + }, + ], + }, ], });