eslint-plugin-ts-data-forge 0.1.0 → 0.1.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 (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,87 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { isIntegerLiteralOrConstant } from './ast-utils.mjs';
3
+ import { getTsDataForgeImport, getNamedImports, buildImportFixes } from './import-utils.mjs';
4
+
5
+ const preferArrIsFixedLengthArray = {
6
+ meta: {
7
+ type: "suggestion",
8
+ docs: {
9
+ description: "Replace `xs.length === n` with `Arr.isFixedLengthArray(xs, n)` from ts-data-forge."
10
+ },
11
+ fixable: "code",
12
+ schema: [],
13
+ messages: {
14
+ useIsFixedLengthArray: "Replace `{{original}}` with `Arr.isFixedLengthArray({{arrayName}}, {{length}})` from ts-data-forge."
15
+ }
16
+ },
17
+ create: (context) => {
18
+ const sourceCode = context.sourceCode;
19
+ const program = sourceCode.ast;
20
+ const tsDataForgeImport = getTsDataForgeImport(program);
21
+ const services = context.sourceCode.parserServices;
22
+ const mut_nodesToFix = [];
23
+ return {
24
+ BinaryExpression: (node) => {
25
+ if (node.operator !== "===" && node.operator !== "!==") return;
26
+ const isNegated = node.operator === "!==";
27
+ const isLengthOnLeft = isLengthAccess(node.left);
28
+ const lengthSide = node[isLengthOnLeft ? "left" : "right"];
29
+ const valueSide = node[isLengthOnLeft ? "right" : "left"];
30
+ if (!isLengthAccess(lengthSide)) return;
31
+ if (!isIntegerLiteralOrConstant(valueSide, sourceCode)) return;
32
+ const arrayExpression = lengthSide.object;
33
+ if (services?.program !== void 0 && services.program !== null) {
34
+ const checker = services.program.getTypeChecker();
35
+ const tsNode = services.esTreeNodeToTSNodeMap?.get(arrayExpression);
36
+ if (tsNode !== void 0) {
37
+ const type = checker.getTypeAtLocation(tsNode);
38
+ const isArrayType = checker.isArrayType(type) || checker.isTupleType(type);
39
+ if (!isArrayType) return;
40
+ } else {
41
+ return;
42
+ }
43
+ } else {
44
+ return;
45
+ }
46
+ mut_nodesToFix.push({
47
+ node,
48
+ arrayExpression,
49
+ lengthExpression: valueSide,
50
+ isNegated
51
+ });
52
+ },
53
+ "Program:exit": () => {
54
+ const namedImports = getNamedImports(tsDataForgeImport);
55
+ const hasArrImport = namedImports.includes("Arr");
56
+ for (const [
57
+ index,
58
+ { node, arrayExpression, lengthExpression, isNegated }
59
+ ] of mut_nodesToFix.entries()) {
60
+ const arrayText = sourceCode.getText(arrayExpression);
61
+ const lengthText = sourceCode.getText(lengthExpression);
62
+ const originalText = sourceCode.getText(node);
63
+ context.report({
64
+ node,
65
+ messageId: "useIsFixedLengthArray",
66
+ data: {
67
+ original: originalText,
68
+ arrayName: arrayText,
69
+ length: lengthText
70
+ },
71
+ fix: (fixer) => {
72
+ const baseCall = `Arr.isFixedLengthArray(${arrayText}, ${lengthText})`;
73
+ const replacement = isNegated ? `!${baseCall}` : baseCall;
74
+ const importFixes = index === 0 && !hasArrImport ? buildImportFixes(fixer, program, tsDataForgeImport, ["Arr"]) : [];
75
+ return [...importFixes, fixer.replaceText(node, replacement)];
76
+ }
77
+ });
78
+ }
79
+ }
80
+ };
81
+ },
82
+ defaultOptions: []
83
+ };
84
+ const isLengthAccess = (node) => node.type === AST_NODE_TYPES.MemberExpression && node.property.type === AST_NODE_TYPES.Identifier && node.property.name === "length";
85
+
86
+ export { preferArrIsFixedLengthArray };
87
+ //# sourceMappingURL=prefer-arr-is-fixed-length-array.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-is-fixed-length-array.mjs","sources":["../../src/rules/prefer-arr-is-fixed-length-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 { isIntegerLiteralOrConstant } from './ast-utils.mjs';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useIsFixedLengthArray';\n\nexport const preferArrIsFixedLengthArray: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `xs.length === n` with `Arr.isFixedLengthArray(xs, n)` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useIsFixedLengthArray:\n 'Replace `{{original}}` with `Arr.isFixedLengthArray({{arrayName}}, {{length}})` 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.BinaryExpression;\n arrayExpression: TSESTree.Expression;\n lengthExpression: TSESTree.Expression;\n isNegated: boolean;\n }[] = [];\n\n return {\n BinaryExpression: (node) => {\n // Check for `xs.length === n` or `n === xs.length` or `xs.length !== n`\n if (node.operator !== '===' && node.operator !== '!==') return;\n\n const isNegated = node.operator === '!==';\n\n const isLengthOnLeft = isLengthAccess(node.left);\n\n const lengthSide = node[isLengthOnLeft ? 'left' : 'right'];\n\n const valueSide = node[isLengthOnLeft ? 'right' : 'left'];\n\n if (!isLengthAccess(lengthSide)) return;\n\n // Only match integer literals or const variables initialized with integer literals\n if (!isIntegerLiteralOrConstant(valueSide, sourceCode)) return;\n\n // lengthSide is MemberExpression accessing .length\n const arrayExpression = lengthSide.object;\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 lengthExpression: valueSide,\n isNegated,\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, lengthExpression, isNegated },\n ] of mut_nodesToFix.entries()) {\n const arrayText = sourceCode.getText(arrayExpression);\n\n const lengthText = sourceCode.getText(lengthExpression);\n\n const originalText = sourceCode.getText(node);\n\n context.report({\n node,\n messageId: 'useIsFixedLengthArray',\n data: {\n original: originalText,\n arrayName: arrayText,\n length: lengthText,\n },\n fix: (fixer) => {\n const baseCall = `Arr.isFixedLengthArray(${arrayText}, ${lengthText})`;\n\n const replacement = isNegated ? `!${baseCall}` : baseCall;\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\nconst isLengthAccess = (\n node: DeepReadonly<TSESTree.Expression>,\n): node is TSESTree.MemberExpression =>\n node.type === AST_NODE_TYPES.MemberExpression &&\n node.property.type === AST_NODE_TYPES.Identifier &&\n node.property.name === 'length';\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 = 'useIsMaxLengthArray';
4
+ export declare const preferArrIsMaxLengthArray: TSESLint.RuleModule<MessageIds, Options>;
5
+ export {};
6
+ //# sourceMappingURL=prefer-arr-is-max-length-array.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-is-max-length-array.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-arr-is-max-length-array.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,0BAA0B,CAAC;AAYxE,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,qBAAqB,CAAC;AAExC,eAAO,MAAM,yBAAyB,EAAE,QAAQ,CAAC,UAAU,CACzD,UAAU,EACV,OAAO,CA0HC,CAAC"}
@@ -0,0 +1,83 @@
1
+ import { isPartOfBoundedLengthCheck, isLengthAccess, isIntegerLiteralOrConstant } from './ast-utils.mjs';
2
+ import { getTsDataForgeImport, getNamedImports, buildImportFixes } from './import-utils.mjs';
3
+
4
+ const preferArrIsMaxLengthArray = {
5
+ meta: {
6
+ type: "suggestion",
7
+ docs: {
8
+ description: "Replace `xs.length <= n` with `Arr.isMaxLengthArray(xs, n)` from ts-data-forge."
9
+ },
10
+ fixable: "code",
11
+ schema: [],
12
+ messages: {
13
+ useIsMaxLengthArray: "Replace `{{original}}` with `Arr.isMaxLengthArray({{arrayName}}, {{length}})` 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
+ BinaryExpression: (node) => {
24
+ if (node.operator !== ">=" && node.operator !== "<=") return;
25
+ if (isPartOfBoundedLengthCheck(node, sourceCode)) return;
26
+ const isLengthOnLeft = node.operator === "<=";
27
+ const lengthSide = node[isLengthOnLeft ? "left" : "right"];
28
+ const valueSide = node[isLengthOnLeft ? "right" : "left"];
29
+ if (!isLengthAccess(lengthSide)) return;
30
+ if (!isIntegerLiteralOrConstant(valueSide, sourceCode)) return;
31
+ const arrayExpression = lengthSide.object;
32
+ if (services?.program !== void 0 && services.program !== null) {
33
+ const checker = services.program.getTypeChecker();
34
+ const tsNode = services.esTreeNodeToTSNodeMap?.get(arrayExpression);
35
+ if (tsNode !== void 0) {
36
+ const type = checker.getTypeAtLocation(tsNode);
37
+ const isArrayType = checker.isArrayType(type) || checker.isTupleType(type);
38
+ if (!isArrayType) return;
39
+ } else {
40
+ return;
41
+ }
42
+ } else {
43
+ return;
44
+ }
45
+ mut_nodesToFix.push({
46
+ node,
47
+ arrayExpression,
48
+ lengthExpression: valueSide
49
+ });
50
+ },
51
+ "Program:exit": () => {
52
+ const namedImports = getNamedImports(tsDataForgeImport);
53
+ const hasArrImport = namedImports.includes("Arr");
54
+ for (const [
55
+ index,
56
+ { node, arrayExpression, lengthExpression }
57
+ ] of mut_nodesToFix.entries()) {
58
+ const arrayText = sourceCode.getText(arrayExpression);
59
+ const lengthText = sourceCode.getText(lengthExpression);
60
+ const originalText = sourceCode.getText(node);
61
+ context.report({
62
+ node,
63
+ messageId: "useIsMaxLengthArray",
64
+ data: {
65
+ original: originalText,
66
+ arrayName: arrayText,
67
+ length: lengthText
68
+ },
69
+ fix: (fixer) => {
70
+ const replacement = `Arr.isMaxLengthArray(${arrayText}, ${lengthText})`;
71
+ const importFixes = index === 0 && !hasArrImport ? buildImportFixes(fixer, program, tsDataForgeImport, ["Arr"]) : [];
72
+ return [...importFixes, fixer.replaceText(node, replacement)];
73
+ }
74
+ });
75
+ }
76
+ }
77
+ };
78
+ },
79
+ defaultOptions: []
80
+ };
81
+
82
+ export { preferArrIsMaxLengthArray };
83
+ //# sourceMappingURL=prefer-arr-is-max-length-array.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-is-max-length-array.mjs","sources":["../../src/rules/prefer-arr-is-max-length-array.mts"],"sourcesContent":["import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';\nimport {\n isIntegerLiteralOrConstant,\n isLengthAccess,\n isPartOfBoundedLengthCheck,\n} from './ast-utils.mjs';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useIsMaxLengthArray';\n\nexport const preferArrIsMaxLengthArray: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `xs.length <= n` with `Arr.isMaxLengthArray(xs, n)` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useIsMaxLengthArray:\n 'Replace `{{original}}` with `Arr.isMaxLengthArray({{arrayName}}, {{length}})` 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.BinaryExpression;\n arrayExpression: TSESTree.Expression;\n lengthExpression: TSESTree.Expression;\n }[] = [];\n\n return {\n BinaryExpression: (node) => {\n // Check for `xs.length <= n` or `n >= xs.length`\n if (node.operator !== '>=' && node.operator !== '<=') return;\n\n // Defer to `prefer-arr-is-bounded-length-array` when this comparison is\n // the upper bound of a `min && max` pair on the same array.\n if (isPartOfBoundedLengthCheck(node, sourceCode)) return;\n\n // xs.length <= n or n >= xs.length\n const isLengthOnLeft = node.operator === '<=';\n\n const lengthSide = node[isLengthOnLeft ? 'left' : 'right'];\n\n const valueSide = node[isLengthOnLeft ? 'right' : 'left'];\n\n // Check if lengthSide is accessing .length\n if (!isLengthAccess(lengthSide)) return;\n\n // Only match integer literals or const variables initialized with integer literals\n if (!isIntegerLiteralOrConstant(valueSide, sourceCode)) return;\n\n // lengthSide is MemberExpression accessing .length\n const arrayExpression = lengthSide.object;\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 lengthExpression: valueSide,\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, lengthExpression },\n ] of mut_nodesToFix.entries()) {\n const arrayText = sourceCode.getText(arrayExpression);\n\n const lengthText = sourceCode.getText(lengthExpression);\n\n const originalText = sourceCode.getText(node);\n\n context.report({\n node,\n messageId: 'useIsMaxLengthArray',\n data: {\n original: originalText,\n arrayName: arrayText,\n length: lengthText,\n },\n fix: (fixer) => {\n const replacement = `Arr.isMaxLengthArray(${arrayText}, ${lengthText})`;\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 = 'useIsMinLengthArray';
4
+ export declare const preferArrIsMinLengthArray: TSESLint.RuleModule<MessageIds, Options>;
5
+ export {};
6
+ //# sourceMappingURL=prefer-arr-is-min-length-array.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-is-min-length-array.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-arr-is-min-length-array.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,0BAA0B,CAAC;AAYxE,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,qBAAqB,CAAC;AAExC,eAAO,MAAM,yBAAyB,EAAE,QAAQ,CAAC,UAAU,CACzD,UAAU,EACV,OAAO,CA0HC,CAAC"}
@@ -0,0 +1,83 @@
1
+ import { isPartOfBoundedLengthCheck, isLengthAccess, isIntegerLiteralOrConstant } from './ast-utils.mjs';
2
+ import { getTsDataForgeImport, getNamedImports, buildImportFixes } from './import-utils.mjs';
3
+
4
+ const preferArrIsMinLengthArray = {
5
+ meta: {
6
+ type: "suggestion",
7
+ docs: {
8
+ description: "Replace `xs.length >= n` with `Arr.isMinLengthArray(xs, n)` from ts-data-forge."
9
+ },
10
+ fixable: "code",
11
+ schema: [],
12
+ messages: {
13
+ useIsMinLengthArray: "Replace `{{original}}` with `Arr.isMinLengthArray({{arrayName}}, {{length}})` 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
+ BinaryExpression: (node) => {
24
+ if (node.operator !== ">=" && node.operator !== "<=") return;
25
+ if (isPartOfBoundedLengthCheck(node, sourceCode)) return;
26
+ const isLengthOnLeft = node.operator === ">=";
27
+ const lengthSide = node[isLengthOnLeft ? "left" : "right"];
28
+ const valueSide = node[isLengthOnLeft ? "right" : "left"];
29
+ if (!isLengthAccess(lengthSide)) return;
30
+ if (!isIntegerLiteralOrConstant(valueSide, sourceCode)) return;
31
+ const arrayExpression = lengthSide.object;
32
+ if (services?.program !== void 0 && services.program !== null) {
33
+ const checker = services.program.getTypeChecker();
34
+ const tsNode = services.esTreeNodeToTSNodeMap?.get(arrayExpression);
35
+ if (tsNode !== void 0) {
36
+ const type = checker.getTypeAtLocation(tsNode);
37
+ const isArrayType = checker.isArrayType(type) || checker.isTupleType(type);
38
+ if (!isArrayType) return;
39
+ } else {
40
+ return;
41
+ }
42
+ } else {
43
+ return;
44
+ }
45
+ mut_nodesToFix.push({
46
+ node,
47
+ arrayExpression,
48
+ lengthExpression: valueSide
49
+ });
50
+ },
51
+ "Program:exit": () => {
52
+ const namedImports = getNamedImports(tsDataForgeImport);
53
+ const hasArrImport = namedImports.includes("Arr");
54
+ for (const [
55
+ index,
56
+ { node, arrayExpression, lengthExpression }
57
+ ] of mut_nodesToFix.entries()) {
58
+ const arrayText = sourceCode.getText(arrayExpression);
59
+ const lengthText = sourceCode.getText(lengthExpression);
60
+ const originalText = sourceCode.getText(node);
61
+ context.report({
62
+ node,
63
+ messageId: "useIsMinLengthArray",
64
+ data: {
65
+ original: originalText,
66
+ arrayName: arrayText,
67
+ length: lengthText
68
+ },
69
+ fix: (fixer) => {
70
+ const replacement = `Arr.isMinLengthArray(${arrayText}, ${lengthText})`;
71
+ const importFixes = index === 0 && !hasArrImport ? buildImportFixes(fixer, program, tsDataForgeImport, ["Arr"]) : [];
72
+ return [...importFixes, fixer.replaceText(node, replacement)];
73
+ }
74
+ });
75
+ }
76
+ }
77
+ };
78
+ },
79
+ defaultOptions: []
80
+ };
81
+
82
+ export { preferArrIsMinLengthArray };
83
+ //# sourceMappingURL=prefer-arr-is-min-length-array.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-is-min-length-array.mjs","sources":["../../src/rules/prefer-arr-is-min-length-array.mts"],"sourcesContent":["import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';\nimport {\n isIntegerLiteralOrConstant,\n isLengthAccess,\n isPartOfBoundedLengthCheck,\n} from './ast-utils.mjs';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useIsMinLengthArray';\n\nexport const preferArrIsMinLengthArray: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `xs.length >= n` with `Arr.isMinLengthArray(xs, n)` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useIsMinLengthArray:\n 'Replace `{{original}}` with `Arr.isMinLengthArray({{arrayName}}, {{length}})` 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.BinaryExpression;\n arrayExpression: TSESTree.Expression;\n lengthExpression: TSESTree.Expression;\n }[] = [];\n\n return {\n BinaryExpression: (node) => {\n // Check for `xs.length >= n` or `n <= xs.length`\n if (node.operator !== '>=' && node.operator !== '<=') return;\n\n // Defer to `prefer-arr-is-bounded-length-array` when this comparison is\n // the lower bound of a `min && max` pair on the same array.\n if (isPartOfBoundedLengthCheck(node, sourceCode)) return;\n\n // xs.length >= n or n <= xs.length\n const isLengthOnLeft = node.operator === '>=';\n\n const lengthSide = node[isLengthOnLeft ? 'left' : 'right'];\n\n const valueSide = node[isLengthOnLeft ? 'right' : 'left'];\n\n // Check if lengthSide is accessing .length\n if (!isLengthAccess(lengthSide)) return;\n\n // Only match integer literals or const variables initialized with integer literals\n if (!isIntegerLiteralOrConstant(valueSide, sourceCode)) return;\n\n // lengthSide is MemberExpression accessing .length\n const arrayExpression = lengthSide.object;\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 lengthExpression: valueSide,\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, lengthExpression },\n ] of mut_nodesToFix.entries()) {\n const arrayText = sourceCode.getText(arrayExpression);\n\n const lengthText = sourceCode.getText(lengthExpression);\n\n const originalText = sourceCode.getText(node);\n\n context.report({\n node,\n messageId: 'useIsMinLengthArray',\n data: {\n original: originalText,\n arrayName: arrayText,\n length: lengthText,\n },\n fix: (fixer) => {\n const replacement = `Arr.isMinLengthArray(${arrayText}, ${lengthText})`;\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 = 'useIsNonEmpty';
4
+ export declare const preferArrIsNonEmpty: TSESLint.RuleModule<MessageIds, Options>;
5
+ export {};
6
+ //# sourceMappingURL=prefer-arr-is-non-empty.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-is-non-empty.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-arr-is-non-empty.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAOlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,eAAe,CAAC;AAElC,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CAmH/D,CAAC"}
@@ -0,0 +1,80 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { getTsDataForgeImport, getNamedImports, buildImportFixes } from './import-utils.mjs';
3
+
4
+ const preferArrIsNonEmpty = {
5
+ meta: {
6
+ type: "suggestion",
7
+ docs: {
8
+ description: "Replace `xs.length > 0` with `Arr.isNonEmpty(xs)` from ts-data-forge."
9
+ },
10
+ fixable: "code",
11
+ schema: [],
12
+ messages: {
13
+ useIsNonEmpty: "Replace `{{original}}` with `Arr.isNonEmpty({{arrayName}})` 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
+ BinaryExpression: (node) => {
24
+ if (node.operator !== ">" && node.operator !== "<") return;
25
+ const isLengthOnLeft = node.operator === ">";
26
+ const lengthSide = node[isLengthOnLeft ? "left" : "right"];
27
+ const numberSide = node[isLengthOnLeft ? "right" : "left"];
28
+ if (lengthSide.type !== AST_NODE_TYPES.MemberExpression || lengthSide.property.type !== AST_NODE_TYPES.Identifier || lengthSide.property.name !== "length" || numberSide.type !== AST_NODE_TYPES.Literal || numberSide.value !== 0) {
29
+ return;
30
+ }
31
+ const arrayExpression = lengthSide.object;
32
+ if (services?.program !== void 0 && services.program !== null) {
33
+ const checker = services.program.getTypeChecker();
34
+ const tsNode = services.esTreeNodeToTSNodeMap?.get(arrayExpression);
35
+ if (tsNode !== void 0) {
36
+ const type = checker.getTypeAtLocation(tsNode);
37
+ const isArrayType = checker.isArrayType(type) || checker.isTupleType(type);
38
+ if (!isArrayType) return;
39
+ } else {
40
+ return;
41
+ }
42
+ } else {
43
+ return;
44
+ }
45
+ mut_nodesToFix.push({
46
+ node,
47
+ arrayExpression
48
+ });
49
+ },
50
+ "Program:exit": () => {
51
+ const namedImports = getNamedImports(tsDataForgeImport);
52
+ const hasArrImport = namedImports.includes("Arr");
53
+ for (const [
54
+ index,
55
+ { node, arrayExpression }
56
+ ] of mut_nodesToFix.entries()) {
57
+ const arrayText = sourceCode.getText(arrayExpression);
58
+ const originalText = sourceCode.getText(node);
59
+ context.report({
60
+ node,
61
+ messageId: "useIsNonEmpty",
62
+ data: {
63
+ original: originalText,
64
+ arrayName: arrayText
65
+ },
66
+ fix: (fixer) => {
67
+ const replacement = `Arr.isNonEmpty(${arrayText})`;
68
+ const importFixes = index === 0 && !hasArrImport ? buildImportFixes(fixer, program, tsDataForgeImport, ["Arr"]) : [];
69
+ return [...importFixes, fixer.replaceText(node, replacement)];
70
+ }
71
+ });
72
+ }
73
+ }
74
+ };
75
+ },
76
+ defaultOptions: []
77
+ };
78
+
79
+ export { preferArrIsNonEmpty };
80
+ //# sourceMappingURL=prefer-arr-is-non-empty.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-is-non-empty.mjs","sources":["../../src/rules/prefer-arr-is-non-empty.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useIsNonEmpty';\n\nexport const preferArrIsNonEmpty: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `xs.length > 0` with `Arr.isNonEmpty(xs)` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useIsNonEmpty:\n 'Replace `{{original}}` with `Arr.isNonEmpty({{arrayName}})` 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.BinaryExpression;\n arrayExpression: TSESTree.Expression;\n }[] = [];\n\n return {\n BinaryExpression: (node) => {\n // Check for `xs.length > 0` or `0 < xs.length`\n if (node.operator !== '>' && node.operator !== '<') return;\n\n const isLengthOnLeft = node.operator === '>';\n\n const lengthSide = node[isLengthOnLeft ? 'left' : 'right'];\n\n const numberSide = node[isLengthOnLeft ? 'right' : 'left'];\n\n // Check if one side is `.length` and the other is 0\n if (\n lengthSide.type !== AST_NODE_TYPES.MemberExpression ||\n lengthSide.property.type !== AST_NODE_TYPES.Identifier ||\n lengthSide.property.name !== 'length' ||\n numberSide.type !== AST_NODE_TYPES.Literal ||\n numberSide.value !== 0\n ) {\n return;\n }\n\n const arrayExpression = lengthSide.object;\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 });\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 },\n ] of mut_nodesToFix.entries()) {\n const arrayText = sourceCode.getText(arrayExpression);\n\n const originalText = sourceCode.getText(node);\n\n context.report({\n node,\n messageId: 'useIsNonEmpty',\n data: {\n original: originalText,\n arrayName: arrayText,\n },\n fix: (fixer) => {\n const replacement = `Arr.isNonEmpty(${arrayText})`;\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 = 'useArrSum' | 'useArrSumBy';
4
+ export declare const preferArrSum: TSESLint.RuleModule<MessageIds, Options>;
5
+ export {};
6
+ //# sourceMappingURL=prefer-arr-sum.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-sum.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-arr-sum.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AASlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,WAAW,GAAG,aAAa,CAAC;AAE9C,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CA2PxD,CAAC"}
@@ -0,0 +1,136 @@
1
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { Arr, pipe } from 'ts-data-forge';
3
+ import { getTsDataForgeImport, getNamedImports, buildImportFixes } from './import-utils.mjs';
4
+
5
+ const preferArrSum = {
6
+ meta: {
7
+ type: "suggestion",
8
+ docs: {
9
+ description: "Replace `xs.reduce((a, b) => a + b, 0)` with `Arr.sum(xs)` or `Arr.sumBy(xs, fn)` from ts-data-forge."
10
+ },
11
+ fixable: "code",
12
+ schema: [],
13
+ messages: {
14
+ useArrSum: "Replace with `Arr.sum({{arrayName}})` from ts-data-forge.",
15
+ useArrSumBy: "Replace with `Arr.sumBy({{arrayName}}, {{mapper}})` from ts-data-forge."
16
+ }
17
+ },
18
+ create: (context) => {
19
+ const sourceCode = context.sourceCode;
20
+ const program = sourceCode.ast;
21
+ const tsDataForgeImport = getTsDataForgeImport(program);
22
+ const services = context.sourceCode.parserServices;
23
+ const mut_nodesToFix = [];
24
+ return {
25
+ CallExpression: (node) => {
26
+ if (node.callee.type !== AST_NODE_TYPES.MemberExpression || node.callee.property.type !== AST_NODE_TYPES.Identifier || node.callee.property.name !== "reduce") {
27
+ return;
28
+ }
29
+ const arrayExpression = node.callee.object;
30
+ if (!Arr.isFixedLengthTuple(node.arguments, 2)) return;
31
+ const reducer = node.arguments[0];
32
+ const initialValue = node.arguments[1];
33
+ if (initialValue.type !== AST_NODE_TYPES.Literal || initialValue.value !== 0) {
34
+ return;
35
+ }
36
+ if (reducer.type !== AST_NODE_TYPES.ArrowFunctionExpression || !Arr.isFixedLengthTuple(reducer.params, 2)) {
37
+ return;
38
+ }
39
+ const [param1, param2] = reducer.params;
40
+ if (param1.type !== AST_NODE_TYPES.Identifier || param2.type !== AST_NODE_TYPES.Identifier) {
41
+ return;
42
+ }
43
+ const body = reducer.body;
44
+ const checker = services?.program?.getTypeChecker();
45
+ if (checker === void 0) return;
46
+ if (body.type === AST_NODE_TYPES.BinaryExpression && body.operator === "+" && body.left.type === AST_NODE_TYPES.Identifier && body.left.name === param1.name && body.right.type === AST_NODE_TYPES.Identifier && body.right.name === param2.name) {
47
+ const type = pipe(
48
+ services?.esTreeNodeToTSNodeMap?.get(arrayExpression)
49
+ ).mapNullable((tsNode) => checker.getTypeAtLocation(tsNode)).value;
50
+ if (type !== void 0 && // Check if it's an array type or tuple type
51
+ (checker.isArrayType(type) || checker.isTupleType(type))) {
52
+ const typeArguments = checker.getTypeArguments(
53
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
54
+ type
55
+ );
56
+ if (Arr.isNonEmpty(typeArguments)) {
57
+ const elementType = typeArguments[0];
58
+ const numberType = checker.getNumberType();
59
+ if (checker.isTypeAssignableTo(elementType, numberType)) {
60
+ mut_nodesToFix.push({
61
+ node,
62
+ arrayExpression,
63
+ messageId: "useArrSum"
64
+ });
65
+ }
66
+ }
67
+ }
68
+ return;
69
+ }
70
+ if (body.type === AST_NODE_TYPES.BinaryExpression && body.operator === "+" && body.left.type === AST_NODE_TYPES.MemberExpression && body.right.type === AST_NODE_TYPES.MemberExpression) {
71
+ const leftObj = body.left.object;
72
+ const rightObj = body.right.object;
73
+ if (leftObj.type !== AST_NODE_TYPES.Identifier || leftObj.name !== param1.name || rightObj.type !== AST_NODE_TYPES.Identifier || rightObj.name !== param2.name) {
74
+ return;
75
+ }
76
+ const leftProp = body.left.property;
77
+ const rightProp = body.right.property;
78
+ let mut_propName;
79
+ if (leftProp.type === AST_NODE_TYPES.Identifier && !body.left.computed && rightProp.type === AST_NODE_TYPES.Identifier && !body.right.computed && leftProp.name === rightProp.name) {
80
+ mut_propName = leftProp.name;
81
+ } else if (leftProp.type === AST_NODE_TYPES.Literal && body.left.computed && rightProp.type === AST_NODE_TYPES.Literal && body.right.computed && leftProp.value === rightProp.value && typeof leftProp.value === "string") {
82
+ mut_propName = leftProp.value;
83
+ }
84
+ if (mut_propName === void 0) return;
85
+ if (services?.program !== void 0 && services.program !== null) {
86
+ const tsLeftProp = services.esTreeNodeToTSNodeMap?.get(body.left);
87
+ const tsRightProp = services.esTreeNodeToTSNodeMap?.get(body.right);
88
+ if (tsLeftProp !== void 0 && tsRightProp !== void 0) {
89
+ const leftPropType = checker.getTypeAtLocation(tsLeftProp);
90
+ const rightPropType = checker.getTypeAtLocation(tsRightProp);
91
+ const numberType = checker.getNumberType();
92
+ if (!checker.isTypeAssignableTo(leftPropType, numberType) || !checker.isTypeAssignableTo(rightPropType, numberType)) {
93
+ return;
94
+ }
95
+ }
96
+ }
97
+ const mapperParam = param1.name;
98
+ const mapper = body.left.computed ? `${mapperParam} => ${mapperParam}['${mut_propName}']` : `${mapperParam} => ${mapperParam}.${mut_propName}`;
99
+ mut_nodesToFix.push({
100
+ node,
101
+ arrayExpression,
102
+ messageId: "useArrSumBy",
103
+ mapper
104
+ });
105
+ }
106
+ },
107
+ "Program:exit": () => {
108
+ const namedImports = getNamedImports(tsDataForgeImport);
109
+ const hasArrImport = namedImports.includes("Arr");
110
+ for (const [index, nodeInfo] of mut_nodesToFix.entries()) {
111
+ const arrayText = sourceCode.getText(nodeInfo.arrayExpression);
112
+ context.report({
113
+ node: nodeInfo.node,
114
+ messageId: nodeInfo.messageId,
115
+ data: {
116
+ arrayName: arrayText,
117
+ mapper: nodeInfo.mapper ?? ""
118
+ },
119
+ fix: (fixer) => {
120
+ const replacement = nodeInfo.messageId === "useArrSum" ? `Arr.sum(${arrayText})` : `Arr.sumBy(${arrayText}, ${nodeInfo.mapper})`;
121
+ const importFixes = index === 0 && !hasArrImport ? buildImportFixes(fixer, program, tsDataForgeImport, ["Arr"]) : [];
122
+ return [
123
+ ...importFixes,
124
+ fixer.replaceText(nodeInfo.node, replacement)
125
+ ];
126
+ }
127
+ });
128
+ }
129
+ }
130
+ };
131
+ },
132
+ defaultOptions: []
133
+ };
134
+
135
+ export { preferArrSum };
136
+ //# sourceMappingURL=prefer-arr-sum.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-sum.mjs","sources":["../../src/rules/prefer-arr-sum.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { Arr, pipe } from 'ts-data-forge';\nimport { type TypeReference } from 'typescript';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useArrSum' | 'useArrSumBy';\n\nexport const preferArrSum: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `xs.reduce((a, b) => a + b, 0)` with `Arr.sum(xs)` or `Arr.sumBy(xs, fn)` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useArrSum: 'Replace with `Arr.sum({{arrayName}})` from ts-data-forge.',\n useArrSumBy:\n 'Replace with `Arr.sumBy({{arrayName}}, {{mapper}})` 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.CallExpression;\n arrayExpression: TSESTree.Expression;\n messageId: 'useArrSum' | 'useArrSumBy';\n mapper?: string;\n }[] = [];\n\n return {\n CallExpression: (node) => {\n // Check for xs.reduce(...)\n if (\n node.callee.type !== AST_NODE_TYPES.MemberExpression ||\n node.callee.property.type !== AST_NODE_TYPES.Identifier ||\n node.callee.property.name !== 'reduce'\n ) {\n return;\n }\n\n const arrayExpression = node.callee.object;\n\n // Check if we have 2 arguments: reducer function and initial value 0\n if (!Arr.isFixedLengthTuple(node.arguments, 2)) return;\n\n const reducer = node.arguments[0];\n\n const initialValue = node.arguments[1];\n\n // Check initial value is 0\n if (\n initialValue.type !== AST_NODE_TYPES.Literal ||\n initialValue.value !== 0\n ) {\n return;\n }\n\n // Check if reducer is an arrow function with 2 parameters\n if (\n reducer.type !== AST_NODE_TYPES.ArrowFunctionExpression ||\n !Arr.isFixedLengthTuple(reducer.params, 2)\n ) {\n return;\n }\n\n const [param1, param2] = reducer.params;\n\n if (\n param1.type !== AST_NODE_TYPES.Identifier ||\n param2.type !== AST_NODE_TYPES.Identifier\n ) {\n return;\n }\n\n const body = reducer.body;\n\n const checker = services?.program?.getTypeChecker();\n\n if (checker === undefined) return;\n\n // Case 1: (a, b) => a + b\n if (\n body.type === AST_NODE_TYPES.BinaryExpression &&\n body.operator === '+' &&\n body.left.type === AST_NODE_TYPES.Identifier &&\n body.left.name === param1.name &&\n body.right.type === AST_NODE_TYPES.Identifier &&\n body.right.name === param2.name\n ) {\n // Check if arrayExpression type is number[] or compatible\n\n const type = pipe(\n services?.esTreeNodeToTSNodeMap?.get(arrayExpression),\n ).mapNullable((tsNode) => checker.getTypeAtLocation(tsNode)).value;\n\n if (\n type !== undefined && // Check if it's an array type or tuple type\n (checker.isArrayType(type) || checker.isTupleType(type))\n ) {\n // Get type arguments using the official API\n // TypeReference has typeArguments that we can access\n const typeArguments = checker.getTypeArguments(\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n type as TypeReference,\n );\n\n if (Arr.isNonEmpty(typeArguments)) {\n const elementType = typeArguments[0];\n\n const numberType = checker.getNumberType();\n\n // Check if element type is assignable to number\n if (checker.isTypeAssignableTo(elementType, numberType)) {\n mut_nodesToFix.push({\n node,\n arrayExpression,\n messageId: 'useArrSum',\n });\n }\n }\n }\n\n return;\n }\n\n // Case 2: (a, b) => a.prop + b.prop or a['prop'] + b['prop']\n if (\n body.type === AST_NODE_TYPES.BinaryExpression &&\n body.operator === '+' &&\n body.left.type === AST_NODE_TYPES.MemberExpression &&\n body.right.type === AST_NODE_TYPES.MemberExpression\n ) {\n const leftObj = body.left.object;\n\n const rightObj = body.right.object;\n\n if (\n leftObj.type !== AST_NODE_TYPES.Identifier ||\n leftObj.name !== param1.name ||\n rightObj.type !== AST_NODE_TYPES.Identifier ||\n rightObj.name !== param2.name\n ) {\n return;\n }\n\n // Check if both access the same property\n const leftProp = body.left.property;\n\n const rightProp = body.right.property;\n\n let mut_propName: string | undefined;\n\n if (\n leftProp.type === AST_NODE_TYPES.Identifier &&\n !body.left.computed &&\n rightProp.type === AST_NODE_TYPES.Identifier &&\n !body.right.computed &&\n leftProp.name === rightProp.name\n ) {\n mut_propName = leftProp.name;\n } else if (\n leftProp.type === AST_NODE_TYPES.Literal &&\n body.left.computed &&\n rightProp.type === AST_NODE_TYPES.Literal &&\n body.right.computed &&\n leftProp.value === rightProp.value &&\n typeof leftProp.value === 'string'\n ) {\n mut_propName = leftProp.value;\n }\n\n if (mut_propName === undefined) return;\n\n // Check if property type is number\n if (services?.program !== undefined && services.program !== null) {\n const tsLeftProp = services.esTreeNodeToTSNodeMap?.get(body.left);\n\n const tsRightProp = services.esTreeNodeToTSNodeMap?.get(body.right);\n\n if (tsLeftProp !== undefined && tsRightProp !== undefined) {\n const leftPropType = checker.getTypeAtLocation(tsLeftProp);\n\n const rightPropType = checker.getTypeAtLocation(tsRightProp);\n\n const numberType = checker.getNumberType();\n\n // Check if property type is assignable to number\n if (\n !checker.isTypeAssignableTo(leftPropType, numberType) ||\n !checker.isTypeAssignableTo(rightPropType, numberType)\n ) {\n return;\n }\n }\n }\n\n // Generate mapper function\n const mapperParam = param1.name;\n\n const mapper = body.left.computed\n ? `${mapperParam} => ${mapperParam}['${mut_propName}']`\n : `${mapperParam} => ${mapperParam}.${mut_propName}`;\n\n mut_nodesToFix.push({\n node,\n arrayExpression,\n messageId: 'useArrSumBy',\n mapper,\n });\n }\n },\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const hasArrImport = namedImports.includes('Arr');\n\n for (const [index, nodeInfo] of mut_nodesToFix.entries()) {\n const arrayText = sourceCode.getText(nodeInfo.arrayExpression);\n\n context.report({\n node: nodeInfo.node,\n messageId: nodeInfo.messageId,\n data: {\n arrayName: arrayText,\n mapper: nodeInfo.mapper ?? '',\n },\n fix: (fixer) => {\n const replacement =\n nodeInfo.messageId === 'useArrSum'\n ? `Arr.sum(${arrayText})`\n : `Arr.sumBy(${arrayText}, ${nodeInfo.mapper})`;\n\n const importFixes =\n index === 0 && !hasArrImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])\n : [];\n\n return [\n ...importFixes,\n fixer.replaceText(nodeInfo.node, replacement),\n ];\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 = 'useBrandedNumberCastFunction';
4
+ export declare const preferAsInt: TSESLint.RuleModule<MessageIds, Options>;
5
+ export {};
6
+ //# sourceMappingURL=prefer-as-int.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-as-int.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-as-int.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AASlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,8BAA8B,CAAC;AAEjD,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CA8FvD,CAAC"}