eslint-plugin-ts-data-forge 0.10.0 → 0.10.2
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/plugin.d.mts +1 -1
- package/dist/rule-types.d.mts +1 -1
- package/dist/rules/ast-utils.d.mts +0 -16
- package/dist/rules/ast-utils.d.mts.map +1 -1
- package/dist/rules/ast-utils.mjs +0 -36
- package/dist/rules/ast-utils.mjs.map +1 -1
- package/dist/rules/no-side-effect-import.d.mts +1 -8
- package/dist/rules/no-side-effect-import.d.mts.map +1 -1
- package/dist/rules/no-side-effect-import.mjs +7 -43
- package/dist/rules/no-side-effect-import.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-array.d.mts +1 -5
- package/dist/rules/prefer-arr-is-array.d.mts.map +1 -1
- package/dist/rules/prefer-arr-is-array.mjs +5 -83
- package/dist/rules/prefer-arr-is-array.mjs.map +1 -1
- package/dist/rules/prefer-is-non-null-object.d.mts +1 -5
- package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -1
- package/dist/rules/prefer-is-non-null-object.mjs +4 -184
- package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.d.mts +1 -5
- package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.mjs +4 -219
- package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
- package/dist/rules/prefer-num-safe-parse-float.d.mts +1 -15
- package/dist/rules/prefer-num-safe-parse-float.d.mts.map +1 -1
- package/dist/rules/prefer-num-safe-parse-float.mjs +6 -123
- package/dist/rules/prefer-num-safe-parse-float.mjs.map +1 -1
- package/dist/rules/prefer-num-safe-parse-int.d.mts +1 -13
- package/dist/rules/prefer-num-safe-parse-int.d.mts.map +1 -1
- package/dist/rules/prefer-num-safe-parse-int.mjs +6 -121
- package/dist/rules/prefer-num-safe-parse-int.mjs.map +1 -1
- package/dist/rules/rules.d.mts +1 -1
- package/package.json +6 -6
- package/src/rule-types.mts +1 -1
- package/src/rules/ast-utils.mts +0 -51
- package/src/rules/no-side-effect-import.mts +7 -61
- package/src/rules/prefer-arr-is-array.mts +5 -122
- package/src/rules/prefer-arr-is-array.test.mts +50 -7
- package/src/rules/prefer-is-non-null-object.mts +4 -274
- package/src/rules/prefer-is-record-and-has-key.mts +4 -318
- package/src/rules/prefer-num-safe-parse-float.mts +6 -193
- package/src/rules/prefer-num-safe-parse-float.test.mts +18 -0
- package/src/rules/prefer-num-safe-parse-int.mts +6 -192
- package/src/rules/prefer-num-safe-parse-int.test.mts +18 -0
|
@@ -1,221 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildNamedImportFix, getArgumentText, getImportDeclarations, getNamedImports, skipTypeWrappers, } from 'eslint-rule-forge';
|
|
3
|
-
import { Arr } from 'ts-data-forge';
|
|
4
|
-
import * as ts from 'typescript';
|
|
5
|
-
import { isIdentifierNamed, typeHoldsAtEveryLayer } from './ast-utils.mjs';
|
|
1
|
+
import { createPreferIsRecordAndHasKey } from 'eslint-rule-forge';
|
|
6
2
|
import { TS_DATA_FORGE_MODULE } from './constants.mjs';
|
|
7
|
-
export const preferIsRecordAndHasKey = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
docs: {
|
|
11
|
-
description: 'Replace `Object.hasOwn(obj, key)` or `key in obj` with `isRecord(obj) && hasKey(obj, key)` from ts-data-forge',
|
|
12
|
-
},
|
|
13
|
-
fixable: 'code',
|
|
14
|
-
schema: [],
|
|
15
|
-
messages: {
|
|
16
|
-
useIsRecordAndHasKey: 'Replace `{{original}}` with `isRecord({{objName}}) && hasKey({{objName}}, {{keyName}})` from ts-data-forge.',
|
|
17
|
-
useHasKey: 'Replace `{{original}}` with `hasKey({{objName}}, {{keyName}})` from ts-data-forge.',
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
create: (context) => {
|
|
21
|
-
const sourceCode = context.sourceCode;
|
|
22
|
-
const program = sourceCode.ast;
|
|
23
|
-
const tsDataForgeImports = getImportDeclarations(program, TS_DATA_FORGE_MODULE);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const isKnownRecord = (
|
|
34
|
-
|
|
35
|
-
expression) => typeHoldsAtEveryLayer(sourceCode, expression, (type, checker) => isAlreadyRecordType(checker, type));
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const mayBeArray = (
|
|
44
|
-
|
|
45
|
-
expression) => sourceCode.parserServices?.program != null &&
|
|
46
|
-
!typeHoldsAtEveryLayer(sourceCode, expression, (type, checker) => !mayBeArrayType(checker, type));
|
|
47
|
-
const mut_nodesToFix = [];
|
|
48
|
-
return {
|
|
49
|
-
|
|
50
|
-
CallExpression: (node) => {
|
|
51
|
-
const callee = skipTypeWrappers(node.callee);
|
|
52
|
-
if (callee.type !== AST_NODE_TYPES.MemberExpression ||
|
|
53
|
-
!isIdentifierNamed(callee.object, 'Object') ||
|
|
54
|
-
callee.property.type !== AST_NODE_TYPES.Identifier ||
|
|
55
|
-
callee.property.name !== 'hasOwn') {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (node.arguments.length !== 2) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const objArg = node.arguments[0];
|
|
63
|
-
const keyArg = node.arguments[1];
|
|
64
|
-
if (objArg === undefined || keyArg === undefined) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (objArg.type === AST_NODE_TYPES.SpreadElement ||
|
|
69
|
-
keyArg.type === AST_NODE_TYPES.SpreadElement ||
|
|
70
|
-
mayBeArray(objArg)) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
mut_nodesToFix.push({
|
|
74
|
-
node,
|
|
75
|
-
objExpression: objArg,
|
|
76
|
-
keyExpression: keyArg,
|
|
77
|
-
});
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
BinaryExpression: (node) => {
|
|
81
|
-
if (node.operator !== 'in') {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const keyExpression = node.left;
|
|
85
|
-
const objExpression = node.right;
|
|
86
|
-
|
|
87
|
-
if (keyExpression.type === AST_NODE_TYPES.PrivateIdentifier ||
|
|
88
|
-
mayBeArray(objExpression)) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
mut_nodesToFix.push({
|
|
92
|
-
node,
|
|
93
|
-
objExpression,
|
|
94
|
-
keyExpression,
|
|
95
|
-
});
|
|
96
|
-
},
|
|
97
|
-
'Program:exit': () => {
|
|
98
|
-
const namedImports = getNamedImports(tsDataForgeImports);
|
|
99
|
-
const rewrites = mut_nodesToFix.map((entry) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const needsIsRecord = !isKnownRecord(entry.objExpression);
|
|
103
|
-
return {
|
|
104
|
-
...entry,
|
|
105
|
-
needsIsRecord,
|
|
106
|
-
|
|
107
|
-
fixable: !needsIsRecord || isSideEffectFree(entry.objExpression),
|
|
108
|
-
};
|
|
109
|
-
});
|
|
110
|
-
const fixableRewrites = rewrites.filter(({ fixable }) => fixable);
|
|
111
|
-
const importsToAdd = Arr.isEmpty(fixableRewrites)
|
|
112
|
-
? []
|
|
113
|
-
: [
|
|
114
|
-
...(fixableRewrites.some(({ needsIsRecord }) => needsIsRecord) && !namedImports.includes('isRecord')
|
|
115
|
-
? ['isRecord']
|
|
116
|
-
: []),
|
|
117
|
-
...(namedImports.includes('hasKey') ? [] : ['hasKey']),
|
|
118
|
-
];
|
|
119
|
-
const importCarrier = fixableRewrites[0];
|
|
120
|
-
for (const rewrite of rewrites) {
|
|
121
|
-
const { node, objExpression, keyExpression, needsIsRecord } = rewrite;
|
|
122
|
-
const objText = getArgumentText(sourceCode, objExpression);
|
|
123
|
-
const keyText = getArgumentText(sourceCode, keyExpression);
|
|
124
|
-
const originalText = sourceCode.getText(node);
|
|
125
|
-
context.report({
|
|
126
|
-
node,
|
|
127
|
-
messageId: needsIsRecord ? 'useIsRecordAndHasKey' : 'useHasKey',
|
|
128
|
-
data: {
|
|
129
|
-
original: originalText,
|
|
130
|
-
objName: objText,
|
|
131
|
-
keyName: keyText,
|
|
132
|
-
},
|
|
133
|
-
fix: !rewrite.fixable
|
|
134
|
-
? undefined
|
|
135
|
-
: (fixer) => {
|
|
136
|
-
const replacement = needsIsRecord
|
|
137
|
-
? `(isRecord(${objText}) && hasKey(${objText}, ${keyText}))`
|
|
138
|
-
: `hasKey(${objText}, ${keyText})`;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const importFixes = rewrite === importCarrier && importsToAdd.length > 0
|
|
144
|
-
? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, importsToAdd)
|
|
145
|
-
: [];
|
|
146
|
-
return [
|
|
147
|
-
...importFixes,
|
|
148
|
-
fixer.replaceText(node, replacement),
|
|
149
|
-
];
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
},
|
|
156
|
-
defaultOptions: [],
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const isSideEffectFree = (
|
|
163
|
-
|
|
164
|
-
wrapped) => {
|
|
165
|
-
const node = skipTypeWrappers(wrapped);
|
|
166
|
-
return (node.type === AST_NODE_TYPES.Identifier ||
|
|
167
|
-
node.type === AST_NODE_TYPES.ThisExpression ||
|
|
168
|
-
(node.type === AST_NODE_TYPES.MemberExpression &&
|
|
169
|
-
!node.computed &&
|
|
170
|
-
isSideEffectFree(node.object)));
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const mayBeArrayType = (
|
|
178
|
-
|
|
179
|
-
checker,
|
|
180
|
-
|
|
181
|
-
type) => {
|
|
182
|
-
if (type.isUnionOrIntersection()) {
|
|
183
|
-
return type.types.some((member) => mayBeArrayType(checker, member));
|
|
184
|
-
}
|
|
185
|
-
if ((type.flags &
|
|
186
|
-
(ts.TypeFlags.TypeParameter |
|
|
187
|
-
ts.TypeFlags.IndexedAccess |
|
|
188
|
-
ts.TypeFlags.Conditional |
|
|
189
|
-
ts.TypeFlags.Substitution)) !==
|
|
190
|
-
0) {
|
|
191
|
-
const constraint = checker.getBaseConstraintOfType(type);
|
|
192
|
-
return constraint !== undefined && mayBeArrayType(checker, constraint);
|
|
193
|
-
}
|
|
194
|
-
return checker.isArrayType(type) || checker.isTupleType(type);
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const isAlreadyRecordType = (
|
|
211
|
-
|
|
212
|
-
checker,
|
|
213
|
-
|
|
214
|
-
type) => type.isUnion()
|
|
215
|
-
? type.types.every((member) => isAlreadyRecordType(checker, member))
|
|
216
|
-
: checker.getIndexInfoOfType(type, ts.IndexKind.String) !== undefined &&
|
|
217
|
-
Arr.isEmpty(checker.getSignaturesOfType(type, ts.SignatureKind.Call)) &&
|
|
218
|
-
Arr.isEmpty(checker.getSignaturesOfType(type, ts.SignatureKind.Construct)) &&
|
|
219
|
-
!checker.isArrayType(type) &&
|
|
220
|
-
!checker.isTupleType(type);
|
|
3
|
+
export const preferIsRecordAndHasKey = createPreferIsRecordAndHasKey({
|
|
4
|
+
moduleName: TS_DATA_FORGE_MODULE,
|
|
5
|
+
});
|
|
221
6
|
//# sourceMappingURL=prefer-is-record-and-has-key.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-is-record-and-has-key.mjs","sourceRoot":"","sources":["../../src/rules/prefer-is-record-and-has-key.mts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"prefer-is-record-and-has-key.mjs","sourceRoot":"","sources":["../../src/rules/prefer-is-record-and-has-key.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,6BAA6B,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,CAAC,MAAM,uBAAuB,GAAG,6BAA6B,CAAC;IACnE,UAAU,EAAE,oBAAoB;CACjC,CAAC,CAAC"}
|
|
@@ -1,16 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
type Options = readonly [];
|
|
3
|
-
type MessageIds = 'useSafeParseFloat';
|
|
4
|
-
/**
|
|
5
|
-
* Detects `parseFloat(x)`, `Number.parseFloat(x)`, and `Number(x)` (when x
|
|
6
|
-
* is typed as `string`) and replaces them with
|
|
7
|
-
* `Result.unwrapOkOr(Num.safeParseFloat(x), Number.NaN)` (ts-data-forge).
|
|
8
|
-
*
|
|
9
|
-
* `Number(x)` is only flagged when x is purely a string type to avoid false
|
|
10
|
-
* positives for `Number(someBoolean)` or `Number(someNumber)` uses. The type is
|
|
11
|
-
* asked of the value under any `as` / `!` / `satisfies` too, so a cast does not
|
|
12
|
-
* vouch for a string that is not one.
|
|
13
|
-
*/
|
|
14
|
-
export declare const preferNumSafeParseFloat: TSESLint.RuleModule<MessageIds, Options>;
|
|
15
|
-
export {};
|
|
1
|
+
export declare const preferNumSafeParseFloat: import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseFloat", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
16
2
|
//# sourceMappingURL=prefer-num-safe-parse-float.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-num-safe-parse-float.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-num-safe-parse-float.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prefer-num-safe-parse-float.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-num-safe-parse-float.mts"],"names":[],"mappings":"AAGA,eAAO,MAAM,uBAAuB,+JAIlC,CAAC"}
|
|
@@ -1,125 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildNamedImportFix, getArgumentText, getImportDeclarations, getNamedImports, typeWrapperLayers, } from 'eslint-rule-forge';
|
|
3
|
-
import * as ts from 'typescript';
|
|
1
|
+
import { createPreferSafeParseFloat } from 'eslint-rule-forge';
|
|
4
2
|
import { TS_DATA_FORGE_MODULE } from './constants.mjs';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const isStringType = (type) => {
|
|
13
|
-
const parts = type.isUnion() ? type.types : [type];
|
|
14
|
-
return (parts.length > 0 &&
|
|
15
|
-
parts.every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0));
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export const preferNumSafeParseFloat = {
|
|
28
|
-
meta: {
|
|
29
|
-
type: 'suggestion',
|
|
30
|
-
docs: {
|
|
31
|
-
description: 'Replace `parseFloat(x)`, `Number.parseFloat(x)`, or `Number(x)` (when x is a string) with `Result.unwrapOkOr(Num.safeParseFloat(x), Number.NaN)` from ts-data-forge.',
|
|
32
|
-
},
|
|
33
|
-
fixable: 'code',
|
|
34
|
-
schema: [],
|
|
35
|
-
messages: {
|
|
36
|
-
useSafeParseFloat: 'Replace `{{original}}` with `Result.unwrapOkOr(Num.safeParseFloat({{argName}}), Number.NaN)` from ts-data-forge.',
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
create: (context) => {
|
|
40
|
-
const sourceCode = context.sourceCode;
|
|
41
|
-
const program = sourceCode.ast;
|
|
42
|
-
const tsDataForgeImports = getImportDeclarations(program, TS_DATA_FORGE_MODULE);
|
|
43
|
-
const services = context.sourceCode.parserServices;
|
|
44
|
-
const mut_nodesToFix = [];
|
|
45
|
-
return {
|
|
46
|
-
CallExpression: (node) => {
|
|
47
|
-
const { callee } = node;
|
|
48
|
-
|
|
49
|
-
const isGlobalParseFloat = callee.type === AST_NODE_TYPES.Identifier &&
|
|
50
|
-
callee.name === 'parseFloat';
|
|
51
|
-
|
|
52
|
-
const isNumberParseFloat = callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
53
|
-
!callee.computed &&
|
|
54
|
-
callee.object.type === AST_NODE_TYPES.Identifier &&
|
|
55
|
-
callee.object.name === 'Number' &&
|
|
56
|
-
callee.property.type === AST_NODE_TYPES.Identifier &&
|
|
57
|
-
callee.property.name === 'parseFloat';
|
|
58
|
-
|
|
59
|
-
const isNumberCall = callee.type === AST_NODE_TYPES.Identifier &&
|
|
60
|
-
callee.name === 'Number';
|
|
61
|
-
if (!isGlobalParseFloat && !isNumberParseFloat && !isNumberCall) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const args = node.arguments;
|
|
65
|
-
const firstArg = args[0];
|
|
66
|
-
if (firstArg === undefined) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (firstArg.type === AST_NODE_TYPES.SpreadElement) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (services?.program == null) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
const checker = services.program.getTypeChecker();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const isString = (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
expression) => {
|
|
89
|
-
const tsNode = services.esTreeNodeToTSNodeMap?.get(expression);
|
|
90
|
-
return (tsNode !== undefined &&
|
|
91
|
-
isStringType(checker.getTypeAtLocation(tsNode)));
|
|
92
|
-
};
|
|
93
|
-
if (!typeWrapperLayers(firstArg).every(isString)) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
mut_nodesToFix.push({ node, argExpression: firstArg });
|
|
97
|
-
},
|
|
98
|
-
'Program:exit': () => {
|
|
99
|
-
const namedImports = getNamedImports(tsDataForgeImports);
|
|
100
|
-
const missingImports = ['Num', 'Result'].filter((name) => !namedImports.includes(name));
|
|
101
|
-
for (const [index, { node, argExpression },] of mut_nodesToFix.entries()) {
|
|
102
|
-
const argText = getArgumentText(sourceCode, argExpression);
|
|
103
|
-
const originalText = sourceCode.getText(node);
|
|
104
|
-
context.report({
|
|
105
|
-
node,
|
|
106
|
-
messageId: 'useSafeParseFloat',
|
|
107
|
-
data: {
|
|
108
|
-
original: originalText,
|
|
109
|
-
argName: argText,
|
|
110
|
-
},
|
|
111
|
-
fix: (fixer) => {
|
|
112
|
-
const core = `Result.unwrapOkOr(Num.safeParseFloat(${argText}), Number.NaN)`;
|
|
113
|
-
const importFixes = index === 0 && missingImports.length > 0
|
|
114
|
-
? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, missingImports)
|
|
115
|
-
: [];
|
|
116
|
-
return [...importFixes, fixer.replaceText(node, core)];
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
},
|
|
123
|
-
defaultOptions: [],
|
|
124
|
-
};
|
|
3
|
+
export const preferNumSafeParseFloat = createPreferSafeParseFloat({
|
|
4
|
+
moduleName: TS_DATA_FORGE_MODULE,
|
|
5
|
+
namespace: 'Num',
|
|
6
|
+
method: 'safeParseFloat',
|
|
7
|
+
});
|
|
125
8
|
//# sourceMappingURL=prefer-num-safe-parse-float.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-num-safe-parse-float.mjs","sourceRoot":"","sources":["../../src/rules/prefer-num-safe-parse-float.mts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"prefer-num-safe-parse-float.mjs","sourceRoot":"","sources":["../../src/rules/prefer-num-safe-parse-float.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,CAAC,MAAM,uBAAuB,GAAG,0BAA0B,CAAC;IAChE,UAAU,EAAE,oBAAoB;IAChC,SAAS,EAAE,KAAK;IAChB,MAAM,EAAE,gBAAgB;CACzB,CAAC,CAAC"}
|
|
@@ -1,14 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
type Options = readonly [];
|
|
3
|
-
type MessageIds = 'useSafeParseInt';
|
|
4
|
-
/**
|
|
5
|
-
* Detects the base-10 forms of `parseInt` / `Number.parseInt` (`parseInt(x)` or
|
|
6
|
-
* `parseInt(x, 10)`) and replaces them with
|
|
7
|
-
* `Result.unwrapOkOr(Num.safeParseInt(x), Number.NaN)` (ts-data-forge).
|
|
8
|
-
*
|
|
9
|
-
* Other radixes (e.g. `parseInt(x, 16)`) are intentionally left untouched, as
|
|
10
|
-
* `Num.safeParseInt` only supports base 10.
|
|
11
|
-
*/
|
|
12
|
-
export declare const preferNumSafeParseInt: TSESLint.RuleModule<MessageIds, Options>;
|
|
13
|
-
export {};
|
|
1
|
+
export declare const preferNumSafeParseInt: import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseInt", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
14
2
|
//# sourceMappingURL=prefer-num-safe-parse-int.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-num-safe-parse-int.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-num-safe-parse-int.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prefer-num-safe-parse-int.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-num-safe-parse-int.mts"],"names":[],"mappings":"AAGA,eAAO,MAAM,qBAAqB,6JAIhC,CAAC"}
|
|
@@ -1,123 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildNamedImportFix, getArgumentText, getImportDeclarations, getNamedImports, skipTypeWrappers, typeWrapperLayers, } from 'eslint-rule-forge';
|
|
3
|
-
import * as ts from 'typescript';
|
|
1
|
+
import { createPreferSafeParseInt } from 'eslint-rule-forge';
|
|
4
2
|
import { TS_DATA_FORGE_MODULE } from './constants.mjs';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const isStringType = (type) => {
|
|
13
|
-
const parts = type.isUnion() ? type.types : [type];
|
|
14
|
-
return (parts.length > 0 &&
|
|
15
|
-
parts.every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0));
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export const preferNumSafeParseInt = {
|
|
26
|
-
meta: {
|
|
27
|
-
type: 'suggestion',
|
|
28
|
-
docs: {
|
|
29
|
-
description: 'Replace `parseInt(x, 10)` with `Result.unwrapOkOr(Num.safeParseInt(x), Number.NaN)` from ts-data-forge.',
|
|
30
|
-
},
|
|
31
|
-
fixable: 'code',
|
|
32
|
-
schema: [],
|
|
33
|
-
messages: {
|
|
34
|
-
useSafeParseInt: 'Replace `{{original}}` with `Result.unwrapOkOr(Num.safeParseInt({{argName}}), Number.NaN)` from ts-data-forge.',
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
create: (context) => {
|
|
38
|
-
const sourceCode = context.sourceCode;
|
|
39
|
-
const program = sourceCode.ast;
|
|
40
|
-
const tsDataForgeImports = getImportDeclarations(program, TS_DATA_FORGE_MODULE);
|
|
41
|
-
const services = context.sourceCode.parserServices;
|
|
42
|
-
const mut_nodesToFix = [];
|
|
43
|
-
return {
|
|
44
|
-
CallExpression: (node) => {
|
|
45
|
-
const { callee } = node;
|
|
46
|
-
|
|
47
|
-
const isGlobalParseInt = callee.type === AST_NODE_TYPES.Identifier &&
|
|
48
|
-
callee.name === 'parseInt';
|
|
49
|
-
const isNumberParseInt = callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
50
|
-
!callee.computed &&
|
|
51
|
-
callee.object.type === AST_NODE_TYPES.Identifier &&
|
|
52
|
-
callee.object.name === 'Number' &&
|
|
53
|
-
callee.property.type === AST_NODE_TYPES.Identifier &&
|
|
54
|
-
callee.property.name === 'parseInt';
|
|
55
|
-
if (!isGlobalParseInt && !isNumberParseInt) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
const args = node.arguments;
|
|
59
|
-
const firstArg = args[0];
|
|
60
|
-
if (firstArg === undefined) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (firstArg.type === AST_NODE_TYPES.SpreadElement) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (args.length >= 2) {
|
|
70
|
-
const radix = args[1] === undefined ? undefined : skipTypeWrappers(args[1]);
|
|
71
|
-
if (radix?.type !== AST_NODE_TYPES.Literal || radix.value !== 10) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (services?.program == null) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
const checker = services.program.getTypeChecker();
|
|
83
|
-
const isString = (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
expression) => {
|
|
87
|
-
const tsNode = services.esTreeNodeToTSNodeMap?.get(expression);
|
|
88
|
-
return (tsNode !== undefined &&
|
|
89
|
-
isStringType(checker.getTypeAtLocation(tsNode)));
|
|
90
|
-
};
|
|
91
|
-
if (!typeWrapperLayers(firstArg).every(isString)) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
mut_nodesToFix.push({ node, argExpression: firstArg });
|
|
95
|
-
},
|
|
96
|
-
'Program:exit': () => {
|
|
97
|
-
const namedImports = getNamedImports(tsDataForgeImports);
|
|
98
|
-
const missingImports = ['Num', 'Result'].filter((name) => !namedImports.includes(name));
|
|
99
|
-
for (const [index, { node, argExpression },] of mut_nodesToFix.entries()) {
|
|
100
|
-
const argText = getArgumentText(sourceCode, argExpression);
|
|
101
|
-
const originalText = sourceCode.getText(node);
|
|
102
|
-
context.report({
|
|
103
|
-
node,
|
|
104
|
-
messageId: 'useSafeParseInt',
|
|
105
|
-
data: {
|
|
106
|
-
original: originalText,
|
|
107
|
-
argName: argText,
|
|
108
|
-
},
|
|
109
|
-
fix: (fixer) => {
|
|
110
|
-
const core = `Result.unwrapOkOr(Num.safeParseInt(${argText}), Number.NaN)`;
|
|
111
|
-
const importFixes = index === 0 && missingImports.length > 0
|
|
112
|
-
? buildNamedImportFix(fixer, program, TS_DATA_FORGE_MODULE, missingImports)
|
|
113
|
-
: [];
|
|
114
|
-
return [...importFixes, fixer.replaceText(node, core)];
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
},
|
|
121
|
-
defaultOptions: [],
|
|
122
|
-
};
|
|
3
|
+
export const preferNumSafeParseInt = createPreferSafeParseInt({
|
|
4
|
+
moduleName: TS_DATA_FORGE_MODULE,
|
|
5
|
+
namespace: 'Num',
|
|
6
|
+
method: 'safeParseInt',
|
|
7
|
+
});
|
|
123
8
|
//# sourceMappingURL=prefer-num-safe-parse-int.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-num-safe-parse-int.mjs","sourceRoot":"","sources":["../../src/rules/prefer-num-safe-parse-int.mts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"prefer-num-safe-parse-int.mjs","sourceRoot":"","sources":["../../src/rules/prefer-num-safe-parse-int.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,CAAC,MAAM,qBAAqB,GAAG,wBAAwB,CAAC;IAC5D,UAAU,EAAE,oBAAoB;IAChC,SAAS,EAAE,KAAK;IAChB,MAAM,EAAE,cAAc;CACvB,CAAC,CAAC"}
|
package/dist/rules/rules.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ export declare const tsDataForgeRules: {
|
|
|
3
3
|
readonly 'prefer-canonical-length-guard': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useCanonicalGuard" | "useIsBoundedLengthArray" | "useIsFixedLengthArray" | "useIsMaxLengthArray" | "useIsMinLengthArray" | "useIsNonEmpty", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
4
4
|
readonly 'prefer-canonical-length-cast': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useCanonicalCast", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
5
5
|
readonly 'prefer-arr-generate': import("@typescript-eslint/utils/ts-eslint").RuleModule<"suggestArrGenerate" | "useArrGenerate", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
6
|
-
readonly 'prefer-arr-is-array': import("@typescript-eslint/utils/ts-eslint").RuleModule<"
|
|
6
|
+
readonly 'prefer-arr-is-array': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsArray", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
7
7
|
readonly 'prefer-arr-scan': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferArrScanAccumulate" | "preferArrScanPrefix", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
8
8
|
readonly 'prefer-arr-sum': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useArrSum" | "useArrSumBy", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
9
9
|
readonly 'prefer-arr-uniq': import("@typescript-eslint/utils/ts-eslint").RuleModule<"suggestArrUniq" | "useArrUniq", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-ts-data-forge",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "ESLint rules that steer code toward ts-data-forge idioms.",
|
|
6
6
|
"keywords": [
|
|
@@ -35,17 +35,17 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@typescript-eslint/utils": "^8.71.1",
|
|
38
|
-
"eslint-rule-forge": "^0.
|
|
39
|
-
"ts-data-forge": "14.
|
|
38
|
+
"eslint-rule-forge": "^0.3.0",
|
|
39
|
+
"ts-data-forge": "14.10.0",
|
|
40
40
|
"ts-type-forge": "~9.2.2"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
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.19.
|
|
47
|
-
"eslint-plugin-ts-fortress": "1.3.
|
|
48
|
-
"eslint-plugin-ts-type-forge": "0.8.
|
|
46
|
+
"eslint-config-typed": "5.19.1",
|
|
47
|
+
"eslint-plugin-ts-fortress": "1.3.7",
|
|
48
|
+
"eslint-plugin-ts-type-forge": "0.8.12",
|
|
49
49
|
"prettier": "3.9.9",
|
|
50
50
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
51
51
|
"prettier-plugin-packagejson": "3.0.2",
|
package/src/rule-types.mts
CHANGED
|
@@ -208,7 +208,7 @@ namespace PreferIsRecordAndHasKey {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
|
-
* @description Replace `parseInt(x, 10)` with `Result.unwrapOkOr(Num.safeParseInt(x), Number.NaN)` from ts-data-forge.
|
|
211
|
+
* @description Replace `parseInt(x)` or `Number.parseInt(x, 10)` with `Result.unwrapOkOr(Num.safeParseInt(x), Number.NaN)` from ts-data-forge.
|
|
212
212
|
*
|
|
213
213
|
* ```md
|
|
214
214
|
* | key | value |
|