eslint-plugin-ts-data-forge 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +1 -0
  2. package/dist/index.mjs +2 -2
  3. package/dist/index.mjs.map +1 -1
  4. package/dist/plugin.d.mts +2 -0
  5. package/dist/plugin.d.mts.map +1 -1
  6. package/dist/plugin.mjs +45 -41
  7. package/dist/plugin.mjs.map +1 -1
  8. package/dist/rule-types.d.mts +15 -0
  9. package/dist/rule-types.d.mts.map +1 -1
  10. package/dist/rule-types.mjs +6 -2
  11. package/dist/rule-types.mjs.map +1 -1
  12. package/dist/rules/ast-utils.mjs +107 -50
  13. package/dist/rules/ast-utils.mjs.map +1 -1
  14. package/dist/rules/branded-number-types.mjs +41 -37
  15. package/dist/rules/branded-number-types.mjs.map +1 -1
  16. package/dist/rules/constants.mjs +2 -4
  17. package/dist/rules/constants.mjs.map +1 -1
  18. package/dist/rules/import-utils.mjs +80 -56
  19. package/dist/rules/import-utils.mjs.map +1 -1
  20. package/dist/rules/index.mjs +2 -2
  21. package/dist/rules/index.mjs.map +1 -1
  22. package/dist/rules/no-side-effect-import.mjs +46 -34
  23. package/dist/rules/no-side-effect-import.mjs.map +1 -1
  24. package/dist/rules/no-unnecessary-type-guard.mjs +376 -265
  25. package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
  26. package/dist/rules/prefer-arr-is-array.mjs +64 -52
  27. package/dist/rules/prefer-arr-is-array.mjs.map +1 -1
  28. package/dist/rules/prefer-arr-is-bounded-length-array.mjs +91 -85
  29. package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
  30. package/dist/rules/prefer-arr-is-fixed-length-array.mjs +92 -83
  31. package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
  32. package/dist/rules/prefer-arr-is-max-length-array.mjs +92 -80
  33. package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
  34. package/dist/rules/prefer-arr-is-min-length-array.mjs +92 -80
  35. package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -1
  36. package/dist/rules/prefer-arr-is-non-empty.mjs +85 -77
  37. package/dist/rules/prefer-arr-is-non-empty.mjs.map +1 -1
  38. package/dist/rules/prefer-arr-scan.d.mts +96 -0
  39. package/dist/rules/prefer-arr-scan.d.mts.map +1 -0
  40. package/dist/rules/prefer-arr-scan.mjs +482 -0
  41. package/dist/rules/prefer-arr-scan.mjs.map +1 -0
  42. package/dist/rules/prefer-arr-sum.mjs +176 -132
  43. package/dist/rules/prefer-arr-sum.mjs.map +1 -1
  44. package/dist/rules/prefer-as-int.mjs +77 -71
  45. package/dist/rules/prefer-as-int.mjs.map +1 -1
  46. package/dist/rules/prefer-canonical-array-slicing.mjs +503 -343
  47. package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
  48. package/dist/rules/prefer-canonical-length-cast.mjs +217 -144
  49. package/dist/rules/prefer-canonical-length-cast.mjs.map +1 -1
  50. package/dist/rules/prefer-canonical-length-guard.mjs +160 -109
  51. package/dist/rules/prefer-canonical-length-guard.mjs.map +1 -1
  52. package/dist/rules/prefer-comparison-over-nullish-guard.mjs +89 -71
  53. package/dist/rules/prefer-comparison-over-nullish-guard.mjs.map +1 -1
  54. package/dist/rules/prefer-is-non-null-object.mjs +91 -72
  55. package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
  56. package/dist/rules/prefer-is-record-and-has-key.mjs +148 -105
  57. package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
  58. package/dist/rules/prefer-num-safe-parse-float.mjs +101 -77
  59. package/dist/rules/prefer-num-safe-parse-float.mjs.map +1 -1
  60. package/dist/rules/prefer-num-safe-parse-int.mjs +103 -81
  61. package/dist/rules/prefer-num-safe-parse-int.mjs.map +1 -1
  62. package/dist/rules/prefer-obj-over-entries-round-trip.mjs +299 -194
  63. package/dist/rules/prefer-obj-over-entries-round-trip.mjs.map +1 -1
  64. package/dist/rules/prefer-range-for-loop.mjs +121 -99
  65. package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
  66. package/dist/rules/rules.d.mts +1 -0
  67. package/dist/rules/rules.d.mts.map +1 -1
  68. package/dist/rules/rules.mjs +19 -20
  69. package/dist/rules/rules.mjs.map +1 -1
  70. package/dist/types.mjs +2 -2
  71. package/dist/types.mjs.map +1 -1
  72. package/package.json +6 -7
  73. package/src/plugin.mts +1 -0
  74. package/src/rule-types.mts +16 -0
  75. package/src/rules/prefer-arr-scan.mts +693 -0
  76. package/src/rules/prefer-arr-scan.test.mts +295 -0
  77. package/src/rules/rules.mts +2 -0
@@ -1,83 +1,95 @@
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."
1
+ import { isIntegerLiteralOrConstant, isLengthAccess, isPartOfBoundedLengthCheck, } from './ast-utils.mjs';
2
+ import { buildImportFixes, getNamedImports, getTsDataForgeImport, } from './import-utils.mjs';
3
+ export const preferArrIsMinLengthArray = {
4
+ meta: {
5
+ type: 'suggestion',
6
+ docs: {
7
+ description: 'Replace `xs.length >= n` with `Arr.isMinLengthArray(xs, n)` from ts-data-forge.',
8
+ },
9
+ fixable: 'code',
10
+ schema: [],
11
+ messages: {
12
+ useIsMinLengthArray: 'Replace `{{original}}` with `Arr.isMinLengthArray({{arrayName}}, {{length}})` from ts-data-forge.',
13
+ },
9
14
  },
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
15
+ create: (context) => {
16
+ const sourceCode = context.sourceCode;
17
+ const program = sourceCode.ast;
18
+ const tsDataForgeImport = getTsDataForgeImport(program);
19
+ const services = context.sourceCode.parserServices;
20
+ const mut_nodesToFix = [];
21
+ return {
22
+ BinaryExpression: (node) => {
23
+
24
+ if (node.operator !== '>=' && node.operator !== '<=')
25
+ return;
26
+
27
+
28
+ if (isPartOfBoundedLengthCheck(node, sourceCode))
29
+ return;
30
+
31
+ const isLengthOnLeft = node.operator === '>=';
32
+ const lengthSide = node[isLengthOnLeft ? 'left' : 'right'];
33
+ const valueSide = node[isLengthOnLeft ? 'right' : 'left'];
34
+
35
+ if (!isLengthAccess(lengthSide))
36
+ return;
37
+
38
+ if (!isIntegerLiteralOrConstant(valueSide, sourceCode))
39
+ return;
40
+
41
+ const arrayExpression = lengthSide.object;
42
+
43
+ if (services?.program !== undefined && services.program !== null) {
44
+ const checker = services.program.getTypeChecker();
45
+ const tsNode = services.esTreeNodeToTSNodeMap?.get(arrayExpression);
46
+ if (tsNode !== undefined) {
47
+ const type = checker.getTypeAtLocation(tsNode);
48
+
49
+ const isArrayType = checker.isArrayType(type) || checker.isTupleType(type);
50
+ if (!isArrayType)
51
+ return;
52
+ }
53
+ else {
54
+ return;
55
+ }
56
+ }
57
+ else {
58
+ return;
59
+ }
60
+ mut_nodesToFix.push({
61
+ node,
62
+ arrayExpression,
63
+ lengthExpression: valueSide,
64
+ });
68
65
  },
69
- fix: (fixer) => {
70
- const replacement = `Arr.isMinLengthArray(${lengthText}, ${arrayText})`;
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: []
66
+ 'Program:exit': () => {
67
+ const namedImports = getNamedImports(tsDataForgeImport);
68
+ const hasArrImport = namedImports.includes('Arr');
69
+ for (const [index, { node, arrayExpression, lengthExpression },] of mut_nodesToFix.entries()) {
70
+ const arrayText = sourceCode.getText(arrayExpression);
71
+ const lengthText = sourceCode.getText(lengthExpression);
72
+ const originalText = sourceCode.getText(node);
73
+ context.report({
74
+ node,
75
+ messageId: 'useIsMinLengthArray',
76
+ data: {
77
+ original: originalText,
78
+ arrayName: arrayText,
79
+ length: lengthText,
80
+ },
81
+ fix: (fixer) => {
82
+ const replacement = `Arr.isMinLengthArray(${lengthText}, ${arrayText})`;
83
+ const importFixes = index === 0 && !hasArrImport
84
+ ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])
85
+ : [];
86
+ return [...importFixes, fixer.replaceText(node, replacement)];
87
+ },
88
+ });
89
+ }
90
+ },
91
+ };
92
+ },
93
+ defaultOptions: [],
80
94
  };
81
-
82
- export { preferArrIsMinLengthArray };
83
- //# sourceMappingURL=prefer-arr-is-min-length-array.mjs.map
95
+ //# sourceMappingURL=prefer-arr-is-min-length-array.mjs.map
@@ -1 +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(${lengthText}, ${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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"}
1
+ {"version":3,"file":"prefer-arr-is-min-length-array.mjs","sourceRoot":"","sources":["../../src/rules/prefer-arr-is-min-length-array.mts"],"names":[],"mappings":"AACA,OAAO,EACL,0BAA0B,EAC1B,cAAc,EACd,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAM5B,MAAM,CAAC,MAAM,yBAAyB,GAGlC;IACF,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EACT,iFAAiF;SACpF;QACD,OAAO,EAAE,MAAM;QACf,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACR,mBAAmB,EACjB,mGAAmG;SACtG;KACF;IAED,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;QAClB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QAEtC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC;QAE/B,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAExD,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC;QAEnD,MAAM,cAAc,GAId,EAAE,CAAC;QAET,OAAO;YACL,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE;gBACzB,iDAAiD;gBACjD,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI;oBAAE,OAAO;gBAE7D,wEAAwE;gBACxE,4DAA4D;gBAC5D,IAAI,0BAA0B,CAAC,IAAI,EAAE,UAAU,CAAC;oBAAE,OAAO;gBAEzD,qCAAqC;gBACrC,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC;gBAE9C,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;gBAE3D,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;gBAE1D,2CAA2C;gBAC3C,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC;oBAAE,OAAO;gBAExC,mFAAmF;gBACnF,IAAI,CAAC,0BAA0B,CAAC,SAAS,EAAE,UAAU,CAAC;oBAAE,OAAO;gBAE/D,mDAAmD;gBACnD,MAAM,eAAe,GAAG,UAAU,CAAC,MAAM,CAAC;gBAE1C,qDAAqD;gBACrD,IAAI,QAAQ,EAAE,OAAO,KAAK,SAAS,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;oBACjE,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;oBAElD,MAAM,MAAM,GAAG,QAAQ,CAAC,qBAAqB,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC;oBAEpE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;wBACzB,MAAM,IAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;wBAE/C,4CAA4C;wBAC5C,MAAM,WAAW,GACf,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;wBAEzD,IAAI,CAAC,WAAW;4BAAE,OAAO;oBAC3B,CAAC;yBAAM,CAAC;wBACN,OAAO;oBACT,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,OAAO;gBACT,CAAC;gBAED,cAAc,CAAC,IAAI,CAAC;oBAClB,IAAI;oBACJ,eAAe;oBACf,gBAAgB,EAAE,SAAS;iBAC5B,CAAC,CAAC;YACL,CAAC;YACD,cAAc,EAAE,GAAG,EAAE;gBACnB,MAAM,YAAY,GAAG,eAAe,CAAC,iBAAiB,CAAC,CAAC;gBAExD,MAAM,YAAY,GAAG,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAElD,KAAK,MAAM,CACT,KAAK,EACL,EAAE,IAAI,EAAE,eAAe,EAAE,gBAAgB,EAAE,EAC5C,IAAI,cAAc,CAAC,OAAO,EAAE,EAAE,CAAC;oBAC9B,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;oBAEtD,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;oBAExD,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;oBAE9C,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,SAAS,EAAE,qBAAqB;wBAChC,IAAI,EAAE;4BACJ,QAAQ,EAAE,YAAY;4BACtB,SAAS,EAAE,SAAS;4BACpB,MAAM,EAAE,UAAU;yBACnB;wBACD,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE;4BACb,MAAM,WAAW,GAAG,wBAAwB,UAAU,KAAK,SAAS,GAAG,CAAC;4BAExE,MAAM,WAAW,GACf,KAAK,KAAK,CAAC,IAAI,CAAC,YAAY;gCAC1B,CAAC,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,KAAK,CAAC,CAAC;gCAC9D,CAAC,CAAC,EAAE,CAAC;4BAET,OAAO,CAAC,GAAG,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;wBAChE,CAAC;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;IACD,cAAc,EAAE,EAAE;CACV,CAAC"}
@@ -1,80 +1,88 @@
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."
1
+ import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
+ import { buildImportFixes, getNamedImports, getTsDataForgeImport, } from './import-utils.mjs';
3
+ export const preferArrIsNonEmpty = {
4
+ meta: {
5
+ type: 'suggestion',
6
+ docs: {
7
+ description: 'Replace `xs.length > 0` with `Arr.isNonEmpty(xs)` from ts-data-forge.',
8
+ },
9
+ fixable: 'code',
10
+ schema: [],
11
+ messages: {
12
+ useIsNonEmpty: 'Replace `{{original}}` with `Arr.isNonEmpty({{arrayName}})` from ts-data-forge.',
13
+ },
9
14
  },
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
15
+ create: (context) => {
16
+ const sourceCode = context.sourceCode;
17
+ const program = sourceCode.ast;
18
+ const tsDataForgeImport = getTsDataForgeImport(program);
19
+ const services = context.sourceCode.parserServices;
20
+ const mut_nodesToFix = [];
21
+ return {
22
+ BinaryExpression: (node) => {
23
+
24
+ if (node.operator !== '>' && node.operator !== '<')
25
+ return;
26
+ const isLengthOnLeft = node.operator === '>';
27
+ const lengthSide = node[isLengthOnLeft ? 'left' : 'right'];
28
+ const numberSide = node[isLengthOnLeft ? 'right' : 'left'];
29
+
30
+ if (lengthSide.type !== AST_NODE_TYPES.MemberExpression ||
31
+ lengthSide.property.type !== AST_NODE_TYPES.Identifier ||
32
+ lengthSide.property.name !== 'length' ||
33
+ numberSide.type !== AST_NODE_TYPES.Literal ||
34
+ numberSide.value !== 0) {
35
+ return;
36
+ }
37
+ const arrayExpression = lengthSide.object;
38
+
39
+ if (services?.program !== undefined && services.program !== null) {
40
+ const checker = services.program.getTypeChecker();
41
+ const tsNode = services.esTreeNodeToTSNodeMap?.get(arrayExpression);
42
+ if (tsNode !== undefined) {
43
+ const type = checker.getTypeAtLocation(tsNode);
44
+
45
+ const isArrayType = checker.isArrayType(type) || checker.isTupleType(type);
46
+ if (!isArrayType)
47
+ return;
48
+ }
49
+ else {
50
+ return;
51
+ }
52
+ }
53
+ else {
54
+ return;
55
+ }
56
+ mut_nodesToFix.push({
57
+ node,
58
+ arrayExpression,
59
+ });
60
+ },
61
+ 'Program:exit': () => {
62
+ const namedImports = getNamedImports(tsDataForgeImport);
63
+ const hasArrImport = namedImports.includes('Arr');
64
+ for (const [index, { node, arrayExpression },] of mut_nodesToFix.entries()) {
65
+ const arrayText = sourceCode.getText(arrayExpression);
66
+ const originalText = sourceCode.getText(node);
67
+ context.report({
68
+ node,
69
+ messageId: 'useIsNonEmpty',
70
+ data: {
71
+ original: originalText,
72
+ arrayName: arrayText,
73
+ },
74
+ fix: (fixer) => {
75
+ const replacement = `Arr.isNonEmpty(${arrayText})`;
76
+ const importFixes = index === 0 && !hasArrImport
77
+ ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])
78
+ : [];
79
+ return [...importFixes, fixer.replaceText(node, replacement)];
80
+ },
81
+ });
82
+ }
65
83
  },
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: []
84
+ };
85
+ },
86
+ defaultOptions: [],
77
87
  };
78
-
79
- export { preferArrIsNonEmpty };
80
- //# sourceMappingURL=prefer-arr-is-non-empty.mjs.map
88
+ //# sourceMappingURL=prefer-arr-is-non-empty.mjs.map
@@ -1 +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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"}
1
+ {"version":3,"file":"prefer-arr-is-non-empty.mjs","sourceRoot":"","sources":["../../src/rules/prefer-arr-is-non-empty.mts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ type Options = readonly [];
3
+ type MessageIds = 'preferArrScanPrefix' | 'preferArrScanAccumulate';
4
+ /**
5
+ * Report an accumulated value that is rebuilt from scratch for every element,
6
+ * and point at `Arr.scan` from ts-data-forge.
7
+ *
8
+ * ## Why
9
+ *
10
+ * Two shapes compute what `Arr.scan` computes — a value carried from one
11
+ * element to the next — by re-deriving it from the whole prefix each time.
12
+ *
13
+ * ```ts
14
+ * // (1) the prefix rebuilt per element: `slice` allocates, and the combining
15
+ * // function runs over the whole prefix again. O(n²).
16
+ * segments.map((_, index) =>
17
+ * path.resolve(root, ...segments.slice(0, index + 1)),
18
+ * );
19
+ *
20
+ * // one pass — and `scan` puts the initial value at the head of its result,
21
+ * // which here is exactly "the root, then each ancestor below it"
22
+ * Arr.scan(
23
+ * segments,
24
+ * (ancestor, segment) => path.resolve(ancestor, segment),
25
+ * root,
26
+ * );
27
+ * ```
28
+ *
29
+ * ```ts
30
+ * // (2) the accumulator copied on every step, for the same O(n²)
31
+ * xs.reduce<readonly number[]>((acc, x) => [...acc, (acc.at(-1) ?? 0) + x], [0]);
32
+ *
33
+ * Arr.scan(xs, (total, x) => total + x, 0);
34
+ * ```
35
+ *
36
+ * The complexity is the smaller half of it. What the rebuilt form loses is the
37
+ * name of what is being accumulated: (1) says "for each index, combine
38
+ * everything up to it", and a reader has to turn that back into "carry a value
39
+ * along" before the code says what it does.
40
+ *
41
+ * ## Why only one of the two is fixed
42
+ *
43
+ * **(1) cannot be fixed at all**, and the obstacle is not difficulty. Turning
44
+ * `f(seed, ...xs.slice(0, i + 1))` into a `scan` is only valid if
45
+ * `f(seed, a, b, c) === f(f(f(seed, a), b), c)` — that the variadic callee is a
46
+ * left fold. That is a semantic property of the callee, invisible in the AST
47
+ * and in the type: `path.resolve` and `Math.max` have it, `Array.of` and
48
+ * `console.log` do not. Nothing static licenses the rewrite, so this shape is
49
+ * reported and left to a reader. The direction is a second obstacle —
50
+ * `slice(0, i + 1)` runs forwards while `slice(0, xs.length - i)` runs
51
+ * backwards and needs the result reversed — and the length a third, `scan`
52
+ * putting the initial value at the head where `map` has n elements.
53
+ *
54
+ * **(2) is fixed**, because none of those three apply to it: the combining
55
+ * function is the callback body itself, there is no slice and so no direction
56
+ * to infer, and the lengths already agree — a `reduce` seeded with `[init]`
57
+ * yields n+1 elements and so does `Arr.scan(xs, f, init)`.
58
+ *
59
+ * The fix is withheld unless every one of these holds, since each is what makes
60
+ * the two forms equal rather than merely similar:
61
+ *
62
+ * - the initial value is a one-element array literal `[E]`, so `scan`'s initial
63
+ * value is `E`;
64
+ * - the body is exactly `[...acc, ELEMENT]` — one spread of the accumulator and
65
+ * one new element, since `scan` appends exactly one value per step;
66
+ * - every other mention of the accumulator in `ELEMENT` is `acc.at(-1)`, and
67
+ * there is at least one. `scan` hands its callback the previous *value*, not
68
+ * the prefix, so `acc.length`, `acc[0]` or `acc.filter(...)` cannot be
69
+ * expressed and block the fix. `at(-1)` is the only spelling substituted:
70
+ * `Arr.last` returns an `Optional`, not the element, so it is not a
71
+ * substitution at all;
72
+ * - the parameters are plain identifiers with no type annotations — an
73
+ * annotation on the accumulator describes the array, and would be wrong on
74
+ * `scan`'s value parameter — and there are at most three of them, `reduce`'s
75
+ * fourth parameter (the whole array) having no counterpart in `scan`.
76
+ *
77
+ * `acc.at(-1)` is typed `T | undefined` while `scan`'s accumulator is `S`, so a
78
+ * `?? fallback` guarding it becomes redundant after the fix. It is left in
79
+ * place rather than removed: it evaluates identically either way, and deciding
80
+ * that it is dead needs to know that `S` excludes `undefined`, which is type
81
+ * information this rule does not take.
82
+ *
83
+ * ## What is not reported
84
+ *
85
+ * A `map` callback that reads its element parameter. Rebuilding a prefix *and*
86
+ * using the current element is not an accumulation spelled out the long way:
87
+ * `scan` hands its callback the accumulated value and the element, never the
88
+ * prefix, so such a callback is not one it can express.
89
+ *
90
+ * The array has to be spelled the same way in the `map` and in the `slice`.
91
+ * Deciding that two differently written expressions denote one array needs type
92
+ * information and would still be a guess about aliasing.
93
+ */
94
+ export declare const preferArrScan: TSESLint.RuleModule<MessageIds, Options>;
95
+ export {};
96
+ //# sourceMappingURL=prefer-arr-scan.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-arr-scan.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-arr-scan.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAUlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,qBAAqB,GAAG,yBAAyB,CAAC;AA+CpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CAwVzD,CAAC"}