eslint-config-typed 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
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.
11
+ */
12
+ export declare const preferNumSafeParseFloat: TSESLint.RuleModule<MessageIds, Options>;
13
+ export {};
14
+ //# sourceMappingURL=prefer-num-safe-parse-float.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-num-safe-parse-float.d.mts","sourceRoot":"","sources":["../../../../src/plugins/ts-data-forge/rules/prefer-num-safe-parse-float.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAQlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,mBAAmB,CAAC;AAkBtC;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CA6HjE,CAAC"}
@@ -0,0 +1,111 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import * as ts from 'typescript';
3
+ import { getTsDataForgeImport, getNamedImports, buildImportFixes } from './import-utils.mjs';
4
+
5
+ /**
6
+ * Returns `true` when every constituent of `type` is string-like, i.e. the
7
+ * type is assignable to the `string` parameter of `Num.safeParseFloat`. A
8
+ * union such as `string | undefined` is rejected so the autofix never
9
+ * produces a type error.
10
+ */
11
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
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
+ * Detects `parseFloat(x)`, `Number.parseFloat(x)`, and `Number(x)` (when x
19
+ * is typed as `string`) and replaces them with
20
+ * `Result.unwrapOkOr(Num.safeParseFloat(x), Number.NaN)` (ts-data-forge).
21
+ *
22
+ * `Number(x)` is only flagged when x is purely a string type to avoid false
23
+ * positives for `Number(someBoolean)` or `Number(someNumber)` uses.
24
+ */
25
+ const preferNumSafeParseFloat = {
26
+ meta: {
27
+ type: 'suggestion',
28
+ docs: {
29
+ 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.',
30
+ },
31
+ fixable: 'code',
32
+ schema: [],
33
+ messages: {
34
+ useSafeParseFloat: 'Replace `{{original}}` with `Result.unwrapOkOr(Num.safeParseFloat({{argName}}), Number.NaN)` from ts-data-forge.',
35
+ },
36
+ },
37
+ create: (context) => {
38
+ const sourceCode = context.sourceCode;
39
+ const program = sourceCode.ast;
40
+ const tsDataForgeImport = getTsDataForgeImport(program);
41
+ const services = context.sourceCode.parserServices;
42
+ const mut_nodesToFix = [];
43
+ return {
44
+ CallExpression: (node) => {
45
+ const { callee } = node;
46
+ // Match `parseFloat(...)` (global function).
47
+ const isGlobalParseFloat = callee.type === AST_NODE_TYPES.Identifier &&
48
+ callee.name === 'parseFloat';
49
+ // Match `Number.parseFloat(...)`.
50
+ const isNumberParseFloat = callee.type === AST_NODE_TYPES.MemberExpression &&
51
+ !callee.computed &&
52
+ callee.object.type === AST_NODE_TYPES.Identifier &&
53
+ callee.object.name === 'Number' &&
54
+ callee.property.type === AST_NODE_TYPES.Identifier &&
55
+ callee.property.name === 'parseFloat';
56
+ // Match `Number(...)` called as a function (not `new Number(...)`).
57
+ const isNumberCall = callee.type === AST_NODE_TYPES.Identifier &&
58
+ callee.name === 'Number';
59
+ if (!isGlobalParseFloat && !isNumberParseFloat && !isNumberCall)
60
+ return;
61
+ const args = node.arguments;
62
+ const firstArg = args[0];
63
+ if (firstArg === undefined)
64
+ return;
65
+ // Spread argument (`parseFloat(...rest)`) cannot be rewritten safely.
66
+ if (firstArg.type === AST_NODE_TYPES.SpreadElement)
67
+ return;
68
+ // The argument must be purely `string` so the autofix is type-safe
69
+ // against `Num.safeParseFloat(s: string)`. Without type information,
70
+ // skip.
71
+ if (services?.program == null)
72
+ return;
73
+ const checker = services.program.getTypeChecker();
74
+ const tsNode = services.esTreeNodeToTSNodeMap?.get(firstArg);
75
+ if (tsNode === undefined)
76
+ return;
77
+ const argType = checker.getTypeAtLocation(tsNode);
78
+ if (!isStringType(argType))
79
+ return;
80
+ mut_nodesToFix.push({ node, argExpression: firstArg });
81
+ },
82
+ 'Program:exit': () => {
83
+ const namedImports = getNamedImports(tsDataForgeImport);
84
+ const missingImports = ['Num', 'Result'].filter((name) => !namedImports.includes(name));
85
+ for (const [index, { node, argExpression },] of mut_nodesToFix.entries()) {
86
+ const argText = sourceCode.getText(argExpression);
87
+ const originalText = sourceCode.getText(node);
88
+ context.report({
89
+ node,
90
+ messageId: 'useSafeParseFloat',
91
+ data: {
92
+ original: originalText,
93
+ argName: argText,
94
+ },
95
+ fix: (fixer) => {
96
+ const core = `Result.unwrapOkOr(Num.safeParseFloat(${argText}), Number.NaN)`;
97
+ const importFixes = index === 0 && missingImports.length > 0
98
+ ? buildImportFixes(fixer, program, tsDataForgeImport, missingImports)
99
+ : [];
100
+ return [...importFixes, fixer.replaceText(node, core)];
101
+ },
102
+ });
103
+ }
104
+ },
105
+ };
106
+ },
107
+ defaultOptions: [],
108
+ };
109
+
110
+ export { preferNumSafeParseFloat };
111
+ //# sourceMappingURL=prefer-num-safe-parse-float.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-num-safe-parse-float.mjs","sources":["../../../../src/plugins/ts-data-forge/rules/prefer-num-safe-parse-float.mts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -9,6 +9,7 @@ export declare const tsDataForgeRules: {
9
9
  readonly 'prefer-range-for-loop': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useRangeForLoop", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
10
10
  readonly 'prefer-is-record-and-has-key': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsRecordAndHasKey", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
11
11
  readonly 'prefer-num-safe-parse-int': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseInt", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
12
+ readonly 'prefer-num-safe-parse-float': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseFloat", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
12
13
  readonly 'no-unnecessary-type-guard': import("@typescript-eslint/utils/ts-eslint").RuleModule<"alwaysTrue" | "alwaysFalse" | "replaceTypeGuard", readonly [(Readonly<{
13
14
  ignore?: readonly string[];
14
15
  }> | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../../../../src/plugins/ts-data-forge/rules/rules.mts"],"names":[],"mappings":"AAcA,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;CAaa,CAAC"}
1
+ {"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../../../../src/plugins/ts-data-forge/rules/rules.mts"],"names":[],"mappings":"AAeA,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;CAca,CAAC"}
@@ -8,6 +8,7 @@ import { preferAsInt } from './prefer-as-int.mjs';
8
8
  import { preferComparisonOverNullishGuard } from './prefer-comparison-over-nullish-guard.mjs';
9
9
  import { preferIsNonNullObject } from './prefer-is-non-null-object.mjs';
10
10
  import { preferIsRecordAndHasKey } from './prefer-is-record-and-has-key.mjs';
11
+ import { preferNumSafeParseFloat } from './prefer-num-safe-parse-float.mjs';
11
12
  import { preferNumSafeParseInt } from './prefer-num-safe-parse-int.mjs';
12
13
  import { preferRangeForLoop } from './prefer-range-for-loop.mjs';
13
14
 
@@ -22,6 +23,7 @@ const tsDataForgeRules = {
22
23
  'prefer-range-for-loop': preferRangeForLoop,
23
24
  'prefer-is-record-and-has-key': preferIsRecordAndHasKey,
24
25
  'prefer-num-safe-parse-int': preferNumSafeParseInt,
26
+ 'prefer-num-safe-parse-float': preferNumSafeParseFloat,
25
27
  'no-unnecessary-type-guard': noUnnecessaryTypeGuard,
26
28
  'prefer-comparison-over-nullish-guard': preferComparisonOverNullishGuard,
27
29
  };
@@ -1 +1 @@
1
- {"version":3,"file":"rules.mjs","sources":["../../../../src/plugins/ts-data-forge/rules/rules.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;AAcO,MAAM,gBAAgB,GAAG;AAC9B,IAAA,qCAAqC,EAAE,6BAA6B;AACpE,IAAA,+BAA+B,EAAE,wBAAwB;AACzD,IAAA,qBAAqB,EAAE,gBAAgB;AACvC,IAAA,yBAAyB,EAAE,mBAAmB;AAC9C,IAAA,gBAAgB,EAAE,YAAY;AAC9B,IAAA,eAAe,EAAE,WAAW;AAC5B,IAAA,2BAA2B,EAAE,qBAAqB;AAClD,IAAA,uBAAuB,EAAE,kBAAkB;AAC3C,IAAA,8BAA8B,EAAE,uBAAuB;AACvD,IAAA,2BAA2B,EAAE,qBAAqB;AAClD,IAAA,2BAA2B,EAAE,sBAAsB;AACnD,IAAA,sCAAsC,EAAE,gCAAgC;;;;;"}
1
+ {"version":3,"file":"rules.mjs","sources":["../../../../src/plugins/ts-data-forge/rules/rules.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;;AAeO,MAAM,gBAAgB,GAAG;AAC9B,IAAA,qCAAqC,EAAE,6BAA6B;AACpE,IAAA,+BAA+B,EAAE,wBAAwB;AACzD,IAAA,qBAAqB,EAAE,gBAAgB;AACvC,IAAA,yBAAyB,EAAE,mBAAmB;AAC9C,IAAA,gBAAgB,EAAE,YAAY;AAC9B,IAAA,eAAe,EAAE,WAAW;AAC5B,IAAA,2BAA2B,EAAE,qBAAqB;AAClD,IAAA,uBAAuB,EAAE,kBAAkB;AAC3C,IAAA,8BAA8B,EAAE,uBAAuB;AACvD,IAAA,2BAA2B,EAAE,qBAAqB;AAClD,IAAA,6BAA6B,EAAE,uBAAuB;AACtD,IAAA,2BAA2B,EAAE,sBAAsB;AACnD,IAAA,sCAAsC,EAAE,gCAAgC;;;;;"}
@@ -9,6 +9,7 @@ export declare const eslintTsDataForgeRules: {
9
9
  readonly 'ts-data-forge/prefer-arr-is-array-of-length': "error";
10
10
  readonly 'ts-data-forge/prefer-is-record-and-has-key': "error";
11
11
  readonly 'ts-data-forge/prefer-num-safe-parse-int': "error";
12
+ readonly 'ts-data-forge/prefer-num-safe-parse-float': "error";
12
13
  readonly 'ts-data-forge/no-unnecessary-type-guard': 2 | 1;
13
14
  readonly 'ts-data-forge/prefer-comparison-over-nullish-guard': "error";
14
15
  };
@@ -1 +1 @@
1
- {"version":3,"file":"eslint-ts-data-forge-rules.d.mts","sourceRoot":"","sources":["../../src/rules/eslint-ts-data-forge-rules.mts"],"names":[],"mappings":"AAKA,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;CAaQ,CAAC"}
1
+ {"version":3,"file":"eslint-ts-data-forge-rules.d.mts","sourceRoot":"","sources":["../../src/rules/eslint-ts-data-forge-rules.mts"],"names":[],"mappings":"AAKA,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;CAcQ,CAAC"}
@@ -11,6 +11,7 @@ const eslintTsDataForgeRules = {
11
11
  'ts-data-forge/prefer-arr-is-array-of-length': 'error',
12
12
  'ts-data-forge/prefer-is-record-and-has-key': 'error',
13
13
  'ts-data-forge/prefer-num-safe-parse-int': 'error',
14
+ 'ts-data-forge/prefer-num-safe-parse-float': 'error',
14
15
  'ts-data-forge/no-unnecessary-type-guard': withDefaultOption('error'),
15
16
  'ts-data-forge/prefer-comparison-over-nullish-guard': 'error',
16
17
  };
@@ -1 +1 @@
1
- {"version":3,"file":"eslint-ts-data-forge-rules.mjs","sources":["../../src/rules/eslint-ts-data-forge-rules.mts"],"sourcesContent":[null],"names":[],"mappings":";;AAKO,MAAM,sBAAsB,GAAG;AACpC,IAAA,6BAA6B,EAAE,OAAO;AACtC,IAAA,yCAAyC,EAAE,OAAO;AAClD,IAAA,mCAAmC,EAAE,OAAO;AAC5C,IAAA,uCAAuC,EAAE,OAAO;AAChD,IAAA,8BAA8B,EAAE,OAAO;AACvC,IAAA,qCAAqC,EAAE,OAAO;AAC9C,IAAA,mDAAmD,EAAE,OAAO;AAC5D,IAAA,6CAA6C,EAAE,OAAO;AACtD,IAAA,4CAA4C,EAAE,OAAO;AACrD,IAAA,yCAAyC,EAAE,OAAO;AAClD,IAAA,yCAAyC,EAAE,iBAAiB,CAAC,OAAO,CAAC;AACrE,IAAA,oDAAoD,EAAE,OAAO;;;;;"}
1
+ {"version":3,"file":"eslint-ts-data-forge-rules.mjs","sources":["../../src/rules/eslint-ts-data-forge-rules.mts"],"sourcesContent":[null],"names":[],"mappings":";;AAKO,MAAM,sBAAsB,GAAG;AACpC,IAAA,6BAA6B,EAAE,OAAO;AACtC,IAAA,yCAAyC,EAAE,OAAO;AAClD,IAAA,mCAAmC,EAAE,OAAO;AAC5C,IAAA,uCAAuC,EAAE,OAAO;AAChD,IAAA,8BAA8B,EAAE,OAAO;AACvC,IAAA,qCAAqC,EAAE,OAAO;AAC9C,IAAA,mDAAmD,EAAE,OAAO;AAC5D,IAAA,6CAA6C,EAAE,OAAO;AACtD,IAAA,4CAA4C,EAAE,OAAO;AACrD,IAAA,yCAAyC,EAAE,OAAO;AAClD,IAAA,2CAA2C,EAAE,OAAO;AACpD,IAAA,yCAAyC,EAAE,iBAAiB,CAAC,OAAO,CAAC;AACrE,IAAA,oDAAoD,EAAE,OAAO;;;;;"}
@@ -140,6 +140,20 @@ declare namespace PreferIsRecordAndHasKey {
140
140
  declare namespace PreferNumSafeParseInt {
141
141
  type RuleEntry = Linter.StringSeverity;
142
142
  }
143
+ /**
144
+ * @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.
145
+ *
146
+ * ```md
147
+ * | key | value |
148
+ * | :--------- | :--------- |
149
+ * | type | suggestion |
150
+ * | deprecated | false |
151
+ * | fixable | code |
152
+ * ```
153
+ */
154
+ declare namespace PreferNumSafeParseFloat {
155
+ type RuleEntry = Linter.StringSeverity;
156
+ }
143
157
  /**
144
158
  * @description Detect ts-data-forge type guard calls that perform no narrowing (the argument type already satisfies, or can never satisfy, the guard).
145
159
  *
@@ -206,6 +220,7 @@ export type EslintTsDataForgeRules = Readonly<{
206
220
  'ts-data-forge/prefer-range-for-loop': PreferRangeForLoop.RuleEntry;
207
221
  'ts-data-forge/prefer-is-record-and-has-key': PreferIsRecordAndHasKey.RuleEntry;
208
222
  'ts-data-forge/prefer-num-safe-parse-int': PreferNumSafeParseInt.RuleEntry;
223
+ 'ts-data-forge/prefer-num-safe-parse-float': PreferNumSafeParseFloat.RuleEntry;
209
224
  'ts-data-forge/no-unnecessary-type-guard': NoUnnecessaryTypeGuard.RuleEntry;
210
225
  'ts-data-forge/prefer-comparison-over-nullish-guard': PreferComparisonOverNullishGuard.RuleEntry;
211
226
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"eslint-ts-data-forge-rules.d.mts","sourceRoot":"","sources":["../../../src/types/rules/eslint-ts-data-forge-rules.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"eslint-ts-data-forge-rules.d.mts","sourceRoot":"","sources":["../../../src/types/rules/eslint-ts-data-forge-rules.mts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-config-typed",
3
- "version": "5.2.0",
3
+ "version": "5.3.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript"
@@ -0,0 +1,166 @@
1
+ import {
2
+ AST_NODE_TYPES,
3
+ type TSESLint,
4
+ type TSESTree,
5
+ } from '@typescript-eslint/utils';
6
+ import * as ts from 'typescript';
7
+ import {
8
+ buildImportFixes,
9
+ getNamedImports,
10
+ getTsDataForgeImport,
11
+ } from './import-utils.mjs';
12
+
13
+ type Options = readonly [];
14
+
15
+ type MessageIds = 'useSafeParseFloat';
16
+
17
+ /**
18
+ * Returns `true` when every constituent of `type` is string-like, i.e. the
19
+ * type is assignable to the `string` parameter of `Num.safeParseFloat`. A
20
+ * union such as `string | undefined` is rejected so the autofix never
21
+ * produces a type error.
22
+ */
23
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
24
+ const isStringType = (type: ts.Type): boolean => {
25
+ const parts = type.isUnion() ? type.types : ([type] as const);
26
+
27
+ return (
28
+ parts.length > 0 &&
29
+ parts.every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0)
30
+ );
31
+ };
32
+
33
+ /**
34
+ * Detects `parseFloat(x)`, `Number.parseFloat(x)`, and `Number(x)` (when x
35
+ * is typed as `string`) and replaces them with
36
+ * `Result.unwrapOkOr(Num.safeParseFloat(x), Number.NaN)` (ts-data-forge).
37
+ *
38
+ * `Number(x)` is only flagged when x is purely a string type to avoid false
39
+ * positives for `Number(someBoolean)` or `Number(someNumber)` uses.
40
+ */
41
+ export const preferNumSafeParseFloat: TSESLint.RuleModule<MessageIds, Options> =
42
+ {
43
+ meta: {
44
+ type: 'suggestion',
45
+ docs: {
46
+ description:
47
+ '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.',
48
+ },
49
+ fixable: 'code',
50
+ schema: [],
51
+ messages: {
52
+ useSafeParseFloat:
53
+ 'Replace `{{original}}` with `Result.unwrapOkOr(Num.safeParseFloat({{argName}}), Number.NaN)` from ts-data-forge.',
54
+ },
55
+ },
56
+
57
+ create: (context) => {
58
+ const sourceCode = context.sourceCode;
59
+
60
+ const program = sourceCode.ast;
61
+
62
+ const tsDataForgeImport = getTsDataForgeImport(program);
63
+
64
+ const services = context.sourceCode.parserServices;
65
+
66
+ const mut_nodesToFix: {
67
+ node: TSESTree.CallExpression;
68
+ argExpression: TSESTree.Expression;
69
+ }[] = [];
70
+
71
+ return {
72
+ CallExpression: (node) => {
73
+ const { callee } = node;
74
+
75
+ // Match `parseFloat(...)` (global function).
76
+ const isGlobalParseFloat =
77
+ callee.type === AST_NODE_TYPES.Identifier &&
78
+ callee.name === 'parseFloat';
79
+
80
+ // Match `Number.parseFloat(...)`.
81
+ const isNumberParseFloat =
82
+ callee.type === AST_NODE_TYPES.MemberExpression &&
83
+ !callee.computed &&
84
+ callee.object.type === AST_NODE_TYPES.Identifier &&
85
+ callee.object.name === 'Number' &&
86
+ callee.property.type === AST_NODE_TYPES.Identifier &&
87
+ callee.property.name === 'parseFloat';
88
+
89
+ // Match `Number(...)` called as a function (not `new Number(...)`).
90
+ const isNumberCall =
91
+ callee.type === AST_NODE_TYPES.Identifier &&
92
+ callee.name === 'Number';
93
+
94
+ if (!isGlobalParseFloat && !isNumberParseFloat && !isNumberCall)
95
+ return;
96
+
97
+ const args = node.arguments;
98
+
99
+ const firstArg = args[0];
100
+
101
+ if (firstArg === undefined) return;
102
+
103
+ // Spread argument (`parseFloat(...rest)`) cannot be rewritten safely.
104
+ if (firstArg.type === AST_NODE_TYPES.SpreadElement) return;
105
+
106
+ // The argument must be purely `string` so the autofix is type-safe
107
+ // against `Num.safeParseFloat(s: string)`. Without type information,
108
+ // skip.
109
+ if (services?.program == null) return;
110
+
111
+ const checker = services.program.getTypeChecker();
112
+
113
+ const tsNode = services.esTreeNodeToTSNodeMap?.get(firstArg);
114
+
115
+ if (tsNode === undefined) return;
116
+
117
+ const argType = checker.getTypeAtLocation(tsNode);
118
+
119
+ if (!isStringType(argType)) return;
120
+
121
+ mut_nodesToFix.push({ node, argExpression: firstArg });
122
+ },
123
+ 'Program:exit': () => {
124
+ const namedImports = getNamedImports(tsDataForgeImport);
125
+
126
+ const missingImports = (['Num', 'Result'] as const).filter(
127
+ (name) => !namedImports.includes(name),
128
+ );
129
+
130
+ for (const [
131
+ index,
132
+ { node, argExpression },
133
+ ] of mut_nodesToFix.entries()) {
134
+ const argText = sourceCode.getText(argExpression);
135
+
136
+ const originalText = sourceCode.getText(node);
137
+
138
+ context.report({
139
+ node,
140
+ messageId: 'useSafeParseFloat',
141
+ data: {
142
+ original: originalText,
143
+ argName: argText,
144
+ },
145
+ fix: (fixer) => {
146
+ const core = `Result.unwrapOkOr(Num.safeParseFloat(${argText}), Number.NaN)`;
147
+
148
+ const importFixes =
149
+ index === 0 && missingImports.length > 0
150
+ ? buildImportFixes(
151
+ fixer,
152
+ program,
153
+ tsDataForgeImport,
154
+ missingImports,
155
+ )
156
+ : [];
157
+
158
+ return [...importFixes, fixer.replaceText(node, core)];
159
+ },
160
+ });
161
+ }
162
+ },
163
+ };
164
+ },
165
+ defaultOptions: [],
166
+ } as const;
@@ -0,0 +1,163 @@
1
+ import parser from '@typescript-eslint/parser';
2
+ import { RuleTester } from '@typescript-eslint/rule-tester';
3
+ import dedent from 'dedent';
4
+ import { preferNumSafeParseFloat } from './prefer-num-safe-parse-float.mjs';
5
+
6
+ const tester = new RuleTester({
7
+ languageOptions: {
8
+ parser,
9
+ parserOptions: {
10
+ ecmaVersion: 2020,
11
+ sourceType: 'module',
12
+ projectService: {
13
+ allowDefaultProject: ['*.ts*'],
14
+ },
15
+ tsconfigRootDir: `${import.meta.dirname}/../..`,
16
+ },
17
+ },
18
+ });
19
+
20
+ describe('prefer-num-safe-parse-float', () => {
21
+ tester.run('prefer-num-safe-parse-float', preferNumSafeParseFloat, {
22
+ valid: [
23
+ {
24
+ name: 'ignores number arguments to Number() (autofix would be unsafe)',
25
+ code: dedent`
26
+ const x = 42;
27
+ const n = Number(x);
28
+ `,
29
+ },
30
+ {
31
+ name: 'ignores boolean arguments to Number()',
32
+ code: dedent`
33
+ const b = true;
34
+ const n = Number(b);
35
+ `,
36
+ },
37
+ {
38
+ name: 'ignores never-typed arguments (string & number = never)',
39
+ code: dedent`
40
+ declare const x: string & number;
41
+ const n = parseFloat(x);
42
+ `,
43
+ },
44
+ {
45
+ name: 'ignores spread arguments',
46
+ code: dedent`
47
+ declare const args: [string];
48
+ const n = parseFloat(...args);
49
+ `,
50
+ },
51
+ {
52
+ name: 'ignores parseInt (handled by prefer-num-safe-parse-int)',
53
+ code: dedent`
54
+ const s = "123";
55
+ const n = parseInt(s, 10);
56
+ `,
57
+ },
58
+ ],
59
+ invalid: [
60
+ {
61
+ name: 'replaces parseFloat(s) and imports Num + Result',
62
+ code: dedent`
63
+ const s = "3.14";
64
+ const n = parseFloat(s);
65
+ `,
66
+ output: dedent`
67
+ import { Num, Result } from 'ts-data-forge';
68
+ const s = "3.14";
69
+ const n = Result.unwrapOkOr(Num.safeParseFloat(s), Number.NaN);
70
+ `,
71
+ errors: [{ messageId: 'useSafeParseFloat' }],
72
+ },
73
+ {
74
+ name: 'replaces Number.parseFloat(s)',
75
+ code: dedent`
76
+ const s = "3.14";
77
+ const n = Number.parseFloat(s);
78
+ `,
79
+ output: dedent`
80
+ import { Num, Result } from 'ts-data-forge';
81
+ const s = "3.14";
82
+ const n = Result.unwrapOkOr(Num.safeParseFloat(s), Number.NaN);
83
+ `,
84
+ errors: [{ messageId: 'useSafeParseFloat' }],
85
+ },
86
+ {
87
+ name: 'replaces Number(s) when s is a string',
88
+ code: dedent`
89
+ const s = "3.14";
90
+ const n = Number(s);
91
+ `,
92
+ output: dedent`
93
+ import { Num, Result } from 'ts-data-forge';
94
+ const s = "3.14";
95
+ const n = Result.unwrapOkOr(Num.safeParseFloat(s), Number.NaN);
96
+ `,
97
+ errors: [{ messageId: 'useSafeParseFloat' }],
98
+ },
99
+ {
100
+ name: 'adds only the missing Result import when Num is present',
101
+ code: dedent`
102
+ import { Num } from 'ts-data-forge';
103
+
104
+ const s = "3.14";
105
+ const n = parseFloat(s);
106
+ `,
107
+ output: dedent`
108
+ import { Result } from 'ts-data-forge';
109
+ import { Num } from 'ts-data-forge';
110
+
111
+ const s = "3.14";
112
+ const n = Result.unwrapOkOr(Num.safeParseFloat(s), Number.NaN);
113
+ `,
114
+ errors: [{ messageId: 'useSafeParseFloat' }],
115
+ },
116
+ {
117
+ name: 'preserves a complex string argument expression',
118
+ code: dedent`
119
+ const get = (k: string): string => k;
120
+ const n = parseFloat(get("x").trim());
121
+ `,
122
+ output: dedent`
123
+ import { Num, Result } from 'ts-data-forge';
124
+ const get = (k: string): string => k;
125
+ const n = Result.unwrapOkOr(Num.safeParseFloat(get("x").trim()), Number.NaN);
126
+ `,
127
+ errors: [{ messageId: 'useSafeParseFloat' }],
128
+ },
129
+ {
130
+ name: 'replaces Number(s) with a string literal argument',
131
+ code: dedent`
132
+ const n = Number("3.14");
133
+ `,
134
+ output: dedent`
135
+ import { Num, Result } from 'ts-data-forge';
136
+ const n = Result.unwrapOkOr(Num.safeParseFloat("3.14"), Number.NaN);
137
+ `,
138
+ errors: [{ messageId: 'useSafeParseFloat' }],
139
+ },
140
+ {
141
+ name: 'replaces all three forms in one file',
142
+ code: dedent`
143
+ const s = "1.5";
144
+ const a = parseFloat(s);
145
+ const b = Number.parseFloat(s);
146
+ const c = Number(s);
147
+ `,
148
+ output: dedent`
149
+ import { Num, Result } from 'ts-data-forge';
150
+ const s = "1.5";
151
+ const a = Result.unwrapOkOr(Num.safeParseFloat(s), Number.NaN);
152
+ const b = Result.unwrapOkOr(Num.safeParseFloat(s), Number.NaN);
153
+ const c = Result.unwrapOkOr(Num.safeParseFloat(s), Number.NaN);
154
+ `,
155
+ errors: [
156
+ { messageId: 'useSafeParseFloat' },
157
+ { messageId: 'useSafeParseFloat' },
158
+ { messageId: 'useSafeParseFloat' },
159
+ ],
160
+ },
161
+ ],
162
+ });
163
+ }, 20000);
@@ -9,6 +9,7 @@ import { preferAsInt } from './prefer-as-int.mjs';
9
9
  import { preferComparisonOverNullishGuard } from './prefer-comparison-over-nullish-guard.mjs';
10
10
  import { preferIsNonNullObject } from './prefer-is-non-null-object.mjs';
11
11
  import { preferIsRecordAndHasKey } from './prefer-is-record-and-has-key.mjs';
12
+ import { preferNumSafeParseFloat } from './prefer-num-safe-parse-float.mjs';
12
13
  import { preferNumSafeParseInt } from './prefer-num-safe-parse-int.mjs';
13
14
  import { preferRangeForLoop } from './prefer-range-for-loop.mjs';
14
15
 
@@ -23,6 +24,7 @@ export const tsDataForgeRules = {
23
24
  'prefer-range-for-loop': preferRangeForLoop,
24
25
  'prefer-is-record-and-has-key': preferIsRecordAndHasKey,
25
26
  'prefer-num-safe-parse-int': preferNumSafeParseInt,
27
+ 'prefer-num-safe-parse-float': preferNumSafeParseFloat,
26
28
  'no-unnecessary-type-guard': noUnnecessaryTypeGuard,
27
29
  'prefer-comparison-over-nullish-guard': preferComparisonOverNullishGuard,
28
30
  } as const satisfies ESLintPlugin['rules'];
@@ -14,6 +14,7 @@ export const eslintTsDataForgeRules = {
14
14
  'ts-data-forge/prefer-arr-is-array-of-length': 'error',
15
15
  'ts-data-forge/prefer-is-record-and-has-key': 'error',
16
16
  'ts-data-forge/prefer-num-safe-parse-int': 'error',
17
+ 'ts-data-forge/prefer-num-safe-parse-float': 'error',
17
18
  'ts-data-forge/no-unnecessary-type-guard': withDefaultOption('error'),
18
19
  'ts-data-forge/prefer-comparison-over-nullish-guard': 'error',
19
20
  } as const satisfies EslintTsDataForgeRules;
@@ -157,6 +157,21 @@ namespace PreferNumSafeParseInt {
157
157
  export type RuleEntry = Linter.StringSeverity;
158
158
  }
159
159
 
160
+ /**
161
+ * @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.
162
+ *
163
+ * ```md
164
+ * | key | value |
165
+ * | :--------- | :--------- |
166
+ * | type | suggestion |
167
+ * | deprecated | false |
168
+ * | fixable | code |
169
+ * ```
170
+ */
171
+ namespace PreferNumSafeParseFloat {
172
+ export type RuleEntry = Linter.StringSeverity;
173
+ }
174
+
160
175
  /**
161
176
  * @description Detect ts-data-forge type guard calls that perform no narrowing (the argument type already satisfies, or can never satisfy, the guard).
162
177
  *
@@ -229,6 +244,7 @@ export type EslintTsDataForgeRules = Readonly<{
229
244
  'ts-data-forge/prefer-range-for-loop': PreferRangeForLoop.RuleEntry;
230
245
  'ts-data-forge/prefer-is-record-and-has-key': PreferIsRecordAndHasKey.RuleEntry;
231
246
  'ts-data-forge/prefer-num-safe-parse-int': PreferNumSafeParseInt.RuleEntry;
247
+ 'ts-data-forge/prefer-num-safe-parse-float': PreferNumSafeParseFloat.RuleEntry;
232
248
  'ts-data-forge/no-unnecessary-type-guard': NoUnnecessaryTypeGuard.RuleEntry;
233
249
  'ts-data-forge/prefer-comparison-over-nullish-guard': PreferComparisonOverNullishGuard.RuleEntry;
234
250
  }>;