Skip to content

Commit

Permalink
feat(eslint-plugin): added new rule typedef (#581)
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Goldberg authored and JamesHenry committed Jul 24, 2019
1 parent 4893aec commit 35cc99b
Show file tree
Hide file tree
Showing 7 changed files with 883 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
| [`@typescript-eslint/strict-boolean-expressions`](./docs/rules/strict-boolean-expressions.md) | Restricts the types allowed in boolean expressions | | | :thought_balloon: |
| [`@typescript-eslint/triple-slash-reference`](./docs/rules/triple-slash-reference.md) | Sets preference level for triple slash directives versus ES6-style import declarations | | | |
| [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/typedef`](./docs/rules/typedef.md) | Requires type annotations to exist | | | |
| [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope | | | :thought_balloon: |
| [`@typescript-eslint/unified-signatures`](./docs/rules/unified-signatures.md) | Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter | | | |

Expand Down
3 changes: 2 additions & 1 deletion packages/eslint-plugin/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
| [`only-arrow-functions`] | 🔌 | [`prefer-arrow/prefer-arrow-functions`] |
| [`prefer-for-of`] || [`@typescript-eslint/prefer-for-of`] |
| [`promise-function-async`] || [`@typescript-eslint/promise-function-async`] |
| [`typedef`] | 🛑 | N/A |
| [`typedef-whitespace`] || [`@typescript-eslint/type-annotation-spacing`] |
| [`typedef`] || [`@typescript-eslint/typedef`] |
| [`unified-signatures`] || [`@typescript-eslint/unified-signatures`] |

<sup>[1]</sup> The ESLint rule only supports exact string matching, rather than regular expressions<br>
Expand Down Expand Up @@ -592,6 +592,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint-
[`@typescript-eslint/no-unnecessary-type-assertion`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md
[`@typescript-eslint/no-var-requires`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-var-requires.md
[`@typescript-eslint/type-annotation-spacing`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/type-annotation-spacing.md
[`@typescript-eslint/typedef`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/typedef.md
[`@typescript-eslint/unified-signatures`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/unified-signatures.md
[`@typescript-eslint/no-misused-new`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-misused-new.md
[`@typescript-eslint/no-object-literal-type-assertion`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-object-literal-type-assertion.md
Expand Down
264 changes: 264 additions & 0 deletions packages/eslint-plugin/docs/rules/typedef.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
# Require type annotations to exist (typedef)

TypeScript cannot always infer types for all places in code.
Some locations require type annotations for their types to be inferred.

```ts
class ContainsText {
// There must be a type annotation here to infer the type
delayedText: string;

// `typedef` requires a type annotation here to maintain consistency
immediateTextExplicit: string = 'text';

// This is still a string type because of its initial value
immediateTextImplicit = 'text';
}
```

> Note: requiring type annotations unnecessarily can be cumbersome to maintain and generally reduces code readability.
> TypeScript is often better at inferring types than easily written type annotations would allow.
> Instead of enabling `typedef`, it is generally recommended to use the `--noImplicitAny` and/or `--strictPropertyInitialization` compiler options to enforce type annotations only when useful.
## Rule Details

This rule can enforce type annotations in locations regardless of whether they're required.
This is typically used to maintain consistency for element types that sometimes require them.

> To enforce type definitions existing on call signatures as per TSLint's `arrow-call-signature` and `call-signature` options, use `explicit-function-return-type`.
## Options

This rule has an object option that may receive any of the following as booleans:

- `"arrayDestructuring"`
- `"arrowParameter"`: `true` by default
- `"memberVariableDeclaration"`: `true` by default
- `"objectDestructuring"`
- `"parameter"`: `true` by default
- `"propertyDeclaration"`: `true` by default
- `"variableDeclaration"`

For example, with the following configuration:

```json
{
"rules": {
"typedef": [
"error",
{
"arrowParameter": false,
"variableDeclaration": true
}
]
}
}
```

- Type annotations on arrow function parameters are not required
- Type annotations on variables are required
- Options otherwise adhere to the defaults

### arrayDestructuring

Whether to enforce type annotations on variables declared using array destructuring.

Examples of **incorrect** code with `{ "arrayDestructuring": true }`:

```ts
const [a] = [1];
const [b, c] = [1, 2];
```

Examples of **correct** code with `{ "arrayDestructuring": true }`:

```ts
const [a]: number[] = [1];
const [b]: [number] = [2];
const [c, d]: [boolean, string] = [true, 'text'];
```

### arrowParameter

Whether to enforce type annotations for parameters of arrow functions.

Examples of **incorrect** code with `{ "arrowParameter": true }`:

```ts
const logsSize = size => console.log(size);

['hello', 'world'].map(text => text.length);

const mapper = {
map: text => text + '...',
};
```

Examples of **correct** code with `{ "arrowParameter": true }`:

```ts
const logsSize = (size: number) => console.log(text);

['hello', 'world'].map((text: string) => text.length);

const mapper = {
map: (text: string) => text + '...',
};
```

### memberVariableDeclaration

Whether to enforce type annotations on member variables of classes.

Examples of **incorrect** code with `{ "memberVariableDeclaration": true }`:

```ts
class ContainsText {
delayedText;
immediateTextImplicit = 'text';
}
```

Examples of **correct** code with `{ "memberVariableDeclaration": true }`:

```ts
class ContainsText {
delayedText: string;
immediateTextImplicit: string = 'text';
}
```

### objectDestructuring

Whether to enforce type annotations on variables declared using object destructuring.

Examples of **incorrect** code with `{ "objectDestructuring": true }`:

```ts
const { length } = 'text';
const [b, c] = Math.random() ? [1, 2] : [3, 4];
```

Examples of **correct** code with `{ "objectDestructuring": true }`:

```ts
const { length }: { length: number } = 'text';
const [b, c]: [number, number] = Math.random() ? [1, 2] : [3, 4];
```

### parameter

Whether to enforce type annotations for parameters of functions and methods.

Examples of **incorrect** code with `{ "parameter": true }`:

```ts
function logsSize(size): void {
console.log(size);
}

const doublesSize = function(size): numeber {
return size * 2;
};

const divider = {
curriesSize(size): number {
return size;
},
dividesSize: function(size): number {
return size / 2;
},
};

class Logger {
log(text): boolean {
console.log('>', text);
return true;
}
}
```

Examples of **correct** code with `{ "parameter": true }`:

```ts
function logsSize(size: number): void {
console.log(size);
}

const doublesSize = function(size: number): numeber {
return size * 2;
};

const divider = {
curriesSize(size: number): number {
return size;
},
dividesSize: function(size: number): number {
return size / 2;
},
};

class Logger {
log(text: boolean): boolean {
console.log('>', text);
return true;
}
}
```

### propertyDeclaration

Whether to enforce type annotations for properties of interfaces and types.

Examples of **incorrect** code with `{ "propertyDeclaration": true }`:

```ts
type Members = {
member;
otherMember;
};
```

Examples of **correct** code with `{ "propertyDeclaration": true }`:

```ts
type Members = {
member: boolean;
otherMember: string;
};
```

### variableDeclaration

Whether to enforce type annotations for variable declarations, excluding array and object destructuring.

Examples of **incorrect** code with `{ "variableDeclaration": true }`:

```ts
const text = 'text';
let initialText = 'text';
let delayedText;
```

Examples of **correct** code with `{ "variableDeclaration": true }`:

```ts
const text: string = 'text';
let initialText: string = 'text';
let delayedText: string;
```

## When Not To Use It

If you are using stricter TypeScript compiler options, particularly `--noImplicitAny` and/or `--strictPropertyInitialization`, you likely don't need this rule.

In general, if you do not consider the cost of writing unnecessary type annotations reasonable, then do not use this rule.

## Further Reading

- [TypeScript Type System](https://basarat.gitbooks.io/typescript/docs/types/type-system.html)
- [Type Inference](https://www.typescriptlang.org/docs/handbook/type-inference.html)

## Compatibility

- TSLint: [typedef](https://palantir.github.io/tslint/rules/typedef)
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/configs/all.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"@typescript-eslint/strict-boolean-expressions": "error",
"@typescript-eslint/triple-slash-reference": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/typedef": "error",
"@typescript-eslint/unbound-method": "error",
"@typescript-eslint/unified-signatures": "error"
}
Expand Down
4 changes: 3 additions & 1 deletion packages/eslint-plugin/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import semi from './semi';
import strictBooleanExpressions from './strict-boolean-expressions';
import tripleSlashReference from './triple-slash-reference';
import typeAnnotationSpacing from './type-annotation-spacing';
import typedef from './typedef';
import unboundMethod from './unbound-method';
import unifiedSignatures from './unified-signatures';

Expand Down Expand Up @@ -114,10 +115,11 @@ export default {
'require-array-sort-compare': requireArraySortCompare,
'require-await': requireAwait,
'restrict-plus-operands': restrictPlusOperands,
semi: semi,
'strict-boolean-expressions': strictBooleanExpressions,
'triple-slash-reference': tripleSlashReference,
'type-annotation-spacing': typeAnnotationSpacing,
'unbound-method': unboundMethod,
'unified-signatures': unifiedSignatures,
semi: semi,
typedef: typedef,
};
Loading

0 comments on commit 35cc99b

Please sign in to comment.