eslint-plugin-ts-data-forge 0.6.1 → 0.7.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 (77) hide show
  1. package/README.md +1 -0
  2. package/dist/index.mjs +2 -2
  3. package/dist/index.mjs.map +1 -1
  4. package/dist/plugin.d.mts +2 -0
  5. package/dist/plugin.d.mts.map +1 -1
  6. package/dist/plugin.mjs +45 -41
  7. package/dist/plugin.mjs.map +1 -1
  8. package/dist/rule-types.d.mts +15 -0
  9. package/dist/rule-types.d.mts.map +1 -1
  10. package/dist/rule-types.mjs +6 -2
  11. package/dist/rule-types.mjs.map +1 -1
  12. package/dist/rules/ast-utils.mjs +107 -50
  13. package/dist/rules/ast-utils.mjs.map +1 -1
  14. package/dist/rules/branded-number-types.mjs +41 -37
  15. package/dist/rules/branded-number-types.mjs.map +1 -1
  16. package/dist/rules/constants.mjs +2 -4
  17. package/dist/rules/constants.mjs.map +1 -1
  18. package/dist/rules/import-utils.mjs +80 -56
  19. package/dist/rules/import-utils.mjs.map +1 -1
  20. package/dist/rules/index.mjs +2 -2
  21. package/dist/rules/index.mjs.map +1 -1
  22. package/dist/rules/no-side-effect-import.mjs +46 -34
  23. package/dist/rules/no-side-effect-import.mjs.map +1 -1
  24. package/dist/rules/no-unnecessary-type-guard.mjs +376 -265
  25. package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
  26. package/dist/rules/prefer-arr-is-array.mjs +64 -52
  27. package/dist/rules/prefer-arr-is-array.mjs.map +1 -1
  28. package/dist/rules/prefer-arr-is-bounded-length-array.mjs +91 -85
  29. package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
  30. package/dist/rules/prefer-arr-is-fixed-length-array.mjs +92 -83
  31. package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
  32. package/dist/rules/prefer-arr-is-max-length-array.mjs +92 -80
  33. package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
  34. package/dist/rules/prefer-arr-is-min-length-array.mjs +92 -80
  35. package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -1
  36. package/dist/rules/prefer-arr-is-non-empty.mjs +85 -77
  37. package/dist/rules/prefer-arr-is-non-empty.mjs.map +1 -1
  38. package/dist/rules/prefer-arr-scan.d.mts +96 -0
  39. package/dist/rules/prefer-arr-scan.d.mts.map +1 -0
  40. package/dist/rules/prefer-arr-scan.mjs +482 -0
  41. package/dist/rules/prefer-arr-scan.mjs.map +1 -0
  42. package/dist/rules/prefer-arr-sum.mjs +176 -132
  43. package/dist/rules/prefer-arr-sum.mjs.map +1 -1
  44. package/dist/rules/prefer-as-int.mjs +77 -71
  45. package/dist/rules/prefer-as-int.mjs.map +1 -1
  46. package/dist/rules/prefer-canonical-array-slicing.mjs +503 -343
  47. package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
  48. package/dist/rules/prefer-canonical-length-cast.mjs +217 -144
  49. package/dist/rules/prefer-canonical-length-cast.mjs.map +1 -1
  50. package/dist/rules/prefer-canonical-length-guard.mjs +160 -109
  51. package/dist/rules/prefer-canonical-length-guard.mjs.map +1 -1
  52. package/dist/rules/prefer-comparison-over-nullish-guard.mjs +89 -71
  53. package/dist/rules/prefer-comparison-over-nullish-guard.mjs.map +1 -1
  54. package/dist/rules/prefer-is-non-null-object.mjs +91 -72
  55. package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
  56. package/dist/rules/prefer-is-record-and-has-key.mjs +148 -105
  57. package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
  58. package/dist/rules/prefer-num-safe-parse-float.mjs +101 -77
  59. package/dist/rules/prefer-num-safe-parse-float.mjs.map +1 -1
  60. package/dist/rules/prefer-num-safe-parse-int.mjs +103 -81
  61. package/dist/rules/prefer-num-safe-parse-int.mjs.map +1 -1
  62. package/dist/rules/prefer-obj-over-entries-round-trip.mjs +299 -194
  63. package/dist/rules/prefer-obj-over-entries-round-trip.mjs.map +1 -1
  64. package/dist/rules/prefer-range-for-loop.mjs +121 -99
  65. package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
  66. package/dist/rules/rules.d.mts +1 -0
  67. package/dist/rules/rules.d.mts.map +1 -1
  68. package/dist/rules/rules.mjs +19 -20
  69. package/dist/rules/rules.mjs.map +1 -1
  70. package/dist/types.mjs +2 -2
  71. package/dist/types.mjs.map +1 -1
  72. package/package.json +6 -7
  73. package/src/plugin.mts +1 -0
  74. package/src/rule-types.mts +16 -0
  75. package/src/rules/prefer-arr-scan.mts +693 -0
  76. package/src/rules/prefer-arr-scan.test.mts +295 -0
  77. package/src/rules/rules.mts +2 -0
@@ -1,4 +1,4 @@
1
- import { AST_NODE_TYPES } from '@typescript-eslint/utils';
1
+ import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
2
  import { getImportedLocalName, getTsDataForgeImport } from './import-utils.mjs';
3
3
  import { preferArrIsBoundedLengthArray } from './prefer-arr-is-bounded-length-array.mjs';
4
4
  import { preferArrIsFixedLengthArray } from './prefer-arr-is-fixed-length-array.mjs';
@@ -6,124 +6,175 @@ import { preferArrIsMaxLengthArray } from './prefer-arr-is-max-length-array.mjs'
6
6
  import { preferArrIsMinLengthArray } from './prefer-arr-is-min-length-array.mjs';
7
7
  import { preferArrIsNonEmpty } from './prefer-arr-is-non-empty.mjs';
8
8
 
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
9
17
  const COMPARISON_RULES = [
10
- preferArrIsNonEmpty,
11
- preferArrIsMinLengthArray,
12
- preferArrIsMaxLengthArray,
13
- preferArrIsBoundedLengthArray,
14
- preferArrIsFixedLengthArray
18
+ preferArrIsNonEmpty,
19
+ preferArrIsMinLengthArray,
20
+ preferArrIsMaxLengthArray,
21
+ preferArrIsBoundedLengthArray,
22
+ preferArrIsFixedLengthArray,
15
23
  ];
16
24
  const MESSAGES = {
17
- useCanonicalGuard: "Replace `{{arrName}}.{{guard}}({{boundsText}})` with `{{arrName}}.{{replacement}}(...)`: the bound makes the two guards equivalent.",
18
- useIsNonEmpty: preferArrIsNonEmpty.meta.messages.useIsNonEmpty,
19
- useIsMinLengthArray: preferArrIsMinLengthArray.meta.messages.useIsMinLengthArray,
20
- useIsMaxLengthArray: preferArrIsMaxLengthArray.meta.messages.useIsMaxLengthArray,
21
- useIsBoundedLengthArray: preferArrIsBoundedLengthArray.meta.messages.useIsBoundedLengthArray,
22
- useIsFixedLengthArray: preferArrIsFixedLengthArray.meta.messages.useIsFixedLengthArray
25
+ useCanonicalGuard: 'Replace `{{arrName}}.{{guard}}({{boundsText}})` with `{{arrName}}.{{replacement}}(...)`: the bound makes the two guards equivalent.',
26
+ useIsNonEmpty: preferArrIsNonEmpty.meta.messages.useIsNonEmpty,
27
+ useIsMinLengthArray: preferArrIsMinLengthArray.meta.messages.useIsMinLengthArray,
28
+ useIsMaxLengthArray: preferArrIsMaxLengthArray.meta.messages.useIsMaxLengthArray,
29
+ useIsBoundedLengthArray: preferArrIsBoundedLengthArray.meta.messages.useIsBoundedLengthArray,
30
+ useIsFixedLengthArray: preferArrIsFixedLengthArray.meta.messages.useIsFixedLengthArray,
23
31
  };
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
24
58
  const GUARD_REWRITES = [
25
- { guard: "isFixedLengthArray", bounds: [0], replacement: "isEmpty" },
26
- { guard: "isMaxLengthArray", bounds: [0], replacement: "isEmpty" },
27
- { guard: "isBoundedLengthArray", bounds: [0, 0], replacement: "isEmpty" },
28
- { guard: "isMinLengthArray", bounds: [1], replacement: "isNonEmpty" },
29
- { guard: "isFixedLengthTuple", bounds: [0], replacement: "isEmptyTuple" },
30
- { guard: "isMaxLengthTuple", bounds: [0], replacement: "isEmptyTuple" },
31
- {
32
- guard: "isBoundedLengthTuple",
33
- bounds: [0, 0],
34
- replacement: "isEmptyTuple"
35
- },
36
- { guard: "isMinLengthTuple", bounds: [1], replacement: "isNonEmptyTuple" }
59
+ { guard: 'isFixedLengthArray', bounds: [0], replacement: 'isEmpty' },
60
+ { guard: 'isMaxLengthArray', bounds: [0], replacement: 'isEmpty' },
61
+ { guard: 'isBoundedLengthArray', bounds: [0, 0], replacement: 'isEmpty' },
62
+ { guard: 'isMinLengthArray', bounds: [1], replacement: 'isNonEmpty' },
63
+ { guard: 'isFixedLengthTuple', bounds: [0], replacement: 'isEmptyTuple' },
64
+ { guard: 'isMaxLengthTuple', bounds: [0], replacement: 'isEmptyTuple' },
65
+ {
66
+ guard: 'isBoundedLengthTuple',
67
+ bounds: [0, 0],
68
+ replacement: 'isEmptyTuple',
69
+ },
70
+ { guard: 'isMinLengthTuple', bounds: [1], replacement: 'isNonEmptyTuple' },
37
71
  ];
38
- const preferCanonicalLengthGuard = {
39
- meta: {
40
- type: "suggestion",
41
- docs: {
42
- description: "Normalize array-length checks to their canonical `Arr` guard: `xs.length <op> n` becomes the matching `Arr.is*` guard, and a degenerate guard becomes the named one for its family (`Arr.isFixedLengthArray(0, xs)` \u2192 `Arr.isEmpty`, `Arr.isFixedLengthTuple(0, xs)` \u2192 `Arr.isEmptyTuple`)."
72
+ export const preferCanonicalLengthGuard = {
73
+ meta: {
74
+ type: 'suggestion',
75
+ docs: {
76
+ description: 'Normalize array-length checks to their canonical `Arr` guard: `xs.length <op> n` becomes the matching `Arr.is*` guard, and a degenerate guard becomes the named one for its family (`Arr.isFixedLengthArray(0, xs)` → `Arr.isEmpty`, `Arr.isFixedLengthTuple(0, xs)` → `Arr.isEmptyTuple`).',
77
+ },
78
+ fixable: 'code',
79
+ schema: [],
80
+ messages: MESSAGES,
43
81
  },
44
- fixable: "code",
45
- schema: [],
46
- messages: MESSAGES
47
- },
48
- create: (context) => {
49
- const sourceCode = context.sourceCode;
50
- const arrLocalName = getImportedLocalName(
51
- getTsDataForgeImport(sourceCode.ast),
52
- "Arr"
53
- );
54
- const comparisonVisitors = COMPARISON_RULES.map(
55
- (rule) => rule.create(context)
56
- );
57
- if (arrLocalName === void 0) return mergeVisitors(comparisonVisitors);
58
- return mergeVisitors([
59
- ...comparisonVisitors,
60
- {
61
- CallExpression: (node) => {
62
- const guardName = getGuardName(node, arrLocalName);
63
- if (guardName === void 0) return;
64
- const rewrite = GUARD_REWRITES.find(
65
- (entry) => entry.guard === guardName
66
- );
67
- if (rewrite === void 0 || !matchesBounds(node, rewrite.bounds)) {
68
- return;
69
- }
70
- const array = node.arguments[rewrite.bounds.length];
71
- if (array === void 0) return;
72
- const arrayText = sourceCode.getText(array);
73
- context.report({
74
- node,
75
- messageId: "useCanonicalGuard",
76
- data: {
77
- arrName: arrLocalName,
78
- guard: guardName,
79
- boundsText: [...rewrite.bounds.map(String), "\u2026"].join(", "),
80
- replacement: rewrite.replacement
82
+ create: (context) => {
83
+ const sourceCode = context.sourceCode;
84
+ const arrLocalName = getImportedLocalName(getTsDataForgeImport(sourceCode.ast), 'Arr');
85
+ const comparisonVisitors = COMPARISON_RULES.map((rule) => rule.create(context));
86
+ if (arrLocalName === undefined)
87
+ return mergeVisitors(comparisonVisitors);
88
+ return mergeVisitors([
89
+ ...comparisonVisitors,
90
+ {
91
+ CallExpression: (node) => {
92
+ const guardName = getGuardName(node, arrLocalName);
93
+ if (guardName === undefined)
94
+ return;
95
+ const rewrite = GUARD_REWRITES.find((entry) => entry.guard === guardName);
96
+ if (rewrite === undefined || !matchesBounds(node, rewrite.bounds)) {
97
+ return;
98
+ }
99
+
100
+ const array = node.arguments[rewrite.bounds.length];
101
+ if (array === undefined)
102
+ return;
103
+ const arrayText = sourceCode.getText(array);
104
+ context.report({
105
+ node,
106
+ messageId: 'useCanonicalGuard',
107
+ data: {
108
+ arrName: arrLocalName,
109
+ guard: guardName,
110
+ boundsText: [...rewrite.bounds.map(String), '…'].join(', '),
111
+ replacement: rewrite.replacement,
112
+ },
113
+ fix: (fixer) => fixer.replaceText(node, `${arrLocalName}.${rewrite.replacement}(${arrayText})`),
114
+ });
115
+ },
81
116
  },
82
- fix: (fixer) => fixer.replaceText(
83
- node,
84
- `${arrLocalName}.${rewrite.replacement}(${arrayText})`
85
- )
86
- });
87
- }
88
- }
89
- ]);
90
- },
91
- defaultOptions: []
117
+ ]);
118
+ },
119
+ defaultOptions: [],
92
120
  };
93
- const mergeVisitors = (visitors) => {
94
- const mut_merged = {};
95
- for (const visitor of visitors) {
96
- for (const [selector, handler] of Object.entries(visitor)) {
97
- if (typeof handler !== "function") continue;
98
- const mut_handlers = mut_merged[selector] ?? [];
99
- mut_handlers.push(handler);
100
- mut_merged[selector] = mut_handlers;
101
- }
102
- }
103
- return Object.fromEntries(
104
- Object.entries(mut_merged).map(([selector, handlers]) => [
105
- selector,
106
- (node) => {
107
- for (const handler of handlers) {
108
- handler(node);
121
+
122
+
123
+
124
+
125
+ const mergeVisitors = (
126
+
127
+
128
+
129
+ visitors) => {
130
+ const mut_merged = {};
131
+ for (const visitor of visitors) {
132
+ for (const [selector, handler] of Object.entries(visitor)) {
133
+ if (typeof handler !== 'function')
134
+ continue;
135
+ const mut_handlers = mut_merged[selector] ?? [];
136
+ mut_handlers.push(handler);
137
+ mut_merged[selector] = mut_handlers;
109
138
  }
110
- }
111
- ])
112
- );
113
- };
114
- const getGuardName = (node, arrLocalName) => {
115
- const { callee } = node;
116
- return callee.type === AST_NODE_TYPES.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES.Identifier && callee.object.name === arrLocalName && callee.property.type === AST_NODE_TYPES.Identifier ? callee.property.name : void 0;
139
+ }
140
+ return Object.fromEntries(Object.entries(mut_merged).map(([selector, handlers]) => [
141
+ selector,
142
+ (node) => {
143
+ for (const handler of handlers) {
144
+ handler(node);
145
+ }
146
+ },
147
+ ]));
117
148
  };
118
- const matchesBounds = (node, bounds) => {
119
- const args = node.arguments;
120
- if (args.length !== bounds.length + 1 || node.typeArguments !== void 0) {
121
- return false;
122
- }
123
- return args.every(
124
- (arg, index) => arg.type !== AST_NODE_TYPES.SpreadElement && (index === bounds.length || arg.type === AST_NODE_TYPES.Literal && arg.value === bounds[index])
125
- );
149
+
150
+ const getGuardName = (
151
+
152
+ node, arrLocalName) => {
153
+ const { callee } = node;
154
+ return callee.type === AST_NODE_TYPES.MemberExpression &&
155
+ !callee.computed &&
156
+ callee.object.type === AST_NODE_TYPES.Identifier &&
157
+ callee.object.name === arrLocalName &&
158
+ callee.property.type === AST_NODE_TYPES.Identifier
159
+ ? callee.property.name
160
+ : undefined;
126
161
  };
127
162
 
128
- export { preferCanonicalLengthGuard };
129
- //# sourceMappingURL=prefer-canonical-length-guard.mjs.map
163
+
164
+
165
+
166
+
167
+
168
+
169
+ const matchesBounds = (
170
+
171
+ node, bounds) => {
172
+ const args = node.arguments;
173
+ if (args.length !== bounds.length + 1 || node.typeArguments !== undefined) {
174
+ return false;
175
+ }
176
+ return args.every((arg, index) => arg.type !== AST_NODE_TYPES.SpreadElement &&
177
+ (index === bounds.length ||
178
+ (arg.type === AST_NODE_TYPES.Literal && arg.value === bounds[index])));
179
+ };
180
+ //# sourceMappingURL=prefer-canonical-length-guard.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-canonical-length-guard.mjs","sources":["../../src/rules/prefer-canonical-length-guard.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type MutableRecord, type ReadonlyRecord } from 'ts-type-forge';\nimport { getImportedLocalName, getTsDataForgeImport } from './import-utils.mjs';\nimport { preferArrIsBoundedLengthArray } from './prefer-arr-is-bounded-length-array.mjs';\nimport { preferArrIsFixedLengthArray } from './prefer-arr-is-fixed-length-array.mjs';\nimport { preferArrIsMaxLengthArray } from './prefer-arr-is-max-length-array.mjs';\nimport { preferArrIsMinLengthArray } from './prefer-arr-is-min-length-array.mjs';\nimport { preferArrIsNonEmpty } from './prefer-arr-is-non-empty.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds =\n | 'useCanonicalGuard'\n | 'useIsBoundedLengthArray'\n | 'useIsFixedLengthArray'\n | 'useIsMaxLengthArray'\n | 'useIsMinLengthArray'\n | 'useIsNonEmpty';\n\n/**\n * The `xs.length <op> n` → `Arr.is*` rules folded into this one.\n *\n * Their implementations are reused as-is rather than rewritten: this rule\n * merges their visitors, so every case they already cover (type-aware array\n * checks, `Arr` import insertion, the `&&`-bounded-range special case) keeps\n * behaving exactly as before, and their co-located tests keep exercising them\n * directly.\n */\nconst COMPARISON_RULES = [\n preferArrIsNonEmpty,\n preferArrIsMinLengthArray,\n preferArrIsMaxLengthArray,\n preferArrIsBoundedLengthArray,\n preferArrIsFixedLengthArray,\n] as const;\n\nconst MESSAGES = {\n useCanonicalGuard:\n 'Replace `{{arrName}}.{{guard}}({{boundsText}})` with `{{arrName}}.{{replacement}}(...)`: the bound makes the two guards equivalent.',\n useIsNonEmpty: preferArrIsNonEmpty.meta.messages.useIsNonEmpty,\n useIsMinLengthArray:\n preferArrIsMinLengthArray.meta.messages.useIsMinLengthArray,\n useIsMaxLengthArray:\n preferArrIsMaxLengthArray.meta.messages.useIsMaxLengthArray,\n useIsBoundedLengthArray:\n preferArrIsBoundedLengthArray.meta.messages.useIsBoundedLengthArray,\n useIsFixedLengthArray:\n preferArrIsFixedLengthArray.meta.messages.useIsFixedLengthArray,\n} as const satisfies ReadonlyRecord<MessageIds, string>;\n\n/**\n * Length guards whose bound makes them redundant with a named degenerate\n * guard, keyed by guard name.\n *\n * Every entry stays inside its own family rather than crossing the\n * branded/structural divide:\n *\n * - the branded `*Array` guards go to `isEmpty` / `isNonEmpty`, which narrow to\n * `FixedLengthArray<0, E> & Xs` / `MinLengthArray<1, E> & Xs`;\n * - the structural `*Tuple` guards go to `isEmptyTuple` / `isNonEmptyTuple`,\n * which narrow to `readonly []` / `MinLengthTuple<1, E>`.\n *\n * Most entries are then **type-identical** — `isFixedLengthArray(0, xs)` and\n * `isMinLengthArray(1, xs)` produce exactly what `isEmpty` / `isNonEmpty`\n * narrow to, and `FixedLengthTuple<0, E>`, `MaxLengthTuple<0, E>` and\n * `BoundedLengthTuple<0, 0, E>` are all `readonly []`. Two *strengthen*:\n * `MaxLengthArray<0, E>` and `BoundedLengthArray<0, 0, E>` do not include the\n * `readonly []` structural part that `FixedLengthArray<0, E>` intersects on, so\n * rewriting them to `isEmpty` adds it. That is sound for a guard — the narrowed\n * type only flows outwards, and the stronger type is assignable everywhere the\n * old one was — and it stays within the branded family.\n *\n * Sending a `*Tuple` guard to the branded `isEmpty` would also be sound, but it\n * would silently change which family the value belongs to, which is why each\n * family keeps its own lane.\n */\nconst GUARD_REWRITES = [\n { guard: 'isFixedLengthArray', bounds: [0], replacement: 'isEmpty' },\n { guard: 'isMaxLengthArray', bounds: [0], replacement: 'isEmpty' },\n { guard: 'isBoundedLengthArray', bounds: [0, 0], replacement: 'isEmpty' },\n { guard: 'isMinLengthArray', bounds: [1], replacement: 'isNonEmpty' },\n { guard: 'isFixedLengthTuple', bounds: [0], replacement: 'isEmptyTuple' },\n { guard: 'isMaxLengthTuple', bounds: [0], replacement: 'isEmptyTuple' },\n {\n guard: 'isBoundedLengthTuple',\n bounds: [0, 0],\n replacement: 'isEmptyTuple',\n },\n { guard: 'isMinLengthTuple', bounds: [1], replacement: 'isNonEmptyTuple' },\n] as const satisfies readonly Readonly<{\n guard: string;\n bounds: readonly number[];\n replacement: string;\n}>[];\n\nexport const preferCanonicalLengthGuard: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Normalize array-length checks to their canonical `Arr` guard: `xs.length <op> n` becomes the matching `Arr.is*` guard, and a degenerate guard becomes the named one for its family (`Arr.isFixedLengthArray(0, xs)` → `Arr.isEmpty`, `Arr.isFixedLengthTuple(0, xs)` → `Arr.isEmptyTuple`).',\n },\n fixable: 'code',\n schema: [],\n messages: MESSAGES,\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const arrLocalName = getImportedLocalName(\n getTsDataForgeImport(sourceCode.ast),\n 'Arr',\n );\n\n const comparisonVisitors = COMPARISON_RULES.map((rule) =>\n rule.create(context),\n );\n\n if (arrLocalName === undefined) return mergeVisitors(comparisonVisitors);\n\n return mergeVisitors([\n ...comparisonVisitors,\n {\n CallExpression: (node) => {\n const guardName = getGuardName(node, arrLocalName);\n\n if (guardName === undefined) return;\n\n const rewrite = GUARD_REWRITES.find(\n (entry) => entry.guard === guardName,\n );\n\n if (rewrite === undefined || !matchesBounds(node, rewrite.bounds)) {\n return;\n }\n\n // The array is the last argument; the bounds precede it.\n const array = node.arguments[rewrite.bounds.length];\n\n if (array === undefined) return;\n\n const arrayText = sourceCode.getText(array);\n\n context.report({\n node,\n messageId: 'useCanonicalGuard',\n data: {\n arrName: arrLocalName,\n guard: guardName,\n boundsText: [...rewrite.bounds.map(String), '…'].join(', '),\n replacement: rewrite.replacement,\n },\n fix: (fixer) =>\n fixer.replaceText(\n node,\n `${arrLocalName}.${rewrite.replacement}(${arrayText})`,\n ),\n });\n },\n },\n ]);\n },\n defaultOptions: [],\n} as const;\n\n/**\n * Combines several rule listeners into one, calling every handler registered\n * for a given selector in order.\n */\nconst mergeVisitors = (\n // `RuleListener` holds mutable AST handler signatures, so it is not deeply\n // readonly.\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n visitors: readonly TSESLint.RuleListener[],\n): TSESLint.RuleListener => {\n const mut_merged: MutableRecord<string, ((node: never) => void)[]> = {};\n\n for (const visitor of visitors) {\n for (const [selector, handler] of Object.entries(visitor)) {\n if (typeof handler !== 'function') continue;\n\n const mut_handlers = mut_merged[selector] ?? [];\n\n mut_handlers.push(handler);\n\n mut_merged[selector] = mut_handlers;\n }\n }\n\n return Object.fromEntries(\n Object.entries(mut_merged).map(([selector, handlers]) => [\n selector,\n (node: never) => {\n for (const handler of handlers) {\n handler(node);\n }\n },\n ]),\n );\n};\n\n/** The `Arr.<guard>` method name of `node`, or `undefined` for other calls. */\nconst getGuardName = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n node: TSESTree.CallExpression,\n arrLocalName: string,\n): string | undefined => {\n const { callee } = node;\n\n return callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n callee.object.name === arrLocalName &&\n callee.property.type === AST_NODE_TYPES.Identifier\n ? callee.property.name\n : undefined;\n};\n\n/**\n * Whether the call is `guard(...bounds, array)` with every bound written as the\n * exact numeric literal the rewrite requires.\n *\n * The arity check also excludes the curried form (`guard(...bounds)`): the\n * degenerate guards take only the array, so there is nothing to rewrite it to.\n */\nconst matchesBounds = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n node: TSESTree.CallExpression,\n bounds: readonly number[],\n): boolean => {\n const args = node.arguments;\n\n if (args.length !== bounds.length + 1 || node.typeArguments !== undefined) {\n return false;\n }\n\n return args.every(\n (arg, index) =>\n arg.type !== AST_NODE_TYPES.SpreadElement &&\n (index === bounds.length ||\n (arg.type === AST_NODE_TYPES.Literal && arg.value === bounds[index])),\n );\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prefer-canonical-length-guard.mjs","sourceRoot":"","sources":["../../src/rules/prefer-canonical-length-guard.mts"],"names":[],"mappings":"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"}
@@ -1,80 +1,98 @@
1
- import { AST_NODE_TYPES } from '@typescript-eslint/utils';
1
+ import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
2
  import { buildCalleeResolver, getTsDataForgeImport } from './import-utils.mjs';
3
3
 
4
+
5
+
6
+
7
+
4
8
  const COMPARISON_GUARDS = {
5
- isUndefined: { op: "===", literal: "undefined" },
6
- isNotUndefined: { op: "!==", literal: "undefined" },
7
- isNull: { op: "===", literal: "null" },
8
- isNotNull: { op: "!==", literal: "null" }
9
+ isUndefined: { op: '===', literal: 'undefined' },
10
+ isNotUndefined: { op: '!==', literal: 'undefined' },
11
+ isNull: { op: '===', literal: 'null' },
12
+ isNotNull: { op: '!==', literal: 'null' },
9
13
  };
10
- const ARG_NEEDS_PARENS = /* @__PURE__ */ new Set([
11
- AST_NODE_TYPES.LogicalExpression,
12
- AST_NODE_TYPES.ConditionalExpression,
13
- AST_NODE_TYPES.AssignmentExpression,
14
- AST_NODE_TYPES.SequenceExpression,
15
- AST_NODE_TYPES.ArrowFunctionExpression,
16
- AST_NODE_TYPES.YieldExpression
14
+
15
+
16
+
17
+
18
+ const ARG_NEEDS_PARENS = new Set([
19
+ AST_NODE_TYPES.LogicalExpression,
20
+ AST_NODE_TYPES.ConditionalExpression,
21
+ AST_NODE_TYPES.AssignmentExpression,
22
+ AST_NODE_TYPES.SequenceExpression,
23
+ AST_NODE_TYPES.ArrowFunctionExpression,
24
+ AST_NODE_TYPES.YieldExpression,
17
25
  ]);
26
+
27
+
28
+
29
+
18
30
  const needsWrappingInParent = (node) => {
19
- const { parent } = node;
20
- switch (parent.type) {
21
- case AST_NODE_TYPES.UnaryExpression:
22
- case AST_NODE_TYPES.AwaitExpression:
23
- case AST_NODE_TYPES.BinaryExpression:
24
- case AST_NODE_TYPES.TSNonNullExpression:
25
- return true;
26
- case AST_NODE_TYPES.MemberExpression:
27
- return parent.object === node;
28
- case AST_NODE_TYPES.CallExpression:
29
- case AST_NODE_TYPES.NewExpression:
30
- return parent.callee === node;
31
- default:
32
- return false;
33
- }
31
+ const { parent } = node;
32
+
33
+
34
+ switch (parent.type) {
35
+ case AST_NODE_TYPES.UnaryExpression:
36
+ case AST_NODE_TYPES.AwaitExpression:
37
+ case AST_NODE_TYPES.BinaryExpression:
38
+ case AST_NODE_TYPES.TSNonNullExpression:
39
+ return true;
40
+ case AST_NODE_TYPES.MemberExpression:
41
+ return parent.object === node;
42
+ case AST_NODE_TYPES.CallExpression:
43
+ case AST_NODE_TYPES.NewExpression:
44
+ return parent.callee === node;
45
+ default:
46
+ return false;
47
+ }
34
48
  };
35
- const preferComparisonOverNullishGuard = {
36
- meta: {
37
- type: "suggestion",
38
- docs: {
39
- description: "Prefer a direct `=== null` / `!== undefined` comparison over calling `isNull`, `isNotNull`, `isUndefined`, or `isNotUndefined` with an explicit argument (those guards are intended for point-free use such as `xs.filter(isNotUndefined)`)."
49
+ export const preferComparisonOverNullishGuard = {
50
+ meta: {
51
+ type: 'suggestion',
52
+ docs: {
53
+ description: 'Prefer a direct `=== null` / `!== undefined` comparison over calling `isNull`, `isNotNull`, `isUndefined`, or `isNotUndefined` with an explicit argument (those guards are intended for point-free use such as `xs.filter(isNotUndefined)`).',
54
+ },
55
+ fixable: 'code',
56
+ schema: [],
57
+ messages: {
58
+ preferComparison: 'Prefer `{{comparison}}` over calling `{{name}}` with an explicit argument.',
59
+ },
40
60
  },
41
- fixable: "code",
42
- schema: [],
43
- messages: {
44
- preferComparison: "Prefer `{{comparison}}` over calling `{{name}}` with an explicit argument."
45
- }
46
- },
47
- create: (context) => {
48
- const sourceCode = context.sourceCode;
49
- const resolveGuard = buildCalleeResolver(
50
- getTsDataForgeImport(sourceCode.ast)
51
- );
52
- return {
53
- CallExpression: (node) => {
54
- if (node.arguments.length !== 1) return;
55
- const argument = node.arguments[0];
56
- if (argument === void 0 || argument.type === AST_NODE_TYPES.SpreadElement) {
57
- return;
58
- }
59
- const resolved = resolveGuard(node.callee);
60
- if (resolved === void 0) return;
61
- const spec = COMPARISON_GUARDS[resolved.canonicalName];
62
- if (spec === void 0) return;
63
- const argRaw = sourceCode.getText(argument);
64
- const argText = ARG_NEEDS_PARENS.has(argument.type) ? `(${argRaw})` : argRaw;
65
- const comparison = `${argText} ${spec.op} ${spec.literal}`;
66
- const replacement = needsWrappingInParent(node) ? `(${comparison})` : comparison;
67
- context.report({
68
- node,
69
- messageId: "preferComparison",
70
- data: { comparison, name: resolved.canonicalName },
71
- fix: (fixer) => fixer.replaceText(node, replacement)
72
- });
73
- }
74
- };
75
- },
76
- defaultOptions: []
61
+ create: (context) => {
62
+ const sourceCode = context.sourceCode;
63
+ const resolveGuard = buildCalleeResolver(getTsDataForgeImport(sourceCode.ast));
64
+ return {
65
+ CallExpression: (node) => {
66
+ if (node.arguments.length !== 1)
67
+ return;
68
+ const argument = node.arguments[0];
69
+ if (argument === undefined ||
70
+ argument.type === AST_NODE_TYPES.SpreadElement) {
71
+ return;
72
+ }
73
+ const resolved = resolveGuard(node.callee);
74
+ if (resolved === undefined)
75
+ return;
76
+ const spec = COMPARISON_GUARDS[resolved.canonicalName];
77
+ if (spec === undefined)
78
+ return;
79
+ const argRaw = sourceCode.getText(argument);
80
+ const argText = ARG_NEEDS_PARENS.has(argument.type)
81
+ ? `(${argRaw})`
82
+ : argRaw;
83
+ const comparison = `${argText} ${spec.op} ${spec.literal}`;
84
+ const replacement = needsWrappingInParent(node)
85
+ ? `(${comparison})`
86
+ : comparison;
87
+ context.report({
88
+ node,
89
+ messageId: 'preferComparison',
90
+ data: { comparison, name: resolved.canonicalName },
91
+ fix: (fixer) => fixer.replaceText(node, replacement),
92
+ });
93
+ },
94
+ };
95
+ },
96
+ defaultOptions: [],
77
97
  };
78
-
79
- export { preferComparisonOverNullishGuard };
80
- //# sourceMappingURL=prefer-comparison-over-nullish-guard.mjs.map
98
+ //# sourceMappingURL=prefer-comparison-over-nullish-guard.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-comparison-over-nullish-guard.mjs","sources":["../../src/rules/prefer-comparison-over-nullish-guard.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type DeepReadonly, type ReadonlyRecord } from 'ts-type-forge';\nimport { buildCalleeResolver, getTsDataForgeImport } from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'preferComparison';\n\ntype ComparisonSpec = Readonly<{\n op: '!==' | '===';\n literal: 'null' | 'undefined';\n}>;\n\n/**\n * The nullish guards that are meant for point-free use (e.g.\n * `xs.filter(isNotUndefined)`). When called with an explicit argument a direct\n * comparison is clearer, so map each to its equivalent operator and literal.\n */\nconst COMPARISON_GUARDS: ReadonlyRecord<string, ComparisonSpec> = {\n isUndefined: { op: '===', literal: 'undefined' },\n isNotUndefined: { op: '!==', literal: 'undefined' },\n isNull: { op: '===', literal: 'null' },\n isNotNull: { op: '!==', literal: 'null' },\n} as const;\n\n/**\n * Argument expression kinds whose precedence is lower than equality, so they\n * must be parenthesized when placed on the left of `===` / `!==`.\n */\nconst ARG_NEEDS_PARENS: ReadonlySet<string> = new Set([\n AST_NODE_TYPES.LogicalExpression,\n AST_NODE_TYPES.ConditionalExpression,\n AST_NODE_TYPES.AssignmentExpression,\n AST_NODE_TYPES.SequenceExpression,\n AST_NODE_TYPES.ArrowFunctionExpression,\n AST_NODE_TYPES.YieldExpression,\n]);\n\n/**\n * Returns `true` when the comparison must be wrapped in parentheses to preserve\n * the original grouping given the call expression's parent context.\n */\nconst needsWrappingInParent = (\n node: DeepReadonly<TSESTree.CallExpression>,\n): boolean => {\n const { parent } = node;\n\n // `parent.type` is one of 130+ AST kinds; only a handful need wrapping.\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check\n switch (parent.type) {\n case AST_NODE_TYPES.UnaryExpression:\n case AST_NODE_TYPES.AwaitExpression:\n case AST_NODE_TYPES.BinaryExpression:\n case AST_NODE_TYPES.TSNonNullExpression:\n return true;\n\n case AST_NODE_TYPES.MemberExpression:\n return parent.object === node;\n\n case AST_NODE_TYPES.CallExpression:\n case AST_NODE_TYPES.NewExpression:\n return parent.callee === node;\n\n default:\n return false;\n }\n};\n\nexport const preferComparisonOverNullishGuard: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Prefer a direct `=== null` / `!== undefined` comparison over calling `isNull`, `isNotNull`, `isUndefined`, or `isNotUndefined` with an explicit argument (those guards are intended for point-free use such as `xs.filter(isNotUndefined)`).',\n },\n fixable: 'code',\n schema: [],\n messages: {\n preferComparison:\n 'Prefer `{{comparison}}` over calling `{{name}}` with an explicit argument.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const resolveGuard = buildCalleeResolver(\n getTsDataForgeImport(sourceCode.ast),\n );\n\n return {\n CallExpression: (node) => {\n if (node.arguments.length !== 1) return;\n\n const argument = node.arguments[0];\n\n if (\n argument === undefined ||\n argument.type === AST_NODE_TYPES.SpreadElement\n ) {\n return;\n }\n\n const resolved = resolveGuard(node.callee);\n\n if (resolved === undefined) return;\n\n const spec = COMPARISON_GUARDS[resolved.canonicalName];\n\n if (spec === undefined) return;\n\n const argRaw = sourceCode.getText(argument);\n\n const argText = ARG_NEEDS_PARENS.has(argument.type)\n ? `(${argRaw})`\n : argRaw;\n\n const comparison = `${argText} ${spec.op} ${spec.literal}`;\n\n const replacement = needsWrappingInParent(node)\n ? `(${comparison})`\n : comparison;\n\n context.report({\n node,\n messageId: 'preferComparison',\n data: { comparison, name: resolved.canonicalName },\n fix: (fixer) => fixer.replaceText(node, replacement),\n });\n },\n };\n },\n defaultOptions: [],\n} as const;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prefer-comparison-over-nullish-guard.mjs","sourceRoot":"","sources":["../../src/rules/prefer-comparison-over-nullish-guard.mts"],"names":[],"mappings":"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"}