eslint-plugin-ts-data-forge 0.10.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.
Files changed (43) hide show
  1. package/dist/plugin.d.mts +1 -1
  2. package/dist/rule-types.d.mts +1 -1
  3. package/dist/rules/ast-utils.d.mts +0 -16
  4. package/dist/rules/ast-utils.d.mts.map +1 -1
  5. package/dist/rules/ast-utils.mjs +0 -36
  6. package/dist/rules/ast-utils.mjs.map +1 -1
  7. package/dist/rules/no-side-effect-import.d.mts +1 -8
  8. package/dist/rules/no-side-effect-import.d.mts.map +1 -1
  9. package/dist/rules/no-side-effect-import.mjs +7 -43
  10. package/dist/rules/no-side-effect-import.mjs.map +1 -1
  11. package/dist/rules/prefer-arr-is-array.d.mts +1 -5
  12. package/dist/rules/prefer-arr-is-array.d.mts.map +1 -1
  13. package/dist/rules/prefer-arr-is-array.mjs +5 -83
  14. package/dist/rules/prefer-arr-is-array.mjs.map +1 -1
  15. package/dist/rules/prefer-is-non-null-object.d.mts +1 -5
  16. package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -1
  17. package/dist/rules/prefer-is-non-null-object.mjs +4 -184
  18. package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
  19. package/dist/rules/prefer-is-record-and-has-key.d.mts +1 -5
  20. package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -1
  21. package/dist/rules/prefer-is-record-and-has-key.mjs +4 -219
  22. package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
  23. package/dist/rules/prefer-num-safe-parse-float.d.mts +1 -15
  24. package/dist/rules/prefer-num-safe-parse-float.d.mts.map +1 -1
  25. package/dist/rules/prefer-num-safe-parse-float.mjs +6 -123
  26. package/dist/rules/prefer-num-safe-parse-float.mjs.map +1 -1
  27. package/dist/rules/prefer-num-safe-parse-int.d.mts +1 -13
  28. package/dist/rules/prefer-num-safe-parse-int.d.mts.map +1 -1
  29. package/dist/rules/prefer-num-safe-parse-int.mjs +6 -121
  30. package/dist/rules/prefer-num-safe-parse-int.mjs.map +1 -1
  31. package/dist/rules/rules.d.mts +1 -1
  32. package/package.json +5 -5
  33. package/src/rule-types.mts +1 -1
  34. package/src/rules/ast-utils.mts +0 -51
  35. package/src/rules/no-side-effect-import.mts +7 -61
  36. package/src/rules/prefer-arr-is-array.mts +5 -122
  37. package/src/rules/prefer-arr-is-array.test.mts +50 -7
  38. package/src/rules/prefer-is-non-null-object.mts +4 -274
  39. package/src/rules/prefer-is-record-and-has-key.mts +4 -318
  40. package/src/rules/prefer-num-safe-parse-float.mts +6 -193
  41. package/src/rules/prefer-num-safe-parse-float.test.mts +18 -0
  42. package/src/rules/prefer-num-safe-parse-int.mts +6 -192
  43. package/src/rules/prefer-num-safe-parse-int.test.mts +18 -0
@@ -1,194 +1,8 @@
1
- import {
2
- AST_NODE_TYPES,
3
- type TSESLint,
4
- type TSESTree,
5
- } from '@typescript-eslint/utils';
6
- import {
7
- buildNamedImportFix,
8
- getArgumentText,
9
- getImportDeclarations,
10
- getNamedImports,
11
- skipTypeWrappers,
12
- typeWrapperLayers,
13
- } from 'eslint-rule-forge';
14
- import * as ts from 'typescript';
1
+ import { createPreferSafeParseInt } from 'eslint-rule-forge';
15
2
  import { TS_DATA_FORGE_MODULE } from './constants.mjs';
16
3
 
17
- type Options = readonly [];
18
-
19
- type MessageIds = 'useSafeParseInt';
20
-
21
- /**
22
- * Returns `true` when every constituent of `type` is string-like, i.e. the type
23
- * is assignable to the `string` parameter of `Num.safeParseInt`. A union such
24
- * as `string | undefined` is rejected so the autofix never produces a type
25
- * error.
26
- */
27
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
28
- const isStringType = (type: ts.Type): boolean => {
29
- const parts = type.isUnion() ? type.types : ([type] as const);
30
-
31
- return (
32
- parts.length > 0 &&
33
- parts.every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0)
34
- );
35
- };
36
-
37
- /**
38
- * Detects the base-10 forms of `parseInt` / `Number.parseInt` (`parseInt(x)` or
39
- * `parseInt(x, 10)`) and replaces them with
40
- * `Result.unwrapOkOr(Num.safeParseInt(x), Number.NaN)` (ts-data-forge).
41
- *
42
- * Other radixes (e.g. `parseInt(x, 16)`) are intentionally left untouched, as
43
- * `Num.safeParseInt` only supports base 10.
44
- */
45
- export const preferNumSafeParseInt: TSESLint.RuleModule<MessageIds, Options> = {
46
- meta: {
47
- type: 'suggestion',
48
- docs: {
49
- description:
50
- 'Replace `parseInt(x, 10)` with `Result.unwrapOkOr(Num.safeParseInt(x), Number.NaN)` from ts-data-forge.',
51
- },
52
- fixable: 'code',
53
- schema: [],
54
- messages: {
55
- useSafeParseInt:
56
- 'Replace `{{original}}` with `Result.unwrapOkOr(Num.safeParseInt({{argName}}), Number.NaN)` from ts-data-forge.',
57
- },
58
- },
59
-
60
- create: (context) => {
61
- const sourceCode = context.sourceCode;
62
-
63
- const program = sourceCode.ast;
64
-
65
- const tsDataForgeImports = getImportDeclarations(
66
- program,
67
- TS_DATA_FORGE_MODULE,
68
- );
69
-
70
- const services = context.sourceCode.parserServices;
71
-
72
- const mut_nodesToFix: {
73
- node: TSESTree.CallExpression;
74
- argExpression: TSESTree.Expression;
75
- }[] = [];
76
-
77
- return {
78
- CallExpression: (node) => {
79
- const { callee } = node;
80
-
81
- // Match `parseInt(...)` or `Number.parseInt(...)`.
82
- const isGlobalParseInt =
83
- callee.type === AST_NODE_TYPES.Identifier &&
84
- callee.name === 'parseInt';
85
-
86
- const isNumberParseInt =
87
- callee.type === AST_NODE_TYPES.MemberExpression &&
88
- !callee.computed &&
89
- callee.object.type === AST_NODE_TYPES.Identifier &&
90
- callee.object.name === 'Number' &&
91
- callee.property.type === AST_NODE_TYPES.Identifier &&
92
- callee.property.name === 'parseInt';
93
-
94
- if (!isGlobalParseInt && !isNumberParseInt) {
95
- return;
96
- }
97
-
98
- const args = node.arguments;
99
-
100
- const firstArg = args[0];
101
-
102
- if (firstArg === undefined) {
103
- return;
104
- }
105
-
106
- // Spread argument (`parseInt(...rest)`) cannot be rewritten safely.
107
- if (firstArg.type === AST_NODE_TYPES.SpreadElement) {
108
- return;
109
- }
110
-
111
- // Only base 10: accept a missing radix or an explicit literal `10`,
112
- // under any type wrapper (`10 as const`).
113
- if (args.length >= 2) {
114
- const radix =
115
- args[1] === undefined ? undefined : skipTypeWrappers(args[1]);
116
-
117
- if (radix?.type !== AST_NODE_TYPES.Literal || radix.value !== 10) {
118
- return;
119
- }
120
- }
121
-
122
- // The argument must be a `string` so the autofix is type-safe against
123
- // `Num.safeParseInt(s: string)`. Without type information, skip. Every
124
- // layer of type wrappers is asked (see typeWrapperLayers): a cast
125
- // does not make a number a string.
126
- if (services?.program == null) {
127
- return;
128
- }
129
-
130
- const checker = services.program.getTypeChecker();
131
-
132
- const isString = (
133
- // AST nodes hold mutable child arrays, so they are not deeply readonly.
134
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
135
- expression: TSESTree.Node,
136
- ): boolean => {
137
- const tsNode = services.esTreeNodeToTSNodeMap?.get(expression);
138
-
139
- return (
140
- tsNode !== undefined &&
141
- isStringType(checker.getTypeAtLocation(tsNode))
142
- );
143
- };
144
-
145
- if (!typeWrapperLayers(firstArg).every(isString)) {
146
- return;
147
- }
148
-
149
- mut_nodesToFix.push({ node, argExpression: firstArg });
150
- },
151
- 'Program:exit': () => {
152
- const namedImports = getNamedImports(tsDataForgeImports);
153
-
154
- const missingImports = (['Num', 'Result'] as const).filter(
155
- (name) => !namedImports.includes(name),
156
- );
157
-
158
- for (const [
159
- index,
160
- { node, argExpression },
161
- ] of mut_nodesToFix.entries()) {
162
- const argText = getArgumentText(sourceCode, argExpression);
163
-
164
- const originalText = sourceCode.getText(node);
165
-
166
- context.report({
167
- node,
168
- messageId: 'useSafeParseInt',
169
- data: {
170
- original: originalText,
171
- argName: argText,
172
- },
173
- fix: (fixer) => {
174
- const core = `Result.unwrapOkOr(Num.safeParseInt(${argText}), Number.NaN)`;
175
-
176
- const importFixes =
177
- index === 0 && missingImports.length > 0
178
- ? buildNamedImportFix(
179
- fixer,
180
- program,
181
- TS_DATA_FORGE_MODULE,
182
- missingImports,
183
- )
184
- : [];
185
-
186
- return [...importFixes, fixer.replaceText(node, core)];
187
- },
188
- });
189
- }
190
- },
191
- };
192
- },
193
- defaultOptions: [],
194
- } as const;
4
+ export const preferNumSafeParseInt = createPreferSafeParseInt({
5
+ moduleName: TS_DATA_FORGE_MODULE,
6
+ namespace: 'Num',
7
+ method: 'safeParseInt',
8
+ });
@@ -173,6 +173,24 @@ describe('prefer-num-safe-parse-int through type wrappers', () => {
173
173
  { messageId: 'useSafeParseInt' },
174
174
  ],
175
175
  },
176
+ {
177
+ name: 'the callee read through `as` and `!`',
178
+ code: dedent`
179
+ declare const s: string;
180
+ const a = (parseInt as typeof parseInt)(s);
181
+ const b = (Number as NumberConstructor)!.parseInt(s, 10);
182
+ `,
183
+ output: dedent`
184
+ import { Num, Result } from 'ts-data-forge';
185
+ declare const s: string;
186
+ const a = Result.unwrapOkOr(Num.safeParseInt(s), Number.NaN);
187
+ const b = Result.unwrapOkOr(Num.safeParseInt(s), Number.NaN);
188
+ `,
189
+ errors: [
190
+ { messageId: 'useSafeParseInt' },
191
+ { messageId: 'useSafeParseInt' },
192
+ ],
193
+ },
176
194
  ],
177
195
  });
178
196
  }, 20000);