diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index c96e1238e06..0f81cf64216 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -106,7 +106,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/default-param-last`](./docs/rules/default-param-last.md) | Enforce default parameters to be last | | | | | [`@typescript-eslint/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | | | [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods | | | | -| [`@typescript-eslint/explicit-module-boundary-types`](./docs/rules/explicit-module-boundary-types.md) | Require explicit return types on exported functions' and classes' public class methods | | | | +| [`@typescript-eslint/explicit-module-boundary-types`](./docs/rules/explicit-module-boundary-types.md) | Require explicit return and argument types on exported functions' and classes' public class methods | | | | | [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | | | [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | | :wrench: | | | [`@typescript-eslint/member-delimiter-style`](./docs/rules/member-delimiter-style.md) | Require a specific member delimiter style for interfaces and type literals | :heavy_check_mark: | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md b/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md index c9722c68332..cc7d5f1cc03 100644 --- a/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md +++ b/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md @@ -1,6 +1,6 @@ -# Require explicit return type on exported functions' and classes' public class methods (explicit-module-boundary-types) +# Require explicit return and argument types on exported functions' and classes' public class methods (`explicit-module-boundary-types`) -Explicit types for function return values makes it clear to any calling code what is the module boundary's output. +Explicit types for function return values and arguments makes it clear to any calling code what is the module boundary's input and output. Consider using this rule in place of [`no-untyped-public-signature`](./no-untyped-public-signature.md) which has been deprecated. @@ -24,6 +24,9 @@ export default function() { // Should indicate that a string is returned export var arrowFn = () => 'test'; +// All arguments should be typed +export var arrowFn = (arg): string => `test ${arg}`; + export class Test { // Should indicate that no value is returned (void) method() { @@ -46,7 +49,7 @@ export var fn = function(): number { }; // A return value of type string -export var arrowFn = (): string => 'test'; +export var arrowFn = (arg: string): string => `test ${arg}`; // Class is not exported class Test { @@ -66,6 +69,8 @@ type Options = { allowTypedFunctionExpressions?: boolean; // if true, functions immediately returning another function expression will not be checked allowHigherOrderFunctions?: boolean; + // if true, body-less arrow functions are allowed to return an object as const + allowDirectConstAssertionInArrowFunctions?: boolean; // an array of function/method names that will not be checked allowedNames?: string[]; }; @@ -165,7 +170,26 @@ export function fn() { } ``` -### `allowedName` +### `allowDirectConstAssertionInArrowFunctions` + +Examples of additional **correct** code for this rule with `{ allowDirectConstAssertionInArrowFunctions: true }`: + +```ts +export const func = (value: number) => ({ type: 'X', value } as const); +``` + +Examples of additional **incorrect** code for this rule with `{ allowDirectConstAssertionInArrowFunctions: true }`: + +```ts +export const func = (value: number) => ({ type: 'X', value }); +export const foo = () => { + return { + bar: true, + } as const; +}; +``` + +### `allowedNames` You may pass function/method names you would like this rule to ignore, like so: diff --git a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts index 2a5badfea40..eeb9d6e39ae 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -13,7 +13,7 @@ type Options = [ allowedNames?: string[]; }, ]; -type MessageIds = 'missingReturnType'; +type MessageIds = 'missingReturnType' | 'missingArgType'; export default util.createRule({ name: 'explicit-module-boundary-types', @@ -21,12 +21,13 @@ export default util.createRule({ type: 'problem', docs: { description: - "Require explicit return types on exported functions' and classes' public class methods", + "Require explicit return and argument types on exported functions' and classes' public class methods", category: 'Stylistic Issues', recommended: false, }, messages: { missingReturnType: 'Missing return type on function.', + missingArgType: "Argument '{{name}}' should be typed.", }, schema: [ { @@ -324,6 +325,22 @@ export default util.createRule({ | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression, ): void { + const paramIdentifiers = node.params.filter( + param => param.type === AST_NODE_TYPES.Identifier, + ) as TSESTree.Identifier[]; + const untypedArgs = paramIdentifiers.filter(isArgumentUntyped); + if (untypedArgs.length) { + untypedArgs.forEach(untypedArg => + context.report({ + node, + messageId: 'missingArgType', + data: { + name: untypedArg.name, + }, + }), + ); + } + if (isAllowedName(node.parent)) { return; } @@ -431,6 +448,13 @@ export default util.createRule({ return false; } + function isArgumentUntyped(node: TSESTree.Identifier): boolean { + return ( + !node.typeAnnotation || + node.typeAnnotation.typeAnnotation.type === AST_NODE_TYPES.TSAnyKeyword + ); + } + return { ArrowFunctionExpression: checkFunctionExpressionReturnType, FunctionDeclaration: checkFunctionReturnType, diff --git a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts index b11720994ac..f67218588d7 100644 --- a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts @@ -62,7 +62,7 @@ export class Test { return 1; } set prop() {} - private method() { + private method(one) { return; } arrow = (): string => 'arrow'; @@ -145,7 +145,7 @@ export class App { filename: 'test.ts', code: ` export const myObj = { - set myProp(val) { + set myProp(val: number) { this.myProp = val; }, }; @@ -252,7 +252,7 @@ export const func4 = (value: number) => x as const; { filename: 'test.ts', code: ` -export const func1 = (value) => value; +export const func1 = (value: string) => value; export const func2 = (value: number) => ({ type: "X", value }); `, options: [ @@ -365,7 +365,7 @@ export class Test { method() { return; } - arrow = () => 'arrow'; + arrow = (arg) => 'arrow'; private method() { return; } @@ -386,12 +386,19 @@ export class Test { column: 3, endColumn: 11, }, + { + messageId: 'missingArgType', + line: 11, + endLine: 11, + column: 11, + endColumn: 27, + }, { messageId: 'missingReturnType', line: 11, endLine: 11, column: 11, - endColumn: 16, + endColumn: 19, }, ], }, @@ -765,5 +772,31 @@ export const func2 = (value: number) => value; }, ], }, + { + filename: 'test.ts', + code: 'export function fn(test): string { return "123" };', + errors: [ + { + messageId: 'missingArgType', + line: 1, + endLine: 1, + column: 8, + endColumn: 50, + }, + ], + }, + { + filename: 'test.ts', + code: 'export const fn = (one: number, two): string => "123";', + errors: [ + { + messageId: 'missingArgType', + line: 1, + endLine: 1, + column: 19, + endColumn: 54, + }, + ], + }, ], });