/
strict-boolean-expressions.ts
124 lines (115 loc) · 3.27 KB
/
strict-boolean-expressions.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import {
TSESTree,
AST_NODE_TYPES,
} from '@typescript-eslint/experimental-utils';
import ts from 'typescript';
import * as tsutils from 'tsutils';
import * as util from '../util';
type ExpressionWithTest =
| TSESTree.ConditionalExpression
| TSESTree.DoWhileStatement
| TSESTree.ForStatement
| TSESTree.IfStatement
| TSESTree.WhileStatement;
type Options = [
{
ignoreRhs?: boolean;
}
];
export default util.createRule<Options, 'strictBooleanExpression'>({
name: 'strict-boolean-expressions',
meta: {
type: 'suggestion',
docs: {
description: 'Restricts the types allowed in boolean expressions',
category: 'Best Practices',
recommended: false,
},
schema: [
{
type: 'object',
properties: {
ignoreRhs: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
messages: {
strictBooleanExpression: 'Unexpected non-boolean in conditional.',
},
},
defaultOptions: [
{
ignoreRhs: false,
},
],
create(context, [{ ignoreRhs }]) {
const service = util.getParserServices(context);
const checker = service.program.getTypeChecker();
/**
* Determines if the node has a boolean type.
*/
function isBooleanType(node: TSESTree.Node): boolean {
const tsNode = service.esTreeNodeToTSNodeMap.get<ts.ExpressionStatement>(
node,
);
const type = util.getConstrainedTypeAtLocation(checker, tsNode);
return tsutils.isTypeFlagSet(type, ts.TypeFlags.BooleanLike);
}
/**
* Asserts that a testable expression contains a boolean, reports otherwise.
* Filters all LogicalExpressions to prevent some duplicate reports.
*/
function assertTestExpressionContainsBoolean(
node: ExpressionWithTest,
): void {
if (
node.test !== null &&
node.test.type !== AST_NODE_TYPES.LogicalExpression &&
!isBooleanType(node.test)
) {
reportNode(node.test);
}
}
/**
* Asserts that a logical expression contains a boolean, reports otherwise.
*/
function assertLocalExpressionContainsBoolean(
node: TSESTree.LogicalExpression,
): void {
if (
!isBooleanType(node.left) ||
(!ignoreRhs && !isBooleanType(node.right))
) {
reportNode(node);
}
}
/**
* Asserts that a unary expression contains a boolean, reports otherwise.
*/
function assertUnaryExpressionContainsBoolean(
node: TSESTree.UnaryExpression,
): void {
if (!isBooleanType(node.argument)) {
reportNode(node.argument);
}
}
/**
* Reports an offending node in context.
*/
function reportNode(node: TSESTree.Node): void {
context.report({ node, messageId: 'strictBooleanExpression' });
}
return {
ConditionalExpression: assertTestExpressionContainsBoolean,
DoWhileStatement: assertTestExpressionContainsBoolean,
ForStatement: assertTestExpressionContainsBoolean,
IfStatement: assertTestExpressionContainsBoolean,
WhileStatement: assertTestExpressionContainsBoolean,
LogicalExpression: assertLocalExpressionContainsBoolean,
'UnaryExpression[operator="!"]': assertUnaryExpressionContainsBoolean,
};
},
});