diff --git a/docs/rules/no-return-void.md b/docs/rules/no-return-void.md index 65cac246b..cc74ba50b 100644 --- a/docs/rules/no-return-void.md +++ b/docs/rules/no-return-void.md @@ -39,6 +39,7 @@ This rule accepts an options object of the following type: { allowNull: boolean; allowUndefined: boolean; + ignoreImplicit: boolean; } ``` @@ -47,7 +48,8 @@ The default options: ```ts { allowNull: true, - allowUndefined: true + allowUndefined: true, + ignoreImplicit: true, } ``` @@ -58,3 +60,7 @@ If true allow returning null. ### allowUndefined If true allow returning undefined. + +### ignoreImplicit + +If true ignore functions that don't explicitly specify a return type. diff --git a/src/rules/no-return-void.ts b/src/rules/no-return-void.ts index eba520cf2..3c3b7297e 100644 --- a/src/rules/no-return-void.ts +++ b/src/rules/no-return-void.ts @@ -3,14 +3,19 @@ import { JSONSchema4 } from "json-schema"; import { createRule, + getTypeOfNode, RuleContext, RuleMetaData, RuleResult, } from "../util/rule"; import { + isFunctionLike, + isNullType, isTSNullKeyword, isTSUndefinedKeyword, isTSVoidKeyword, + isUndefinedType, + isVoidType, } from "../util/typeguard"; // The name of this rule. @@ -20,6 +25,7 @@ export const name = "no-return-void" as const; type Options = { readonly allowNull: boolean; readonly allowUndefined: boolean; + readonly ignoreImplicit: boolean; }; // The schema for the rule options. @@ -33,6 +39,9 @@ const schema: JSONSchema4 = [ allowUndefined: { type: "boolean", }, + ignoreImplicit: { + type: "boolean", + }, }, additionalProperties: false, }, @@ -42,6 +51,7 @@ const schema: JSONSchema4 = [ const defaultOptions: Options = { allowNull: true, allowUndefined: true, + ignoreImplicit: true, }; // The possible error messages. @@ -73,17 +83,40 @@ function checkFunction( context: RuleContext, options: Options ): RuleResult { + if (node.returnType === undefined) { + if (!options.ignoreImplicit && isFunctionLike(node)) { + const functionType = getTypeOfNode(node, context); + const returnType = functionType + ?.getCallSignatures()?.[0] + ?.getReturnType(); + + if ( + returnType !== undefined && + (isVoidType(returnType) || + (!options.allowNull && isNullType(returnType)) || + (!options.allowUndefined && isUndefinedType(returnType))) + ) { + return { + context, + descriptors: [{ node, messageId: "generic" }], + }; + } + } + } else if ( + isTSVoidKeyword(node.returnType.typeAnnotation) || + (!options.allowNull && isTSNullKeyword(node.returnType.typeAnnotation)) || + (!options.allowUndefined && + isTSUndefinedKeyword(node.returnType.typeAnnotation)) + ) { + return { + context, + descriptors: [{ node: node.returnType, messageId: "generic" }], + }; + } + return { context, - descriptors: - node.returnType !== undefined && - (isTSVoidKeyword(node.returnType.typeAnnotation) || - (!options.allowNull && - isTSNullKeyword(node.returnType.typeAnnotation)) || - (!options.allowUndefined && - isTSUndefinedKeyword(node.returnType.typeAnnotation))) - ? [{ node: node.returnType, messageId: "generic" }] - : [], + descriptors: [], }; } diff --git a/src/util/typeguard.ts b/src/util/typeguard.ts index ef314dc2b..5dc13db8c 100644 --- a/src/util/typeguard.ts +++ b/src/util/typeguard.ts @@ -403,3 +403,15 @@ export function isObjectConstructorType( export function isNeverType(type: Type): boolean { return ts !== undefined && type.flags === ts.TypeFlags.Never; } + +export function isVoidType(type: Type): boolean { + return ts !== undefined && type.flags === ts.TypeFlags.Void; +} + +export function isNullType(type: Type): boolean { + return ts !== undefined && type.flags === ts.TypeFlags.Null; +} + +export function isUndefinedType(type: Type): boolean { + return ts !== undefined && type.flags === ts.TypeFlags.Undefined; +} diff --git a/tests/rules/no-return-void.test.ts b/tests/rules/no-return-void.test.ts index a68358127..b8d70ddec 100644 --- a/tests/rules/no-return-void.test.ts +++ b/tests/rules/no-return-void.test.ts @@ -38,7 +38,11 @@ const valid: ReadonlyArray = [ function foo(bar) { console.log(bar); }`, - optionsSet: [[], [{ allowNull: false }], [{ allowUndefined: false }]], + optionsSet: [ + [{ ignoreImplicit: true }], + [{ ignoreImplicit: true, allowNull: false }], + [{ ignoreImplicit: true, allowUndefined: false }], + ], }, // Allow null. { @@ -116,7 +120,7 @@ const invalid: ReadonlyArray = [ function foo(bar: number): (baz: number) => void { return baz => { console.log(bar, baz); } }`, - optionsSet: [[]], + optionsSet: [[{ ignoreImplicit: true }]], errors: [ { messageId: "generic", @@ -126,6 +130,26 @@ const invalid: ReadonlyArray = [ }, ], }, + // Disallow implicit return type. + { + code: dedent` + function foo(bar) { + console.log(bar); + }`, + optionsSet: [ + [{ ignoreImplicit: false }], + [{ ignoreImplicit: false, allowNull: false }], + [{ ignoreImplicit: false, allowUndefined: false }], + ], + errors: [ + { + messageId: "generic", + type: "FunctionDeclaration", + line: 1, + column: 1, + }, + ], + }, ]; describeTsOnly("TypeScript", () => {