Skip to content

Commit

Permalink
feat(eslint-plugin): add explicit-module-boundary-types rule (#1020)
Browse files Browse the repository at this point in the history
Co-authored-by: Brad Zacher <brad.zacher@gmail.com>
  • Loading branch information
GuyPie and bradzacher committed Jan 15, 2020
1 parent 61eb434 commit bb0a846
Show file tree
Hide file tree
Showing 7 changed files with 1,480 additions and 2 deletions.
2 changes: 1 addition & 1 deletion packages/eslint-plugin/README.md
Expand Up @@ -106,6 +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 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: | |
Expand Down Expand Up @@ -138,7 +139,6 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int
| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary | | :wrench: | :thought_balloon: |
| [`@typescript-eslint/no-unnecessary-type-arguments`](./docs/rules/no-unnecessary-type-arguments.md) | Enforces that type arguments will not be used if not required | | :wrench: | :thought_balloon: |
| [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression | :heavy_check_mark: | :wrench: | :thought_balloon: |
| [`@typescript-eslint/no-untyped-public-signature`](./docs/rules/no-untyped-public-signature.md) | Disallow untyped public methods | | | |
| [`@typescript-eslint/no-unused-expressions`](./docs/rules/no-unused-expressions.md) | Disallow unused expressions | | | |
| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | |
| [`@typescript-eslint/no-unused-vars-experimental`](./docs/rules/no-unused-vars-experimental.md) | Disallow unused variables and arguments | | | :thought_balloon: |
Expand Down
208 changes: 208 additions & 0 deletions packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md
@@ -0,0 +1,208 @@
# Require explicit return and argument types on exported functions' and classes' public class methods (`explicit-module-boundary-types`)

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.

## Rule Details

This rule aims to ensure that the values returned from a module are of the expected type.

The following patterns are considered warnings:

```ts
// Should indicate that no value is returned (void)
export function test() {
return;
}

// Should indicate that a number is returned
export default function() {
return 1;
}

// 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() {
return;
}
}
```

The following patterns are not warnings:

```ts
// Function is not exported
function test() {
return;
}

// A return value of type number
export var fn = function(): number {
return 1;
};

// A return value of type string
export var arrowFn = (arg: string): string => `test ${arg}`;

// Class is not exported
class Test {
method() {
return;
}
}
```

## Options

The rule accepts an options object with the following properties:

```ts
type Options = {
// if true, type annotations are also allowed on the variable of a function expression rather than on the function directly
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[];
};

const defaults = {
allowTypedFunctionExpressions: true,
allowHigherOrderFunctions: true,
allowedNames: [],
};
```

### Configuring in a mixed JS/TS codebase

If you are working on a codebase within which you lint non-TypeScript code (i.e. `.js`/`.jsx`), you should ensure that you should use [ESLint `overrides`](https://eslint.org/docs/user-guide/configuring#disabling-rules-only-for-a-group-of-files) to only enable the rule on `.ts`/`.tsx` files. If you don't, then you will get unfixable lint errors reported within `.js`/`.jsx` files.

```jsonc
{
"rules": {
// disable the rule for all files
"@typescript-eslint/explicit-module-boundary-types": "off"
},
"overrides": [
{
// enable the rule specifically for TypeScript files
"files": ["*.ts", "*.tsx"],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": ["error"]
}
}
]
}
```

### `allowTypedFunctionExpressions`

Examples of **incorrect** code for this rule with `{ allowTypedFunctionExpressions: true }`:

```ts
export let arrowFn = () => 'test';

export let funcExpr = function() {
return 'test';
};

export let objectProp = {
foo: () => 1,
};
```

Examples of additional **correct** code for this rule with `{ allowTypedFunctionExpressions: true }`:

```ts
type FuncType = () => string;

export let arrowFn: FuncType = () => 'test';

export let funcExpr: FuncType = function() {
return 'test';
};

export let asTyped = (() => '') as () => string;
export let castTyped = <() => string>(() => '');

interface ObjectType {
foo(): number;
}
export let objectProp: ObjectType = {
foo: () => 1,
};
export let objectPropAs = {
foo: () => 1,
} as ObjectType;
export let objectPropCast = <ObjectType>{
foo: () => 1,
};
```

### `allowHigherOrderFunctions`

Examples of **incorrect** code for this rule with `{ allowHigherOrderFunctions: true }`:

```ts
export var arrowFn = () => () => {};

export function fn() {
return function() {};
}
```

Examples of **correct** code for this rule with `{ allowHigherOrderFunctions: true }`:

```ts
export var arrowFn = () => (): void => {};

export function fn() {
return function(): void {};
}
```

### `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:

```cjson
{
"@typescript-eslint/explicit-module-boundary-types": ["error", { "allowedName": ["ignoredFunctionName", "ignoredMethodName"] }]
}
```

## When Not To Use It

If you wish to make sure all functions have explicit return types, as opposed to only the module boundaries, you can use [explicit-function-return-type](https://github.com/eslint/eslint/blob/master/docs/rules/explicit-function-return-type.md)

## Further Reading

- TypeScript [Functions](https://www.typescriptlang.org/docs/handbook/functions.html#function-types)
2 changes: 1 addition & 1 deletion packages/eslint-plugin/src/configs/all.json
Expand Up @@ -14,6 +14,7 @@
"@typescript-eslint/default-param-last": "error",
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/explicit-member-accessibility": "error",
"@typescript-eslint/explicit-module-boundary-types": "error",
"func-call-spacing": "off",
"@typescript-eslint/func-call-spacing": "error",
"indent": "off",
Expand Down Expand Up @@ -53,7 +54,6 @@
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-arguments": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-untyped-public-signature": "error",
"no-unused-expressions": "off",
"@typescript-eslint/no-unused-expressions": "error",
"no-unused-vars": "off",
Expand Down

0 comments on commit bb0a846

Please sign in to comment.