eslint-plugin-ts-data-forge 0.9.0 → 0.10.1
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/plugin.d.mts +3 -1
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +1 -0
- package/dist/plugin.mjs.map +1 -1
- package/dist/rule-types.d.mts +16 -1
- package/dist/rule-types.d.mts.map +1 -1
- package/dist/rule-types.mjs.map +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/prefer-pipe-map-nullable.d.mts +24 -0
- package/dist/rules/prefer-pipe-map-nullable.d.mts.map +1 -0
- package/dist/rules/prefer-pipe-map-nullable.mjs +266 -0
- package/dist/rules/prefer-pipe-map-nullable.mjs.map +1 -0
- package/dist/rules/rules.d.mts +2 -1
- package/dist/rules/rules.d.mts.map +1 -1
- package/dist/rules/rules.mjs +2 -0
- package/dist/rules/rules.mjs.map +1 -1
- package/package.json +6 -6
- package/src/plugin.mts +1 -0
- package/src/rule-types.mts +17 -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
- package/src/rules/prefer-pipe-map-nullable.mts +391 -0
- package/src/rules/prefer-pipe-map-nullable.test.mts +470 -0
- package/src/rules/rules.mts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-is-non-null-object.mjs","sourceRoot":"","sources":["../../src/rules/prefer-is-non-null-object.mts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"prefer-is-non-null-object.mjs","sourceRoot":"","sources":["../../src/rules/prefer-is-non-null-object.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,CAAC,MAAM,qBAAqB,GAAG,2BAA2B,CAAC;IAC/D,UAAU,EAAE,oBAAoB;CACjC,CAAC,CAAC"}
|
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
type Options = readonly [];
|
|
3
|
-
type MessageIds = 'useIsRecordAndHasKey' | 'useHasKey';
|
|
4
|
-
export declare const preferIsRecordAndHasKey: TSESLint.RuleModule<MessageIds, Options>;
|
|
5
|
-
export {};
|
|
1
|
+
export declare const preferIsRecordAndHasKey: import("@typescript-eslint/utils/ts-eslint").RuleModule<"useHasKey" | "useIsRecordAndHasKey", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
6
2
|
//# sourceMappingURL=prefer-is-record-and-has-key.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-is-record-and-has-key.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-is-record-and-has-key.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prefer-is-record-and-has-key.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-is-record-and-has-key.mts"],"names":[],"mappings":"AAGA,eAAO,MAAM,uBAAuB,gLAElC,CAAC"}
|
|
@@ -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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
type Options = readonly [];
|
|
3
|
+
type MessageIds = 'useMapNullable' | 'suggestMapNullable';
|
|
4
|
+
/**
|
|
5
|
+
* `pipe(x).map(mapNullable(fn))` and `pipe(x).mapNullable(fn)` compute the same
|
|
6
|
+
* value: `mapNullable(fn)` is `(v) => v == null ? undefined : fn(v)`, and
|
|
7
|
+
* `Pipe.mapNullable(fn)` is `pipe(a == null ? undefined : fn(a))`.
|
|
8
|
+
*
|
|
9
|
+
* Only the curried `mapNullable(fn)` imported from ts-data-forge is rewritten,
|
|
10
|
+
* and only on a receiver this rule can see is a pipe: a `pipe(…)` call, or a
|
|
11
|
+
* chain of `map` / `mapNullable` / `mapOptional` calls on one.
|
|
12
|
+
*
|
|
13
|
+
* The static type can differ. `mapNullable` declares `const B` and
|
|
14
|
+
* `Pipe.mapNullable` a plain `B`, so for an inline function `(v) => 'a'` the
|
|
15
|
+
* first infers `'a'` and the second `string`, and `(v) => [v, 1]` becomes
|
|
16
|
+
* `number[]` instead of `readonly [number, 1]`. The fix is therefore applied
|
|
17
|
+
* automatically only when the function's return type is not inferred, or is
|
|
18
|
+
* inferred with nothing such a widening could change; otherwise, and whenever
|
|
19
|
+
* a comment or a type wrapper around `mapNullable` would be dropped, it is
|
|
20
|
+
* offered as a suggestion.
|
|
21
|
+
*/
|
|
22
|
+
export declare const preferPipeMapNullable: TSESLint.RuleModule<MessageIds, Options>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=prefer-pipe-map-nullable.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-pipe-map-nullable.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-pipe-map-nullable.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAalC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;AAE1D;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CAuPjE,CAAC"}
|