8000 feat(eslint-plugin): no-inferrable-types: Support more primitives (#442) · andersk/typescript-eslint@4e193ca · GitHub
[go: up one dir, main page]

Skip to content

Commit 4e193ca

Browse files
authored
feat(eslint-plugin): no-inferrable-types: Support more primitives (typescript-eslint#442)
1 parent 4cd5590 commit 4e193ca

File tree

4 files changed

+281
-129
lines changed

4 files changed

+281
-129
lines changed

packages/eslint-plugin/docs/rules/no-inferrable-types.md

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,59 @@ and properties where the type can be easily inferred from its value.
99

1010
## Options
1111

12-
This rule has an options object:
12+
This rule accepts the following options:
1313

14-
```json
15-
{
16-
"ignoreProperties": false,
17-
"ignoreParameters": false
14+
```ts
15+
interface Options {
16+
ignoreParameters?: boolean;
17+
ignoreProperties?: boolean;
1818
}
1919
```
2020

2121
### Default
2222

23-
When none of the options are truthy, the following patterns are valid:
23+
The default options are:
24+
25+
```JSON
26+
{
27+
"ignoreParameters": true,
28+
"ignoreProperties": true,
29+
}
30+
```
31+
32+
With these options, the following patterns are valid:
2433

2534
```ts
26-
const foo = 5;
27-
const bar = true;
28-
const baz = 'str';
35+
const a = 10n;
36+
const a = -10n;
37+
const a = BigInt(10);
38+
const a = -BigInt(10);
39+
const a = false;
40+
const a = true;
41+
const a = Boolean(null);
42+
const a = !0;
43+
const a = 10;
44+
const a = +10;
45+
const a = -10;
46+
const a = Number('1');
47+
const a = +Number('1');
48+
const a = -Number('1');
49+
const a = Infinity;
50+
const a = +Infinity;
51+
const a = -Infinity;
52+
const a = NaN;
53+
const a = +NaN;
5 57AE 4+
const a = -NaN;
55+
const a = null;
56+
const a = /a/;
57+
const a = RegExp('a');
58+
const a = new RegExp('a');
59+
const a = 'str';
60+
const a = `str`;
61+
const a = String(1);
62+
const a = Symbol('a');
63+
const a = undefined;
64+
const a = void someValue;
2965

3066
class Foo {
3167
prop = 5;
@@ -39,9 +75,36 @@ function fn(a: number, b: boolean, c: string) {}
3975
The following are invalid:
4076

4177
```ts
42-
const foo: number = 5;
43-
const bar: boolean = true;
44-
const baz: string = 'str';
78+
const a: bigint = 10n;
79+
const a: bigint = -10n;
80+
const a: bigint = BigInt(10);
81+
const a: bigint = -BigInt(10);
82+
const a: boolean = false;
83+
const a: boolean = true;
84+
const a: boolean = Boolean(null);
85+
const a: boolean = !0;
86+
const a: number = 10;
87+
const a: number = +10;
88+
const a: number = -10;
89+
const a: number = Number('1');
90+
const a: number = +Number('1');
91+
const a: number = -Number('1');
92+
const a: number = Infinity;
93+
const a: number = +Infinity;
94+
const a: number = -Infinity;
95+
const a: number = NaN;
96+
const a: number = +NaN;
97+
const a: number = -NaN;
98+
const a: null = null;
99+
const a: RegExp = /a/;
100+
const a: RegExp = RegExp('a');
101+
const a: RegExp = new RegExp('a');
102+
const a: string = 'str';
103+
const a: string = `str`;
104+
const a: string = String(1);
105+
const a: symbol = Symbol('a');
106+
const a: undefined = undefined;
107+
const a: undefined = void someValue;
45108

46109
class Foo {
47110
prop: number = 5;
@@ -50,23 +113,23 @@ class Foo {
50113
function fn(a: number = 5, b: boolean = true) {}
51114
```
52115

53-
### `ignoreProperties`
116+
### `ignoreParameters`
54117

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

57120
```ts
58-
class Foo {
59-
prop: number = 5;
121+
function foo(a: number = 5, b: boolean = true) {
122+
// ...
60123
}
61124
```
62125

63-
### `ignoreParameters`
126+
### `ignoreProperties`
64127

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

67130
```ts
68-
function foo(a: number = 5, b: boolean = true) {
69-
// ...
131+
class Foo {
132+
prop: number = 5;
70133
}
71134
```
72135

packages/eslint-plugin/src/rules/no-inferrable-types.ts

Lines changed: 116 additions & 51 deletions
10000
Original file line numberDiff line numberDiff line change
@@ -47,60 +47,135 @@ export default util.createRule<Options, MessageIds>({
4747
},
4848
],
4949
create(context, [{ ignoreParameters, ignoreProperties }]) {
50+
function isFunctionCall(init: TSESTree.Expression, callName: string) {
51+
return (
52+
init.type === AST_NODE_TYPES.CallExpression &&
53+
init.callee.type === AST_NODE_TYPES.Identifier &&
54+
init.callee.name === callName
55+
);
56+
}
57+
function isLiteral(init: TSESTree.Expression, typeName: string) {
58+
return (
59+
init.type === AST_NODE_TYPES.Literal && typeof init.value === typeName
60+
);
61+
}
62+
function isIdentifier(init: TSESTree.Expression, ...names: string[]) {
63+
return (
64+
init.type === AST_NODE_TYPES.Identifier && names.includes(init.name)
65+
);
66+
}
67+
function hasUnaryPrefix(
68+
init: TSESTree.Expression,
69+
...operators: string[]
70+
): init is TSESTree.UnaryExpression {
71+
return (
72+
init.type === AST_NODE_TYPES.UnaryExpression &&
73+
operators.includes(init.operator)
74+
);
75+
}
76+
77+
type Keywords =
78+
| TSESTree.TSBigIntKeyword
79+
| TSESTree.TSBooleanKeyword
80+
| TSESTree.TSNumberKeyword
81+
| TSESTree.TSNullKeyword
82+
| TSESTree.TSStringKeyword
83+
| TSESTree.TSSymbolKeyword
84+
| TSESTree.TSUndefinedKeyword
85+
| TSESTree.TSTypeReference;
86+
const keywordMap = {
87+
[AST_NODE_TYPES.TSBigIntKeyword]: 'bigint',
88+
[AST_NODE_TYPES.TSBooleanKeyword]: 'boolean',
89+
[AST_NODE_TYPES.TSNumberKeyword]: 'number',
90+
[AST_NODE_TYPES.TSNullKeyword]: 'null',
91+
[AST_NODE_TYPES.TSStringKeyword]: 'string',
92+
[AST_NODE_TYPES.TSSymbolKeyword]: 'symbol',
93+
[AST_NODE_TYPES.TSUndefinedKeyword]: 'undefined',
94+
};
95+
5096
/**
5197
* Returns whether a node has an inferrable value or not
52-
* @param node the node to check
53-
* @param init the initializer
5498
*/
5599
function isInferrable(
56-
node: TSESTree.TSTypeAnnotation,
100+
annotation: TSESTree.TypeNode,
57101
init: TSESTree.Expression,
58-
): boolean {
59-
if (
60-
node.type !== AST_NODE_TYPES.TSTypeAnnotation ||
61-
!node.typeAnnotation
62-
) {
63-
return false;
64-
}
102+
): annotation is Keywords {
103+
switch (annotation.type) {
104+
case AST_NODE_TYPES.TSBigIntKeyword: {
105+
// note that bigint cannot have + prefixed to it
106+
const unwrappedInit = hasUnaryPrefix(init, '-')
107+
? init.argument
108+
: init;
109+
110+
return (
111+
isFunctionCall(unwrappedInit, 'BigInt') ||
112+
unwrappedInit.type === AST_NODE_TYPES.BigIntLiteral
113+
);
114+
}
115+
116+
case AST_NODE_TYPES.TSBooleanKeyword:
117+
return (
118+
hasUnaryPrefix(init, '!') ||
119+
isFunctionCall(init, 'Boolean') ||
120+
isLiteral(init, 'boolean')
121+
);
65122

66-
const annotation = node.typeAnnotation;
123+
case AST_NODE_TYPES.TSNumberKeyword: {
124+
const unwrappedInit = hasUnaryPrefix(init, '+', '-')
125+
? init.argument
126+
: init;
67127

68-
if (annotation.type === AST_NODE_TYPES.TSStringKeyword) {
69-
if (init.type === AST_NODE_TYPES.Literal) {
70-
return typeof init.value === 'string';
128+
return (
129+
isIdentifier(unwrappedInit, 'Infinity', 'NaN') ||
130+
isFunctionCall(unwrappedInit, 'Number') ||
131+
isLiteral(unwrappedInit, 'number')
132+
);
71133
}
72-
return false;
73-
}
74134

75-
if (annotation.type === AST_NODE_TYPES.TSBooleanKeyword) {
76-
return init.type === AST_NODE_TYPES.Literal;
77-
}
135+
case AST_NODE_TYPES.TSNullKeyword:
136+
return init.type === AST_NODE_TYPES.Literal && init.value === null;
137+
138+
case AST_NODE_TYPES.TSStringKeyword:
139+
return (
140+
isFunctionCall(init, 'String') ||
141+
isLiteral(init, 'string') ||
142+
init.type === AST_NODE_TYPES.TemplateLiteral
143+
);
78144

79-
if (annotation.type === AST_NODE_TYPES.TSNumberKeyword) {
80-
// Infinity is special
81-
if (
82-
(init.type === AST_NODE_TYPES.UnaryExpression &&
83-
init.operator === '-' &&
84-
init.argument.type === AST_NODE_TYPES.Identifier &&
85-
init.argument.name === 'Infinity') ||
86-
(init.type === AST_NODE_TYPES.Identifier && init.name === 'Infinity')
87-
) {
88-
return true;
145+
case AST_NODE_TYPES.TSSymbolKeyword:
146+
return isFunctionCall(init, 'Symbol');
147+
148+
case AST_NODE_TYPES.TSTypeReference: {
149+
if (
150+
annotation.typeName.type === AST_NODE_TYPES.Identifier &&
151+
annotation.typeName.name === 'RegExp'
152+
) {
153+
const isRegExpLiteral =
154+
init.type === AST_NODE_TYPES.Literal &&
155+
init.value instanceof RegExp;
156+
const isRegExpNewCall =
157+
init.type === AST_NODE_TYPES.NewExpression &&
158+
init.callee.type === 'Identifier' &&
159+
init.callee.name === 'RegExp';
160+
const isRegExpCall = isFunctionCall(init, 'RegExp');
161+
162+
return isRegExpLiteral || isRegExpCall || isRegExpNewCall;
163+
}
164+
165+
return false;
89166
}
90167

91-
return (
92-
init.type === AST_NODE_TYPES.Literal && typeof init.value === 'number'
93-
);
168+
case AST_NODE_TYPES.TSUndefinedKeyword:
169+
return (
170+
hasUnaryPrefix(init, 'void') || isIdentifier(init, 'undefined')
171+
);
94172
}
95173

96174
return false;
97175
}
98176

99177
/**
100178
* Reports an inferrable type declaration, if any
101-
* @param node the node being visited
102-
* @param typeNode the type annotation node
103-
* @param initNode the initializer node
104179
*/
105180
function reportInferrableType(
106181
node:
@@ -114,25 +189,15 @@ export default util.createRule<Options, MessageIds>({
114189
return;
115190
}
116191

117-
if (!isInferrable(typeNode, initNode)) {
192+
if (!isInferrable(typeNode.typeAnnotation, initNode)) {
118193
return;
119194
}
120195

121-
let type = null;
122-
if (typeNode.typeAnnotation.type === AST_NODE_TYPES.TSBooleanKeyword) {
123-
type = 'boolean';
124-
} else if (
125-
typeNode.typeAnnotation.type === AST_NODE_TYPES.TSNumberKeyword
126-
) {
127-
type = 'number';
128-
} else if (
129-
typeNode.typeAnnotation.type === AST_NODE_TYPES.TSStringKeyword
130-
) {
131-
type = 'string';
132-
} else {
133-
// shouldn't happen...
134-
return;
135-
}
196+
const type =
197+
typeNode.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference
198+
? // TODO - if we add more references
199+
'RegExp'
200+
: keywordMap[typeNode.typeAnnotation.type];
136201

137202
context.report({
138203
node,

0 commit comments

Comments
 (0)
0