eslint-plugin-ts-data-forge 0.1.0 → 0.1.2

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 (105) hide show
  1. package/dist/index.d.mts +5 -0
  2. package/dist/index.d.mts.map +1 -0
  3. package/dist/index.mjs +3 -0
  4. package/dist/index.mjs.map +1 -0
  5. package/dist/plugin.d.mts +3 -0
  6. package/dist/plugin.d.mts.map +1 -0
  7. package/dist/plugin.mjs +11 -0
  8. package/dist/plugin.mjs.map +1 -0
  9. package/dist/rule-types.d.mts +279 -0
  10. package/dist/rule-types.d.mts.map +1 -0
  11. package/dist/rule-types.mjs +2 -0
  12. package/dist/rule-types.mjs.map +1 -0
  13. package/dist/rules/ast-utils.d.mts +29 -0
  14. package/dist/rules/ast-utils.d.mts.map +1 -0
  15. package/dist/rules/ast-utils.mjs +54 -0
  16. package/dist/rules/ast-utils.mjs.map +1 -0
  17. package/dist/rules/branded-number-types.d.mts +5 -0
  18. package/dist/rules/branded-number-types.d.mts.map +1 -0
  19. package/dist/rules/branded-number-types.mjs +40 -0
  20. package/dist/rules/branded-number-types.mjs.map +1 -0
  21. package/dist/rules/constants.d.mts +2 -0
  22. package/dist/rules/constants.d.mts.map +1 -0
  23. package/dist/rules/constants.mjs +4 -0
  24. package/dist/rules/constants.mjs.map +1 -0
  25. package/dist/rules/import-utils.d.mts +30 -0
  26. package/dist/rules/import-utils.d.mts.map +1 -0
  27. package/dist/rules/import-utils.mjs +61 -0
  28. package/dist/rules/import-utils.mjs.map +1 -0
  29. package/dist/rules/index.d.mts +2 -0
  30. package/dist/rules/index.d.mts.map +1 -0
  31. package/dist/rules/index.mjs +2 -0
  32. package/dist/rules/index.mjs.map +1 -0
  33. package/dist/rules/no-unnecessary-type-guard.d.mts +11 -0
  34. package/dist/rules/no-unnecessary-type-guard.d.mts.map +1 -0
  35. package/dist/rules/no-unnecessary-type-guard.mjs +278 -0
  36. package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -0
  37. package/dist/rules/prefer-arr-is-array.d.mts +6 -0
  38. package/dist/rules/prefer-arr-is-array.d.mts.map +1 -0
  39. package/dist/rules/prefer-arr-is-array.mjs +57 -0
  40. package/dist/rules/prefer-arr-is-array.mjs.map +1 -0
  41. package/dist/rules/prefer-arr-is-bounded-length-array.d.mts +6 -0
  42. package/dist/rules/prefer-arr-is-bounded-length-array.d.mts.map +1 -0
  43. package/dist/rules/prefer-arr-is-bounded-length-array.mjs +89 -0
  44. package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -0
  45. package/dist/rules/prefer-arr-is-fixed-length-array.d.mts +6 -0
  46. package/dist/rules/prefer-arr-is-fixed-length-array.d.mts.map +1 -0
  47. package/dist/rules/prefer-arr-is-fixed-length-array.mjs +87 -0
  48. package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -0
  49. package/dist/rules/prefer-arr-is-max-length-array.d.mts +6 -0
  50. package/dist/rules/prefer-arr-is-max-length-array.d.mts.map +1 -0
  51. package/dist/rules/prefer-arr-is-max-length-array.mjs +83 -0
  52. package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -0
  53. package/dist/rules/prefer-arr-is-min-length-array.d.mts +6 -0
  54. package/dist/rules/prefer-arr-is-min-length-array.d.mts.map +1 -0
  55. package/dist/rules/prefer-arr-is-min-length-array.mjs +83 -0
  56. package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -0
  57. package/dist/rules/prefer-arr-is-non-empty.d.mts +6 -0
  58. package/dist/rules/prefer-arr-is-non-empty.d.mts.map +1 -0
  59. package/dist/rules/prefer-arr-is-non-empty.mjs +80 -0
  60. package/dist/rules/prefer-arr-is-non-empty.mjs.map +1 -0
  61. package/dist/rules/prefer-arr-sum.d.mts +6 -0
  62. package/dist/rules/prefer-arr-sum.d.mts.map +1 -0
  63. package/dist/rules/prefer-arr-sum.mjs +136 -0
  64. package/dist/rules/prefer-arr-sum.mjs.map +1 -0
  65. package/dist/rules/prefer-as-int.d.mts +6 -0
  66. package/dist/rules/prefer-as-int.d.mts.map +1 -0
  67. package/dist/rules/prefer-as-int.mjs +77 -0
  68. package/dist/rules/prefer-as-int.mjs.map +1 -0
  69. package/dist/rules/prefer-canonical-array-slicing.d.mts +6 -0
  70. package/dist/rules/prefer-canonical-array-slicing.d.mts.map +1 -0
  71. package/dist/rules/prefer-canonical-array-slicing.mjs +362 -0
  72. package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -0
  73. package/dist/rules/prefer-comparison-over-nullish-guard.d.mts +6 -0
  74. package/dist/rules/prefer-comparison-over-nullish-guard.d.mts.map +1 -0
  75. package/dist/rules/prefer-comparison-over-nullish-guard.mjs +80 -0
  76. package/dist/rules/prefer-comparison-over-nullish-guard.mjs.map +1 -0
  77. package/dist/rules/prefer-is-non-null-object.d.mts +6 -0
  78. package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -0
  79. package/dist/rules/prefer-is-non-null-object.mjs +81 -0
  80. package/dist/rules/prefer-is-non-null-object.mjs.map +1 -0
  81. package/dist/rules/prefer-is-record-and-has-key.d.mts +6 -0
  82. package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -0
  83. package/dist/rules/prefer-is-record-and-has-key.mjs +97 -0
  84. package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -0
  85. package/dist/rules/prefer-num-safe-parse-float.d.mts +14 -0
  86. package/dist/rules/prefer-num-safe-parse-float.d.mts.map +1 -0
  87. package/dist/rules/prefer-num-safe-parse-float.mjs +84 -0
  88. package/dist/rules/prefer-num-safe-parse-float.mjs.map +1 -0
  89. package/dist/rules/prefer-num-safe-parse-int.d.mts +14 -0
  90. package/dist/rules/prefer-num-safe-parse-int.d.mts.map +1 -0
  91. package/dist/rules/prefer-num-safe-parse-int.mjs +88 -0
  92. package/dist/rules/prefer-num-safe-parse-int.mjs.map +1 -0
  93. package/dist/rules/prefer-range-for-loop.d.mts +6 -0
  94. package/dist/rules/prefer-range-for-loop.d.mts.map +1 -0
  95. package/dist/rules/prefer-range-for-loop.mjs +103 -0
  96. package/dist/rules/prefer-range-for-loop.mjs.map +1 -0
  97. package/dist/rules/rules.d.mts +21 -0
  98. package/dist/rules/rules.d.mts.map +1 -0
  99. package/dist/rules/rules.mjs +38 -0
  100. package/dist/rules/rules.mjs.map +1 -0
  101. package/dist/types.d.mts +4 -0
  102. package/dist/types.d.mts.map +1 -0
  103. package/dist/types.mjs +2 -0
  104. package/dist/types.mjs.map +1 -0
  105. package/package.json +20 -20
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/rules/index.mts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { tsDataForgeRules } from './rules.mjs';
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ type Options = readonly [
3
+ Readonly<{
4
+ /** Names of ts-data-forge guard functions to skip checking. */
5
+ ignore?: readonly string[];
6
+ }>?
7
+ ];
8
+ type MessageIds = 'alwaysTrue' | 'alwaysFalse' | 'replaceTypeGuard';
9
+ export declare const noUnnecessaryTypeGuard: TSESLint.RuleModule<MessageIds, Options>;
10
+ export {};
11
+ //# sourceMappingURL=no-unnecessary-type-guard.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unnecessary-type-guard.d.mts","sourceRoot":"","sources":["../../src/rules/no-unnecessary-type-guard.mts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAUlC,KAAK,OAAO,GAAG,SAAS;IACtB,QAAQ,CAAC;QACP,+DAA+D;QAC/D,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KAC5B,CAAC,CAAC;CACJ,CAAC;AAEF,KAAK,UAAU,GAAG,YAAY,GAAG,aAAa,GAAG,kBAAkB,CAAC;AAyFpE,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CAoRhE,CAAC"}
@@ -0,0 +1,278 @@
1
+ import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import * as ts from 'typescript';
3
+ import { getTsDataForgeImport, buildCalleeResolver, getImportedLocalName, buildImportFixes } from './import-utils.mjs';
4
+
5
+ const BRAND_MARKER_PROPERTY = "TSTypeForgeInternals--edd2f9ce-7ca5-45b0-9d1a-bd61b9b5d9c3";
6
+ const MIN_LENGTH_BRAND_KEY = "MinLength";
7
+ const NULLISH_REPLACEMENTS_EXCLUDE = {
8
+ undefined: "isNotUndefined",
9
+ null: "isNotNull"
10
+ };
11
+ const NULLISH_REPLACEMENTS_NARROW = {
12
+ undefined: "isUndefined",
13
+ null: "isNull"
14
+ };
15
+ const GUARD_SPECS = {
16
+ isUndefined: { kind: "narrowTo", atoms: ["undefined"] },
17
+ isNotUndefined: { kind: "excludeFrom", atoms: ["undefined"] },
18
+ isNull: { kind: "narrowTo", atoms: ["null"] },
19
+ isNotNull: { kind: "excludeFrom", atoms: ["null"] },
20
+ isBoolean: { kind: "narrowTo", atoms: ["boolean"] },
21
+ isNotBoolean: { kind: "excludeFrom", atoms: ["boolean"] },
22
+ isNumber: { kind: "narrowTo", atoms: ["number"] },
23
+ isNotNumber: { kind: "excludeFrom", atoms: ["number"] },
24
+ isString: { kind: "narrowTo", atoms: ["string"] },
25
+ isNotString: { kind: "excludeFrom", atoms: ["string"] },
26
+ isBigint: { kind: "narrowTo", atoms: ["bigint"] },
27
+ isNotBigint: { kind: "excludeFrom", atoms: ["bigint"] },
28
+ isSymbol: { kind: "narrowTo", atoms: ["symbol"] },
29
+ isNotSymbol: { kind: "excludeFrom", atoms: ["symbol"] },
30
+ isNullish: {
31
+ kind: "narrowTo",
32
+ atoms: ["null", "undefined"],
33
+ replacements: NULLISH_REPLACEMENTS_NARROW
34
+ },
35
+ isNonNullish: {
36
+ kind: "excludeFrom",
37
+ atoms: ["null", "undefined"],
38
+ replacements: NULLISH_REPLACEMENTS_EXCLUDE
39
+ },
40
+ isNonEmptyString: { kind: "nonEmptyString" }
41
+ };
42
+ const DEFERRED_OR_OPAQUE_FLAGS = ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never | ts.TypeFlags.TypeParameter | ts.TypeFlags.IndexedAccess | ts.TypeFlags.Conditional | ts.TypeFlags.Substitution;
43
+ const noUnnecessaryTypeGuard = {
44
+ meta: {
45
+ type: "suggestion",
46
+ docs: {
47
+ description: "Detect ts-data-forge type guard calls that perform no narrowing (the argument type already satisfies, or can never satisfy, the guard)."
48
+ },
49
+ fixable: "code",
50
+ schema: [
51
+ {
52
+ type: "object",
53
+ properties: {
54
+ ignore: {
55
+ type: "array",
56
+ items: { type: "string" },
57
+ description: "Names of ts-data-forge guard functions to skip checking."
58
+ }
59
+ },
60
+ additionalProperties: false
61
+ }
62
+ ],
63
+ messages: {
64
+ alwaysTrue: "Unnecessary `{{name}}` call: the argument type guarantees it always returns `true`. Replace the call with `true`.",
65
+ alwaysFalse: "Unnecessary `{{name}}` call: the argument type guarantees it always returns `false`. Replace the call with `false`.",
66
+ replaceTypeGuard: "Unnecessary `{{from}}` call: the argument can only be narrowed by `{{to}}`. Use `{{to}}` instead."
67
+ }
68
+ },
69
+ create: (context) => {
70
+ const options = context.options[0];
71
+ const parserServices = ESLintUtils.getParserServices(context);
72
+ const compilerOptions = parserServices.program.getCompilerOptions();
73
+ const strictNullChecks = compilerOptions.strictNullChecks ?? compilerOptions.strict ?? false;
74
+ if (!strictNullChecks) return {};
75
+ const checker = parserServices.program.getTypeChecker();
76
+ const sourceCode = context.sourceCode;
77
+ const program = sourceCode.ast;
78
+ const ignored = new Set(options?.ignore);
79
+ const tsDataForgeImport = getTsDataForgeImport(program);
80
+ const resolveGuard = buildCalleeResolver(tsDataForgeImport);
81
+ const buildReplaceFix = (isNamespace, propertyNode, replacement) => (fixer) => {
82
+ if (isNamespace) {
83
+ return [fixer.replaceText(propertyNode, replacement)];
84
+ }
85
+ const localName = getImportedLocalName(
86
+ tsDataForgeImport,
87
+ replacement
88
+ );
89
+ if (localName !== void 0) {
90
+ return [fixer.replaceText(propertyNode, localName)];
91
+ }
92
+ return [
93
+ ...buildImportFixes(fixer, program, tsDataForgeImport, [
94
+ replacement
95
+ ]),
96
+ fixer.replaceText(propertyNode, replacement)
97
+ ];
98
+ };
99
+ const reportConstant = (node, name, value, argument) => {
100
+ const canFix = isSideEffectFreeArg(argument);
101
+ context.report({
102
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
103
+ node,
104
+ messageId: value ? "alwaysTrue" : "alwaysFalse",
105
+ data: { name },
106
+ fix: canFix ? (fixer) => fixer.replaceText(
107
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
108
+ node,
109
+ value ? "true" : "false"
110
+ ) : void 0
111
+ });
112
+ };
113
+ const reportReplace = (node, from, to, isNamespace, propertyNode) => {
114
+ context.report({
115
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
116
+ node,
117
+ messageId: "replaceTypeGuard",
118
+ data: { from, to },
119
+ fix: buildReplaceFix(isNamespace, propertyNode, to)
120
+ });
121
+ };
122
+ return {
123
+ CallExpression: (node) => {
124
+ if (node.arguments.length !== 1) return;
125
+ const argument = node.arguments[0];
126
+ if (argument === void 0 || argument.type === AST_NODE_TYPES.SpreadElement) {
127
+ return;
128
+ }
129
+ const resolved = resolveGuard(node.callee);
130
+ if (resolved === void 0) return;
131
+ const { canonicalName, propertyNode, isNamespace } = resolved;
132
+ if (ignored.has(canonicalName)) return;
133
+ const spec = GUARD_SPECS[canonicalName];
134
+ if (spec === void 0) return;
135
+ const argTsNode = parserServices.esTreeNodeToTSNodeMap.get(argument);
136
+ const argType = checker.getTypeAtLocation(argTsNode);
137
+ const parts = collectUnionParts(argType);
138
+ if (parts === void 0) return;
139
+ const inputAtoms = new Set(parts.map(classifyAtom));
140
+ const hasMemberOutsideTarget = (atoms) => {
141
+ const targetSet = new Set(atoms);
142
+ return parts.some((p) => !targetSet.has(classifyAtom(p)));
143
+ };
144
+ switch (spec.kind) {
145
+ case "excludeFrom": {
146
+ const removable = spec.atoms.filter((a) => inputAtoms.has(a));
147
+ if (removable.length === 0) {
148
+ reportConstant(node, canonicalName, true, argument);
149
+ } else if (!hasMemberOutsideTarget(spec.atoms)) {
150
+ reportConstant(node, canonicalName, false, argument);
151
+ } else if (removable.length < spec.atoms.length) {
152
+ const replacement = spec.replacements?.[joinAtoms(removable)];
153
+ if (replacement !== void 0) {
154
+ reportReplace(
155
+ node,
156
+ canonicalName,
157
+ replacement,
158
+ isNamespace,
159
+ propertyNode
160
+ );
161
+ }
162
+ }
163
+ break;
164
+ }
165
+ case "narrowTo": {
166
+ const present = spec.atoms.filter((a) => inputAtoms.has(a));
167
+ if (present.length === 0) {
168
+ reportConstant(node, canonicalName, false, argument);
169
+ } else if (!hasMemberOutsideTarget(spec.atoms)) {
170
+ reportConstant(node, canonicalName, true, argument);
171
+ } else if (present.length < spec.atoms.length) {
172
+ const replacement = spec.replacements?.[joinAtoms(present)];
173
+ if (replacement !== void 0) {
174
+ reportReplace(
175
+ node,
176
+ canonicalName,
177
+ replacement,
178
+ isNamespace,
179
+ propertyNode
180
+ );
181
+ }
182
+ }
183
+ break;
184
+ }
185
+ case "nonEmptyString": {
186
+ const hasNullish = inputAtoms.has("undefined") || inputAtoms.has("null");
187
+ const nonNullishParts = parts.filter((p) => {
188
+ const atom = classifyAtom(p);
189
+ return atom !== "undefined" && atom !== "null";
190
+ });
191
+ if (!nonNullishParts.some(couldBeNonEmptyString)) {
192
+ reportConstant(node, canonicalName, false, argument);
193
+ break;
194
+ }
195
+ if (nonNullishParts.some(
196
+ (p) => !isGuaranteedNonEmptyString(p, checker)
197
+ )) {
198
+ break;
199
+ }
200
+ if (hasNullish) {
201
+ reportReplace(
202
+ node,
203
+ canonicalName,
204
+ "isNonNullish",
205
+ isNamespace,
206
+ propertyNode
207
+ );
208
+ } else {
209
+ reportConstant(node, canonicalName, true, argument);
210
+ }
211
+ break;
212
+ }
213
+ }
214
+ }
215
+ };
216
+ },
217
+ defaultOptions: [{}]
218
+ };
219
+ const collectUnionParts = (type) => {
220
+ const parts = type.isUnion() ? type.types : [type];
221
+ for (const part of parts) {
222
+ if ((part.flags & DEFERRED_OR_OPAQUE_FLAGS) !== 0) return void 0;
223
+ }
224
+ return parts;
225
+ };
226
+ const classifyAtom = (type) => {
227
+ const { flags } = type;
228
+ if ((flags & ts.TypeFlags.Undefined) !== 0) return "undefined";
229
+ if ((flags & ts.TypeFlags.Null) !== 0) return "null";
230
+ if ((flags & ts.TypeFlags.BooleanLike) !== 0) return "boolean";
231
+ if ((flags & ts.TypeFlags.NumberLike) !== 0) return "number";
232
+ if ((flags & ts.TypeFlags.StringLike) !== 0) return "string";
233
+ if ((flags & ts.TypeFlags.BigIntLike) !== 0) return "bigint";
234
+ if ((flags & ts.TypeFlags.ESSymbolLike) !== 0) return "symbol";
235
+ if (type.isIntersection()) {
236
+ for (const constituent of type.types) {
237
+ const atom = classifyAtom(constituent);
238
+ if (atom !== "other") return atom;
239
+ }
240
+ }
241
+ return "other";
242
+ };
243
+ const ATOM_ORDER = [
244
+ "undefined",
245
+ "null",
246
+ "boolean",
247
+ "number",
248
+ "string",
249
+ "bigint",
250
+ "symbol"
251
+ ];
252
+ const joinAtoms = (atoms) => ATOM_ORDER.filter((atom) => atoms.includes(atom)).join("|");
253
+ const isGuaranteedNonEmptyString = (type, checker) => {
254
+ if (type.isStringLiteral()) return type.value !== "";
255
+ if (type.getProperty(BRAND_MARKER_PROPERTY) === void 0) return false;
256
+ const minLengthSymbol = type.getProperty(MIN_LENGTH_BRAND_KEY);
257
+ if (minLengthSymbol === void 0) return false;
258
+ return checker.getTypeOfSymbol(minLengthSymbol).getProperty("0") !== void 0;
259
+ };
260
+ const couldBeNonEmptyString = (type) => {
261
+ if (type.isStringLiteral()) return type.value !== "";
262
+ return classifyAtom(type) === "string";
263
+ };
264
+ const isSideEffectFreeArg = (node) => {
265
+ switch (node.type) {
266
+ case AST_NODE_TYPES.Identifier:
267
+ case AST_NODE_TYPES.ThisExpression:
268
+ case AST_NODE_TYPES.Literal:
269
+ return true;
270
+ case AST_NODE_TYPES.MemberExpression:
271
+ return isSideEffectFreeArg(node.object) && (!node.computed || isSideEffectFreeArg(node.property));
272
+ default:
273
+ return false;
274
+ }
275
+ };
276
+
277
+ export { noUnnecessaryTypeGuard };
278
+ //# sourceMappingURL=no-unnecessary-type-guard.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unnecessary-type-guard.mjs","sources":["../../src/rules/no-unnecessary-type-guard.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n ESLintUtils,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport * as ts from 'typescript';\nimport {\n buildCalleeResolver,\n buildImportFixes,\n getImportedLocalName,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [\n Readonly<{\n /** Names of ts-data-forge guard functions to skip checking. */\n ignore?: readonly string[];\n }>?,\n];\n\ntype MessageIds = 'alwaysTrue' | 'alwaysFalse' | 'replaceTypeGuard';\n\n/**\n * The primitive categories a (non-`null`/`undefined`) union member can fall\n * into. `other` covers anything the rule does not reason about (objects,\n * functions, `void`, …).\n */\ntype AtomKey =\n 'undefined' | 'null' | 'boolean' | 'number' | 'string' | 'bigint' | 'symbol';\n\ntype Atom = AtomKey | 'other';\n\ntype GuardSpec = DeepReadonly<\n | {\n kind: 'narrowTo';\n atoms: AtomKey[];\n replacements?: Record<string, string>;\n }\n | {\n kind: 'excludeFrom';\n atoms: AtomKey[];\n replacements?: Record<string, string>;\n }\n | { kind: 'nonEmptyString' }\n>;\n\n/**\n * Internal marker property attached to every `ts-type-forge` branded type. Its\n * presence (together with the `MinLength` brand key) lets us recognize a\n * `NonEmptyString` without holding a reference to the type itself.\n */\nconst BRAND_MARKER_PROPERTY =\n 'TSTypeForgeInternals--edd2f9ce-7ca5-45b0-9d1a-bd61b9b5d9c3';\n\n/**\n * Brand key carried by `MinLengthString<N>` — and therefore by\n * `NonEmptyString = MinLengthString<1>`. Its value is `MinLengthTuple<N, 0>`, a\n * tuple of `0`s with at least `N` elements, so a required first element means\n * `N >= 1` (the string cannot be empty).\n */\nconst MIN_LENGTH_BRAND_KEY = 'MinLength';\n\nconst NULLISH_REPLACEMENTS_EXCLUDE: Readonly<Record<string, string>> = {\n undefined: 'isNotUndefined',\n null: 'isNotNull',\n} as const;\n\nconst NULLISH_REPLACEMENTS_NARROW: Readonly<Record<string, string>> = {\n undefined: 'isUndefined',\n null: 'isNull',\n} as const;\n\nconst GUARD_SPECS: DeepReadonly<Record<string, GuardSpec>> = {\n isUndefined: { kind: 'narrowTo', atoms: ['undefined'] },\n isNotUndefined: { kind: 'excludeFrom', atoms: ['undefined'] },\n isNull: { kind: 'narrowTo', atoms: ['null'] },\n isNotNull: { kind: 'excludeFrom', atoms: ['null'] },\n isBoolean: { kind: 'narrowTo', atoms: ['boolean'] },\n isNotBoolean: { kind: 'excludeFrom', atoms: ['boolean'] },\n isNumber: { kind: 'narrowTo', atoms: ['number'] },\n isNotNumber: { kind: 'excludeFrom', atoms: ['number'] },\n isString: { kind: 'narrowTo', atoms: ['string'] },\n isNotString: { kind: 'excludeFrom', atoms: ['string'] },\n isBigint: { kind: 'narrowTo', atoms: ['bigint'] },\n isNotBigint: { kind: 'excludeFrom', atoms: ['bigint'] },\n isSymbol: { kind: 'narrowTo', atoms: ['symbol'] },\n isNotSymbol: { kind: 'excludeFrom', atoms: ['symbol'] },\n isNullish: {\n kind: 'narrowTo',\n atoms: ['null', 'undefined'],\n replacements: NULLISH_REPLACEMENTS_NARROW,\n },\n isNonNullish: {\n kind: 'excludeFrom',\n atoms: ['null', 'undefined'],\n replacements: NULLISH_REPLACEMENTS_EXCLUDE,\n },\n isNonEmptyString: { kind: 'nonEmptyString' },\n} as const;\n\nconst DEFERRED_OR_OPAQUE_FLAGS =\n ts.TypeFlags.Any |\n ts.TypeFlags.Unknown |\n ts.TypeFlags.Never |\n ts.TypeFlags.TypeParameter |\n ts.TypeFlags.IndexedAccess |\n ts.TypeFlags.Conditional |\n ts.TypeFlags.Substitution;\n\nexport const noUnnecessaryTypeGuard: TSESLint.RuleModule<MessageIds, Options> =\n {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Detect ts-data-forge type guard calls that perform no narrowing (the argument type already satisfies, or can never satisfy, the guard).',\n },\n fixable: 'code',\n schema: [\n {\n type: 'object',\n properties: {\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Names of ts-data-forge guard functions to skip checking.',\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n alwaysTrue:\n 'Unnecessary `{{name}}` call: the argument type guarantees it always returns `true`. Replace the call with `true`.',\n alwaysFalse:\n 'Unnecessary `{{name}}` call: the argument type guarantees it always returns `false`. Replace the call with `false`.',\n replaceTypeGuard:\n 'Unnecessary `{{from}}` call: the argument can only be narrowed by `{{to}}`. Use `{{to}}` instead.',\n },\n },\n\n create: (context) => {\n const options = context.options[0];\n\n const parserServices = ESLintUtils.getParserServices(context);\n\n const compilerOptions = parserServices.program.getCompilerOptions();\n\n // Without `strictNullChecks`, `null` / `undefined` are erased from the type\n // system, so reasoning about nullish narrowing is unsound. Disable entirely.\n const strictNullChecks =\n compilerOptions.strictNullChecks ?? compilerOptions.strict ?? false;\n\n if (!strictNullChecks) return {};\n\n const checker = parserServices.program.getTypeChecker();\n\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const ignored = new Set(options?.ignore);\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const resolveGuard = buildCalleeResolver(tsDataForgeImport);\n\n const buildReplaceFix =\n (\n isNamespace: boolean,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n propertyNode: TSESTree.Node,\n replacement: string,\n ): TSESLint.ReportFixFunction =>\n (fixer) => {\n // A namespace member access (`tf.isX`) exposes every export, so the\n // canonical name is always in scope.\n if (isNamespace) {\n return [fixer.replaceText(propertyNode, replacement)];\n }\n\n // If the replacement guard is already imported (possibly under an\n // alias), rename to its in-scope local name. Otherwise add a new\n // import and rename to the canonical name.\n const localName = getImportedLocalName(\n tsDataForgeImport,\n replacement,\n );\n\n if (localName !== undefined) {\n return [fixer.replaceText(propertyNode, localName)];\n }\n\n return [\n ...buildImportFixes(fixer, program, tsDataForgeImport, [\n replacement,\n ]),\n fixer.replaceText(propertyNode, replacement),\n ];\n };\n\n const reportConstant = (\n node: DeepReadonly<TSESTree.CallExpression>,\n name: string,\n value: boolean,\n argument: DeepReadonly<TSESTree.Expression>,\n ): void => {\n // Replacing the call with a literal drops the argument expression, so only\n // autofix when evaluating it cannot have observable side effects.\n const canFix = isSideEffectFreeArg(argument);\n\n context.report({\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n node: node as TSESTree.Node,\n messageId: value ? 'alwaysTrue' : 'alwaysFalse',\n data: { name },\n fix: canFix\n ? (fixer) =>\n fixer.replaceText(\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n node as TSESTree.Node,\n value ? 'true' : 'false',\n )\n : undefined,\n });\n };\n\n const reportReplace = (\n node: DeepReadonly<TSESTree.CallExpression>,\n from: string,\n to: string,\n isNamespace: boolean,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n propertyNode: TSESTree.Node,\n ): void => {\n context.report({\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n node: node as TSESTree.Node,\n messageId: 'replaceTypeGuard',\n data: { from, to },\n fix: buildReplaceFix(isNamespace, propertyNode, to),\n });\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 { canonicalName, propertyNode, isNamespace } = resolved;\n\n if (ignored.has(canonicalName)) return;\n\n const spec = GUARD_SPECS[canonicalName];\n\n if (spec === undefined) return;\n\n const argTsNode = parserServices.esTreeNodeToTSNodeMap.get(argument);\n\n const argType = checker.getTypeAtLocation(argTsNode);\n\n const parts = collectUnionParts(argType);\n\n if (parts === undefined) return; // opaque / generic → bail conservatively\n\n const inputAtoms = new Set(parts.map(classifyAtom));\n\n const hasMemberOutsideTarget = (\n atoms: readonly AtomKey[],\n ): boolean => {\n const targetSet = new Set<Atom>(atoms);\n\n return parts.some((p) => !targetSet.has(classifyAtom(p)));\n };\n\n switch (spec.kind) {\n case 'excludeFrom': {\n const removable = spec.atoms.filter((a) => inputAtoms.has(a));\n\n if (removable.length === 0) {\n // Nothing to exclude → the guard always holds.\n reportConstant(node, canonicalName, true, argument);\n } else if (!hasMemberOutsideTarget(spec.atoms)) {\n // Every member is excluded → the guard never holds.\n reportConstant(node, canonicalName, false, argument);\n } else if (removable.length < spec.atoms.length) {\n const replacement = spec.replacements?.[joinAtoms(removable)];\n\n if (replacement !== undefined) {\n reportReplace(\n node,\n canonicalName,\n replacement,\n isNamespace,\n propertyNode,\n );\n }\n }\n\n break;\n }\n\n case 'narrowTo': {\n const present = spec.atoms.filter((a) => inputAtoms.has(a));\n\n if (present.length === 0) {\n // No member is in the target set → the guard never holds.\n reportConstant(node, canonicalName, false, argument);\n } else if (!hasMemberOutsideTarget(spec.atoms)) {\n // Input ⊆ target → the guard always holds and narrows nothing.\n reportConstant(node, canonicalName, true, argument);\n } else if (present.length < spec.atoms.length) {\n const replacement = spec.replacements?.[joinAtoms(present)];\n\n if (replacement !== undefined) {\n reportReplace(\n node,\n canonicalName,\n replacement,\n isNamespace,\n propertyNode,\n );\n }\n }\n\n break;\n }\n\n case 'nonEmptyString': {\n const hasNullish =\n inputAtoms.has('undefined') || inputAtoms.has('null');\n\n const nonNullishParts = parts.filter((p) => {\n const atom = classifyAtom(p);\n\n return atom !== 'undefined' && atom !== 'null';\n });\n\n if (!nonNullishParts.some(couldBeNonEmptyString)) {\n // No member can be a non-empty string → the guard never holds.\n reportConstant(node, canonicalName, false, argument);\n\n break;\n }\n\n if (\n nonNullishParts.some(\n (p) => !isGuaranteedNonEmptyString(p, checker),\n )\n ) {\n break; // still does real string/empty work\n }\n\n if (hasNullish) {\n reportReplace(\n node,\n canonicalName,\n 'isNonNullish',\n isNamespace,\n propertyNode,\n );\n } else {\n reportConstant(node, canonicalName, true, argument);\n }\n\n break;\n }\n }\n },\n };\n },\n defaultOptions: [{}],\n } as const;\n\n/**\n * Decomposes a type into its union members. Returns `undefined` when any member\n * is opaque (`any` / `unknown` / `never`) or deferred (type parameter, indexed\n * access, …), signalling that the rule should not draw conclusions.\n */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst collectUnionParts = (type: ts.Type): readonly ts.Type[] | undefined => {\n const parts = type.isUnion() ? type.types : ([type] as const);\n\n for (const part of parts) {\n if ((part.flags & DEFERRED_OR_OPAQUE_FLAGS) !== 0) return undefined;\n }\n\n return parts;\n};\n\n/** Classifies a single (non-union) type into a primitive {@link Atom}. */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst classifyAtom = (type: ts.Type): Atom => {\n const { flags } = type;\n\n if ((flags & ts.TypeFlags.Undefined) !== 0) return 'undefined';\n\n if ((flags & ts.TypeFlags.Null) !== 0) return 'null';\n\n if ((flags & ts.TypeFlags.BooleanLike) !== 0) return 'boolean';\n\n if ((flags & ts.TypeFlags.NumberLike) !== 0) return 'number';\n\n if ((flags & ts.TypeFlags.StringLike) !== 0) return 'string';\n\n if ((flags & ts.TypeFlags.BigIntLike) !== 0) return 'bigint';\n\n if ((flags & ts.TypeFlags.ESSymbolLike) !== 0) return 'symbol';\n\n // Branded primitives (e.g. `NonEmptyString = string & {...}`) are\n // intersections; classify them by their underlying primitive constituent.\n if (type.isIntersection()) {\n for (const constituent of type.types) {\n const atom = classifyAtom(constituent);\n\n if (atom !== 'other') return atom;\n }\n }\n\n return 'other';\n};\n\nconst ATOM_ORDER: readonly AtomKey[] = [\n 'undefined',\n 'null',\n 'boolean',\n 'number',\n 'string',\n 'bigint',\n 'symbol',\n] as const;\n\n/** A stable key for a set of atoms, used to look up replacement guards. */\nconst joinAtoms = (atoms: readonly AtomKey[]): string =>\n ATOM_ORDER.filter((atom) => atoms.includes(atom)).join('|');\n\n/**\n * Returns `true` when the type is guaranteed to be a non-empty string: either a\n * non-empty string literal or a value branded as `NonEmptyString`\n * (`MinLengthString<N>` with `N >= 1`).\n */\nconst isGuaranteedNonEmptyString = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n type: ts.Type,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n checker: ts.TypeChecker,\n): boolean => {\n if (type.isStringLiteral()) return type.value !== '';\n\n if (type.getProperty(BRAND_MARKER_PROPERTY) === undefined) return false;\n\n const minLengthSymbol = type.getProperty(MIN_LENGTH_BRAND_KEY);\n\n if (minLengthSymbol === undefined) return false;\n\n // The `MinLength` brand value is `MinLengthTuple<N, 0>`; it has a required\n // first element (`'0'`) exactly when `N >= 1`, i.e. the string is non-empty.\n // `MinLengthString<0>` brands with a plain array and has no such element.\n return (\n checker.getTypeOfSymbol(minLengthSymbol).getProperty('0') !== undefined\n );\n};\n\n/**\n * Returns `true` when the type can possibly be a non-empty string (a general\n * `string`, a branded string, or a non-empty string literal). The empty string\n * literal and non-string types cannot.\n */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst couldBeNonEmptyString = (type: ts.Type): boolean => {\n if (type.isStringLiteral()) return type.value !== '';\n\n return classifyAtom(type) === 'string';\n};\n\n/**\n * Returns `true` when evaluating the expression cannot have observable side\n * effects, so replacing the surrounding call with a literal is safe. Limited to\n * simple references (identifiers, `this`, literals, and member-access chains).\n */\n\nconst isSideEffectFreeArg = (node: DeepReadonly<TSESTree.Node>): boolean => {\n // `node.type` is one of 130+ AST kinds; only a handful are relevant here.\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check\n switch (node.type) {\n case AST_NODE_TYPES.Identifier:\n case AST_NODE_TYPES.ThisExpression:\n case AST_NODE_TYPES.Literal:\n return true;\n\n case AST_NODE_TYPES.MemberExpression:\n return (\n isSideEffectFreeArg(node.object) &&\n (!node.computed || isSideEffectFreeArg(node.property))\n );\n\n default:\n return false;\n }\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ type Options = readonly [];
3
+ type MessageIds = 'useArrIsArray';
4
+ export declare const preferArrIsArray: TSESLint.RuleModule<MessageIds, Options>;
5
+ export {};
6
+ //# sourceMappingURL=prefer-arr-is-array.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-is-array.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-arr-is-array.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAQlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,eAAe,CAAC;AAElC,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CA+D5D,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { getTsDataForgeImport, getNamedImports, buildImportFixes } from './import-utils.mjs';
3
+
4
+ const preferArrIsArray = {
5
+ meta: {
6
+ type: "suggestion",
7
+ docs: {
8
+ description: "Replace `Array.isArray` with `Arr.isArray` from ts-data-forge."
9
+ },
10
+ fixable: "code",
11
+ schema: [],
12
+ messages: {
13
+ useArrIsArray: "Replace `Array.isArray` with `Arr.isArray` from ts-data-forge."
14
+ }
15
+ },
16
+ create: (context) => {
17
+ const sourceCode = context.sourceCode;
18
+ const program = sourceCode.ast;
19
+ const tsDataForgeImport = getTsDataForgeImport(program);
20
+ const mut_nodesToFix = [];
21
+ return {
22
+ CallExpression: (node) => {
23
+ if (!isArrayIsArrayCall(node)) return;
24
+ mut_nodesToFix.push(node);
25
+ },
26
+ "Program:exit": () => {
27
+ const hasArrImport = getNamedImports(tsDataForgeImport).includes("Arr");
28
+ for (const [index, node] of mut_nodesToFix.entries()) {
29
+ context.report({
30
+ node,
31
+ messageId: "useArrIsArray",
32
+ fix: (fixer) => {
33
+ const callee = node.callee;
34
+ if (callee.type !== AST_NODE_TYPES.MemberExpression) return [];
35
+ const replacement = `Arr.isArray${sourceCode.getText(node).slice(sourceCode.getText(callee).length)}`;
36
+ const importFixes = index === 0 && !hasArrImport ? buildImportFixes(fixer, program, tsDataForgeImport, ["Arr"]) : [];
37
+ return [...importFixes, fixer.replaceText(node, replacement)];
38
+ }
39
+ });
40
+ }
41
+ }
42
+ };
43
+ },
44
+ defaultOptions: []
45
+ };
46
+ const isArrayIsArrayCall = (node) => {
47
+ if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return false;
48
+ const { object, property } = node.callee;
49
+ if (object.type !== AST_NODE_TYPES.Identifier) return false;
50
+ if (object.name !== "Array") return false;
51
+ if (property.type !== AST_NODE_TYPES.Identifier) return false;
52
+ if (property.name !== "isArray") return false;
53
+ return true;
54
+ };
55
+
56
+ export { preferArrIsArray };
57
+ //# sourceMappingURL=prefer-arr-is-array.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-is-array.mjs","sources":["../../src/rules/prefer-arr-is-array.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useArrIsArray';\n\nexport const preferArrIsArray: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `Array.isArray` with `Arr.isArray` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useArrIsArray:\n 'Replace `Array.isArray` with `Arr.isArray` from ts-data-forge.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const mut_nodesToFix: TSESTree.CallExpression[] = [];\n\n return {\n CallExpression: (node) => {\n if (!isArrayIsArrayCall(node)) return;\n\n mut_nodesToFix.push(node);\n },\n 'Program:exit': () => {\n // Check if Arr is already imported\n const hasArrImport = getNamedImports(tsDataForgeImport).includes('Arr');\n\n // Note: We add import only for the first node to avoid conflicts when\n // multiple fixes try to insert at the same position. This means if the\n // first node is disabled via eslint-disable comment, no import will be\n // added.\n for (const [index, node] of mut_nodesToFix.entries()) {\n context.report({\n node,\n messageId: 'useArrIsArray',\n fix: (fixer) => {\n const callee = node.callee;\n\n if (callee.type !== AST_NODE_TYPES.MemberExpression) return [];\n\n const replacement = `Arr.isArray${sourceCode.getText(node).slice(sourceCode.getText(callee).length)}`;\n\n // Add import only for the first node and only if not already imported\n const importFixes =\n index === 0 && !hasArrImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])\n : [];\n\n return [...importFixes, fixer.replaceText(node, replacement)];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n} as const;\n\nconst isArrayIsArrayCall = (\n node: DeepReadonly<TSESTree.CallExpression>,\n): boolean => {\n if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return false;\n\n const { object, property } = node.callee;\n\n if (object.type !== AST_NODE_TYPES.Identifier) return false;\n\n if (object.name !== 'Array') return false;\n\n if (property.type !== AST_NODE_TYPES.Identifier) return false;\n\n if (property.name !== 'isArray') return false;\n\n return true;\n};\n"],"names":[],"mappings":";;;AAgBO,MAAM,gBAAA,GAA6D;AAAA,EACxE,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,YAAA;AAAA,IACN,IAAA,EAAM;AAAA,MACJ,WAAA,EACE;AAAA,KACJ;AAAA,IACA,OAAA,EAAS,MAAA;AAAA,IACT,QAAQ,EAAC;AAAA,IACT,QAAA,EAAU;AAAA,MACR,aAAA,EACE;AAAA;AACJ,GACF;AAAA,EAEA,MAAA,EAAQ,CAAC,OAAA,KAAY;AACnB,IAAA,MAAM,aAAa,OAAA,CAAQ,UAAA;AAE3B,IAAA,MAAM,UAAU,UAAA,CAAW,GAAA;AAE3B,IAAA,MAAM,iBAAA,GAAoB,qBAAqB,OAAO,CAAA;AAEtD,IAAA,MAAM,iBAA4C,EAAC;AAEnD,IAAA,OAAO;AAAA,MACL,cAAA,EAAgB,CAAC,IAAA,KAAS;AACxB,QAAA,IAAI,CAAC,kBAAA,CAAmB,IAAI,CAAA,EAAG;AAE/B,QAAA,cAAA,CAAe,KAAK,IAAI,CAAA;AAAA,MAC1B,CAAA;AAAA,MACA,gBAAgB,MAAM;AAEpB,QAAA,MAAM,YAAA,GAAe,eAAA,CAAgB,iBAAiB,CAAA,CAAE,SAAS,KAAK,CAAA;AAMtE,QAAA,KAAA,MAAW,CAAC,KAAA,EAAO,IAAI,CAAA,IAAK,cAAA,CAAe,SAAQ,EAAG;AACpD,UAAA,OAAA,CAAQ,MAAA,CAAO;AAAA,YACb,IAAA;AAAA,YACA,SAAA,EAAW,eAAA;AAAA,YACX,GAAA,EAAK,CAAC,KAAA,KAAU;AACd,cAAA,MAAM,SAAS,IAAA,CAAK,MAAA;AAEpB,cAAA,IAAI,MAAA,CAAO,IAAA,KAAS,cAAA,CAAe,gBAAA,SAAyB,EAAC;AAE7D,cAAA,MAAM,WAAA,GAAc,CAAA,WAAA,EAAc,UAAA,CAAW,OAAA,CAAQ,IAAI,CAAA,CAAE,KAAA,CAAM,UAAA,CAAW,OAAA,CAAQ,MAAM,CAAA,CAAE,MAAM,CAAC,CAAA,CAAA;AAGnG,cAAA,MAAM,WAAA,GACJ,KAAA,KAAU,CAAA,IAAK,CAAC,YAAA,GACZ,gBAAA,CAAiB,KAAA,EAAO,OAAA,EAAS,iBAAA,EAAmB,CAAC,KAAK,CAAC,IAC3D,EAAC;AAEP,cAAA,OAAO,CAAC,GAAG,WAAA,EAAa,MAAM,WAAA,CAAY,IAAA,EAAM,WAAW,CAAC,CAAA;AAAA,YAC9D;AAAA,WACD,CAAA;AAAA,QACH;AAAA,MACF;AAAA,KACF;AAAA,EACF,CAAA;AAAA,EACA,gBAAgB;AAClB;AAEA,MAAM,kBAAA,GAAqB,CACzB,IAAA,KACY;AACZ,EAAA,IAAI,IAAA,CAAK,MAAA,CAAO,IAAA,KAAS,cAAA,CAAe,kBAAkB,OAAO,KAAA;AAEjE,EAAA,MAAM,EAAE,MAAA,EAAQ,QAAA,EAAS,GAAI,IAAA,CAAK,MAAA;AAElC,EAAA,IAAI,MAAA,CAAO,IAAA,KAAS,cAAA,CAAe,UAAA,EAAY,OAAO,KAAA;AAEtD,EAAA,IAAI,MAAA,CAAO,IAAA,KAAS,OAAA,EAAS,OAAO,KAAA;AAEpC,EAAA,IAAI,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,UAAA,EAAY,OAAO,KAAA;AAExD,EAAA,IAAI,QAAA,CAAS,IAAA,KAAS,SAAA,EAAW,OAAO,KAAA;AAExC,EAAA,OAAO,IAAA;AACT,CAAA;;;;"}
@@ -0,0 +1,6 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ type Options = readonly [];
3
+ type MessageIds = 'useIsBoundedLengthArray';
4
+ export declare const preferArrIsBoundedLengthArray: TSESLint.RuleModule<MessageIds, Options>;
5
+ export {};
6
+ //# sourceMappingURL=prefer-arr-is-bounded-length-array.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-is-bounded-length-array.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-arr-is-bounded-length-array.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,0BAA0B,CAAC;AAQxE,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,yBAAyB,CAAC;AAE5C,eAAO,MAAM,6BAA6B,EAAE,QAAQ,CAAC,UAAU,CAC7D,UAAU,EACV,OAAO,CAgIC,CAAC"}
@@ -0,0 +1,89 @@
1
+ import { parseLengthComparison } from './ast-utils.mjs';
2
+ import { getTsDataForgeImport, getNamedImports, buildImportFixes } from './import-utils.mjs';
3
+
4
+ const preferArrIsBoundedLengthArray = {
5
+ meta: {
6
+ type: "suggestion",
7
+ docs: {
8
+ description: "Replace `xs.length >= min && xs.length <= max` with `Arr.isBoundedLengthArray(xs, min, max)` from ts-data-forge."
9
+ },
10
+ fixable: "code",
11
+ schema: [],
12
+ messages: {
13
+ useIsBoundedLengthArray: "Replace `{{original}}` with `Arr.isBoundedLengthArray({{arrayName}}, {{min}}, {{max}})` from ts-data-forge."
14
+ }
15
+ },
16
+ create: (context) => {
17
+ const sourceCode = context.sourceCode;
18
+ const program = sourceCode.ast;
19
+ const tsDataForgeImport = getTsDataForgeImport(program);
20
+ const services = context.sourceCode.parserServices;
21
+ const mut_nodesToFix = [];
22
+ return {
23
+ LogicalExpression: (node) => {
24
+ if (node.operator !== "&&") return;
25
+ const left = parseLengthComparison(node.left, sourceCode);
26
+ const right = parseLengthComparison(node.right, sourceCode);
27
+ if (left === void 0 || right === void 0) return;
28
+ if (left.kind === right.kind) return;
29
+ if (sourceCode.getText(left.array) !== sourceCode.getText(right.array)) {
30
+ return;
31
+ }
32
+ const min = left.kind === "min" ? left : right;
33
+ const max = left.kind === "min" ? right : left;
34
+ const arrayExpression = min.array;
35
+ if (services?.program !== void 0 && services.program !== null) {
36
+ const checker = services.program.getTypeChecker();
37
+ const tsNode = services.esTreeNodeToTSNodeMap?.get(arrayExpression);
38
+ if (tsNode !== void 0) {
39
+ const type = checker.getTypeAtLocation(tsNode);
40
+ const isArrayType = checker.isArrayType(type) || checker.isTupleType(type);
41
+ if (!isArrayType) return;
42
+ } else {
43
+ return;
44
+ }
45
+ } else {
46
+ return;
47
+ }
48
+ mut_nodesToFix.push({
49
+ node,
50
+ arrayExpression,
51
+ minExpression: min.bound,
52
+ maxExpression: max.bound
53
+ });
54
+ },
55
+ "Program:exit": () => {
56
+ const namedImports = getNamedImports(tsDataForgeImport);
57
+ const hasArrImport = namedImports.includes("Arr");
58
+ for (const [
59
+ index,
60
+ { node, arrayExpression, minExpression, maxExpression }
61
+ ] of mut_nodesToFix.entries()) {
62
+ const arrayText = sourceCode.getText(arrayExpression);
63
+ const minText = sourceCode.getText(minExpression);
64
+ const maxText = sourceCode.getText(maxExpression);
65
+ const originalText = sourceCode.getText(node);
66
+ context.report({
67
+ node,
68
+ messageId: "useIsBoundedLengthArray",
69
+ data: {
70
+ original: originalText,
71
+ arrayName: arrayText,
72
+ min: minText,
73
+ max: maxText
74
+ },
75
+ fix: (fixer) => {
76
+ const replacement = `Arr.isBoundedLengthArray(${arrayText}, ${minText}, ${maxText})`;
77
+ const importFixes = index === 0 && !hasArrImport ? buildImportFixes(fixer, program, tsDataForgeImport, ["Arr"]) : [];
78
+ return [...importFixes, fixer.replaceText(node, replacement)];
79
+ }
80
+ });
81
+ }
82
+ }
83
+ };
84
+ },
85
+ defaultOptions: []
86
+ };
87
+
88
+ export { preferArrIsBoundedLengthArray };
89
+ //# sourceMappingURL=prefer-arr-is-bounded-length-array.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-is-bounded-length-array.mjs","sources":["../../src/rules/prefer-arr-is-bounded-length-array.mts"],"sourcesContent":["import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';\nimport { parseLengthComparison } from './ast-utils.mjs';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useIsBoundedLengthArray';\n\nexport const preferArrIsBoundedLengthArray: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `xs.length >= min && xs.length <= max` with `Arr.isBoundedLengthArray(xs, min, max)` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useIsBoundedLengthArray:\n 'Replace `{{original}}` with `Arr.isBoundedLengthArray({{arrayName}}, {{min}}, {{max}})` from ts-data-forge.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const services = context.sourceCode.parserServices;\n\n const mut_nodesToFix: {\n node: TSESTree.LogicalExpression;\n arrayExpression: TSESTree.Expression;\n minExpression: TSESTree.Expression;\n maxExpression: TSESTree.Expression;\n }[] = [];\n\n return {\n LogicalExpression: (node) => {\n // Only `a && b` can encode a `min && max` length range.\n if (node.operator !== '&&') return;\n\n const left = parseLengthComparison(node.left, sourceCode);\n\n const right = parseLengthComparison(node.right, sourceCode);\n\n if (left === undefined || right === undefined) return;\n\n // Need one lower bound and one upper bound on the same array.\n if (left.kind === right.kind) return;\n\n if (\n sourceCode.getText(left.array) !== sourceCode.getText(right.array)\n ) {\n return;\n }\n\n const min = left.kind === 'min' ? left : right;\n\n const max = left.kind === 'min' ? right : left;\n\n const arrayExpression = min.array;\n\n // Check if arrayExpression is actually an array type\n if (services?.program !== undefined && services.program !== null) {\n const checker = services.program.getTypeChecker();\n\n const tsNode = services.esTreeNodeToTSNodeMap?.get(arrayExpression);\n\n if (tsNode !== undefined) {\n const type = checker.getTypeAtLocation(tsNode);\n\n // Check if it's an array type or tuple type\n const isArrayType =\n checker.isArrayType(type) || checker.isTupleType(type);\n\n if (!isArrayType) return;\n } else {\n return;\n }\n } else {\n return;\n }\n\n mut_nodesToFix.push({\n node,\n arrayExpression,\n minExpression: min.bound,\n maxExpression: max.bound,\n });\n },\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const hasArrImport = namedImports.includes('Arr');\n\n for (const [\n index,\n { node, arrayExpression, minExpression, maxExpression },\n ] of mut_nodesToFix.entries()) {\n const arrayText = sourceCode.getText(arrayExpression);\n\n const minText = sourceCode.getText(minExpression);\n\n const maxText = sourceCode.getText(maxExpression);\n\n const originalText = sourceCode.getText(node);\n\n context.report({\n node,\n messageId: 'useIsBoundedLengthArray',\n data: {\n original: originalText,\n arrayName: arrayText,\n min: minText,\n max: maxText,\n },\n fix: (fixer) => {\n const replacement = `Arr.isBoundedLengthArray(${arrayText}, ${minText}, ${maxText})`;\n\n const importFixes =\n index === 0 && !hasArrImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])\n : [];\n\n return [...importFixes, fixer.replaceText(node, replacement)];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n} as const;\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ type Options = readonly [];
3
+ type MessageIds = 'useIsFixedLengthArray';
4
+ export declare const preferArrIsFixedLengthArray: TSESLint.RuleModule<MessageIds, Options>;
5
+ export {};
6
+ //# sourceMappingURL=prefer-arr-is-fixed-length-array.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-is-fixed-length-array.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-arr-is-fixed-length-array.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AASlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,uBAAuB,CAAC;AAE1C,eAAO,MAAM,2BAA2B,EAAE,QAAQ,CAAC,UAAU,CAC3D,UAAU,EACV,OAAO,CA0HC,CAAC"}