Skip to content

Commit 34e7d1e

Browse files
jonathanrdelgadobradzacher
authored andcommitted
feat(eslint-plugin): add rule strict-boolean-expressions (typescript-eslint#579)
1 parent 44677b4 commit 34e7d1e

File tree

6 files changed

+1069
-1
lines changed

6 files changed

+1069
-1
lines changed

packages/eslint-plugin/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
178178
| [`@typescript-eslint/require-array-sort-compare`](./docs/rules/require-array-sort-compare.md) | Enforce giving `compare` argument to `Array#sort` | | | :thought_balloon: |
179179
| [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string | | | :thought_balloon: |
180180
| [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | |
181+
| [`@typescript-eslint/strict-boolean-expressions`](./docs/rules/strict-boolean-expressions.md) | Restricts the types allowed in boolean expressions | | | :thought_balloon: |
181182
| [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations | :heavy_check_mark: | :wrench: | |
182183
| [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope | | | :thought_balloon: |
183184
| [`@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 | | | |

packages/eslint-plugin/ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
| [`prefer-object-spread`] | 🌟 | [`prefer-object-spread`][prefer-object-spread] |
9393
| [`radix`] | 🌟 | [`radix`][radix] |
9494
| [`restrict-plus-operands`] || [`@typescript-eslint/restrict-plus-operands`] |
95-
| [`strict-boolean-expressions`] | 🛑 | N/A |
95+
| [`strict-boolean-expressions`] | | [`@typescript-eslint/strict-boolean-expressions`] |
9696
| [`strict-type-predicates`] | 🛑 | N/A |
9797
| [`switch-default`] | 🌟 | [`default-case`][default-case] |
9898
| [`triple-equals`] | 🌟 | [`eqeqeq`][eqeqeq] |
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Boolean expressions are limited to booleans (strict-boolean-expressions)
2+
3+
Requires that any boolean expression is limited to true booleans rather than
4+
casting another primitive to a boolean at runtime.
5+
6+
It is useful to be explicit, for example, if you were trying to check if a
7+
number was defined. Doing `if (number)` would evaluate to `false` if `number`
8+
was defined and `0`. This rule forces these expressions to be explicit and to
9+
strictly use booleans.
10+
11+
The following nodes are checked:
12+
13+
- Arguments to the `!`, `&&`, and `||` operators
14+
- The condition in a conditional expression `(cond ? x : y)`
15+
- Conditions for `if`, `for`, `while`, and `do-while` statements.
16+
17+
Examples of **incorrect** code for this rule:
18+
19+
```ts
20+
const number = 0;
21+
if (number) {
22+
return;
23+
}
24+
25+
let foo = bar || 'foobar';
26+
27+
let undefinedItem;
28+
let foo = undefinedItem ? 'foo' : 'bar';
29+
30+
let str = 'foo';
31+
while (str) {
32+
break;
33+
}
34+
```
35+
36+
Examples of **correct** code for this rule:
37+
38+
```ts
39+
const number = 0;
40+
if (typeof number !== 'undefined') {
41+
return;
42+
}
43+
44+
let foo = typeof bar !== 'undefined' ? bar : 'foobar';
45+
46+
let undefinedItem;
47+
let foo = typeof undefinedItem !== 'undefined' ? 'foo' : 'bar';
48+
49+
let str = 'foo';
50+
while (typeof str !== 'undefined') {
51+
break;
52+
}
53+
```
54+
55+
## Related To
56+
57+
- TSLint: [strict-boolean-expressions](https://palantir.github.io/tslint/rules/strict-boolean-expressions)

packages/eslint-plugin/src/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import promiseFunctionAsync from './promise-function-async';
5353
import requireArraySortCompare from './require-array-sort-compare';
5454
import restrictPlusOperands from './restrict-plus-operands';
5555
import semi from './semi';
56+
import strictBooleanExpressions from './strict-boolean-expressions';
5657
import typeAnnotationSpacing from './type-annotation-spacing';
5758
import unboundMethod from './unbound-method';
5859
import unifiedSignatures from './unified-signatures';
@@ -113,6 +114,7 @@ export default {
113114
'require-array-sort-compare': requireArraySortCompare,
114115
'restrict-plus-operands': restrictPlusOperands,
115116
semi: semi,
117+
'strict-boolean-expressions': strictBooleanExpressions,
116118
'type-annotation-spacing': typeAnnotationSpacing,
117119
'unbound-method': unboundMethod,
118120
'unified-signatures': unifiedSignatures,
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import {
2+
TSESTree,
3+
AST_NODE_TYPES,
4+
} from '@typescript-eslint/experimental-utils';
5+
import ts from 'typescript';
6+
import * as tsutils from 'tsutils';
7+
import * as util from '../util';
8+
9+
type ExpressionWithTest =
10+
| TSESTree.ConditionalExpression
11+
| TSESTree.DoWhileStatement
12+
| TSESTree.ForStatement
13+
| TSESTree.IfStatement
14+
| TSESTree.WhileStatement;
15+
16+
export default util.createRule({
17+
name: 'strict-boolean-expressions',
18+
meta: {
19+
type: 'suggestion',
20+
docs: {
21+
description: 'Restricts the types allowed in boolean expressions',
22+
category: 'Best Practices',
23+
recommended: false,
24+
},
25+
schema: [],
26+
messages: {
27+
strictBooleanExpression: 'Unexpected non-boolean in conditional.',
28+
},
29+
},
30+
defaultOptions: [],
31+
create(context) {
32+
const service = util.getParserServices(context);
33+
const checker = service.program.getTypeChecker();
34+
35+
/**
36+
* Determines if the node has a boolean type.
37+
*/
38+
function isBooleanType(node: TSESTree.Node): boolean {
39+
const tsNode = service.esTreeNodeToTSNodeMap.get<ts.ExpressionStatement>(
40+
node,
41+
);
42+
const type = util.getConstrainedTypeAtLocation(checker, tsNode);
43+
return tsutils.isTypeFlagSet(type, ts.TypeFlags.BooleanLike);
44+
}
45+
46+
/**
47+
* Asserts that a testable expression contains a boolean, reports otherwise.
48+
* Filters all LogicalExpressions to prevent some duplicate reports.
49+
*/
50+
function assertTestExpressionContainsBoolean(
51+
node: ExpressionWithTest,
52+
): void {
53+
if (
54+
node.test !== null &&
55+
node.test.type !== AST_NODE_TYPES.LogicalExpression &&
56+
!isBooleanType(node.test)
57+
) {
58+
reportNode(node.test);
59+
}
60+
}
61+
62+
/**
63+
* Asserts that a logical expression contains a boolean, reports otherwise.
64+
*/
65+
function assertLocalExpressionContainsBoolean(
66+
node: TSESTree.LogicalExpression,
67+
): void {
68+
if (!isBooleanType(node.left) || !isBooleanType(node.right)) {
69+
reportNode(node);
70+
}
71+
}
72+
73+
/**
74+
* Asserts that a unary expression contains a boolean, reports otherwise.
75+
*/
76+
function assertUnaryExpressionContainsBoolean(
77+
node: TSESTree.UnaryExpression,
78+
): void {
79+
if (!isBooleanType(node.argument)) {
80+
reportNode(node.argument);
81+
}
82+
}
83+
84+
/**
85+
* Reports an offending node in context.
86+
*/
87+
function reportNode(node: TSESTree.Node): void {
88+
context.report({ node, messageId: 'strictBooleanExpression' });
89+
}
90+
91+
return {
92+
ConditionalExpression: assertTestExpressionContainsBoolean,
93+
DoWhileStatement: assertTestExpressionContainsBoolean,
94+
ForStatement: assertTestExpressionContainsBoolean,
95+
IfStatement: assertTestExpressionContainsBoolean,
96+
WhileStatement: assertTestExpressionContainsBoolean,
97+
LogicalExpression: assertLocalExpressionContainsBoolean,
98+
'UnaryExpression[operator="!"]': assertUnaryExpressionContainsBoolean,
99+
};
100+
},
101+
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy