Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin): add explicit-module-boundary-types rule #1020

Merged
merged 4 commits into from Jan 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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