1 | ;
|
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3 | if (k2 === undefined) k2 = k;
|
4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
5 | }) : (function(o, m, k, k2) {
|
6 | if (k2 === undefined) k2 = k;
|
7 | o[k2] = m[k];
|
8 | }));
|
9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
10 | Object.defineProperty(o, "default", { enumerable: true, value: v });
|
11 | }) : function(o, v) {
|
12 | o["default"] = v;
|
13 | });
|
14 | var __importStar = (this && this.__importStar) || function (mod) {
|
15 | if (mod && mod.__esModule) return mod;
|
16 | var result = {};
|
17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
18 | __setModuleDefault(result, mod);
|
19 | return result;
|
20 | };
|
21 | Object.defineProperty(exports, "__esModule", { value: true });
|
22 | const utils_1 = require("@typescript-eslint/utils");
|
23 | const tsutils = __importStar(require("tsutils"));
|
24 | const ts = __importStar(require("typescript"));
|
25 | const util = __importStar(require("../util"));
|
26 | exports.default = util.createRule({
|
27 | name: 'strict-boolean-expressions',
|
28 | meta: {
|
29 | type: 'suggestion',
|
30 | fixable: 'code',
|
31 | hasSuggestions: true,
|
32 | docs: {
|
33 | description: 'Restricts the types allowed in boolean expressions',
|
34 | recommended: false,
|
35 | requiresTypeChecking: true,
|
36 | },
|
37 | schema: [
|
38 | {
|
39 | type: 'object',
|
40 | properties: {
|
41 | allowString: { type: 'boolean' },
|
42 | allowNumber: { type: 'boolean' },
|
43 | allowNullableObject: { type: 'boolean' },
|
44 | allowNullableBoolean: { type: 'boolean' },
|
45 | allowNullableString: { type: 'boolean' },
|
46 | allowNullableNumber: { type: 'boolean' },
|
47 | allowAny: { type: 'boolean' },
|
48 | allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: {
|
49 | type: 'boolean',
|
50 | },
|
51 | },
|
52 | additionalProperties: false,
|
53 | },
|
54 | ],
|
55 | messages: {
|
56 | conditionErrorOther: 'Unexpected value in conditional. ' +
|
57 | 'A boolean expression is required.',
|
58 | conditionErrorAny: 'Unexpected any value in conditional. ' +
|
59 | 'An explicit comparison or type cast is required.',
|
60 | conditionErrorNullish: 'Unexpected nullish value in conditional. ' +
|
61 | 'The condition is always false.',
|
62 | conditionErrorNullableBoolean: 'Unexpected nullable boolean value in conditional. ' +
|
63 | 'Please handle the nullish case explicitly.',
|
64 | conditionErrorString: 'Unexpected string value in conditional. ' +
|
65 | 'An explicit empty string check is required.',
|
66 | conditionErrorNullableString: 'Unexpected nullable string value in conditional. ' +
|
67 | 'Please handle the nullish/empty cases explicitly.',
|
68 | conditionErrorNumber: 'Unexpected number value in conditional. ' +
|
69 | 'An explicit zero/NaN check is required.',
|
70 | conditionErrorNullableNumber: 'Unexpected nullable number value in conditional. ' +
|
71 | 'Please handle the nullish/zero/NaN cases explicitly.',
|
72 | conditionErrorObject: 'Unexpected object value in conditional. ' +
|
73 | 'The condition is always true.',
|
74 | conditionErrorNullableObject: 'Unexpected nullable object value in conditional. ' +
|
75 | 'An explicit null check is required.',
|
76 | noStrictNullCheck: 'This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.',
|
77 | conditionFixDefaultFalse: 'Explicitly treat nullish value the same as false (`value ?? false`)',
|
78 | conditionFixDefaultEmptyString: 'Explicitly treat nullish value the same as an empty string (`value ?? ""`)',
|
79 | conditionFixDefaultZero: 'Explicitly treat nullish value the same as 0 (`value ?? 0`)',
|
80 | conditionFixCompareNullish: 'Change condition to check for null/undefined (`value != null`)',
|
81 | conditionFixCastBoolean: 'Explicitly cast value to a boolean (`Boolean(value)`)',
|
82 | conditionFixCompareTrue: 'Change condition to check if true (`value === true`)',
|
83 | conditionFixCompareFalse: 'Change condition to check if false (`value === false`)',
|
84 | conditionFixCompareStringLength: "Change condition to check string's length (`value.length !== 0`)",
|
85 | conditionFixCompareEmptyString: 'Change condition to check for empty string (`value !== ""`)',
|
86 | conditionFixCompareZero: 'Change condition to check for 0 (`value !== 0`)',
|
87 | conditionFixCompareNaN: 'Change condition to check for NaN (`!Number.isNaN(value)`)',
|
88 | },
|
89 | },
|
90 | defaultOptions: [
|
91 | {
|
92 | allowString: true,
|
93 | allowNumber: true,
|
94 | allowNullableObject: true,
|
95 | allowNullableBoolean: false,
|
96 | allowNullableString: false,
|
97 | allowNullableNumber: false,
|
98 | allowAny: false,
|
99 | allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false,
|
100 | },
|
101 | ],
|
102 | create(context, [options]) {
|
103 | const parserServices = util.getParserServices(context);
|
104 | const typeChecker = parserServices.program.getTypeChecker();
|
105 | const compilerOptions = parserServices.program.getCompilerOptions();
|
106 | const sourceCode = context.getSourceCode();
|
107 | const isStrictNullChecks = tsutils.isStrictCompilerOptionEnabled(compilerOptions, 'strictNullChecks');
|
108 | if (!isStrictNullChecks &&
|
109 | options.allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing !== true) {
|
110 | context.report({
|
111 | loc: {
|
112 | start: { line: 0, column: 0 },
|
113 | end: { line: 0, column: 0 },
|
114 | },
|
115 | messageId: 'noStrictNullCheck',
|
116 | });
|
117 | }
|
118 | const checkedNodes = new Set();
|
119 | return {
|
120 | ConditionalExpression: checkTestExpression,
|
121 | DoWhileStatement: checkTestExpression,
|
122 | ForStatement: checkTestExpression,
|
123 | IfStatement: checkTestExpression,
|
124 | WhileStatement: checkTestExpression,
|
125 | 'LogicalExpression[operator!="??"]': checkNode,
|
126 | 'UnaryExpression[operator="!"]': checkUnaryLogicalExpression,
|
127 | };
|
128 | function checkTestExpression(node) {
|
129 | if (node.test == null) {
|
130 | return;
|
131 | }
|
132 | checkNode(node.test, true);
|
133 | }
|
134 | function checkUnaryLogicalExpression(node) {
|
135 | checkNode(node.argument, true);
|
136 | }
|
137 | /**
|
138 | * This function analyzes the type of a node and checks if it is allowed in a boolean context.
|
139 | * It can recurse when checking nested logical operators, so that only the outermost operands are reported.
|
140 | * The right operand of a logical expression is ignored unless it's a part of a test expression (if/while/ternary/etc).
|
141 | * @param node The AST node to check.
|
142 | * @param isTestExpr Whether the node is a descendant of a test expression.
|
143 | */
|
144 | function checkNode(node, isTestExpr = false) {
|
145 | // prevent checking the same node multiple times
|
146 | if (checkedNodes.has(node)) {
|
147 | return;
|
148 | }
|
149 | checkedNodes.add(node);
|
150 | // for logical operator, we check its operands
|
151 | if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression &&
|
152 | node.operator !== '??') {
|
153 | checkNode(node.left, isTestExpr);
|
154 | // we ignore the right operand when not in a context of a test expression
|
155 | if (isTestExpr) {
|
156 | checkNode(node.right, isTestExpr);
|
157 | }
|
158 | return;
|
159 | }
|
160 | const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
161 | const type = util.getConstrainedTypeAtLocation(typeChecker, tsNode);
|
162 | const types = inspectVariantTypes(tsutils.unionTypeParts(type));
|
163 | const is = (...wantedTypes) => types.size === wantedTypes.length &&
|
164 | wantedTypes.every(type => types.has(type));
|
165 | // boolean
|
166 | if (is('boolean') || is('truthy boolean')) {
|
167 | // boolean is always okay
|
168 | return;
|
169 | }
|
170 | // never
|
171 | if (is('never')) {
|
172 | // never is always okay
|
173 | return;
|
174 | }
|
175 | // nullish
|
176 | if (is('nullish')) {
|
177 | // condition is always false
|
178 | context.report({ node, messageId: 'conditionErrorNullish' });
|
179 | return;
|
180 | }
|
181 | // Known edge case: boolean `true` and nullish values are always valid boolean expressions
|
182 | if (is('nullish', 'truthy boolean')) {
|
183 | return;
|
184 | }
|
185 | // nullable boolean
|
186 | if (is('nullish', 'boolean')) {
|
187 | if (!options.allowNullableBoolean) {
|
188 | if (isLogicalNegationExpression(node.parent)) {
|
189 | // if (!nullableBoolean)
|
190 | context.report({
|
191 | node,
|
192 | messageId: 'conditionErrorNullableBoolean',
|
193 | suggest: [
|
194 | {
|
195 | messageId: 'conditionFixDefaultFalse',
|
196 | fix: util.getWrappingFixer({
|
197 | sourceCode,
|
198 | node,
|
199 | wrap: code => `${code} ?? false`,
|
200 | }),
|
201 | },
|
202 | {
|
203 | messageId: 'conditionFixCompareFalse',
|
204 | fix: util.getWrappingFixer({
|
205 | sourceCode,
|
206 | node: node.parent,
|
207 | innerNode: node,
|
208 | wrap: code => `${code} === false`,
|
209 | }),
|
210 | },
|
211 | ],
|
212 | });
|
213 | }
|
214 | else {
|
215 | // if (nullableBoolean)
|
216 | context.report({
|
217 | node,
|
218 | messageId: 'conditionErrorNullableBoolean',
|
219 | suggest: [
|
220 | {
|
221 | messageId: 'conditionFixDefaultFalse',
|
222 | fix: util.getWrappingFixer({
|
223 | sourceCode,
|
224 | node,
|
225 | wrap: code => `${code} ?? false`,
|
226 | }),
|
227 | },
|
228 | {
|
229 | messageId: 'conditionFixCompareTrue',
|
230 | fix: util.getWrappingFixer({
|
231 | sourceCode,
|
232 | node,
|
233 | wrap: code => `${code} === true`,
|
234 | }),
|
235 | },
|
236 | ],
|
237 | });
|
238 | }
|
239 | }
|
240 | return;
|
241 | }
|
242 | // Known edge case: truthy primitives and nullish values are always valid boolean expressions
|
243 | if ((options.allowNumber && is('nullish', 'truthy number')) ||
|
244 | (options.allowString && is('nullish', 'truthy string'))) {
|
245 | return;
|
246 | }
|
247 | // string
|
248 | if (is('string') || is('truthy string')) {
|
249 | if (!options.allowString) {
|
250 | if (isLogicalNegationExpression(node.parent)) {
|
251 | // if (!string)
|
252 | context.report({
|
253 | node,
|
254 | messageId: 'conditionErrorString',
|
255 | suggest: [
|
256 | {
|
257 | messageId: 'conditionFixCompareStringLength',
|
258 | fix: util.getWrappingFixer({
|
259 | sourceCode,
|
260 | node: node.parent,
|
261 | innerNode: node,
|
262 | wrap: code => `${code}.length === 0`,
|
263 | }),
|
264 | },
|
265 | {
|
266 | messageId: 'conditionFixCompareEmptyString',
|
267 | fix: util.getWrappingFixer({
|
268 | sourceCode,
|
269 | node: node.parent,
|
270 | innerNode: node,
|
271 | wrap: code => `${code} === ""`,
|
272 | }),
|
273 | },
|
274 | {
|
275 | messageId: 'conditionFixCastBoolean',
|
276 | fix: util.getWrappingFixer({
|
277 | sourceCode,
|
278 | node: node.parent,
|
279 | innerNode: node,
|
280 | wrap: code => `!Boolean(${code})`,
|
281 | }),
|
282 | },
|
283 | ],
|
284 | });
|
285 | }
|
286 | else {
|
287 | // if (string)
|
288 | context.report({
|
289 | node,
|
290 | messageId: 'conditionErrorString',
|
291 | suggest: [
|
292 | {
|
293 | messageId: 'conditionFixCompareStringLength',
|
294 | fix: util.getWrappingFixer({
|
295 | sourceCode,
|
296 | node,
|
297 | wrap: code => `${code}.length > 0`,
|
298 | }),
|
299 | },
|
300 | {
|
301 | messageId: 'conditionFixCompareEmptyString',
|
302 | fix: util.getWrappingFixer({
|
303 | sourceCode,
|
304 | node,
|
305 | wrap: code => `${code} !== ""`,
|
306 | }),
|
307 | },
|
308 | {
|
309 | messageId: 'conditionFixCastBoolean',
|
310 | fix: util.getWrappingFixer({
|
311 | sourceCode,
|
312 | node,
|
313 | wrap: code => `Boolean(${code})`,
|
314 | }),
|
315 | },
|
316 | ],
|
317 | });
|
318 | }
|
319 | }
|
320 | return;
|
321 | }
|
322 | // nullable string
|
323 | if (is('nullish', 'string')) {
|
324 | if (!options.allowNullableString) {
|
325 | if (isLogicalNegationExpression(node.parent)) {
|
326 | // if (!nullableString)
|
327 | context.report({
|
328 | node,
|
329 | messageId: 'conditionErrorNullableString',
|
330 | suggest: [
|
331 | {
|
332 | messageId: 'conditionFixCompareNullish',
|
333 | fix: util.getWrappingFixer({
|
334 | sourceCode,
|
335 | node: node.parent,
|
336 | innerNode: node,
|
337 | wrap: code => `${code} == null`,
|
338 | }),
|
339 | },
|
340 | {
|
341 | messageId: 'conditionFixDefaultEmptyString',
|
342 | fix: util.getWrappingFixer({
|
343 | sourceCode,
|
344 | node,
|
345 | wrap: code => `${code} ?? ""`,
|
346 | }),
|
347 | },
|
348 | {
|
349 | messageId: 'conditionFixCastBoolean',
|
350 | fix: util.getWrappingFixer({
|
351 | sourceCode,
|
352 | node: node.parent,
|
353 | innerNode: node,
|
354 | wrap: code => `!Boolean(${code})`,
|
355 | }),
|
356 | },
|
357 | ],
|
358 | });
|
359 | }
|
360 | else {
|
361 | // if (nullableString)
|
362 | context.report({
|
363 | node,
|
364 | messageId: 'conditionErrorNullableString',
|
365 | suggest: [
|
366 | {
|
367 | messageId: 'conditionFixCompareNullish',
|
368 | fix: util.getWrappingFixer({
|
369 | sourceCode,
|
370 | node,
|
371 | wrap: code => `${code} != null`,
|
372 | }),
|
373 | },
|
374 | {
|
375 | messageId: 'conditionFixDefaultEmptyString',
|
376 | fix: util.getWrappingFixer({
|
377 | sourceCode,
|
378 | node,
|
379 | wrap: code => `${code} ?? ""`,
|
380 | }),
|
381 | },
|
382 | {
|
383 | messageId: 'conditionFixCastBoolean',
|
384 | fix: util.getWrappingFixer({
|
385 | sourceCode,
|
386 | node,
|
387 | wrap: code => `Boolean(${code})`,
|
388 | }),
|
389 | },
|
390 | ],
|
391 | });
|
392 | }
|
393 | }
|
394 | return;
|
395 | }
|
396 | // number
|
397 | if (is('number') || is('truthy number')) {
|
398 | if (!options.allowNumber) {
|
399 | if (isArrayLengthExpression(node, typeChecker, parserServices)) {
|
400 | if (isLogicalNegationExpression(node.parent)) {
|
401 | // if (!array.length)
|
402 | context.report({
|
403 | node,
|
404 | messageId: 'conditionErrorNumber',
|
405 | fix: util.getWrappingFixer({
|
406 | sourceCode,
|
407 | node: node.parent,
|
408 | innerNode: node,
|
409 | wrap: code => `${code} === 0`,
|
410 | }),
|
411 | });
|
412 | }
|
413 | else {
|
414 | // if (array.length)
|
415 | context.report({
|
416 | node,
|
417 | messageId: 'conditionErrorNumber',
|
418 | fix: util.getWrappingFixer({
|
419 | sourceCode,
|
420 | node,
|
421 | wrap: code => `${code} > 0`,
|
422 | }),
|
423 | });
|
424 | }
|
425 | }
|
426 | else if (isLogicalNegationExpression(node.parent)) {
|
427 | // if (!number)
|
428 | context.report({
|
429 | node,
|
430 | messageId: 'conditionErrorNumber',
|
431 | suggest: [
|
432 | {
|
433 | messageId: 'conditionFixCompareZero',
|
434 | fix: util.getWrappingFixer({
|
435 | sourceCode,
|
436 | node: node.parent,
|
437 | innerNode: node,
|
438 | // TODO: we have to compare to 0n if the type is bigint
|
439 | wrap: code => `${code} === 0`,
|
440 | }),
|
441 | },
|
442 | {
|
443 | // TODO: don't suggest this for bigint because it can't be NaN
|
444 | messageId: 'conditionFixCompareNaN',
|
445 | fix: util.getWrappingFixer({
|
446 | sourceCode,
|
447 | node: node.parent,
|
448 | innerNode: node,
|
449 | wrap: code => `Number.isNaN(${code})`,
|
450 | }),
|
451 | },
|
452 | {
|
453 | messageId: 'conditionFixCastBoolean',
|
454 | fix: util.getWrappingFixer({
|
455 | sourceCode,
|
456 | node: node.parent,
|
457 | innerNode: node,
|
458 | wrap: code => `!Boolean(${code})`,
|
459 | }),
|
460 | },
|
461 | ],
|
462 | });
|
463 | }
|
464 | else {
|
465 | // if (number)
|
466 | context.report({
|
467 | node,
|
468 | messageId: 'conditionErrorNumber',
|
469 | suggest: [
|
470 | {
|
471 | messageId: 'conditionFixCompareZero',
|
472 | fix: util.getWrappingFixer({
|
473 | sourceCode,
|
474 | node,
|
475 | wrap: code => `${code} !== 0`,
|
476 | }),
|
477 | },
|
478 | {
|
479 | messageId: 'conditionFixCompareNaN',
|
480 | fix: util.getWrappingFixer({
|
481 | sourceCode,
|
482 | node,
|
483 | wrap: code => `!Number.isNaN(${code})`,
|
484 | }),
|
485 | },
|
486 | {
|
487 | messageId: 'conditionFixCastBoolean',
|
488 | fix: util.getWrappingFixer({
|
489 | sourceCode,
|
490 | node,
|
491 | wrap: code => `Boolean(${code})`,
|
492 | }),
|
493 | },
|
494 | ],
|
495 | });
|
496 | }
|
497 | }
|
498 | return;
|
499 | }
|
500 | // nullable number
|
501 | if (is('nullish', 'number')) {
|
502 | if (!options.allowNullableNumber) {
|
503 | if (isLogicalNegationExpression(node.parent)) {
|
504 | // if (!nullableNumber)
|
505 | context.report({
|
506 | node,
|
507 | messageId: 'conditionErrorNullableNumber',
|
508 | suggest: [
|
509 | {
|
510 | messageId: 'conditionFixCompareNullish',
|
511 | fix: util.getWrappingFixer({
|
512 | sourceCode,
|
513 | node: node.parent,
|
514 | innerNode: node,
|
515 | wrap: code => `${code} == null`,
|
516 | }),
|
517 | },
|
518 | {
|
519 | messageId: 'conditionFixDefaultZero',
|
520 | fix: util.getWrappingFixer({
|
521 | sourceCode,
|
522 | node,
|
523 | wrap: code => `${code} ?? 0`,
|
524 | }),
|
525 | },
|
526 | {
|
527 | messageId: 'conditionFixCastBoolean',
|
528 | fix: util.getWrappingFixer({
|
529 | sourceCode,
|
530 | node: node.parent,
|
531 | innerNode: node,
|
532 | wrap: code => `!Boolean(${code})`,
|
533 | }),
|
534 | },
|
535 | ],
|
536 | });
|
537 | }
|
538 | else {
|
539 | // if (nullableNumber)
|
540 | context.report({
|
541 | node,
|
542 | messageId: 'conditionErrorNullableNumber',
|
543 | suggest: [
|
544 | {
|
545 | messageId: 'conditionFixCompareNullish',
|
546 | fix: util.getWrappingFixer({
|
547 | sourceCode,
|
548 | node,
|
549 | wrap: code => `${code} != null`,
|
550 | }),
|
551 | },
|
552 | {
|
553 | messageId: 'conditionFixDefaultZero',
|
554 | fix: util.getWrappingFixer({
|
555 | sourceCode,
|
556 | node,
|
557 | wrap: code => `${code} ?? 0`,
|
558 | }),
|
559 | },
|
560 | {
|
561 | messageId: 'conditionFixCastBoolean',
|
562 | fix: util.getWrappingFixer({
|
563 | sourceCode,
|
564 | node,
|
565 | wrap: code => `Boolean(${code})`,
|
566 | }),
|
567 | },
|
568 | ],
|
569 | });
|
570 | }
|
571 | }
|
572 | return;
|
573 | }
|
574 | // object
|
575 | if (is('object')) {
|
576 | // condition is always true
|
577 | context.report({ node, messageId: 'conditionErrorObject' });
|
578 | return;
|
579 | }
|
580 | // nullable object
|
581 | if (is('nullish', 'object')) {
|
582 | if (!options.allowNullableObject) {
|
583 | if (isLogicalNegationExpression(node.parent)) {
|
584 | // if (!nullableObject)
|
585 | context.report({
|
586 | node,
|
587 | messageId: 'conditionErrorNullableObject',
|
588 | fix: util.getWrappingFixer({
|
589 | sourceCode,
|
590 | node: node.parent,
|
591 | innerNode: node,
|
592 | wrap: code => `${code} == null`,
|
593 | }),
|
594 | });
|
595 | }
|
596 | else {
|
597 | // if (nullableObject)
|
598 | context.report({
|
599 | node,
|
600 | messageId: 'conditionErrorNullableObject',
|
601 | fix: util.getWrappingFixer({
|
602 | sourceCode,
|
603 | node,
|
604 | wrap: code => `${code} != null`,
|
605 | }),
|
606 | });
|
607 | }
|
608 | }
|
609 | return;
|
610 | }
|
611 | // any
|
612 | if (is('any')) {
|
613 | if (!options.allowAny) {
|
614 | context.report({
|
615 | node,
|
616 | messageId: 'conditionErrorAny',
|
617 | suggest: [
|
618 | {
|
619 | messageId: 'conditionFixCastBoolean',
|
620 | fix: util.getWrappingFixer({
|
621 | sourceCode,
|
622 | node,
|
623 | wrap: code => `Boolean(${code})`,
|
624 | }),
|
625 | },
|
626 | ],
|
627 | });
|
628 | }
|
629 | return;
|
630 | }
|
631 | // other
|
632 | context.report({ node, messageId: 'conditionErrorOther' });
|
633 | }
|
634 | /**
|
635 | * Check union variants for the types we care about
|
636 | */
|
637 | function inspectVariantTypes(types) {
|
638 | const variantTypes = new Set();
|
639 | if (types.some(type => tsutils.isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.VoidLike))) {
|
640 | variantTypes.add('nullish');
|
641 | }
|
642 | const booleans = types.filter(type => tsutils.isTypeFlagSet(type, ts.TypeFlags.BooleanLike));
|
643 | // If incoming type is either "true" or "false", there will be one type
|
644 | // object with intrinsicName set accordingly
|
645 | // If incoming type is boolean, there will be two type objects with
|
646 | // intrinsicName set "true" and "false" each because of tsutils.unionTypeParts()
|
647 | if (booleans.length === 1) {
|
648 | tsutils.isBooleanLiteralType(booleans[0], true)
|
649 | ? variantTypes.add('truthy boolean')
|
650 | : variantTypes.add('boolean');
|
651 | }
|
652 | else if (booleans.length === 2) {
|
653 | variantTypes.add('boolean');
|
654 | }
|
655 | const strings = types.filter(type => tsutils.isTypeFlagSet(type, ts.TypeFlags.StringLike));
|
656 | if (strings.length) {
|
657 | if (strings.some(type => type.isStringLiteral() && type.value !== '')) {
|
658 | variantTypes.add('truthy string');
|
659 | }
|
660 | else {
|
661 | variantTypes.add('string');
|
662 | }
|
663 | }
|
664 | const numbers = types.filter(type => tsutils.isTypeFlagSet(type, ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike));
|
665 | if (numbers.length) {
|
666 | if (numbers.some(type => type.isNumberLiteral() && type.value !== 0)) {
|
667 | variantTypes.add('truthy number');
|
668 | }
|
669 | else {
|
670 | variantTypes.add('number');
|
671 | }
|
672 | }
|
673 | if (types.some(type => !tsutils.isTypeFlagSet(type, ts.TypeFlags.Null |
|
674 | ts.TypeFlags.Undefined |
|
675 | ts.TypeFlags.VoidLike |
|
676 | ts.TypeFlags.BooleanLike |
|
677 | ts.TypeFlags.StringLike |
|
678 | ts.TypeFlags.NumberLike |
|
679 | ts.TypeFlags.BigIntLike |
|
680 | ts.TypeFlags.TypeParameter |
|
681 | ts.TypeFlags.Any |
|
682 | ts.TypeFlags.Unknown |
|
683 | ts.TypeFlags.Never))) {
|
684 | variantTypes.add('object');
|
685 | }
|
686 | if (types.some(type => util.isTypeFlagSet(type, ts.TypeFlags.TypeParameter |
|
687 | ts.TypeFlags.Any |
|
688 | ts.TypeFlags.Unknown))) {
|
689 | variantTypes.add('any');
|
690 | }
|
691 | if (types.some(type => tsutils.isTypeFlagSet(type, ts.TypeFlags.Never))) {
|
692 | variantTypes.add('never');
|
693 | }
|
694 | return variantTypes;
|
695 | }
|
696 | },
|
697 | });
|
698 | function isLogicalNegationExpression(node) {
|
699 | return node.type === utils_1.AST_NODE_TYPES.UnaryExpression && node.operator === '!';
|
700 | }
|
701 | function isArrayLengthExpression(node, typeChecker, parserServices) {
|
702 | if (node.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
|
703 | return false;
|
704 | }
|
705 | if (node.computed) {
|
706 | return false;
|
707 | }
|
708 | if (node.property.name !== 'length') {
|
709 | return false;
|
710 | }
|
711 | const objectTsNode = parserServices.esTreeNodeToTSNodeMap.get(node.object);
|
712 | const objectType = util.getConstrainedTypeAtLocation(typeChecker, objectTsNode);
|
713 | return util.isTypeArrayTypeOrUnionOfArrayTypes(objectType, typeChecker);
|
714 | }
|
715 | //# sourceMappingURL=strict-boolean-expressions.js.map |
\ | No newline at end of file |