eslint-plugin-ts-std-forge 0.1.15 → 0.1.17
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 +11 -11
- package/dist/plugin.d.mts +2 -2
- package/dist/plugin.mjs.map +1 -1
- package/dist/rule-types.d.mts +2 -2
- package/dist/rules/prefer-safe-array-length-guard.d.mts.map +1 -1
- package/dist/rules/prefer-safe-array-length-guard.mjs +28 -25
- package/dist/rules/prefer-safe-array-length-guard.mjs.map +1 -1
- package/dist/rules/prefer-safe-number-parse-integer.d.mts +1 -1
- package/dist/rules/prefer-safe-number-parse-integer.d.mts.map +1 -1
- package/dist/rules/prefer-safe-number-parse.d.mts +1 -1
- package/dist/rules/prefer-safe-number-parse.d.mts.map +1 -1
- package/dist/rules/rules.d.mts +2 -2
- package/package.json +5 -5
- package/src/plugin.mts +5 -5
- package/src/rule-types.mts +2 -2
- package/src/rules/prefer-is-non-null-object.test.mts +3 -3
- package/src/rules/prefer-safe-array-length-guard.mts +51 -49
- package/src/rules/prefer-safe-array-length-guard.test.mts +147 -7
- package/src/rules/prefer-safe-number-parse-integer.test.mts +167 -52
- package/src/rules/prefer-safe-number-parse.test.mts +182 -59
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# eslint-plugin-ts-std-forge
|
|
2
2
|
|
|
3
|
-
ESLint rules that steer TypeScript code toward [`ts-std-forge`](https://www.npmjs.com/package/ts-std-forge) idioms — e.g. preferring `SafeArray.isNonEmpty(xs)` over `xs.length > 0`, `isRecord(x) && hasKey(x, k)` over `Object.hasOwn(x, k)`, or `SafeNumber.parseInteger` over `parseInt`.
|
|
3
|
+
ESLint rules that steer TypeScript code toward [`ts-std-forge`](https://www.npmjs.com/package/ts-std-forge) idioms — e.g. preferring `SafeArray.isNonEmpty(xs)` over `xs.length > 0`, `isRecord(x) && hasKey(x, k)` over `Object.hasOwn(x, k)`, or `SafeNumber.parseInteger` over `parseInt`. Most rules are auto-fixable; where the rewrite can change what the code does, the rule suggests it instead.
|
|
4
4
|
|
|
5
5
|
## Relation to `eslint-plugin-ts-data-forge`
|
|
6
6
|
|
|
@@ -19,8 +19,8 @@ plugin covers the part that moved, and names the new home:
|
|
|
19
19
|
| `prefer-is-non-null-object` | same name | same rule, pointing at `ts-std-forge` |
|
|
20
20
|
|
|
21
21
|
**Do not enable both plugins on the same files.** Every pair above would
|
|
22
|
-
report the same code twice with conflicting
|
|
23
|
-
code actually depends on: `ts-std-forge` alone, or `ts-data-forge` (which
|
|
22
|
+
report the same code twice with conflicting fixes or suggestions. Pick the
|
|
23
|
+
package the code actually depends on: `ts-std-forge` alone, or `ts-data-forge` (which
|
|
24
24
|
brings `ts-std-forge` with it, and whose rules point at the branded API).
|
|
25
25
|
|
|
26
26
|
The rest of `eslint-plugin-ts-data-forge` — the branded number and array
|
|
@@ -90,14 +90,14 @@ export default [
|
|
|
90
90
|
|
|
91
91
|
## Rules
|
|
92
92
|
|
|
93
|
-
| Rule | Description
|
|
94
|
-
| :--------------------------------- |
|
|
95
|
-
| `prefer-is-non-null-object` | Replace `typeof u === 'object' && u !== null` with `isNonNullObject(u)`.
|
|
96
|
-
| `prefer-is-record-and-has-key` | Replace `Object.hasOwn(obj, key)` / `key in obj` with `isRecord(obj) && hasKey(obj, key)`. Drops the `isRecord` half when the type already makes it true.
|
|
97
|
-
| `prefer-safe-array-is-array` | Replace `Array.isArray(x)` with `SafeArray.isArray(x)`, which keeps the array members of a union instead of widening to `any[]`.
|
|
98
|
-
| `prefer-safe-array-length-guard` | Replace an emptiness check on an array (`xs.length === 0`, `> 0`, `>= 1`, `!== 0`) with `SafeArray.isEmpty(xs)` / `SafeArray.isNonEmpty(xs)`, which narrow.
|
|
99
|
-
| `prefer-safe-number-parse` | Replace `parseFloat(x)` / `Number.parseFloat(x)` / `Number(x)` (string argument) with `Result.unwrapOkOr(SafeNumber.parse(x), Number.NaN)`.
|
|
100
|
-
| `prefer-safe-number-parse-integer` | Replace `parseInt(x)` / `Number.parseInt(x, 10)` with `Result.unwrapOkOr(SafeNumber.parseInteger(x), Number.NaN)`.
|
|
93
|
+
| Rule | Description |
|
|
94
|
+
| :--------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
95
|
+
| `prefer-is-non-null-object` | Replace `typeof u === 'object' && u !== null` with `isNonNullObject(u)`. |
|
|
96
|
+
| `prefer-is-record-and-has-key` | Replace `Object.hasOwn(obj, key)` / `key in obj` with `isRecord(obj) && hasKey(obj, key)`. Drops the `isRecord` half when the type already makes it true. |
|
|
97
|
+
| `prefer-safe-array-is-array` | Replace `Array.isArray(x)` with `SafeArray.isArray(x)`, which keeps the array members of a union instead of widening to `any[]`. |
|
|
98
|
+
| `prefer-safe-array-length-guard` | Replace an emptiness check on an array (`xs.length === 0`, `> 0`, `>= 1`, `!== 0`) with `SafeArray.isEmpty(xs)` / `SafeArray.isNonEmpty(xs)`, which narrow. |
|
|
99
|
+
| `prefer-safe-number-parse` | Replace `parseFloat(x)` / `Number.parseFloat(x)` / `Number(x)` (string argument) with `Result.unwrapOkOr(SafeNumber.parse(x), Number.NaN)`. Always a suggestion, since some strings parse differently. |
|
|
100
|
+
| `prefer-safe-number-parse-integer` | Replace `parseInt(x)` / `Number.parseInt(x, 10)` with `Result.unwrapOkOr(SafeNumber.parseInteger(x), Number.NaN)`. Always a suggestion, since some strings parse differently. |
|
|
101
101
|
|
|
102
102
|
## License
|
|
103
103
|
|
package/dist/plugin.d.mts
CHANGED
|
@@ -8,8 +8,8 @@ export declare const eslintPluginTsStdForge: {
|
|
|
8
8
|
readonly 'prefer-is-record-and-has-key': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useHasKey" | "useIsRecordAndHasKey", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
9
9
|
readonly 'prefer-safe-array-is-array': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsArray", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
10
10
|
readonly 'prefer-safe-array-length-guard': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty" | "useIsNonEmpty", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
11
|
-
readonly 'prefer-safe-number-parse': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseFloat", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
12
|
-
readonly 'prefer-safe-number-parse-integer': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseInt", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
11
|
+
readonly 'prefer-safe-number-parse': import("@typescript-eslint/utils/ts-eslint").RuleModule<"replaceWithSafeParse" | "useSafeParseFloat", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
12
|
+
readonly 'prefer-safe-number-parse-integer': import("@typescript-eslint/utils/ts-eslint").RuleModule<"replaceWithSafeParse" | "useSafeParseInt", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
13
13
|
};
|
|
14
14
|
readonly configs: {
|
|
15
15
|
/** Enables every rule of this plugin at `error`. */
|
package/dist/plugin.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.mjs","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD;;;;;;GAMG;AACH,MAAM,gBAAgB,GAAG;IACvB,wCAAwC,EAAE,OAAO;IACjD,2CAA2C,EAAE,OAAO;IACpD,yCAAyC,EAAE,OAAO;IAClD,6CAA6C,EAAE,OAAO;IACtD,uCAAuC,EAAE,OAAO;IAChD,+CAA+C,EAAE,OAAO;CAIzD,CAAC;AAEF,MAAM,iBAAiB,GAAG;IACxB,IAAI,EAAE,0BAA0B;IAChC,OAAO,EAAE;QACP,uEAAuE;QACvE,wEAAwE;QACxE,wEAAwE;QACxE,mDAAmD;QACnD,
|
|
1
|
+
{"version":3,"file":"plugin.mjs","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD;;;;;;GAMG;AACH,MAAM,gBAAgB,GAAG;IACvB,wCAAwC,EAAE,OAAO;IACjD,2CAA2C,EAAE,OAAO;IACpD,yCAAyC,EAAE,OAAO;IAClD,6CAA6C,EAAE,OAAO;IACtD,uCAAuC,EAAE,OAAO;IAChD,+CAA+C,EAAE,OAAO;CAIzD,CAAC;AAEF,MAAM,iBAAiB,GAAG;IACxB,IAAI,EAAE,0BAA0B;IAChC,OAAO,EAAE;QACP,uEAAuE;QACvE,wEAAwE;QACxE,wEAAwE;QACxE,mDAAmD;QACnD,2EAA2E;QAC3E,qDAAqD;QACrD,IAAI,cAAc;YAChB,OAAO,sBAAsB,CAAC;QAChC,CAAC;KACF;IACD,KAAK,EAAE,gBAAgB;CACY,CAAC;AAEtC,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,IAAI,EAAE;QACJ,IAAI,EAAE,4BAA4B;KACnC;IACD,KAAK,EAAE,eAAe;IACtB,OAAO,EAAE;QACP,oDAAoD;QACpD,WAAW,EAAE,iBAAiB;KAC/B;CAC8B,CAAC"}
|
package/dist/rule-types.d.mts
CHANGED
|
@@ -64,7 +64,7 @@ declare namespace PreferSafeArrayLengthGuard {
|
|
|
64
64
|
* | :--------- | :--------- |
|
|
65
65
|
* | type | suggestion |
|
|
66
66
|
* | deprecated | false |
|
|
67
|
-
* | fixable |
|
|
67
|
+
* | fixable | false |
|
|
68
68
|
* ```
|
|
69
69
|
*/
|
|
70
70
|
declare namespace PreferSafeNumberParse {
|
|
@@ -78,7 +78,7 @@ declare namespace PreferSafeNumberParse {
|
|
|
78
78
|
* | :--------- | :--------- |
|
|
79
79
|
* | type | suggestion |
|
|
80
80
|
* | deprecated | false |
|
|
81
|
-
* | fixable |
|
|
81
|
+
* | fixable | false |
|
|
82
82
|
* ```
|
|
83
83
|
*/
|
|
84
84
|
declare namespace PreferSafeNumberParseInteger {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-safe-array-length-guard.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-safe-array-length-guard.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"prefer-safe-array-length-guard.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-safe-array-length-guard.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAWlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,YAAY,GAAG,eAAe,CAAC;AAwCjD,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,UAAU,CAC1D,UAAU,EACV,OAAO,CA8GC,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
|
|
2
|
-
import {
|
|
2
|
+
import { bindValueImports, getArgumentText, hasForeignBinding, skipTypeWrappers, typeHoldsAtEveryLayer, } from 'eslint-rule-forge';
|
|
3
3
|
import { TS_STD_FORGE_MODULE } from './constants.mjs';
|
|
4
4
|
|
|
5
5
|
|
|
@@ -39,15 +39,13 @@ export const preferSafeArrayLengthGuard = {
|
|
|
39
39
|
fixable: 'code',
|
|
40
40
|
schema: [],
|
|
41
41
|
messages: {
|
|
42
|
-
useIsEmpty: 'Replace `{{original}}` with `
|
|
43
|
-
useIsNonEmpty: 'Replace `{{original}}` with `
|
|
42
|
+
useIsEmpty: 'Replace `{{original}}` with `{{safeArray}}.isEmpty({{arrayName}})` from ts-std-forge: the comparison narrows nothing, the guard narrows to `readonly []`.',
|
|
43
|
+
useIsNonEmpty: 'Replace `{{original}}` with `{{safeArray}}.isNonEmpty({{arrayName}})` from ts-std-forge: the comparison narrows nothing, so `{{arrayName}}[0]` stays `| undefined`.',
|
|
44
44
|
},
|
|
45
45
|
defaultOptions: [],
|
|
46
46
|
},
|
|
47
47
|
create: (context) => {
|
|
48
48
|
const sourceCode = context.sourceCode;
|
|
49
|
-
const program = sourceCode.ast;
|
|
50
|
-
const tsStdForgeImports = getImportDeclarations(program, TS_STD_FORGE_MODULE);
|
|
51
49
|
|
|
52
50
|
|
|
53
51
|
|
|
@@ -72,36 +70,47 @@ export const preferSafeArrayLengthGuard = {
|
|
|
72
70
|
mut_nodesToFix.push({ node, ...matched });
|
|
73
71
|
},
|
|
74
72
|
'Program:exit': () => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
if (mut_nodesToFix.length === 0) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const binding = bindValueImports(sourceCode, TS_STD_FORGE_MODULE, [
|
|
77
|
+
'SafeArray',
|
|
78
|
+
]);
|
|
79
|
+
const localName = binding?.localNames[0];
|
|
79
80
|
|
|
80
81
|
|
|
81
|
-
for (const
|
|
82
|
+
for (const { node, arrayExpression, guard } of mut_nodesToFix) {
|
|
82
83
|
const arrayText = getArgumentText(sourceCode, arrayExpression);
|
|
84
|
+
const writable = binding !== undefined &&
|
|
85
|
+
localName !== undefined &&
|
|
86
|
+
!hasForeignBinding(sourceCode, node, localName, TS_STD_FORGE_MODULE);
|
|
83
87
|
context.report({
|
|
84
88
|
node,
|
|
85
89
|
messageId: MESSAGE_IDS[guard],
|
|
86
90
|
data: {
|
|
87
91
|
original: sourceCode.getText(node),
|
|
88
92
|
arrayName: arrayText,
|
|
93
|
+
|
|
94
|
+
safeArray: writable ? localName : 'SafeArray',
|
|
89
95
|
},
|
|
90
|
-
fix:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
: [];
|
|
97
|
-
return [...importFixes, fixer.replaceText(node, replacement)];
|
|
98
|
-
},
|
|
96
|
+
fix: writable
|
|
97
|
+
? (fixer) => [
|
|
98
|
+
...binding.edits(fixer),
|
|
99
|
+
fixer.replaceText(node, `${localName}.${guard}(${arrayText})`),
|
|
100
|
+
]
|
|
101
|
+
: null,
|
|
99
102
|
});
|
|
100
103
|
}
|
|
101
104
|
},
|
|
102
105
|
};
|
|
103
106
|
},
|
|
104
107
|
};
|
|
108
|
+
const isLengthAccess = (
|
|
109
|
+
|
|
110
|
+
node) => node.type === AST_NODE_TYPES.MemberExpression &&
|
|
111
|
+
!node.computed &&
|
|
112
|
+
node.property.type === AST_NODE_TYPES.Identifier &&
|
|
113
|
+
node.property.name === 'length';
|
|
105
114
|
|
|
106
115
|
|
|
107
116
|
|
|
@@ -134,10 +143,4 @@ node) => {
|
|
|
134
143
|
? undefined
|
|
135
144
|
: { arrayExpression: lengthSide.object, guard: matched.guard };
|
|
136
145
|
};
|
|
137
|
-
const isLengthAccess = (
|
|
138
|
-
|
|
139
|
-
node) => node.type === AST_NODE_TYPES.MemberExpression &&
|
|
140
|
-
!node.computed &&
|
|
141
|
-
node.property.type === AST_NODE_TYPES.Identifier &&
|
|
142
|
-
node.property.name === 'length';
|
|
143
146
|
//# sourceMappingURL=prefer-safe-array-length-guard.mjs.map
|
|
@@ -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,
|
|
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,2 +1,2 @@
|
|
|
1
|
-
export declare const preferSafeNumberParseInteger: import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseInt", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
1
|
+
export declare const preferSafeNumberParseInteger: import("@typescript-eslint/utils/ts-eslint").RuleModule<"replaceWithSafeParse" | "useSafeParseInt", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
2
2
|
//# sourceMappingURL=prefer-safe-number-parse-integer.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-safe-number-parse-integer.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse-integer.mts"],"names":[],"mappings":"AAGA,eAAO,MAAM,4BAA4B,
|
|
1
|
+
{"version":3,"file":"prefer-safe-number-parse-integer.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse-integer.mts"],"names":[],"mappings":"AAGA,eAAO,MAAM,4BAA4B,sLAIvC,CAAC"}
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* result in a `FiniteNumber` brand. This one names the implementation, for
|
|
5
5
|
* callers who do not want the brand or the extra dependency.
|
|
6
6
|
*/
|
|
7
|
-
export declare const preferSafeNumberParse: import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseFloat", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
7
|
+
export declare const preferSafeNumberParse: import("@typescript-eslint/utils/ts-eslint").RuleModule<"replaceWithSafeParse" | "useSafeParseFloat", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
8
8
|
//# sourceMappingURL=prefer-safe-number-parse.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-safe-number-parse.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse.mts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB
|
|
1
|
+
{"version":3,"file":"prefer-safe-number-parse.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-safe-number-parse.mts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,wLAIhC,CAAC"}
|
package/dist/rules/rules.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ export declare const tsStdForgeRules: {
|
|
|
3
3
|
readonly 'prefer-is-record-and-has-key': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useHasKey" | "useIsRecordAndHasKey", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
4
4
|
readonly 'prefer-safe-array-is-array': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsArray", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
5
5
|
readonly 'prefer-safe-array-length-guard': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty" | "useIsNonEmpty", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
6
|
-
readonly 'prefer-safe-number-parse': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseFloat", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
7
|
-
readonly 'prefer-safe-number-parse-integer': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseInt", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
6
|
+
readonly 'prefer-safe-number-parse': import("@typescript-eslint/utils/ts-eslint").RuleModule<"replaceWithSafeParse" | "useSafeParseFloat", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
7
|
+
readonly 'prefer-safe-number-parse-integer': import("@typescript-eslint/utils/ts-eslint").RuleModule<"replaceWithSafeParse" | "useSafeParseInt", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=rules.d.mts.map
|
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.17",
|
|
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.72.0",
|
|
38
|
-
"eslint-rule-forge": "^0.
|
|
38
|
+
"eslint-rule-forge": "^0.5.0",
|
|
39
39
|
"ts-std-forge": "0.5.2",
|
|
40
40
|
"ts-type-forge": "~9.2.2"
|
|
41
41
|
},
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"@typescript-eslint/parser": "8.72.0",
|
|
44
44
|
"@typescript-eslint/rule-tester": "8.72.0",
|
|
45
45
|
"dedent": "1.7.2",
|
|
46
|
-
"eslint-config-typed": "7.
|
|
47
|
-
"eslint-plugin-ts-fortress": "1.3.
|
|
48
|
-
"eslint-plugin-ts-type-forge": "0.8.
|
|
46
|
+
"eslint-config-typed": "7.3.0",
|
|
47
|
+
"eslint-plugin-ts-fortress": "1.3.12",
|
|
48
|
+
"eslint-plugin-ts-type-forge": "0.8.17",
|
|
49
49
|
"prettier": "3.9.10",
|
|
50
50
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
51
51
|
"prettier-plugin-packagejson": "3.0.2",
|
package/src/plugin.mts
CHANGED
|
@@ -5,9 +5,9 @@ import type { ESLintFlatConfig, ESLintPlugin } from './types.mjs';
|
|
|
5
5
|
/**
|
|
6
6
|
* Every rule this plugin ships, at `error`.
|
|
7
7
|
*
|
|
8
|
-
* The `satisfies` clause below is keyed off {@link tsStdForgeRules}, so leaving
|
|
9
|
-
* rule out, naming one that does not exist, or setting one to anything
|
|
10
|
-
*
|
|
8
|
+
* The `satisfies` clause below is keyed off {@link tsStdForgeRules}, so leaving
|
|
9
|
+
* a rule out, naming one that does not exist, or setting one to anything but
|
|
10
|
+
* `error` fails to type-check. That is the whole check; no test repeats it.
|
|
11
11
|
*/
|
|
12
12
|
const recommendedRules = {
|
|
13
13
|
'ts-std-forge/prefer-is-non-null-object': 'error',
|
|
@@ -28,8 +28,8 @@ const recommendedConfig = {
|
|
|
28
28
|
// `eslintPluginTsStdForge` refers to. ESLint rejects a plugin name that
|
|
29
29
|
// maps to two different objects with `Cannot redefine plugin`, which is
|
|
30
30
|
// what a user combining this preset with their own
|
|
31
|
-
// `plugins: { 'ts-std-forge': eslintPluginTsStdForge }` entry would hit
|
|
32
|
-
//
|
|
31
|
+
// `plugins: { 'ts-std-forge': eslintPluginTsStdForge }` entry would hit if
|
|
32
|
+
// the preset embedded a separate copy of the plugin.
|
|
33
33
|
get 'ts-std-forge'(): ESLintPlugin {
|
|
34
34
|
return eslintPluginTsStdForge;
|
|
35
35
|
},
|
package/src/rule-types.mts
CHANGED
|
@@ -76,7 +76,7 @@ namespace PreferSafeArrayLengthGuard {
|
|
|
76
76
|
* | :--------- | :--------- |
|
|
77
77
|
* | type | suggestion |
|
|
78
78
|
* | deprecated | false |
|
|
79
|
-
* | fixable |
|
|
79
|
+
* | fixable | false |
|
|
80
80
|
* ```
|
|
81
81
|
*/
|
|
82
82
|
namespace PreferSafeNumberParse {
|
|
@@ -91,7 +91,7 @@ namespace PreferSafeNumberParse {
|
|
|
91
91
|
* | :--------- | :--------- |
|
|
92
92
|
* | type | suggestion |
|
|
93
93
|
* | deprecated | false |
|
|
94
|
-
* | fixable |
|
|
94
|
+
* | fixable | false |
|
|
95
95
|
* ```
|
|
96
96
|
*/
|
|
97
97
|
namespace PreferSafeNumberParseInteger {
|
|
@@ -53,9 +53,9 @@ ruleTester.run('prefer-is-non-null-object', preferIsNonNullObject, {
|
|
|
53
53
|
],
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
// The replacement names the bare identifier: that is the reference both
|
|
57
|
-
//
|
|
58
|
-
//
|
|
56
|
+
// The replacement names the bare identifier: that is the reference both halves
|
|
57
|
+
// narrow, and a wrapper around the argument would stop the guard from narrowing
|
|
58
|
+
// it.
|
|
59
59
|
describe('prefer-is-non-null-object through type wrappers', () => {
|
|
60
60
|
ruleTester.run('prefer-is-non-null-object', preferIsNonNullObject, {
|
|
61
61
|
valid: [
|
|
@@ -4,10 +4,9 @@ import {
|
|
|
4
4
|
type TSESTree,
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
bindValueImports,
|
|
8
8
|
getArgumentText,
|
|
9
|
-
|
|
10
|
-
getNamedImports,
|
|
9
|
+
hasForeignBinding,
|
|
11
10
|
skipTypeWrappers,
|
|
12
11
|
typeHoldsAtEveryLayer,
|
|
13
12
|
} from 'eslint-rule-forge';
|
|
@@ -70,9 +69,9 @@ export const preferSafeArrayLengthGuard: TSESLint.RuleModule<
|
|
|
70
69
|
schema: [],
|
|
71
70
|
messages: {
|
|
72
71
|
useIsEmpty:
|
|
73
|
-
'Replace `{{original}}` with `
|
|
72
|
+
'Replace `{{original}}` with `{{safeArray}}.isEmpty({{arrayName}})` from ts-std-forge: the comparison narrows nothing, the guard narrows to `readonly []`.',
|
|
74
73
|
useIsNonEmpty:
|
|
75
|
-
'Replace `{{original}}` with `
|
|
74
|
+
'Replace `{{original}}` with `{{safeArray}}.isNonEmpty({{arrayName}})` from ts-std-forge: the comparison narrows nothing, so `{{arrayName}}[0]` stays `| undefined`.',
|
|
76
75
|
},
|
|
77
76
|
defaultOptions: [],
|
|
78
77
|
},
|
|
@@ -80,20 +79,13 @@ export const preferSafeArrayLengthGuard: TSESLint.RuleModule<
|
|
|
80
79
|
create: (context) => {
|
|
81
80
|
const sourceCode = context.sourceCode;
|
|
82
81
|
|
|
83
|
-
const program = sourceCode.ast;
|
|
84
|
-
|
|
85
|
-
const tsStdForgeImports = getImportDeclarations(
|
|
86
|
-
program,
|
|
87
|
-
TS_STD_FORGE_MODULE,
|
|
88
|
-
);
|
|
89
|
-
|
|
90
82
|
/**
|
|
91
83
|
* Whether `expression` is an array or tuple. Without type information the
|
|
92
84
|
* answer is no: `.length` is also a string's, a function's and a
|
|
93
85
|
* `TypedArray`'s, and none of those can be handed to the guard. It has to
|
|
94
86
|
* be one at every layer of type wrappers: a string cast to an array is
|
|
95
|
-
* still a string, and the fix, which keeps the wrapper, hands the guard
|
|
96
|
-
*
|
|
87
|
+
* still a string, and the fix, which keeps the wrapper, hands the guard the
|
|
88
|
+
* type the outermost layer states.
|
|
97
89
|
*/
|
|
98
90
|
const isArrayLike = (
|
|
99
91
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -127,39 +119,49 @@ export const preferSafeArrayLengthGuard: TSESLint.RuleModule<
|
|
|
127
119
|
mut_nodesToFix.push({ node, ...matched });
|
|
128
120
|
},
|
|
129
121
|
'Program:exit': () => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
122
|
+
if (mut_nodesToFix.length === 0) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const binding = bindValueImports(sourceCode, TS_STD_FORGE_MODULE, [
|
|
127
|
+
'SafeArray',
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
const localName = binding?.localNames[0];
|
|
131
|
+
|
|
132
|
+
// Every fix brings `SafeArray` into scope itself, so that one applied
|
|
133
|
+
// alone (an editor's quick fix) is complete.
|
|
134
|
+
for (const { node, arrayExpression, guard } of mut_nodesToFix) {
|
|
142
135
|
const arrayText = getArgumentText(sourceCode, arrayExpression);
|
|
143
136
|
|
|
137
|
+
const writable =
|
|
138
|
+
binding !== undefined &&
|
|
139
|
+
localName !== undefined &&
|
|
140
|
+
!hasForeignBinding(
|
|
141
|
+
sourceCode,
|
|
142
|
+
node,
|
|
143
|
+
localName,
|
|
144
|
+
TS_STD_FORGE_MODULE,
|
|
145
|
+
);
|
|
146
|
+
|
|
144
147
|
context.report({
|
|
145
148
|
node,
|
|
146
149
|
messageId: MESSAGE_IDS[guard],
|
|
147
150
|
data: {
|
|
148
151
|
original: sourceCode.getText(node),
|
|
149
152
|
arrayName: arrayText,
|
|
153
|
+
// The name the fix writes; with no fix, the export's own.
|
|
154
|
+
safeArray: writable ? localName : 'SafeArray',
|
|
150
155
|
},
|
|
151
|
-
fix:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return [...importFixes, fixer.replaceText(node, replacement)];
|
|
162
|
-
},
|
|
156
|
+
fix: writable
|
|
157
|
+
? (fixer) => [
|
|
158
|
+
...binding.edits(fixer),
|
|
159
|
+
fixer.replaceText(
|
|
160
|
+
node,
|
|
161
|
+
`${localName}.${guard}(${arrayText})`,
|
|
162
|
+
),
|
|
163
|
+
]
|
|
164
|
+
: null,
|
|
163
165
|
});
|
|
164
166
|
}
|
|
165
167
|
},
|
|
@@ -167,6 +169,15 @@ export const preferSafeArrayLengthGuard: TSESLint.RuleModule<
|
|
|
167
169
|
},
|
|
168
170
|
} as const;
|
|
169
171
|
|
|
172
|
+
const isLengthAccess = (
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
174
|
+
node: TSESTree.Node,
|
|
175
|
+
): node is TSESTree.MemberExpression =>
|
|
176
|
+
node.type === AST_NODE_TYPES.MemberExpression &&
|
|
177
|
+
!node.computed &&
|
|
178
|
+
node.property.type === AST_NODE_TYPES.Identifier &&
|
|
179
|
+
node.property.name === 'length';
|
|
180
|
+
|
|
170
181
|
/**
|
|
171
182
|
* Parses `<array>.length <op> <literal>` (in either operand order) into the
|
|
172
183
|
* array expression and the guard that replaces it, or `undefined` for anything
|
|
@@ -196,8 +207,8 @@ const matchLengthComparison = (
|
|
|
196
207
|
return undefined;
|
|
197
208
|
}
|
|
198
209
|
|
|
199
|
-
// `xs.length === ys.length` has `.length` on both sides and no literal
|
|
200
|
-
//
|
|
210
|
+
// `xs.length === ys.length` has `.length` on both sides and no literal bound,
|
|
211
|
+
// so the bound check below rejects it.
|
|
201
212
|
if (
|
|
202
213
|
boundSide.type !== AST_NODE_TYPES.Literal ||
|
|
203
214
|
typeof boundSide.value !== 'number'
|
|
@@ -215,12 +226,3 @@ const matchLengthComparison = (
|
|
|
215
226
|
? undefined
|
|
216
227
|
: { arrayExpression: lengthSide.object, guard: matched.guard };
|
|
217
228
|
};
|
|
218
|
-
|
|
219
|
-
const isLengthAccess = (
|
|
220
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
221
|
-
node: TSESTree.Node,
|
|
222
|
-
): node is TSESTree.MemberExpression =>
|
|
223
|
-
node.type === AST_NODE_TYPES.MemberExpression &&
|
|
224
|
-
!node.computed &&
|
|
225
|
-
node.property.type === AST_NODE_TYPES.Identifier &&
|
|
226
|
-
node.property.name === 'length';
|
|
@@ -138,13 +138,22 @@ describe('prefer-safe-array-length-guard', () => {
|
|
|
138
138
|
const a = xs.length === 0;
|
|
139
139
|
const b = ys.length > 0;
|
|
140
140
|
`,
|
|
141
|
-
output:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
141
|
+
output: [
|
|
142
|
+
dedent`
|
|
143
|
+
import { SafeArray } from 'ts-std-forge';
|
|
144
|
+
declare const xs: readonly number[];
|
|
145
|
+
declare const ys: readonly number[];
|
|
146
|
+
const a = SafeArray.isEmpty(xs);
|
|
147
|
+
const b = ys.length > 0;
|
|
148
|
+
`,
|
|
149
|
+
dedent`
|
|
150
|
+
import { SafeArray } from 'ts-std-forge';
|
|
151
|
+
declare const xs: readonly number[];
|
|
152
|
+
declare const ys: readonly number[];
|
|
153
|
+
const a = SafeArray.isEmpty(xs);
|
|
154
|
+
const b = SafeArray.isNonEmpty(ys);
|
|
155
|
+
`,
|
|
156
|
+
],
|
|
148
157
|
errors: [{ messageId: 'useIsEmpty' }, { messageId: 'useIsNonEmpty' }],
|
|
149
158
|
},
|
|
150
159
|
],
|
|
@@ -262,3 +271,134 @@ describe('prefer-safe-array-length-guard with parenthesized operands', () => {
|
|
|
262
271
|
],
|
|
263
272
|
});
|
|
264
273
|
});
|
|
274
|
+
|
|
275
|
+
describe('prefer-safe-array-length-guard and the import it adds', () => {
|
|
276
|
+
tester.run('prefer-safe-array-length-guard', preferSafeArrayLengthGuard, {
|
|
277
|
+
valid: [],
|
|
278
|
+
invalid: [
|
|
279
|
+
{
|
|
280
|
+
name: 'a leading directive stays first',
|
|
281
|
+
code: dedent`
|
|
282
|
+
'use client';
|
|
283
|
+
declare const xs: readonly number[];
|
|
284
|
+
const ok = xs.length > 0;
|
|
285
|
+
`,
|
|
286
|
+
output: dedent`
|
|
287
|
+
'use client';
|
|
288
|
+
import { SafeArray } from 'ts-std-forge';
|
|
289
|
+
declare const xs: readonly number[];
|
|
290
|
+
const ok = SafeArray.isNonEmpty(xs);
|
|
291
|
+
`,
|
|
292
|
+
errors: [{ messageId: 'useIsNonEmpty' }],
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: 'joins an existing named import from the module',
|
|
296
|
+
code: dedent`
|
|
297
|
+
import { isNonNullObject } from 'ts-std-forge';
|
|
298
|
+
declare const xs: readonly number[];
|
|
299
|
+
const ok = xs.length > 0;
|
|
300
|
+
`,
|
|
301
|
+
output: dedent`
|
|
302
|
+
import { isNonNullObject, SafeArray } from 'ts-std-forge';
|
|
303
|
+
declare const xs: readonly number[];
|
|
304
|
+
const ok = SafeArray.isNonEmpty(xs);
|
|
305
|
+
`,
|
|
306
|
+
errors: [{ messageId: 'useIsNonEmpty' }],
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: 'turns a type-only specifier into a value import',
|
|
310
|
+
code: dedent`
|
|
311
|
+
import { type SafeArray } from 'ts-std-forge';
|
|
312
|
+
declare const xs: readonly number[];
|
|
313
|
+
const ok = xs.length > 0;
|
|
314
|
+
`,
|
|
315
|
+
output: dedent`
|
|
316
|
+
import { SafeArray } from 'ts-std-forge';
|
|
317
|
+
declare const xs: readonly number[];
|
|
318
|
+
const ok = SafeArray.isNonEmpty(xs);
|
|
319
|
+
`,
|
|
320
|
+
errors: [{ messageId: 'useIsNonEmpty' }],
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: 'writes the local name of an aliased import',
|
|
324
|
+
code: dedent`
|
|
325
|
+
import { SafeArray as SA } from 'ts-std-forge';
|
|
326
|
+
declare const xs: readonly number[];
|
|
327
|
+
const ok = xs.length > 0;
|
|
328
|
+
`,
|
|
329
|
+
output: dedent`
|
|
330
|
+
import { SafeArray as SA } from 'ts-std-forge';
|
|
331
|
+
declare const xs: readonly number[];
|
|
332
|
+
const ok = SA.isNonEmpty(xs);
|
|
333
|
+
`,
|
|
334
|
+
errors: [
|
|
335
|
+
{
|
|
336
|
+
messageId: 'useIsNonEmpty',
|
|
337
|
+
data: {
|
|
338
|
+
original: 'xs.length > 0',
|
|
339
|
+
arrayName: 'xs',
|
|
340
|
+
safeArray: 'SA',
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: 'no fix when an import type binds the name',
|
|
347
|
+
code: dedent`
|
|
348
|
+
import type { SafeArray } from 'ts-std-forge';
|
|
349
|
+
declare const xs: readonly number[];
|
|
350
|
+
const ok = xs.length > 0;
|
|
351
|
+
`,
|
|
352
|
+
output: null,
|
|
353
|
+
errors: [{ messageId: 'useIsNonEmpty' }],
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: 'no fix when another module binds the name',
|
|
357
|
+
code: dedent`
|
|
358
|
+
import { SafeArray } from 'other';
|
|
359
|
+
declare const xs: readonly number[];
|
|
360
|
+
const ok = xs.length > 0;
|
|
361
|
+
`,
|
|
362
|
+
output: null,
|
|
363
|
+
errors: [
|
|
364
|
+
{
|
|
365
|
+
messageId: 'useIsNonEmpty',
|
|
366
|
+
data: {
|
|
367
|
+
original: 'xs.length > 0',
|
|
368
|
+
arrayName: 'xs',
|
|
369
|
+
safeArray: 'SafeArray',
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: 'no fix, and the export named, where a local binding takes the alias',
|
|
376
|
+
code: dedent`
|
|
377
|
+
import { SafeArray as SA } from 'ts-std-forge';
|
|
378
|
+
declare const xs: readonly number[];
|
|
379
|
+
const f = (SA: unknown) => xs.length > 0;
|
|
380
|
+
`,
|
|
381
|
+
output: null,
|
|
382
|
+
errors: [
|
|
383
|
+
{
|
|
384
|
+
messageId: 'useIsNonEmpty',
|
|
385
|
+
data: {
|
|
386
|
+
original: 'xs.length > 0',
|
|
387
|
+
arrayName: 'xs',
|
|
388
|
+
safeArray: 'SafeArray',
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: 'no fix where a local binding takes the name',
|
|
395
|
+
code: dedent`
|
|
396
|
+
declare const xs: readonly number[];
|
|
397
|
+
const f = (SafeArray: unknown) => xs.length > 0;
|
|
398
|
+
`,
|
|
399
|
+
output: null,
|
|
400
|
+
errors: [{ messageId: 'useIsNonEmpty' }],
|
|
401
|
+
},
|
|
402
|
+
],
|
|
403
|
+
});
|
|
404
|
+
});
|
|
@@ -51,17 +51,75 @@ describe('prefer-safe-number-parse-integer', () => {
|
|
|
51
51
|
],
|
|
52
52
|
invalid: [
|
|
53
53
|
{
|
|
54
|
-
name: '
|
|
54
|
+
name: 'offers no suggestion when `Result` is bound to another module',
|
|
55
55
|
code: dedent`
|
|
56
|
+
import { Result } from 'other-lib';
|
|
57
|
+
|
|
56
58
|
declare const s: string;
|
|
57
59
|
const n = parseInt(s);
|
|
60
|
+
export const r = Result;
|
|
61
|
+
`,
|
|
62
|
+
errors: [{ messageId: 'useSafeParseInt', suggestions: [] }],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
// The replacement reads these strings differently ('1e3' is 1000, not
|
|
66
|
+
// 1; '12px' is NaN, not 12), so it is offered, never applied by
|
|
67
|
+
// --fix
|
|
68
|
+
name: 'a string the replacement parses differently is a suggestion, not a fix',
|
|
69
|
+
code: dedent`
|
|
70
|
+
const a = parseInt('1e3');
|
|
71
|
+
const b = parseInt('12px', 10);
|
|
58
72
|
`,
|
|
59
|
-
|
|
60
|
-
|
|
73
|
+
errors: [
|
|
74
|
+
{
|
|
75
|
+
messageId: 'useSafeParseInt',
|
|
76
|
+
suggestions: [
|
|
77
|
+
{
|
|
78
|
+
messageId: 'replaceWithSafeParse',
|
|
79
|
+
output: dedent`
|
|
80
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
81
|
+
const a = Result.unwrapOkOr(SafeNumber.parseInteger('1e3'), Number.NaN);
|
|
82
|
+
const b = parseInt('12px', 10);
|
|
83
|
+
`,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
messageId: 'useSafeParseInt',
|
|
89
|
+
suggestions: [
|
|
90
|
+
{
|
|
91
|
+
messageId: 'replaceWithSafeParse',
|
|
92
|
+
output: dedent`
|
|
93
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
94
|
+
const a = parseInt('1e3');
|
|
95
|
+
const b = Result.unwrapOkOr(SafeNumber.parseInteger('12px'), Number.NaN);
|
|
96
|
+
`,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'rewrites parseInt without a radix',
|
|
104
|
+
code: dedent`
|
|
61
105
|
declare const s: string;
|
|
62
|
-
const n =
|
|
106
|
+
const n = parseInt(s);
|
|
63
107
|
`,
|
|
64
|
-
errors: [
|
|
108
|
+
errors: [
|
|
109
|
+
{
|
|
110
|
+
messageId: 'useSafeParseInt',
|
|
111
|
+
suggestions: [
|
|
112
|
+
{
|
|
113
|
+
messageId: 'replaceWithSafeParse',
|
|
114
|
+
output: dedent`
|
|
115
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
116
|
+
declare const s: string;
|
|
117
|
+
const n = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
|
|
118
|
+
`,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
],
|
|
65
123
|
},
|
|
66
124
|
{
|
|
67
125
|
name: 'rewrites Number.parseInt(x, 10)',
|
|
@@ -69,12 +127,21 @@ describe('prefer-safe-number-parse-integer', () => {
|
|
|
69
127
|
declare const s: string;
|
|
70
128
|
const n = Number.parseInt(s, 10);
|
|
71
129
|
`,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
130
|
+
errors: [
|
|
131
|
+
{
|
|
132
|
+
messageId: 'useSafeParseInt',
|
|
133
|
+
suggestions: [
|
|
134
|
+
{
|
|
135
|
+
messageId: 'replaceWithSafeParse',
|
|
136
|
+
output: dedent`
|
|
137
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
138
|
+
declare const s: string;
|
|
139
|
+
const n = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
|
|
140
|
+
`,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
],
|
|
78
145
|
},
|
|
79
146
|
],
|
|
80
147
|
});
|
|
@@ -120,12 +187,21 @@ describe('prefer-safe-number-parse-integer through type wrappers', () => {
|
|
|
120
187
|
declare const s: string;
|
|
121
188
|
const n = parseInt(s, 10 as const);
|
|
122
189
|
`,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
190
|
+
errors: [
|
|
191
|
+
{
|
|
192
|
+
messageId: 'useSafeParseInt',
|
|
193
|
+
suggestions: [
|
|
194
|
+
{
|
|
195
|
+
messageId: 'replaceWithSafeParse',
|
|
196
|
+
output: dedent`
|
|
197
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
198
|
+
declare const s: string;
|
|
199
|
+
const n = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
|
|
200
|
+
`,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
],
|
|
129
205
|
},
|
|
130
206
|
{
|
|
131
207
|
name: 'reads a radix of 10 through `satisfies` and keeps `satisfies` on the argument',
|
|
@@ -133,12 +209,21 @@ describe('prefer-safe-number-parse-integer through type wrappers', () => {
|
|
|
133
209
|
declare const s: string;
|
|
134
210
|
const n = Number.parseInt(s satisfies string, 10 satisfies number);
|
|
135
211
|
`,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
212
|
+
errors: [
|
|
213
|
+
{
|
|
214
|
+
messageId: 'useSafeParseInt',
|
|
215
|
+
suggestions: [
|
|
216
|
+
{
|
|
217
|
+
messageId: 'replaceWithSafeParse',
|
|
218
|
+
output: dedent`
|
|
219
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
220
|
+
declare const s: string;
|
|
221
|
+
const n = Result.unwrapOkOr(SafeNumber.parseInteger(s satisfies string), Number.NaN);
|
|
222
|
+
`,
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
],
|
|
142
227
|
},
|
|
143
228
|
{
|
|
144
229
|
name: 'reads the callee through `as` and `!`',
|
|
@@ -147,23 +232,35 @@ describe('prefer-safe-number-parse-integer through type wrappers', () => {
|
|
|
147
232
|
const a = (parseInt as typeof parseInt)(s);
|
|
148
233
|
const b = (Number as NumberConstructor)!.parseInt(s, 10);
|
|
149
234
|
`,
|
|
150
|
-
output: [
|
|
151
|
-
dedent`
|
|
152
|
-
import { Result, SafeNumber } from 'ts-std-forge';
|
|
153
|
-
declare const s: string;
|
|
154
|
-
const a = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
|
|
155
|
-
const b = (Number as NumberConstructor)!.parseInt(s, 10);
|
|
156
|
-
`,
|
|
157
|
-
dedent`
|
|
158
|
-
import { Result, SafeNumber } from 'ts-std-forge';
|
|
159
|
-
declare const s: string;
|
|
160
|
-
const a = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
|
|
161
|
-
const b = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
|
|
162
|
-
`,
|
|
163
|
-
],
|
|
164
235
|
errors: [
|
|
165
|
-
{
|
|
166
|
-
|
|
236
|
+
{
|
|
237
|
+
messageId: 'useSafeParseInt',
|
|
238
|
+
suggestions: [
|
|
239
|
+
{
|
|
240
|
+
messageId: 'replaceWithSafeParse',
|
|
241
|
+
output: dedent`
|
|
242
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
243
|
+
declare const s: string;
|
|
244
|
+
const a = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
|
|
245
|
+
const b = (Number as NumberConstructor)!.parseInt(s, 10);
|
|
246
|
+
`,
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
messageId: 'useSafeParseInt',
|
|
252
|
+
suggestions: [
|
|
253
|
+
{
|
|
254
|
+
messageId: 'replaceWithSafeParse',
|
|
255
|
+
output: dedent`
|
|
256
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
257
|
+
declare const s: string;
|
|
258
|
+
const a = (parseInt as typeof parseInt)(s);
|
|
259
|
+
const b = Result.unwrapOkOr(SafeNumber.parseInteger(s), Number.NaN);
|
|
260
|
+
`,
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
},
|
|
167
264
|
],
|
|
168
265
|
},
|
|
169
266
|
{
|
|
@@ -172,12 +269,21 @@ describe('prefer-safe-number-parse-integer through type wrappers', () => {
|
|
|
172
269
|
declare const s: string;
|
|
173
270
|
const n = parseInt(s! as string);
|
|
174
271
|
`,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
272
|
+
errors: [
|
|
273
|
+
{
|
|
274
|
+
messageId: 'useSafeParseInt',
|
|
275
|
+
suggestions: [
|
|
276
|
+
{
|
|
277
|
+
messageId: 'replaceWithSafeParse',
|
|
278
|
+
output: dedent`
|
|
279
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
280
|
+
declare const s: string;
|
|
281
|
+
const n = Result.unwrapOkOr(SafeNumber.parseInteger(s! as string), Number.NaN);
|
|
282
|
+
`,
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
],
|
|
181
287
|
},
|
|
182
288
|
],
|
|
183
289
|
});
|
|
@@ -194,13 +300,22 @@ describe('prefer-safe-number-parse-integer with parenthesized operands', () => {
|
|
|
194
300
|
declare const s: string;
|
|
195
301
|
const n = parseInt((a, s), (10));
|
|
196
302
|
`,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
303
|
+
errors: [
|
|
304
|
+
{
|
|
305
|
+
messageId: 'useSafeParseInt',
|
|
306
|
+
suggestions: [
|
|
307
|
+
{
|
|
308
|
+
messageId: 'replaceWithSafeParse',
|
|
309
|
+
output: dedent`
|
|
310
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
311
|
+
declare const a: number;
|
|
312
|
+
declare const s: string;
|
|
313
|
+
const n = Result.unwrapOkOr(SafeNumber.parseInteger((a, s)), Number.NaN);
|
|
314
|
+
`,
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
],
|
|
204
319
|
},
|
|
205
320
|
],
|
|
206
321
|
});
|
|
@@ -21,7 +21,7 @@ describe('prefer-safe-number-parse', () => {
|
|
|
21
21
|
tester.run('prefer-safe-number-parse', preferSafeNumberParse, {
|
|
22
22
|
valid: [
|
|
23
23
|
{
|
|
24
|
-
name: 'ignores number arguments to Number() (
|
|
24
|
+
name: 'ignores number arguments to Number() (the rewrite would be unsafe)',
|
|
25
25
|
code: dedent`
|
|
26
26
|
const x = 42;
|
|
27
27
|
const n = Number(x);
|
|
@@ -72,17 +72,74 @@ describe('prefer-safe-number-parse', () => {
|
|
|
72
72
|
],
|
|
73
73
|
invalid: [
|
|
74
74
|
{
|
|
75
|
-
name: '
|
|
75
|
+
name: 'offers no suggestion when `Result` is bound to another module',
|
|
76
76
|
code: dedent`
|
|
77
|
+
import { Result } from 'other-lib';
|
|
78
|
+
|
|
77
79
|
declare const s: string;
|
|
78
80
|
const n = parseFloat(s);
|
|
81
|
+
export const r = Result;
|
|
82
|
+
`,
|
|
83
|
+
errors: [{ messageId: 'useSafeParseFloat', suggestions: [] }],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
// The replacement reads these strings differently ('12px' is NaN, not
|
|
87
|
+
// 12; '' is NaN, not 0), so it is offered, never applied by --fix
|
|
88
|
+
name: 'a string the replacement parses differently is a suggestion, not a fix',
|
|
89
|
+
code: dedent`
|
|
90
|
+
const a = parseFloat('12px');
|
|
91
|
+
const b = Number('');
|
|
79
92
|
`,
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
errors: [
|
|
94
|
+
{
|
|
95
|
+
messageId: 'useSafeParseFloat',
|
|
96
|
+
suggestions: [
|
|
97
|
+
{
|
|
98
|
+
messageId: 'replaceWithSafeParse',
|
|
99
|
+
output: dedent`
|
|
100
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
101
|
+
const a = Result.unwrapOkOr(SafeNumber.parse('12px'), Number.NaN);
|
|
102
|
+
const b = Number('');
|
|
103
|
+
`,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
messageId: 'useSafeParseFloat',
|
|
109
|
+
suggestions: [
|
|
110
|
+
{
|
|
111
|
+
messageId: 'replaceWithSafeParse',
|
|
112
|
+
output: dedent`
|
|
113
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
114
|
+
const a = parseFloat('12px');
|
|
115
|
+
const b = Result.unwrapOkOr(SafeNumber.parse(''), Number.NaN);
|
|
116
|
+
`,
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'rewrites parseFloat on a string',
|
|
124
|
+
code: dedent`
|
|
82
125
|
declare const s: string;
|
|
83
|
-
const n =
|
|
126
|
+
const n = parseFloat(s);
|
|
84
127
|
`,
|
|
85
|
-
errors: [
|
|
128
|
+
errors: [
|
|
129
|
+
{
|
|
130
|
+
messageId: 'useSafeParseFloat',
|
|
131
|
+
suggestions: [
|
|
132
|
+
{
|
|
133
|
+
messageId: 'replaceWithSafeParse',
|
|
134
|
+
output: dedent`
|
|
135
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
136
|
+
declare const s: string;
|
|
137
|
+
const n = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
|
|
138
|
+
`,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
],
|
|
86
143
|
},
|
|
87
144
|
{
|
|
88
145
|
name: 'rewrites Number.parseFloat on a string',
|
|
@@ -90,12 +147,21 @@ describe('prefer-safe-number-parse', () => {
|
|
|
90
147
|
declare const s: string;
|
|
91
148
|
const n = Number.parseFloat(s);
|
|
92
149
|
`,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
150
|
+
errors: [
|
|
151
|
+
{
|
|
152
|
+
messageId: 'useSafeParseFloat',
|
|
153
|
+
suggestions: [
|
|
154
|
+
{
|
|
155
|
+
messageId: 'replaceWithSafeParse',
|
|
156
|
+
output: dedent`
|
|
157
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
158
|
+
declare const s: string;
|
|
159
|
+
const n = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
|
|
160
|
+
`,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
],
|
|
99
165
|
},
|
|
100
166
|
{
|
|
101
167
|
name: 'rewrites Number(x) when x is a string',
|
|
@@ -103,12 +169,21 @@ describe('prefer-safe-number-parse', () => {
|
|
|
103
169
|
declare const s: string;
|
|
104
170
|
const n = Number(s);
|
|
105
171
|
`,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
172
|
+
errors: [
|
|
173
|
+
{
|
|
174
|
+
messageId: 'useSafeParseFloat',
|
|
175
|
+
suggestions: [
|
|
176
|
+
{
|
|
177
|
+
messageId: 'replaceWithSafeParse',
|
|
178
|
+
output: dedent`
|
|
179
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
180
|
+
declare const s: string;
|
|
181
|
+
const n = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
|
|
182
|
+
`,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
],
|
|
112
187
|
},
|
|
113
188
|
{
|
|
114
189
|
name: 'adds only the missing import',
|
|
@@ -117,12 +192,21 @@ describe('prefer-safe-number-parse', () => {
|
|
|
117
192
|
declare const s: string;
|
|
118
193
|
const n = parseFloat(s);
|
|
119
194
|
`,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
195
|
+
errors: [
|
|
196
|
+
{
|
|
197
|
+
messageId: 'useSafeParseFloat',
|
|
198
|
+
suggestions: [
|
|
199
|
+
{
|
|
200
|
+
messageId: 'replaceWithSafeParse',
|
|
201
|
+
output: dedent`
|
|
202
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
203
|
+
declare const s: string;
|
|
204
|
+
const n = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
|
|
205
|
+
`,
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
],
|
|
126
210
|
},
|
|
127
211
|
],
|
|
128
212
|
});
|
|
@@ -174,12 +258,21 @@ describe('prefer-safe-number-parse through type wrappers', () => {
|
|
|
174
258
|
declare const s: string;
|
|
175
259
|
const n = Number(s satisfies string);
|
|
176
260
|
`,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
261
|
+
errors: [
|
|
262
|
+
{
|
|
263
|
+
messageId: 'useSafeParseFloat',
|
|
264
|
+
suggestions: [
|
|
265
|
+
{
|
|
266
|
+
messageId: 'replaceWithSafeParse',
|
|
267
|
+
output: dedent`
|
|
268
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
269
|
+
declare const s: string;
|
|
270
|
+
const n = Result.unwrapOkOr(SafeNumber.parse(s satisfies string), Number.NaN);
|
|
271
|
+
`,
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
],
|
|
183
276
|
},
|
|
184
277
|
{
|
|
185
278
|
name: 'reads the callee through `as` and `!`',
|
|
@@ -188,23 +281,35 @@ describe('prefer-safe-number-parse through type wrappers', () => {
|
|
|
188
281
|
const a = (Number as NumberConstructor)(s);
|
|
189
282
|
const b = Number!.parseFloat(s);
|
|
190
283
|
`,
|
|
191
|
-
output: [
|
|
192
|
-
dedent`
|
|
193
|
-
import { Result, SafeNumber } from 'ts-std-forge';
|
|
194
|
-
declare const s: string;
|
|
195
|
-
const a = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
|
|
196
|
-
const b = Number!.parseFloat(s);
|
|
197
|
-
`,
|
|
198
|
-
dedent`
|
|
199
|
-
import { Result, SafeNumber } from 'ts-std-forge';
|
|
200
|
-
declare const s: string;
|
|
201
|
-
const a = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
|
|
202
|
-
const b = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
|
|
203
|
-
`,
|
|
204
|
-
],
|
|
205
284
|
errors: [
|
|
206
|
-
{
|
|
207
|
-
|
|
285
|
+
{
|
|
286
|
+
messageId: 'useSafeParseFloat',
|
|
287
|
+
suggestions: [
|
|
288
|
+
{
|
|
289
|
+
messageId: 'replaceWithSafeParse',
|
|
290
|
+
output: dedent`
|
|
291
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
292
|
+
declare const s: string;
|
|
293
|
+
const a = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
|
|
294
|
+
const b = Number!.parseFloat(s);
|
|
295
|
+
`,
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
messageId: 'useSafeParseFloat',
|
|
301
|
+
suggestions: [
|
|
302
|
+
{
|
|
303
|
+
messageId: 'replaceWithSafeParse',
|
|
304
|
+
output: dedent`
|
|
305
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
306
|
+
declare const s: string;
|
|
307
|
+
const a = (Number as NumberConstructor)(s);
|
|
308
|
+
const b = Result.unwrapOkOr(SafeNumber.parse(s), Number.NaN);
|
|
309
|
+
`,
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
},
|
|
208
313
|
],
|
|
209
314
|
},
|
|
210
315
|
{
|
|
@@ -213,12 +318,21 @@ describe('prefer-safe-number-parse through type wrappers', () => {
|
|
|
213
318
|
declare const s: string;
|
|
214
319
|
const n = parseFloat(s! as string);
|
|
215
320
|
`,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
321
|
+
errors: [
|
|
322
|
+
{
|
|
323
|
+
messageId: 'useSafeParseFloat',
|
|
324
|
+
suggestions: [
|
|
325
|
+
{
|
|
326
|
+
messageId: 'replaceWithSafeParse',
|
|
327
|
+
output: dedent`
|
|
328
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
329
|
+
declare const s: string;
|
|
330
|
+
const n = Result.unwrapOkOr(SafeNumber.parse(s! as string), Number.NaN);
|
|
331
|
+
`,
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
},
|
|
335
|
+
],
|
|
222
336
|
},
|
|
223
337
|
],
|
|
224
338
|
});
|
|
@@ -235,13 +349,22 @@ describe('prefer-safe-number-parse with parenthesized operands', () => {
|
|
|
235
349
|
declare const s: string;
|
|
236
350
|
const n = parseFloat((a, s));
|
|
237
351
|
`,
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
352
|
+
errors: [
|
|
353
|
+
{
|
|
354
|
+
messageId: 'useSafeParseFloat',
|
|
355
|
+
suggestions: [
|
|
356
|
+
{
|
|
357
|
+
messageId: 'replaceWithSafeParse',
|
|
358
|
+
output: dedent`
|
|
359
|
+
import { Result, SafeNumber } from 'ts-std-forge';
|
|
360
|
+
declare const a: number;
|
|
361
|
+
declare const s: string;
|
|
362
|
+
const n = Result.unwrapOkOr(SafeNumber.parse((a, s)), Number.NaN);
|
|
363
|
+
`,
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
],
|
|
245
368
|
},
|
|
246
369
|
],
|
|
247
370
|
});
|