forked from typescript-eslint/typescript-eslint
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New: no-inferrable-types rule (typescript-eslint#124)
- Loading branch information
1 parent
863baef
commit 071bbb0
Showing
4 changed files
with
439 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
packages/eslint-plugin-typescript/docs/rules/no-inferrable-types.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
191
packages/eslint-plugin-typescript/lib/rules/no-inferrable-types.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
}; | ||
} | ||
}; |
Oops, something went wrong.