Skip to content

Commit

Permalink
New: no-inferrable-types rule (typescript-eslint#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
43081j authored and JamesHenry committed Jan 18, 2019
1 parent 863baef commit 071bbb0
Show file tree
Hide file tree
Showing 4 changed files with 439 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/eslint-plugin-typescript/README.md
Expand Up @@ -63,6 +63,7 @@ Then configure the rules you want to use under the rules section.
* [`typescript/no-array-constructor`](./docs/rules/no-array-constructor.md) — Disallow generic `Array` constructors
* [`typescript/no-empty-interface`](./docs/rules/no-empty-interface.md) — Disallow the declaration of empty interfaces (`no-empty-interface` from TSLint)
* [`typescript/no-explicit-any`](./docs/rules/no-explicit-any.md) — Disallow usage of the `any` type (`no-any` from TSLint)
* [`typescript/no-inferrable-types`](./docs/rules/no-inferrable-types.md) — Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. (`no-inferrable-types` from TSLint)
* [`typescript/no-namespace`](./docs/rules/no-namespace.md) — Disallow the use of custom TypeScript modules and namespaces
* [`typescript/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) — Disallows non-null assertions using the `!` postfix operator (`no-non-null-assertion` from TSLint)
* [`typescript/no-parameter-properties`](./docs/rules/no-parameter-properties.md) — Disallow the use of parameter properties in class constructors. (`no-parameter-properties` from TSLint)
Expand Down
@@ -0,0 +1,86 @@
# Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. (no-inferrable-types)

Explicit types where they can be easily inferred may add unnecessary verbosity.

## Rule Details

This rule disallows explicit type declarations on parameters, variables
and properties where the type can be easily inferred from its value.

## Options

This rule has an options object:

```json
{
"ignoreProperties": false,
"ignoreParameters": false
}
```

### Default

When none of the options are truthy, the following patterns are valid:

```ts
const foo = 5;
const bar = true;
const baz = 'str';

class Foo {
prop = 5;
}

function fn(a = 5, b = true) {
}

function fn(a: number, b: boolean, c: string) {
}
```

The following are invalid:

```ts
const foo: number = 5;
const bar: boolean = true;
const baz: string = 'str';

class Foo {
prop: number = 5;
}

function fn(a: number = 5, b: boolean = true) {
}
```

### `ignoreProperties`

When set to true, the following pattern is considered valid:

```ts
class Foo {
prop: number = 5;
}
```

### `ignoreParameters`

When set to true, the following pattern is considered valid:

```ts
function foo(a: number = 5, b: boolean = true) {
// ...
}
```

## When Not To Use It

If you do not want to enforce inferred types.

## Further Reading

TypeScript [Inference](https://www.typescriptlang.org/docs/handbook/type-inference.html)

## Compatibility

TSLint: [no-inferrable-types](https://palantir.github.io/tslint/rules/no-inferrable-types/)
191 changes: 191 additions & 0 deletions packages/eslint-plugin-typescript/lib/rules/no-inferrable-types.js
@@ -0,0 +1,191 @@
/**
* @fileoverview Disallows explicit type declarations for inferrable types
* @author James Garbutt <https://github.com/43081j>
*/

"use strict";

const util = require("../util");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description:
"Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean.",
extraDescription: [util.tslintRule("no-inferrable-types")],
category: "TypeScript",
url:
"https://github.com/nzakas/eslint-plugin-typescript/blob/master/docs/rules/no-inferrable-types.md"
},
fixable: "code",
schema: [
{
type: "object",
properties: {
ignoreParameters: {
type: "boolean"
},
ignoreProperties: {
type: "boolean"
}
},
additionalProperties: false
}
]
},

create(context) {
const ignoreParameters = context.options[0]
? context.options[0].ignoreParameters
: false;
const ignoreProperties = context.options[0]
? context.options[0].ignoreProperties
: false;

/**
* Returns whether a node has an inferrable value or not
* @param {ASTNode} node the node to check
* @param {ASTNode} init the initializer
* @returns {boolean} whether the node has an inferrable type
*/
function isInferrable(node, init) {
if (node.type !== "TSTypeAnnotation" || !node.typeAnnotation) {
return false;
}

if (!init) {
return false;
}

const annotation = node.typeAnnotation;

if (annotation.type === "TSStringKeyword") {
return (
(init.type === "Literal" &&
typeof init.value === "string") ||
(init.type === "TemplateElement" &&
(!init.expressions || init.expressions.length === 0))
);
}

if (annotation.type === "TSBooleanKeyword") {
return init.type === "Literal";
}

if (annotation.type === "TSNumberKeyword") {
// Infinity is special
if (
(init.type === "UnaryExpression" &&
init.operator === "-" &&
init.argument.type === "Identifier" &&
init.argument.name === "Infinity") ||
(init.type === "Identifier" && init.name === "Infinity")
) {
return true;
}

return (
init.type === "Literal" && typeof init.value === "number"
);
}

return false;
}

/**
* Reports an inferrable type declaration, if any
* @param {ASTNode} node the node being visited
* @param {ASTNode} typeNode the type annotation node
* @param {ASTNode} initNode the initializer node
* @returns {void}
*/
function reportInferrableType(node, typeNode, initNode) {
if (!typeNode || !initNode || !typeNode.typeAnnotation) {
return;
}

if (!isInferrable(typeNode, initNode)) {
return;
}

const typeMap = {
TSBooleanKeyword: "boolean",
TSNumberKeyword: "number",
TSStringKeyword: "string"
};

const type = typeMap[typeNode.typeAnnotation.type];

context.report({
node,
message: `Type ${type} trivially inferred from a ${type} literal, remove type annotation`,
fix: fixer => fixer.remove(typeNode)
});
}

/**
* Visits variables
* @param {ASTNode} node the node to be visited
* @returns {void}
*/
function inferrableVariableVisitor(node) {
if (!node.id) {
return;
}
reportInferrableType(node, node.id.typeAnnotation, node.init);
}

/**
* Visits parameters
* @param {ASTNode} node the node to be visited
* @returns {void}
*/
function inferrableParameterVisitor(node) {
if (ignoreParameters || !node.params) {
return;
}
node.params
.filter(
param =>
param.type === "AssignmentPattern" &&
param.left &&
param.right
)
.forEach(param => {
reportInferrableType(
param,
param.left.typeAnnotation,
param.right
);
});
}

/**
* Visits properties
* @param {ASTNode} node the node to be visited
* @returns {void}
*/
function inferrablePropertyVisitor(node) {
// We ignore `readonly` because of Microsoft/TypeScript#14416
// Essentially a readonly property without a type
// will result in its value being the type, leading to
// compile errors if the type is stripped.
if (ignoreProperties || node.readonly) {
return;
}
reportInferrableType(node, node.typeAnnotation, node.value);
}

return {
VariableDeclarator: inferrableVariableVisitor,
FunctionExpression: inferrableParameterVisitor,
FunctionDeclaration: inferrableParameterVisitor,
ArrowFunctionExpression: inferrableParameterVisitor,
ClassProperty: inferrablePropertyVisitor
};
}
};

0 comments on commit 071bbb0

Please sign in to comment.