eslint-plugin-ts-data-forge 0.6.1 → 0.7.0
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/README.md +1 -0
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/plugin.d.mts +2 -0
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +45 -41
- package/dist/plugin.mjs.map +1 -1
- package/dist/rule-types.d.mts +15 -0
- package/dist/rule-types.d.mts.map +1 -1
- package/dist/rule-types.mjs +6 -2
- package/dist/rule-types.mjs.map +1 -1
- package/dist/rules/ast-utils.mjs +107 -50
- package/dist/rules/ast-utils.mjs.map +1 -1
- package/dist/rules/branded-number-types.mjs +41 -37
- package/dist/rules/branded-number-types.mjs.map +1 -1
- package/dist/rules/constants.mjs +2 -4
- package/dist/rules/constants.mjs.map +1 -1
- package/dist/rules/import-utils.mjs +80 -56
- package/dist/rules/import-utils.mjs.map +1 -1
- package/dist/rules/index.mjs +2 -2
- package/dist/rules/index.mjs.map +1 -1
- package/dist/rules/no-side-effect-import.mjs +46 -34
- package/dist/rules/no-side-effect-import.mjs.map +1 -1
- package/dist/rules/no-unnecessary-type-guard.mjs +376 -265
- package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-array.mjs +64 -52
- package/dist/rules/prefer-arr-is-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-bounded-length-array.mjs +91 -85
- package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-fixed-length-array.mjs +92 -83
- package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-max-length-array.mjs +92 -80
- package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-min-length-array.mjs +92 -80
- package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-non-empty.mjs +85 -77
- package/dist/rules/prefer-arr-is-non-empty.mjs.map +1 -1
- package/dist/rules/prefer-arr-scan.d.mts +96 -0
- package/dist/rules/prefer-arr-scan.d.mts.map +1 -0
- package/dist/rules/prefer-arr-scan.mjs +482 -0
- package/dist/rules/prefer-arr-scan.mjs.map +1 -0
- package/dist/rules/prefer-arr-sum.mjs +176 -132
- package/dist/rules/prefer-arr-sum.mjs.map +1 -1
- package/dist/rules/prefer-as-int.mjs +77 -71
- package/dist/rules/prefer-as-int.mjs.map +1 -1
- package/dist/rules/prefer-canonical-array-slicing.mjs +503 -343
- package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
- package/dist/rules/prefer-canonical-length-cast.mjs +217 -144
- package/dist/rules/prefer-canonical-length-cast.mjs.map +1 -1
- package/dist/rules/prefer-canonical-length-guard.mjs +160 -109
- package/dist/rules/prefer-canonical-length-guard.mjs.map +1 -1
- package/dist/rules/prefer-comparison-over-nullish-guard.mjs +89 -71
- package/dist/rules/prefer-comparison-over-nullish-guard.mjs.map +1 -1
- package/dist/rules/prefer-is-non-null-object.mjs +91 -72
- package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.mjs +148 -105
- package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
- package/dist/rules/prefer-num-safe-parse-float.mjs +101 -77
- package/dist/rules/prefer-num-safe-parse-float.mjs.map +1 -1
- package/dist/rules/prefer-num-safe-parse-int.mjs +103 -81
- package/dist/rules/prefer-num-safe-parse-int.mjs.map +1 -1
- package/dist/rules/prefer-obj-over-entries-round-trip.mjs +299 -194
- package/dist/rules/prefer-obj-over-entries-round-trip.mjs.map +1 -1
- package/dist/rules/prefer-range-for-loop.mjs +121 -99
- package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
- package/dist/rules/rules.d.mts +1 -0
- package/dist/rules/rules.d.mts.map +1 -1
- package/dist/rules/rules.mjs +19 -20
- package/dist/rules/rules.mjs.map +1 -1
- package/dist/types.mjs +2 -2
- package/dist/types.mjs.map +1 -1
- package/package.json +6 -7
- package/src/plugin.mts +1 -0
- package/src/rule-types.mts +16 -0
- package/src/rules/prefer-arr-scan.mts +693 -0
- package/src/rules/prefer-arr-scan.test.mts +295 -0
- package/src/rules/rules.mts +2 -0
|
@@ -1,290 +1,401 @@
|
|
|
1
|
-
import { ESLintUtils,
|
|
1
|
+
import { AST_NODE_TYPES, ESLintUtils, } from '@typescript-eslint/utils';
|
|
2
2
|
import * as ts from 'typescript';
|
|
3
|
-
import {
|
|
3
|
+
import { buildCalleeResolver, buildImportFixes, getImportedLocalName, getTsDataForgeImport, } from './import-utils.mjs';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const BRAND_MARKER_PROPERTY = 'TSTypeForgeInternals--edd2f9ce-7ca5-45b0-9d1a-bd61b9b5d9c3';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const MIN_LENGTH_BRAND_KEY = 'MinLength';
|
|
7
17
|
const NULLISH_REPLACEMENTS_EXCLUDE = {
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
undefined: 'isNotUndefined',
|
|
19
|
+
null: 'isNotNull',
|
|
10
20
|
};
|
|
11
21
|
const NULLISH_REPLACEMENTS_NARROW = {
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
undefined: 'isUndefined',
|
|
23
|
+
null: 'isNull',
|
|
14
24
|
};
|
|
15
25
|
const GUARD_SPECS = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
},
|
|
35
|
-
isNonNullish: {
|
|
36
|
-
kind: "excludeFrom",
|
|
37
|
-
atoms: ["null", "undefined"],
|
|
38
|
-
replacements: NULLISH_REPLACEMENTS_EXCLUDE
|
|
39
|
-
},
|
|
40
|
-
isNonEmptyString: { kind: "nonEmptyString" },
|
|
41
|
-
isRecord: { kind: "record" }
|
|
42
|
-
};
|
|
43
|
-
const DEFERRED_OR_OPAQUE_FLAGS = ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never | ts.TypeFlags.TypeParameter | ts.TypeFlags.IndexedAccess | ts.TypeFlags.Conditional | ts.TypeFlags.Substitution;
|
|
44
|
-
const noUnnecessaryTypeGuard = {
|
|
45
|
-
meta: {
|
|
46
|
-
type: "suggestion",
|
|
47
|
-
docs: {
|
|
48
|
-
description: "Detect ts-data-forge type guard calls that perform no narrowing (the argument type already satisfies, or can never satisfy, the guard)."
|
|
26
|
+
isUndefined: { kind: 'narrowTo', atoms: ['undefined'] },
|
|
27
|
+
isNotUndefined: { kind: 'excludeFrom', atoms: ['undefined'] },
|
|
28
|
+
isNull: { kind: 'narrowTo', atoms: ['null'] },
|
|
29
|
+
isNotNull: { kind: 'excludeFrom', atoms: ['null'] },
|
|
30
|
+
isBoolean: { kind: 'narrowTo', atoms: ['boolean'] },
|
|
31
|
+
isNotBoolean: { kind: 'excludeFrom', atoms: ['boolean'] },
|
|
32
|
+
isNumber: { kind: 'narrowTo', atoms: ['number'] },
|
|
33
|
+
isNotNumber: { kind: 'excludeFrom', atoms: ['number'] },
|
|
34
|
+
isString: { kind: 'narrowTo', atoms: ['string'] },
|
|
35
|
+
isNotString: { kind: 'excludeFrom', atoms: ['string'] },
|
|
36
|
+
isBigint: { kind: 'narrowTo', atoms: ['bigint'] },
|
|
37
|
+
isNotBigint: { kind: 'excludeFrom', atoms: ['bigint'] },
|
|
38
|
+
isSymbol: { kind: 'narrowTo', atoms: ['symbol'] },
|
|
39
|
+
isNotSymbol: { kind: 'excludeFrom', atoms: ['symbol'] },
|
|
40
|
+
isNullish: {
|
|
41
|
+
kind: 'narrowTo',
|
|
42
|
+
atoms: ['null', 'undefined'],
|
|
43
|
+
replacements: NULLISH_REPLACEMENTS_NARROW,
|
|
49
44
|
},
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
isNonNullish: {
|
|
46
|
+
kind: 'excludeFrom',
|
|
47
|
+
atoms: ['null', 'undefined'],
|
|
48
|
+
replacements: NULLISH_REPLACEMENTS_EXCLUDE,
|
|
49
|
+
},
|
|
50
|
+
isNonEmptyString: { kind: 'nonEmptyString' },
|
|
51
|
+
isRecord: { kind: 'record' },
|
|
52
|
+
};
|
|
53
|
+
const DEFERRED_OR_OPAQUE_FLAGS = ts.TypeFlags.Any |
|
|
54
|
+
ts.TypeFlags.Unknown |
|
|
55
|
+
ts.TypeFlags.Never |
|
|
56
|
+
ts.TypeFlags.TypeParameter |
|
|
57
|
+
ts.TypeFlags.IndexedAccess |
|
|
58
|
+
ts.TypeFlags.Conditional |
|
|
59
|
+
ts.TypeFlags.Substitution;
|
|
60
|
+
export const noUnnecessaryTypeGuard = {
|
|
61
|
+
meta: {
|
|
62
|
+
type: 'suggestion',
|
|
63
|
+
docs: {
|
|
64
|
+
description: 'Detect ts-data-forge type guard calls that perform no narrowing (the argument type already satisfies, or can never satisfy, the guard).',
|
|
60
65
|
},
|
|
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
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
104
|
-
node,
|
|
105
|
-
messageId: value ? "alwaysTrue" : "alwaysFalse",
|
|
106
|
-
data: { name },
|
|
107
|
-
fix: canFix ? (fixer) => fixer.replaceText(
|
|
108
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
109
|
-
node,
|
|
110
|
-
value ? "true" : "false"
|
|
111
|
-
) : void 0
|
|
112
|
-
});
|
|
113
|
-
};
|
|
114
|
-
const reportReplace = (node, from, to, isNamespace, propertyNode) => {
|
|
115
|
-
context.report({
|
|
116
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
117
|
-
node,
|
|
118
|
-
messageId: "replaceTypeGuard",
|
|
119
|
-
data: { from, to },
|
|
120
|
-
fix: buildReplaceFix(isNamespace, propertyNode, to)
|
|
121
|
-
});
|
|
122
|
-
};
|
|
123
|
-
return {
|
|
124
|
-
CallExpression: (node) => {
|
|
125
|
-
if (node.arguments.length !== 1) return;
|
|
126
|
-
const argument = node.arguments[0];
|
|
127
|
-
if (argument === void 0 || argument.type === AST_NODE_TYPES.SpreadElement) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const resolved = resolveGuard(node.callee);
|
|
131
|
-
if (resolved === void 0) return;
|
|
132
|
-
const { canonicalName, propertyNode, isNamespace } = resolved;
|
|
133
|
-
if (ignored.has(canonicalName)) return;
|
|
134
|
-
const spec = GUARD_SPECS[canonicalName];
|
|
135
|
-
if (spec === void 0) return;
|
|
136
|
-
const argTsNode = parserServices.esTreeNodeToTSNodeMap.get(argument);
|
|
137
|
-
const argType = checker.getTypeAtLocation(argTsNode);
|
|
138
|
-
const parts = collectUnionParts(argType);
|
|
139
|
-
if (parts === void 0) return;
|
|
140
|
-
const inputAtoms = new Set(parts.map(classifyAtom));
|
|
141
|
-
const hasMemberOutsideTarget = (atoms) => {
|
|
142
|
-
const targetSet = new Set(atoms);
|
|
143
|
-
return parts.some((p) => !targetSet.has(classifyAtom(p)));
|
|
144
|
-
};
|
|
145
|
-
switch (spec.kind) {
|
|
146
|
-
case "excludeFrom": {
|
|
147
|
-
const removable = spec.atoms.filter((a) => inputAtoms.has(a));
|
|
148
|
-
if (removable.length === 0) {
|
|
149
|
-
reportConstant(node, canonicalName, true, argument);
|
|
150
|
-
} else if (!hasMemberOutsideTarget(spec.atoms)) {
|
|
151
|
-
reportConstant(node, canonicalName, false, argument);
|
|
152
|
-
} else if (removable.length < spec.atoms.length) {
|
|
153
|
-
const replacement = spec.replacements?.[joinAtoms(removable)];
|
|
154
|
-
if (replacement !== void 0) {
|
|
155
|
-
reportReplace(
|
|
156
|
-
node,
|
|
157
|
-
canonicalName,
|
|
158
|
-
replacement,
|
|
159
|
-
isNamespace,
|
|
160
|
-
propertyNode
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
case "narrowTo": {
|
|
167
|
-
const present = spec.atoms.filter((a) => inputAtoms.has(a));
|
|
168
|
-
if (present.length === 0) {
|
|
169
|
-
reportConstant(node, canonicalName, false, argument);
|
|
170
|
-
} else if (!hasMemberOutsideTarget(spec.atoms)) {
|
|
171
|
-
reportConstant(node, canonicalName, true, argument);
|
|
172
|
-
} else if (present.length < spec.atoms.length) {
|
|
173
|
-
const replacement = spec.replacements?.[joinAtoms(present)];
|
|
174
|
-
if (replacement !== void 0) {
|
|
175
|
-
reportReplace(
|
|
176
|
-
node,
|
|
177
|
-
canonicalName,
|
|
178
|
-
replacement,
|
|
179
|
-
isNamespace,
|
|
180
|
-
propertyNode
|
|
181
|
-
);
|
|
182
|
-
}
|
|
66
|
+
fixable: 'code',
|
|
67
|
+
schema: [
|
|
68
|
+
{
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
ignore: {
|
|
72
|
+
type: 'array',
|
|
73
|
+
items: { type: 'string' },
|
|
74
|
+
description: 'Names of ts-data-forge guard functions to skip checking.',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
additionalProperties: false,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
messages: {
|
|
81
|
+
alwaysTrue: 'Unnecessary `{{name}}` call: the argument type guarantees it always returns `true`. Replace the call with `true`.',
|
|
82
|
+
alwaysFalse: 'Unnecessary `{{name}}` call: the argument type guarantees it always returns `false`. Replace the call with `false`.',
|
|
83
|
+
replaceTypeGuard: 'Unnecessary `{{from}}` call: the argument can only be narrowed by `{{to}}`. Use `{{to}}` instead.',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
create: (context) => {
|
|
87
|
+
const options = context.options[0];
|
|
88
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
89
|
+
const compilerOptions = parserServices.program.getCompilerOptions();
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
const strictNullChecks = compilerOptions.strictNullChecks ?? compilerOptions.strict ?? false;
|
|
93
|
+
if (!strictNullChecks)
|
|
94
|
+
return {};
|
|
95
|
+
const checker = parserServices.program.getTypeChecker();
|
|
96
|
+
const sourceCode = context.sourceCode;
|
|
97
|
+
const program = sourceCode.ast;
|
|
98
|
+
const ignored = new Set(options?.ignore);
|
|
99
|
+
const tsDataForgeImport = getTsDataForgeImport(program);
|
|
100
|
+
const resolveGuard = buildCalleeResolver(tsDataForgeImport);
|
|
101
|
+
const buildReplaceFix = (isNamespace,
|
|
102
|
+
|
|
103
|
+
propertyNode, replacement) => (fixer) => {
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if (isNamespace) {
|
|
107
|
+
return [fixer.replaceText(propertyNode, replacement)];
|
|
183
108
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
reportConstant(node, canonicalName, false, argument);
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
const localName = getImportedLocalName(tsDataForgeImport, replacement);
|
|
113
|
+
if (localName !== undefined) {
|
|
114
|
+
return [fixer.replaceText(propertyNode, localName)];
|
|
191
115
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
116
|
+
return [
|
|
117
|
+
...buildImportFixes(fixer, program, tsDataForgeImport, [
|
|
118
|
+
replacement,
|
|
119
|
+
]),
|
|
120
|
+
fixer.replaceText(propertyNode, replacement),
|
|
121
|
+
];
|
|
122
|
+
};
|
|
123
|
+
const reportConstant = (node, name, value, argument) => {
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
const canFix = isSideEffectFreeArg(argument);
|
|
127
|
+
context.report({
|
|
128
|
+
|
|
129
|
+
node: node,
|
|
130
|
+
messageId: value ? 'alwaysTrue' : 'alwaysFalse',
|
|
131
|
+
data: { name },
|
|
132
|
+
fix: canFix
|
|
133
|
+
? (fixer) => fixer.replaceText(
|
|
134
|
+
|
|
135
|
+
node, value ? 'true' : 'false')
|
|
136
|
+
: undefined,
|
|
199
137
|
});
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
138
|
+
};
|
|
139
|
+
const reportReplace = (node, from, to, isNamespace,
|
|
140
|
+
|
|
141
|
+
propertyNode) => {
|
|
142
|
+
context.report({
|
|
143
|
+
|
|
144
|
+
node: node,
|
|
145
|
+
messageId: 'replaceTypeGuard',
|
|
146
|
+
data: { from, to },
|
|
147
|
+
fix: buildReplaceFix(isNamespace, propertyNode, to),
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
return {
|
|
151
|
+
CallExpression: (node) => {
|
|
152
|
+
if (node.arguments.length !== 1)
|
|
153
|
+
return;
|
|
154
|
+
const argument = node.arguments[0];
|
|
155
|
+
if (argument === undefined ||
|
|
156
|
+
argument.type === AST_NODE_TYPES.SpreadElement) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const resolved = resolveGuard(node.callee);
|
|
160
|
+
if (resolved === undefined)
|
|
161
|
+
return;
|
|
162
|
+
const { canonicalName, propertyNode, isNamespace } = resolved;
|
|
163
|
+
if (ignored.has(canonicalName))
|
|
164
|
+
return;
|
|
165
|
+
const spec = GUARD_SPECS[canonicalName];
|
|
166
|
+
if (spec === undefined)
|
|
167
|
+
return;
|
|
168
|
+
const argTsNode = parserServices.esTreeNodeToTSNodeMap.get(argument);
|
|
169
|
+
const argType = checker.getTypeAtLocation(argTsNode);
|
|
170
|
+
const parts = collectUnionParts(argType);
|
|
171
|
+
if (parts === undefined)
|
|
172
|
+
return;
|
|
173
|
+
const inputAtoms = new Set(parts.map(classifyAtom));
|
|
174
|
+
const hasMemberOutsideTarget = (atoms) => {
|
|
175
|
+
const targetSet = new Set(atoms);
|
|
176
|
+
return parts.some((p) => !targetSet.has(classifyAtom(p)));
|
|
177
|
+
};
|
|
178
|
+
switch (spec.kind) {
|
|
179
|
+
case 'excludeFrom': {
|
|
180
|
+
const removable = spec.atoms.filter((a) => inputAtoms.has(a));
|
|
181
|
+
if (removable.length === 0) {
|
|
182
|
+
|
|
183
|
+
reportConstant(node, canonicalName, true, argument);
|
|
184
|
+
}
|
|
185
|
+
else if (!hasMemberOutsideTarget(spec.atoms)) {
|
|
186
|
+
|
|
187
|
+
reportConstant(node, canonicalName, false, argument);
|
|
188
|
+
}
|
|
189
|
+
else if (removable.length < spec.atoms.length) {
|
|
190
|
+
const replacement = spec.replacements?.[joinAtoms(removable)];
|
|
191
|
+
if (replacement !== undefined) {
|
|
192
|
+
reportReplace(node, canonicalName, replacement, isNamespace, propertyNode);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case 'narrowTo': {
|
|
198
|
+
const present = spec.atoms.filter((a) => inputAtoms.has(a));
|
|
199
|
+
if (present.length === 0) {
|
|
200
|
+
|
|
201
|
+
reportConstant(node, canonicalName, false, argument);
|
|
202
|
+
}
|
|
203
|
+
else if (!hasMemberOutsideTarget(spec.atoms)) {
|
|
204
|
+
|
|
205
|
+
reportConstant(node, canonicalName, true, argument);
|
|
206
|
+
}
|
|
207
|
+
else if (present.length < spec.atoms.length) {
|
|
208
|
+
const replacement = spec.replacements?.[joinAtoms(present)];
|
|
209
|
+
if (replacement !== undefined) {
|
|
210
|
+
reportReplace(node, canonicalName, replacement, isNamespace, propertyNode);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case 'record': {
|
|
216
|
+
if (parts.every((p) => isGuaranteedRecord(p, checker))) {
|
|
217
|
+
|
|
218
|
+
reportConstant(node, canonicalName, true, argument);
|
|
219
|
+
}
|
|
220
|
+
else if (parts.every((p) => isGuaranteedNotRecord(p, checker))) {
|
|
221
|
+
|
|
222
|
+
reportConstant(node, canonicalName, false, argument);
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case 'nonEmptyString': {
|
|
227
|
+
const hasNullish = inputAtoms.has('undefined') || inputAtoms.has('null');
|
|
228
|
+
const nonNullishParts = parts.filter((p) => {
|
|
229
|
+
const atom = classifyAtom(p);
|
|
230
|
+
return atom !== 'undefined' && atom !== 'null';
|
|
231
|
+
});
|
|
232
|
+
if (!nonNullishParts.some(couldBeNonEmptyString)) {
|
|
233
|
+
|
|
234
|
+
reportConstant(node, canonicalName, false, argument);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
if (nonNullishParts.some((p) => !isGuaranteedNonEmptyString(p, checker))) {
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
if (hasNullish) {
|
|
241
|
+
reportReplace(node, canonicalName, 'isNonNullish', isNamespace, propertyNode);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
reportConstant(node, canonicalName, true, argument);
|
|
245
|
+
}
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
},
|
|
252
|
+
defaultOptions: [{}],
|
|
227
253
|
};
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
228
260
|
const collectUnionParts = (type) => {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
261
|
+
const parts = type.isUnion() ? type.types : [type];
|
|
262
|
+
for (const part of parts) {
|
|
263
|
+
if ((part.flags & DEFERRED_OR_OPAQUE_FLAGS) !== 0)
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
return parts;
|
|
234
267
|
};
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
const isGuaranteedRecord = (
|
|
282
|
+
|
|
283
|
+
type,
|
|
284
|
+
|
|
285
|
+
checker) => checker.getIndexInfoOfType(type, ts.IndexKind.String) !== undefined &&
|
|
286
|
+
!isCallableType(type, checker) &&
|
|
287
|
+
!checker.isArrayType(type) &&
|
|
288
|
+
!checker.isTupleType(type);
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
const isGuaranteedNotRecord = (
|
|
295
|
+
|
|
296
|
+
type,
|
|
297
|
+
|
|
298
|
+
checker) => classifyAtom(type) !== 'other' ||
|
|
299
|
+
checker.isArrayType(type) ||
|
|
300
|
+
checker.isTupleType(type) ||
|
|
301
|
+
isCallableType(type, checker);
|
|
302
|
+
const isCallableType = (
|
|
303
|
+
|
|
304
|
+
type,
|
|
305
|
+
|
|
306
|
+
checker) => checker.getSignaturesOfType(type, ts.SignatureKind.Call).length > 0 ||
|
|
307
|
+
checker.getSignaturesOfType(type, ts.SignatureKind.Construct).length > 0;
|
|
308
|
+
|
|
309
|
+
|
|
238
310
|
const classifyAtom = (type) => {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
311
|
+
const { flags } = type;
|
|
312
|
+
if ((flags & ts.TypeFlags.Undefined) !== 0)
|
|
313
|
+
return 'undefined';
|
|
314
|
+
if ((flags & ts.TypeFlags.Null) !== 0)
|
|
315
|
+
return 'null';
|
|
316
|
+
if ((flags & ts.TypeFlags.BooleanLike) !== 0)
|
|
317
|
+
return 'boolean';
|
|
318
|
+
if ((flags & ts.TypeFlags.NumberLike) !== 0)
|
|
319
|
+
return 'number';
|
|
320
|
+
if ((flags & ts.TypeFlags.StringLike) !== 0)
|
|
321
|
+
return 'string';
|
|
322
|
+
if ((flags & ts.TypeFlags.BigIntLike) !== 0)
|
|
323
|
+
return 'bigint';
|
|
324
|
+
if ((flags & ts.TypeFlags.ESSymbolLike) !== 0)
|
|
325
|
+
return 'symbol';
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
if (type.isIntersection()) {
|
|
329
|
+
for (const constituent of type.types) {
|
|
330
|
+
const atom = classifyAtom(constituent);
|
|
331
|
+
if (atom !== 'other')
|
|
332
|
+
return atom;
|
|
333
|
+
}
|
|
251
334
|
}
|
|
252
|
-
|
|
253
|
-
return "other";
|
|
335
|
+
return 'other';
|
|
254
336
|
};
|
|
255
337
|
const ATOM_ORDER = [
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
338
|
+
'undefined',
|
|
339
|
+
'null',
|
|
340
|
+
'boolean',
|
|
341
|
+
'number',
|
|
342
|
+
'string',
|
|
343
|
+
'bigint',
|
|
344
|
+
'symbol',
|
|
263
345
|
];
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
346
|
+
|
|
347
|
+
const joinAtoms = (atoms) => ATOM_ORDER.filter((atom) => atoms.includes(atom)).join('|');
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
const isGuaranteedNonEmptyString = (
|
|
354
|
+
|
|
355
|
+
type,
|
|
356
|
+
|
|
357
|
+
checker) => {
|
|
358
|
+
if (type.isStringLiteral())
|
|
359
|
+
return type.value !== '';
|
|
360
|
+
if (type.getProperty(BRAND_MARKER_PROPERTY) === undefined)
|
|
361
|
+
return false;
|
|
362
|
+
const minLengthSymbol = type.getProperty(MIN_LENGTH_BRAND_KEY);
|
|
363
|
+
if (minLengthSymbol === undefined)
|
|
364
|
+
return false;
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
return (checker.getTypeOfSymbol(minLengthSymbol).getProperty('0') !== undefined);
|
|
271
369
|
};
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
|
|
272
376
|
const couldBeNonEmptyString = (type) => {
|
|
273
|
-
|
|
274
|
-
|
|
377
|
+
if (type.isStringLiteral())
|
|
378
|
+
return type.value !== '';
|
|
379
|
+
return classifyAtom(type) === 'string';
|
|
275
380
|
};
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
|
|
276
386
|
const isSideEffectFreeArg = (node) => {
|
|
277
|
-
switch (node.type) {
|
|
278
|
-
case AST_NODE_TYPES.Identifier:
|
|
279
|
-
case AST_NODE_TYPES.ThisExpression:
|
|
280
|
-
case AST_NODE_TYPES.Literal:
|
|
281
|
-
return true;
|
|
282
|
-
case AST_NODE_TYPES.MemberExpression:
|
|
283
|
-
return isSideEffectFreeArg(node.object) && (!node.computed || isSideEffectFreeArg(node.property));
|
|
284
|
-
default:
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
387
|
|
|
289
|
-
|
|
290
|
-
|
|
388
|
+
|
|
389
|
+
switch (node.type) {
|
|
390
|
+
case AST_NODE_TYPES.Identifier:
|
|
391
|
+
case AST_NODE_TYPES.ThisExpression:
|
|
392
|
+
case AST_NODE_TYPES.Literal:
|
|
393
|
+
return true;
|
|
394
|
+
case AST_NODE_TYPES.MemberExpression:
|
|
395
|
+
return (isSideEffectFreeArg(node.object) &&
|
|
396
|
+
(!node.computed || isSideEffectFreeArg(node.property)));
|
|
397
|
+
default:
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
//# sourceMappingURL=no-unnecessary-type-guard.mjs.map
|