eslint-plugin-ts-std-forge 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/rule-types.d.mts +3 -2
- package/dist/rule-types.d.mts.map +1 -1
- package/dist/rule-types.mjs.map +1 -1
- package/dist/rules/ast-utils.d.mts +8 -5
- package/dist/rules/ast-utils.d.mts.map +1 -1
- package/dist/rules/ast-utils.mjs +14 -13
- package/dist/rules/ast-utils.mjs.map +1 -1
- package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -1
- package/dist/rules/prefer-is-non-null-object.mjs +113 -38
- package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.mjs +95 -34
- package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
- package/dist/rules/prefer-safe-array-length-guard.d.mts.map +1 -1
- package/dist/rules/prefer-safe-array-length-guard.mjs +4 -10
- package/dist/rules/prefer-safe-array-length-guard.mjs.map +1 -1
- package/dist/rules/prefer-safe-number-parse-integer.mjs +4 -3
- package/dist/rules/prefer-safe-number-parse-integer.mjs.map +1 -1
- package/dist/rules/prefer-safe-number-parse.mjs +4 -3
- package/dist/rules/prefer-safe-number-parse.mjs.map +1 -1
- package/package.json +10 -10
- package/src/rule-types.mts +3 -2
- package/src/rules/ast-utils.mts +20 -21
- package/src/rules/prefer-is-non-null-object.mts +152 -49
- package/src/rules/prefer-is-non-null-object.test.mts +107 -0
- package/src/rules/prefer-is-record-and-has-key.mts +141 -53
- package/src/rules/prefer-is-record-and-has-key.test.mts +116 -29
- package/src/rules/prefer-safe-array-length-guard.mts +11 -16
- package/src/rules/prefer-safe-array-length-guard.test.mts +19 -5
- package/src/rules/prefer-safe-number-parse-integer.mts +8 -8
- package/src/rules/prefer-safe-number-parse-integer.test.mts +19 -5
- package/src/rules/prefer-safe-number-parse.mts +8 -8
- package/src/rules/prefer-safe-number-parse.test.mts +19 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-safe-array-length-guard.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-array-length-guard.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"prefer-safe-array-length-guard.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-array-length-guard.mts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
|
|
2
2
|
import { buildNamedImportFix, getArgumentText, getImportDeclarations, getNamedImports, skipTypeWrappers, } from 'eslint-rule-forge';
|
|
3
3
|
import * as ts from 'typescript';
|
|
4
|
-
import {
|
|
4
|
+
import { isIdentifierNamed, typeHoldsAtEveryLayer } from './ast-utils.mjs';
|
|
5
5
|
import { TS_STD_FORGE_MODULE } from './constants.mjs';
|
|
6
6
|
|
|
7
7
|
|
|
@@ -74,8 +74,9 @@ export const preferSafeNumberParseInteger = {
|
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if (!typeHoldsAtEveryLayer(sourceCode, firstArg, isStringType)) {
|
|
79
80
|
return;
|
|
80
81
|
}
|
|
81
82
|
mut_nodesToFix.push({ node, argExpression: firstArg });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-safe-number-parse-integer.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse-integer.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"prefer-safe-number-parse-integer.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse-integer.mts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
|
|
2
2
|
import { buildNamedImportFix, getArgumentText, getImportDeclarations, getNamedImports, skipTypeWrappers, } from 'eslint-rule-forge';
|
|
3
3
|
import * as ts from 'typescript';
|
|
4
|
-
import {
|
|
4
|
+
import { isIdentifierNamed, typeHoldsAtEveryLayer } from './ast-utils.mjs';
|
|
5
5
|
import { TS_STD_FORGE_MODULE } from './constants.mjs';
|
|
6
6
|
|
|
7
7
|
|
|
@@ -75,8 +75,9 @@ export const preferSafeNumberParse = {
|
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if (!typeHoldsAtEveryLayer(sourceCode, firstArg, isStringType)) {
|
|
80
81
|
return;
|
|
81
82
|
}
|
|
82
83
|
mut_nodesToFix.push({ node, argExpression: firstArg });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-safe-number-parse.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"prefer-safe-number-parse.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse.mts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-ts-std-forge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "ESLint rules that steer code toward ts-std-forge idioms.",
|
|
6
6
|
"keywords": [
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@typescript-eslint/utils": "^8.71.1",
|
|
38
|
-
"eslint-rule-forge": "^0.
|
|
38
|
+
"eslint-rule-forge": "^0.2.0",
|
|
39
39
|
"ts-std-forge": "0.5.1",
|
|
40
40
|
"ts-type-forge": "~9.2.2"
|
|
41
41
|
},
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"@typescript-eslint/parser": "8.71.1",
|
|
44
44
|
"@typescript-eslint/rule-tester": "8.71.1",
|
|
45
45
|
"dedent": "1.7.2",
|
|
46
|
-
"eslint-config-typed": "5.
|
|
47
|
-
"eslint-plugin-ts-fortress": "1.3.
|
|
48
|
-
"eslint-plugin-ts-type-forge": "0.8.
|
|
46
|
+
"eslint-config-typed": "5.19.0",
|
|
47
|
+
"eslint-plugin-ts-fortress": "1.3.4",
|
|
48
|
+
"eslint-plugin-ts-type-forge": "0.8.9",
|
|
49
49
|
"prettier": "3.9.9",
|
|
50
50
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
51
51
|
"prettier-plugin-packagejson": "3.0.2",
|
|
@@ -66,15 +66,15 @@
|
|
|
66
66
|
"scripts": {
|
|
67
67
|
"build": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/build.mts",
|
|
68
68
|
"check:ext": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-ext.mts",
|
|
69
|
-
"check:lint": "oxlint . && eslint .",
|
|
70
|
-
"check:test": "pnpm run z:vitest run",
|
|
71
|
-
"check:test:cov": "pnpm run z:vitest run --coverage",
|
|
72
|
-
"check:types": "node ../../node_modules/typescript-native/bin/tsc --noEmit",
|
|
69
|
+
"check:lint": "node ../../tools/scripts/cmd/assert-deps-built.mts && oxlint . && eslint .",
|
|
70
|
+
"check:test": "node ../../tools/scripts/cmd/assert-deps-built.mts && pnpm run z:vitest run",
|
|
71
|
+
"check:test:cov": "node ../../tools/scripts/cmd/assert-deps-built.mts && pnpm run z:vitest run --coverage",
|
|
72
|
+
"check:types": "node ../../tools/scripts/cmd/assert-deps-built.mts && node ../../node_modules/typescript-native/bin/tsc --noEmit",
|
|
73
73
|
"doc": "pnpm run gen:readme",
|
|
74
74
|
"fix:fmt": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-uncommitted.mts --cwd .",
|
|
75
75
|
"fix:fmt:diff": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-diff-from.mts origin/main --cwd .",
|
|
76
76
|
"fix:fmt:full": "pnpm run z:prettier .",
|
|
77
|
-
"fix:lint": "oxlint . --fix && eslint . --fix",
|
|
77
|
+
"fix:lint": "node ../../tools/scripts/cmd/assert-deps-built.mts && oxlint . --fix && eslint . --fix",
|
|
78
78
|
"fmt": "pnpm run fix:fmt",
|
|
79
79
|
"gen": "run-s gen:rule-types gen:readme",
|
|
80
80
|
"gen:readme": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/embed-examples.mts",
|
package/src/rule-types.mts
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import type { Linter } from 'eslint';
|
|
7
7
|
import { expectType } from 'ts-std-forge';
|
|
8
|
+
import type { ReadonlyRecord } from 'ts-type-forge';
|
|
8
9
|
import type { tsStdForgeRules } from './rules/index.mjs';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
* @description Replace `typeof u === "object" && u !== null
|
|
12
|
+
* @description Replace `typeof u === "object" && u !== null`, in either order and anywhere in an `&&` chain, with `isNonNullObject(u)` from ts-std-forge.
|
|
12
13
|
*
|
|
13
14
|
* ```md
|
|
14
15
|
* | key | value |
|
|
@@ -106,7 +107,7 @@ export type EslintTsStdForgeRules = Readonly<{
|
|
|
106
107
|
'ts-std-forge/prefer-safe-number-parse-integer': PreferSafeNumberParseInteger.RuleEntry;
|
|
107
108
|
}>;
|
|
108
109
|
|
|
109
|
-
export type EslintTsStdForgeRulesOption =
|
|
110
|
+
export type EslintTsStdForgeRulesOption = ReadonlyRecord<never, never>;
|
|
110
111
|
|
|
111
112
|
// If this assertion fails to type-check, this generated file has drifted from
|
|
112
113
|
// the actual plugin rules — re-run `pnpm run gen:rule-types`.
|
package/src/rules/ast-utils.mts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type TSESLint,
|
|
4
4
|
type TSESTree,
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
|
-
import {
|
|
6
|
+
import { skipTypeWrappers, typeWrapperLayers } from 'eslint-rule-forge';
|
|
7
7
|
import type * as ts from 'typescript';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -23,36 +23,35 @@ export const isIdentifierNamed = (
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* information. A
|
|
29
|
-
*
|
|
26
|
+
* Whether `holds` is true of the type of every layer of type wrappers around
|
|
27
|
+
* `node` (see {@link typeWrapperLayers}), outermost to innermost; `false`
|
|
28
|
+
* without type information. A widening cast (`s as string | number`) is the
|
|
29
|
+
* author saying the value may be more than it is, and is respected; a
|
|
30
|
+
* narrowing one (`s!`, `v as unknown as string`) cannot vouch for what the
|
|
31
|
+
* value's own type does not, and the fix, which keeps the wrapper, has to
|
|
32
|
+
* type-check at every layer too.
|
|
30
33
|
*/
|
|
31
|
-
export const
|
|
34
|
+
export const typeHoldsAtEveryLayer = (
|
|
32
35
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
33
36
|
sourceCode: TSESLint.SourceCode,
|
|
34
37
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
35
38
|
node: TSESTree.Node,
|
|
36
|
-
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
40
|
+
holds: (type: ts.Type, typeChecker: ts.TypeChecker) => boolean,
|
|
41
|
+
): boolean => {
|
|
37
42
|
const services = sourceCode.parserServices;
|
|
38
43
|
|
|
39
44
|
const checker = services?.program?.getTypeChecker();
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (checker === undefined || tsNode === undefined) {
|
|
44
|
-
return undefined;
|
|
46
|
+
if (checker === undefined) {
|
|
47
|
+
return false;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
return typeWrapperLayers(node).every((layer) => {
|
|
51
|
+
const tsNode = services?.esTreeNodeToTSNodeMap?.get(layer);
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
return (
|
|
54
|
+
tsNode !== undefined && holds(checker.getTypeAtLocation(tsNode), checker)
|
|
55
|
+
);
|
|
56
|
+
});
|
|
50
57
|
};
|
|
51
|
-
|
|
52
|
-
const hasNonNullAssertion = (
|
|
53
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
54
|
-
node: TSESTree.Node,
|
|
55
|
-
): boolean =>
|
|
56
|
-
isTypeWrapper(node) &&
|
|
57
|
-
(node.type === AST_NODE_TYPES.TSNonNullExpression ||
|
|
58
|
-
hasNonNullAssertion(node.expression));
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
buildNamedImportFix,
|
|
8
8
|
getImportDeclarations,
|
|
9
9
|
getNamedImports,
|
|
10
|
+
getRangeWithParens,
|
|
11
|
+
parenthesesDepth,
|
|
10
12
|
skipTypeWrappers,
|
|
11
13
|
} from 'eslint-rule-forge';
|
|
12
14
|
import { TS_STD_FORGE_MODULE } from './constants.mjs';
|
|
@@ -20,7 +22,7 @@ export const preferIsNonNullObject: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
20
22
|
type: 'suggestion',
|
|
21
23
|
docs: {
|
|
22
24
|
description:
|
|
23
|
-
'Replace `typeof u === "object" && u !== null
|
|
25
|
+
'Replace `typeof u === "object" && u !== null`, in either order and anywhere in an `&&` chain, with `isNonNullObject(u)` from ts-std-forge.',
|
|
24
26
|
},
|
|
25
27
|
fixable: 'code',
|
|
26
28
|
schema: [],
|
|
@@ -40,20 +42,16 @@ export const preferIsNonNullObject: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
40
42
|
TS_STD_FORGE_MODULE,
|
|
41
43
|
);
|
|
42
44
|
|
|
43
|
-
const
|
|
44
|
-
node: TSESTree.LogicalExpression;
|
|
45
|
-
identifierName: string;
|
|
46
|
-
}[] = [];
|
|
45
|
+
const mut_pairsToFix: NonNullObjectPair[] = [];
|
|
47
46
|
|
|
48
47
|
return {
|
|
49
48
|
LogicalExpression: (node) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (identifierName === undefined) {
|
|
49
|
+
// A chain is read once, from its outermost `&&`.
|
|
50
|
+
if (node.operator !== '&&' || isChainOperand(sourceCode, node)) {
|
|
53
51
|
return;
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
mut_pairsToFix.push(...findNonNullObjectPairs(sourceCode, node));
|
|
57
55
|
},
|
|
58
56
|
'Program:exit': () => {
|
|
59
57
|
const hasIsNonNullObjectImport =
|
|
@@ -66,10 +64,13 @@ export const preferIsNonNullObject: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
66
64
|
// with it.
|
|
67
65
|
for (const [
|
|
68
66
|
index,
|
|
69
|
-
{
|
|
70
|
-
] of
|
|
67
|
+
{ range, identifierName },
|
|
68
|
+
] of mut_pairsToFix.entries()) {
|
|
71
69
|
context.report({
|
|
72
|
-
|
|
70
|
+
loc: {
|
|
71
|
+
start: sourceCode.getLocFromIndex(range[0]),
|
|
72
|
+
end: sourceCode.getLocFromIndex(range[1]),
|
|
73
|
+
},
|
|
73
74
|
messageId: 'useIsNonNullObject',
|
|
74
75
|
fix: (fixer) => {
|
|
75
76
|
const replacement = `isNonNullObject(${identifierName})`;
|
|
@@ -81,7 +82,10 @@ export const preferIsNonNullObject: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
81
82
|
])
|
|
82
83
|
: [];
|
|
83
84
|
|
|
84
|
-
return [
|
|
85
|
+
return [
|
|
86
|
+
...importFixes,
|
|
87
|
+
fixer.replaceTextRange(range, replacement),
|
|
88
|
+
];
|
|
85
89
|
},
|
|
86
90
|
});
|
|
87
91
|
}
|
|
@@ -91,41 +95,124 @@ export const preferIsNonNullObject: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
91
95
|
defaultOptions: [],
|
|
92
96
|
} as const;
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
/**
|
|
99
|
+
* A `typeof u === 'object'` and a `u !== null` next to each other in an `&&`
|
|
100
|
+
* chain, as the source range from the first to the second (parentheses
|
|
101
|
+
* included) and the name of `u`.
|
|
102
|
+
*/
|
|
103
|
+
type NonNullObjectPair = Readonly<{
|
|
104
|
+
range: TSESTree.Range;
|
|
105
|
+
identifierName: string;
|
|
106
|
+
}>;
|
|
96
107
|
|
|
97
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Whether `node` is an operand of an enclosing `&&` chain: a `&&` written
|
|
110
|
+
* directly under another one, without parentheses.
|
|
111
|
+
*/
|
|
112
|
+
const isChainOperand = (
|
|
113
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
114
|
+
sourceCode: TSESLint.SourceCode,
|
|
98
115
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
99
116
|
node: TSESTree.LogicalExpression,
|
|
100
|
-
):
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
): boolean =>
|
|
118
|
+
node.parent.type === AST_NODE_TYPES.LogicalExpression &&
|
|
119
|
+
node.parent.operator === '&&' &&
|
|
120
|
+
parenthesesDepth(sourceCode, node) === 0;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Every non-overlapping pair in the `&&` chain `node` heads, left to right. The
|
|
124
|
+
* two halves may come in either order, and the pair may have operands before
|
|
125
|
+
* and after it: `a && u !== null && typeof u === 'object' && b`. They have to
|
|
126
|
+
* be next to each other, so that the pair is one range of the source.
|
|
127
|
+
*/
|
|
128
|
+
const findNonNullObjectPairs = (
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
130
|
+
sourceCode: TSESLint.SourceCode,
|
|
131
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
132
|
+
node: TSESTree.LogicalExpression,
|
|
133
|
+
): readonly NonNullObjectPair[] => {
|
|
134
|
+
const operands = chainOperands(sourceCode, node);
|
|
135
|
+
|
|
136
|
+
const pairsFrom = (index: number): readonly NonNullObjectPair[] => {
|
|
137
|
+
const first = operands[index];
|
|
138
|
+
|
|
139
|
+
const second = operands[index + 1];
|
|
140
|
+
|
|
141
|
+
if (first === undefined || second === undefined) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const identifierName =
|
|
146
|
+
pairIdentifierName(first, second) ?? pairIdentifierName(second, first);
|
|
147
|
+
|
|
148
|
+
return identifierName === undefined
|
|
149
|
+
? pairsFrom(index + 1)
|
|
150
|
+
: [
|
|
151
|
+
{
|
|
152
|
+
range: [
|
|
153
|
+
getRangeWithParens(sourceCode, first)[0],
|
|
154
|
+
getRangeWithParens(sourceCode, second)[1],
|
|
155
|
+
],
|
|
156
|
+
identifierName,
|
|
157
|
+
},
|
|
158
|
+
...pairsFrom(index + 2),
|
|
159
|
+
];
|
|
160
|
+
};
|
|
104
161
|
|
|
105
|
-
|
|
106
|
-
|
|
162
|
+
return pairsFrom(0);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The operands of the `&&` chain `node`, left to right. A parenthesized `&&`
|
|
167
|
+
* is one operand: its halves cannot be replaced without its parentheses.
|
|
168
|
+
*/
|
|
169
|
+
const chainOperands = (
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
171
|
+
sourceCode: TSESLint.SourceCode,
|
|
172
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
173
|
+
node: TSESTree.LogicalExpression,
|
|
174
|
+
): readonly TSESTree.Expression[] =>
|
|
175
|
+
[node.left, node.right].flatMap((operand) =>
|
|
176
|
+
operand.type === AST_NODE_TYPES.LogicalExpression &&
|
|
177
|
+
operand.operator === '&&' &&
|
|
178
|
+
parenthesesDepth(sourceCode, operand) === 0
|
|
179
|
+
? chainOperands(sourceCode, operand)
|
|
180
|
+
: [operand],
|
|
107
181
|
);
|
|
108
182
|
|
|
109
|
-
|
|
110
|
-
|
|
183
|
+
// Every operand below is read through its type wrappers: `typeof (u satisfies
|
|
184
|
+
// unknown) === 'object' && u! !== null` is the same check.
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The name of `u` when `typeofCheck` is `typeof u === 'object'` and
|
|
188
|
+
* `nullCheck` is `u !== null` for the same `u`.
|
|
189
|
+
*/
|
|
190
|
+
const pairIdentifierName = (
|
|
191
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
192
|
+
typeofCheck: TSESTree.Expression,
|
|
193
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
194
|
+
nullCheck: TSESTree.Expression,
|
|
195
|
+
): string | undefined => {
|
|
196
|
+
const identifierName = getTypeofObjectIdentifierName(
|
|
197
|
+
skipTypeWrappers(typeofCheck),
|
|
111
198
|
);
|
|
112
199
|
|
|
113
|
-
return
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
?
|
|
117
|
-
:
|
|
200
|
+
return identifierName !== undefined &&
|
|
201
|
+
identifierName ===
|
|
202
|
+
getNonNullCheckIdentifierName(skipTypeWrappers(nullCheck))
|
|
203
|
+
? identifierName
|
|
204
|
+
: undefined;
|
|
118
205
|
};
|
|
119
206
|
|
|
207
|
+
/** `u` of `typeof u === 'object'`, in either operand order. */
|
|
120
208
|
const getTypeofObjectIdentifierName = (
|
|
121
209
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
122
210
|
node: TSESTree.Node,
|
|
123
211
|
): string | undefined => {
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (node.operator !== '===') {
|
|
212
|
+
if (
|
|
213
|
+
node.type !== AST_NODE_TYPES.BinaryExpression ||
|
|
214
|
+
node.operator !== '==='
|
|
215
|
+
) {
|
|
129
216
|
return undefined;
|
|
130
217
|
}
|
|
131
218
|
|
|
@@ -133,31 +220,39 @@ const getTypeofObjectIdentifierName = (
|
|
|
133
220
|
|
|
134
221
|
const right = skipTypeWrappers(node.right);
|
|
135
222
|
|
|
223
|
+
const typeofSide = isLiteralOf(right, 'object')
|
|
224
|
+
? left
|
|
225
|
+
: isLiteralOf(left, 'object')
|
|
226
|
+
? right
|
|
227
|
+
: undefined;
|
|
228
|
+
|
|
136
229
|
if (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
right.type !== AST_NODE_TYPES.Literal ||
|
|
140
|
-
right.value !== 'object'
|
|
230
|
+
typeofSide?.type !== AST_NODE_TYPES.UnaryExpression ||
|
|
231
|
+
typeofSide.operator !== 'typeof'
|
|
141
232
|
) {
|
|
142
233
|
return undefined;
|
|
143
234
|
}
|
|
144
235
|
|
|
145
|
-
const argument = skipTypeWrappers(
|
|
236
|
+
const argument = skipTypeWrappers(typeofSide.argument);
|
|
146
237
|
|
|
147
238
|
return argument.type === AST_NODE_TYPES.Identifier
|
|
148
239
|
? argument.name
|
|
149
240
|
: undefined;
|
|
150
241
|
};
|
|
151
242
|
|
|
243
|
+
/**
|
|
244
|
+
* `u` of `u !== null`, in either operand order. `u != null` also excludes
|
|
245
|
+
* `undefined`, which `typeof u === 'object'` has already excluded, so it is
|
|
246
|
+
* the same check here.
|
|
247
|
+
*/
|
|
152
248
|
const getNonNullCheckIdentifierName = (
|
|
153
249
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
154
250
|
node: TSESTree.Node,
|
|
155
251
|
): string | undefined => {
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (node.operator !== '!==') {
|
|
252
|
+
if (
|
|
253
|
+
node.type !== AST_NODE_TYPES.BinaryExpression ||
|
|
254
|
+
(node.operator !== '!==' && node.operator !== '!=')
|
|
255
|
+
) {
|
|
161
256
|
return undefined;
|
|
162
257
|
}
|
|
163
258
|
|
|
@@ -165,9 +260,17 @@ const getNonNullCheckIdentifierName = (
|
|
|
165
260
|
|
|
166
261
|
const right = skipTypeWrappers(node.right);
|
|
167
262
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
263
|
+
const checked = isLiteralOf(right, null)
|
|
264
|
+
? left
|
|
265
|
+
: isLiteralOf(left, null)
|
|
266
|
+
? right
|
|
267
|
+
: undefined;
|
|
268
|
+
|
|
269
|
+
return checked?.type === AST_NODE_TYPES.Identifier ? checked.name : undefined;
|
|
173
270
|
};
|
|
271
|
+
|
|
272
|
+
const isLiteralOf = (
|
|
273
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
274
|
+
node: TSESTree.Node,
|
|
275
|
+
value: string | null,
|
|
276
|
+
): boolean => node.type === AST_NODE_TYPES.Literal && node.value === value;
|
|
@@ -106,3 +106,110 @@ describe('prefer-is-non-null-object through type wrappers', () => {
|
|
|
106
106
|
],
|
|
107
107
|
});
|
|
108
108
|
});
|
|
109
|
+
|
|
110
|
+
describe('prefer-is-non-null-object in other forms', () => {
|
|
111
|
+
ruleTester.run('prefer-is-non-null-object', preferIsNonNullObject, {
|
|
112
|
+
valid: [
|
|
113
|
+
{
|
|
114
|
+
name: 'the two halves joined by `||`',
|
|
115
|
+
code: dedent`
|
|
116
|
+
const ok = typeof u === 'object' || u !== null;
|
|
117
|
+
`,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'the two halves not next to each other in the chain',
|
|
121
|
+
code: dedent`
|
|
122
|
+
declare const a: boolean;
|
|
123
|
+
const ok = typeof u === 'object' && a && u !== null;
|
|
124
|
+
`,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'an undefined check is not a null check',
|
|
128
|
+
code: dedent`
|
|
129
|
+
const ok = u !== undefined && typeof u === 'object';
|
|
130
|
+
`,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'the halves split by parentheses',
|
|
134
|
+
code: dedent`
|
|
135
|
+
declare const b: boolean;
|
|
136
|
+
const ok = typeof u === 'object' && (u !== null && b);
|
|
137
|
+
`,
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
invalid: [
|
|
141
|
+
{
|
|
142
|
+
name: 'the halves in reverse order',
|
|
143
|
+
code: dedent`
|
|
144
|
+
const ok = u !== null && typeof u === 'object';
|
|
145
|
+
`,
|
|
146
|
+
output: dedent`
|
|
147
|
+
import { isNonNullObject } from 'ts-std-forge';
|
|
148
|
+
const ok = isNonNullObject(u);
|
|
149
|
+
`,
|
|
150
|
+
errors: [{ messageId: 'useIsNonNullObject' }],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'each comparison with its operands swapped',
|
|
154
|
+
code: dedent`
|
|
155
|
+
const ok = 'object' === typeof u && null !== u;
|
|
156
|
+
`,
|
|
157
|
+
output: dedent`
|
|
158
|
+
import { isNonNullObject } from 'ts-std-forge';
|
|
159
|
+
const ok = isNonNullObject(u);
|
|
160
|
+
`,
|
|
161
|
+
errors: [{ messageId: 'useIsNonNullObject' }],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: '`!= null`, which typeof has already made the same check',
|
|
165
|
+
code: dedent`
|
|
166
|
+
const ok = typeof u === 'object' && u != null;
|
|
167
|
+
`,
|
|
168
|
+
output: dedent`
|
|
169
|
+
import { isNonNullObject } from 'ts-std-forge';
|
|
170
|
+
const ok = isNonNullObject(u);
|
|
171
|
+
`,
|
|
172
|
+
errors: [{ messageId: 'useIsNonNullObject' }],
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'the pair at the start, the end and the middle of a longer chain',
|
|
176
|
+
code: dedent`
|
|
177
|
+
declare const a: boolean;
|
|
178
|
+
declare const b: boolean;
|
|
179
|
+
const x = typeof u === 'object' && u !== null && 'k' in u;
|
|
180
|
+
const y = a && typeof u === 'object' && u !== null;
|
|
181
|
+
const z = a && u !== null && (typeof u === 'object') && b;
|
|
182
|
+
`,
|
|
183
|
+
output: dedent`
|
|
184
|
+
import { isNonNullObject } from 'ts-std-forge';
|
|
185
|
+
declare const a: boolean;
|
|
186
|
+
declare const b: boolean;
|
|
187
|
+
const x = isNonNullObject(u) && 'k' in u;
|
|
188
|
+
const y = a && isNonNullObject(u);
|
|
189
|
+
const z = a && isNonNullObject(u) && b;
|
|
190
|
+
`,
|
|
191
|
+
errors: [
|
|
192
|
+
{ messageId: 'useIsNonNullObject' },
|
|
193
|
+
{ messageId: 'useIsNonNullObject' },
|
|
194
|
+
{ messageId: 'useIsNonNullObject' },
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: 'two pairs in one chain',
|
|
199
|
+
code: dedent`
|
|
200
|
+
declare const v: unknown;
|
|
201
|
+
const ok = typeof u === 'object' && u !== null && v !== null && typeof v === 'object';
|
|
202
|
+
`,
|
|
203
|
+
output: dedent`
|
|
204
|
+
import { isNonNullObject } from 'ts-std-forge';
|
|
205
|
+
declare const v: unknown;
|
|
206
|
+
const ok = isNonNullObject(u) && isNonNullObject(v);
|
|
207
|
+
`,
|
|
208
|
+
errors: [
|
|
209
|
+
{ messageId: 'useIsNonNullObject' },
|
|
210
|
+
{ messageId: 'useIsNonNullObject' },
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
});
|
|
215
|
+
});
|