eslint-plugin-ts-data-forge 0.1.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +49 -19
  2. package/dist/index.d.mts +1 -1
  3. package/dist/index.d.mts.map +1 -1
  4. package/dist/plugin.d.mts +46 -1
  5. package/dist/plugin.d.mts.map +1 -1
  6. package/dist/plugin.mjs +35 -1
  7. package/dist/plugin.mjs.map +1 -1
  8. package/dist/rule-types.d.mts +6 -51
  9. package/dist/rule-types.d.mts.map +1 -1
  10. package/dist/rules/import-utils.mjs.map +1 -1
  11. package/dist/rules/prefer-arr-is-bounded-length-array.mjs +1 -1
  12. package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
  13. package/dist/rules/prefer-arr-is-fixed-length-array.mjs +1 -1
  14. package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
  15. package/dist/rules/prefer-arr-is-max-length-array.mjs +1 -1
  16. package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
  17. package/dist/rules/prefer-arr-is-min-length-array.mjs +1 -1
  18. package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -1
  19. package/dist/rules/prefer-arr-sum.mjs +2 -2
  20. package/dist/rules/prefer-arr-sum.mjs.map +1 -1
  21. package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
  22. package/dist/rules/prefer-canonical-length-cast.d.mts +6 -0
  23. package/dist/rules/prefer-canonical-length-cast.d.mts.map +1 -0
  24. package/dist/rules/prefer-canonical-length-cast.mjs +155 -0
  25. package/dist/rules/prefer-canonical-length-cast.mjs.map +1 -0
  26. package/dist/rules/prefer-canonical-length-guard.d.mts +6 -0
  27. package/dist/rules/prefer-canonical-length-guard.d.mts.map +1 -0
  28. package/dist/rules/prefer-canonical-length-guard.mjs +129 -0
  29. package/dist/rules/prefer-canonical-length-guard.mjs.map +1 -0
  30. package/dist/rules/prefer-range-for-loop.mjs +1 -1
  31. package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
  32. package/dist/rules/rules.d.mts +2 -5
  33. package/dist/rules/rules.d.mts.map +1 -1
  34. package/dist/rules/rules.mjs +4 -10
  35. package/dist/rules/rules.mjs.map +1 -1
  36. package/dist/types.d.mts +1 -0
  37. package/dist/types.d.mts.map +1 -1
  38. package/package.json +7 -3
  39. package/src/index.mts +1 -1
  40. package/src/plugin.mts +47 -3
  41. package/src/plugin.test.mts +24 -0
  42. package/src/rule-types.mts +6 -54
  43. package/src/rules/import-utils.mts +0 -1
  44. package/src/rules/prefer-arr-is-bounded-length-array.mts +1 -1
  45. package/src/rules/prefer-arr-is-bounded-length-array.test.mts +5 -5
  46. package/src/rules/prefer-arr-is-fixed-length-array.mts +1 -1
  47. package/src/rules/prefer-arr-is-fixed-length-array.test.mts +11 -11
  48. package/src/rules/prefer-arr-is-max-length-array.mts +1 -1
  49. package/src/rules/prefer-arr-is-max-length-array.test.mts +8 -8
  50. package/src/rules/prefer-arr-is-min-length-array.mts +1 -1
  51. package/src/rules/prefer-arr-is-min-length-array.test.mts +9 -9
  52. package/src/rules/prefer-arr-sum.mts +2 -2
  53. package/src/rules/prefer-canonical-array-slicing.mts +0 -2
  54. package/src/rules/prefer-canonical-length-cast.mts +290 -0
  55. package/src/rules/prefer-canonical-length-cast.test.mts +305 -0
  56. package/src/rules/prefer-canonical-length-guard.mts +250 -0
  57. package/src/rules/prefer-canonical-length-guard.test.mts +307 -0
  58. package/src/rules/prefer-range-for-loop.mts +1 -1
  59. package/src/rules/rules.mts +4 -10
  60. package/src/types.mts +2 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-canonical-length-guard.mjs","sources":["../../src/rules/prefer-canonical-length-guard.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { getImportedLocalName, getTsDataForgeImport } from './import-utils.mjs';\nimport { preferArrIsBoundedLengthArray } from './prefer-arr-is-bounded-length-array.mjs';\nimport { preferArrIsFixedLengthArray } from './prefer-arr-is-fixed-length-array.mjs';\nimport { preferArrIsMaxLengthArray } from './prefer-arr-is-max-length-array.mjs';\nimport { preferArrIsMinLengthArray } from './prefer-arr-is-min-length-array.mjs';\nimport { preferArrIsNonEmpty } from './prefer-arr-is-non-empty.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds =\n | 'useCanonicalGuard'\n | 'useIsBoundedLengthArray'\n | 'useIsFixedLengthArray'\n | 'useIsMaxLengthArray'\n | 'useIsMinLengthArray'\n | 'useIsNonEmpty';\n\n/**\n * The `xs.length <op> n` → `Arr.is*` rules folded into this one.\n *\n * Their implementations are reused as-is rather than rewritten: this rule\n * merges their visitors, so every case they already cover (type-aware array\n * checks, `Arr` import insertion, the `&&`-bounded-range special case) keeps\n * behaving exactly as before, and their co-located tests keep exercising them\n * directly.\n */\nconst COMPARISON_RULES = [\n preferArrIsNonEmpty,\n preferArrIsMinLengthArray,\n preferArrIsMaxLengthArray,\n preferArrIsBoundedLengthArray,\n preferArrIsFixedLengthArray,\n] as const;\n\nconst MESSAGES = {\n useCanonicalGuard:\n 'Replace `{{arrName}}.{{guard}}({{boundsText}})` with `{{arrName}}.{{replacement}}(...)`: the bound makes the two guards equivalent.',\n useIsNonEmpty: preferArrIsNonEmpty.meta.messages.useIsNonEmpty,\n useIsMinLengthArray:\n preferArrIsMinLengthArray.meta.messages.useIsMinLengthArray,\n useIsMaxLengthArray:\n preferArrIsMaxLengthArray.meta.messages.useIsMaxLengthArray,\n useIsBoundedLengthArray:\n preferArrIsBoundedLengthArray.meta.messages.useIsBoundedLengthArray,\n useIsFixedLengthArray:\n preferArrIsFixedLengthArray.meta.messages.useIsFixedLengthArray,\n} as const satisfies Record<MessageIds, string>;\n\n/**\n * Length guards whose bound makes them redundant with a named degenerate\n * guard, keyed by guard name.\n *\n * Every entry stays inside its own family rather than crossing the\n * branded/structural divide:\n *\n * - the branded `*Array` guards go to `isEmpty` / `isNonEmpty`, which narrow to\n * `FixedLengthArray<0, E> & Xs` / `MinLengthArray<1, E> & Xs`;\n * - the structural `*Tuple` guards go to `isEmptyTuple` / `isNonEmptyTuple`,\n * which narrow to `readonly []` / `MinLengthTuple<1, E>`.\n *\n * Most entries are then **type-identical** — `isFixedLengthArray(0, xs)` and\n * `isMinLengthArray(1, xs)` produce exactly what `isEmpty` / `isNonEmpty`\n * narrow to, and `FixedLengthTuple<0, E>`, `MaxLengthTuple<0, E>` and\n * `BoundedLengthTuple<0, 0, E>` are all `readonly []`. Two *strengthen*:\n * `MaxLengthArray<0, E>` and `BoundedLengthArray<0, 0, E>` do not include the\n * `readonly []` structural part that `FixedLengthArray<0, E>` intersects on, so\n * rewriting them to `isEmpty` adds it. That is sound for a guard — the narrowed\n * type only flows outwards, and the stronger type is assignable everywhere the\n * old one was — and it stays within the branded family.\n *\n * Sending a `*Tuple` guard to the branded `isEmpty` would also be sound, but it\n * would silently change which family the value belongs to, which is why each\n * family keeps its own lane.\n */\nconst GUARD_REWRITES = [\n { guard: 'isFixedLengthArray', bounds: [0], replacement: 'isEmpty' },\n { guard: 'isMaxLengthArray', bounds: [0], replacement: 'isEmpty' },\n { guard: 'isBoundedLengthArray', bounds: [0, 0], replacement: 'isEmpty' },\n { guard: 'isMinLengthArray', bounds: [1], replacement: 'isNonEmpty' },\n { guard: 'isFixedLengthTuple', bounds: [0], replacement: 'isEmptyTuple' },\n { guard: 'isMaxLengthTuple', bounds: [0], replacement: 'isEmptyTuple' },\n {\n guard: 'isBoundedLengthTuple',\n bounds: [0, 0],\n replacement: 'isEmptyTuple',\n },\n { guard: 'isMinLengthTuple', bounds: [1], replacement: 'isNonEmptyTuple' },\n] as const satisfies readonly Readonly<{\n guard: string;\n bounds: readonly number[];\n replacement: string;\n}>[];\n\nexport const preferCanonicalLengthGuard: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Normalize array-length checks to their canonical `Arr` guard: `xs.length <op> n` becomes the matching `Arr.is*` guard, and a degenerate guard becomes the named one for its family (`Arr.isFixedLengthArray(0, xs)` → `Arr.isEmpty`, `Arr.isFixedLengthTuple(0, xs)` → `Arr.isEmptyTuple`).',\n },\n fixable: 'code',\n schema: [],\n messages: MESSAGES,\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const arrLocalName = getImportedLocalName(\n getTsDataForgeImport(sourceCode.ast),\n 'Arr',\n );\n\n const comparisonVisitors = COMPARISON_RULES.map((rule) =>\n rule.create(context),\n );\n\n if (arrLocalName === undefined) return mergeVisitors(comparisonVisitors);\n\n return mergeVisitors([\n ...comparisonVisitors,\n {\n CallExpression: (node) => {\n const guardName = getGuardName(node, arrLocalName);\n\n if (guardName === undefined) return;\n\n const rewrite = GUARD_REWRITES.find(\n (entry) => entry.guard === guardName,\n );\n\n if (rewrite === undefined || !matchesBounds(node, rewrite.bounds)) {\n return;\n }\n\n // The array is the last argument; the bounds precede it.\n const array = node.arguments[rewrite.bounds.length];\n\n if (array === undefined) return;\n\n const arrayText = sourceCode.getText(array);\n\n context.report({\n node,\n messageId: 'useCanonicalGuard',\n data: {\n arrName: arrLocalName,\n guard: guardName,\n boundsText: [...rewrite.bounds.map(String), '…'].join(', '),\n replacement: rewrite.replacement,\n },\n fix: (fixer) =>\n fixer.replaceText(\n node,\n `${arrLocalName}.${rewrite.replacement}(${arrayText})`,\n ),\n });\n },\n },\n ]);\n },\n defaultOptions: [],\n} as const;\n\n/**\n * Combines several rule listeners into one, calling every handler registered\n * for a given selector in order.\n */\nconst mergeVisitors = (\n // `RuleListener` holds mutable AST handler signatures, so it is not deeply\n // readonly.\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n visitors: readonly TSESLint.RuleListener[],\n): TSESLint.RuleListener => {\n const mut_merged: Record<string, ((node: never) => void)[]> = {};\n\n for (const visitor of visitors) {\n for (const [selector, handler] of Object.entries(visitor)) {\n if (typeof handler !== 'function') continue;\n\n const mut_handlers = mut_merged[selector] ?? [];\n\n mut_handlers.push(handler);\n\n mut_merged[selector] = mut_handlers;\n }\n }\n\n return Object.fromEntries(\n Object.entries(mut_merged).map(([selector, handlers]) => [\n selector,\n (node: never) => {\n for (const handler of handlers) {\n handler(node);\n }\n },\n ]),\n );\n};\n\n/** The `Arr.<guard>` method name of `node`, or `undefined` for other calls. */\nconst getGuardName = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n node: TSESTree.CallExpression,\n arrLocalName: string,\n): string | undefined => {\n const { callee } = node;\n\n return callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n callee.object.name === arrLocalName &&\n callee.property.type === AST_NODE_TYPES.Identifier\n ? callee.property.name\n : undefined;\n};\n\n/**\n * Whether the call is `guard(...bounds, array)` with every bound written as the\n * exact numeric literal the rewrite requires.\n *\n * The arity check also excludes the curried form (`guard(...bounds)`): the\n * degenerate guards take only the array, so there is nothing to rewrite it to.\n */\nconst matchesBounds = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n node: TSESTree.CallExpression,\n bounds: readonly number[],\n): boolean => {\n const args = node.arguments;\n\n if (args.length !== bounds.length + 1 || node.typeArguments !== undefined) {\n return false;\n }\n\n return args.every(\n (arg, index) =>\n arg.type !== AST_NODE_TYPES.SpreadElement &&\n (index === bounds.length ||\n (arg.type === AST_NODE_TYPES.Literal && arg.value === bounds[index])),\n );\n};\n"],"names":[],"mappings":"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"}
@@ -21,7 +21,7 @@ const preferRangeForLoop = {
21
21
  const mut_nodesToFix = [];
22
22
  return {
23
23
  ForStatement: (node) => {
24
- if (node.init?.type !== AST_NODE_TYPES.VariableDeclaration || node.init.kind !== "let" || !Arr.isFixedLengthTuple(node.init.declarations, 1)) {
24
+ if (node.init?.type !== AST_NODE_TYPES.VariableDeclaration || node.init.kind !== "let" || !Arr.isFixedLengthTuple(1, node.init.declarations)) {
25
25
  return;
26
26
  }
27
27
  const declaration = node.init.declarations[0];
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-range-for-loop.mjs","sources":["../../src/rules/prefer-range-for-loop.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { Arr } from 'ts-data-forge';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useRangeForLoop';\n\nexport const preferRangeForLoop: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `for (let i = begin; i < end; ++i)` with `for (const i of range(begin, end))` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useRangeForLoop:\n 'Replace with `for (const {{varName}} of range({{begin}}, {{end}}))` 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: {\n node: TSESTree.ForStatement;\n varName: string;\n begin: string;\n end: string;\n step?: string;\n }[] = [];\n\n return {\n ForStatement: (node) => {\n // Check init: let i = begin\n if (\n node.init?.type !== AST_NODE_TYPES.VariableDeclaration ||\n node.init.kind !== 'let' ||\n !Arr.isFixedLengthTuple(node.init.declarations, 1)\n ) {\n return;\n }\n\n const declaration = node.init.declarations[0];\n\n if (\n declaration.id.type !== AST_NODE_TYPES.Identifier ||\n declaration.init === null\n ) {\n return;\n }\n\n const varName = declaration.id.name;\n\n const beginExpr = declaration.init;\n\n // Check test: i < end\n if (\n node.test?.type !== AST_NODE_TYPES.BinaryExpression ||\n node.test.operator !== '<' ||\n node.test.left.type !== AST_NODE_TYPES.Identifier ||\n node.test.left.name !== varName\n ) {\n return;\n }\n\n const endExpr = node.test.right;\n\n // Check update: ++i, i++, i += 1, or i += step\n if (node.update === null) return;\n\n let mut_step: string | undefined;\n\n if (node.update.type === AST_NODE_TYPES.UpdateExpression) {\n // ++i or i++\n if (\n node.update.operator === '++' &&\n node.update.argument.type === AST_NODE_TYPES.Identifier &&\n node.update.argument.name === varName\n ) {\n mut_step = undefined; // step defaults to 1\n } else {\n return;\n }\n } else if (\n node.update.type === AST_NODE_TYPES.AssignmentExpression &&\n node.update.operator === '+=' &&\n node.update.left.type === AST_NODE_TYPES.Identifier &&\n node.update.left.name === varName\n ) {\n // i += step\n const stepValue = node.update.right;\n\n if (stepValue.type === AST_NODE_TYPES.Literal) {\n const stepNum = stepValue.value;\n\n // Only support positive integer steps\n if (typeof stepNum === 'number' && stepNum > 0) {\n mut_step = stepNum === 1 ? undefined : String(stepNum);\n } else {\n return;\n }\n } else {\n // Variable step like i += step\n mut_step = sourceCode.getText(stepValue);\n }\n } else {\n return;\n }\n\n const beginText = sourceCode.getText(beginExpr);\n\n const endText = sourceCode.getText(endExpr);\n\n mut_nodesToFix.push({\n node,\n varName,\n begin: beginText,\n end: endText,\n step: mut_step,\n });\n },\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const hasRangeImport = namedImports.includes('range');\n\n for (const [\n index,\n { node, varName, begin, end, step },\n ] of mut_nodesToFix.entries()) {\n context.report({\n node: node.init ?? node,\n messageId: 'useRangeForLoop',\n data: {\n varName,\n begin,\n end,\n },\n fix: (fixer) => {\n const bodyText = sourceCode.getText(node.body);\n\n const rangeArgs =\n step === undefined\n ? `${begin}, ${end}`\n : `${begin}, ${end}, ${step}`;\n\n const replacement = `for (const ${varName} of range(${rangeArgs})) ${bodyText}`;\n\n const importFixes =\n index === 0 && !hasRangeImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, [\n 'range',\n ])\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-range-for-loop.mjs","sources":["../../src/rules/prefer-range-for-loop.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { Arr } from 'ts-data-forge';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useRangeForLoop';\n\nexport const preferRangeForLoop: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `for (let i = begin; i < end; ++i)` with `for (const i of range(begin, end))` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useRangeForLoop:\n 'Replace with `for (const {{varName}} of range({{begin}}, {{end}}))` 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: {\n node: TSESTree.ForStatement;\n varName: string;\n begin: string;\n end: string;\n step?: string;\n }[] = [];\n\n return {\n ForStatement: (node) => {\n // Check init: let i = begin\n if (\n node.init?.type !== AST_NODE_TYPES.VariableDeclaration ||\n node.init.kind !== 'let' ||\n !Arr.isFixedLengthTuple(1, node.init.declarations)\n ) {\n return;\n }\n\n const declaration = node.init.declarations[0];\n\n if (\n declaration.id.type !== AST_NODE_TYPES.Identifier ||\n declaration.init === null\n ) {\n return;\n }\n\n const varName = declaration.id.name;\n\n const beginExpr = declaration.init;\n\n // Check test: i < end\n if (\n node.test?.type !== AST_NODE_TYPES.BinaryExpression ||\n node.test.operator !== '<' ||\n node.test.left.type !== AST_NODE_TYPES.Identifier ||\n node.test.left.name !== varName\n ) {\n return;\n }\n\n const endExpr = node.test.right;\n\n // Check update: ++i, i++, i += 1, or i += step\n if (node.update === null) return;\n\n let mut_step: string | undefined;\n\n if (node.update.type === AST_NODE_TYPES.UpdateExpression) {\n // ++i or i++\n if (\n node.update.operator === '++' &&\n node.update.argument.type === AST_NODE_TYPES.Identifier &&\n node.update.argument.name === varName\n ) {\n mut_step = undefined; // step defaults to 1\n } else {\n return;\n }\n } else if (\n node.update.type === AST_NODE_TYPES.AssignmentExpression &&\n node.update.operator === '+=' &&\n node.update.left.type === AST_NODE_TYPES.Identifier &&\n node.update.left.name === varName\n ) {\n // i += step\n const stepValue = node.update.right;\n\n if (stepValue.type === AST_NODE_TYPES.Literal) {\n const stepNum = stepValue.value;\n\n // Only support positive integer steps\n if (typeof stepNum === 'number' && stepNum > 0) {\n mut_step = stepNum === 1 ? undefined : String(stepNum);\n } else {\n return;\n }\n } else {\n // Variable step like i += step\n mut_step = sourceCode.getText(stepValue);\n }\n } else {\n return;\n }\n\n const beginText = sourceCode.getText(beginExpr);\n\n const endText = sourceCode.getText(endExpr);\n\n mut_nodesToFix.push({\n node,\n varName,\n begin: beginText,\n end: endText,\n step: mut_step,\n });\n },\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const hasRangeImport = namedImports.includes('range');\n\n for (const [\n index,\n { node, varName, begin, end, step },\n ] of mut_nodesToFix.entries()) {\n context.report({\n node: node.init ?? node,\n messageId: 'useRangeForLoop',\n data: {\n varName,\n begin,\n end,\n },\n fix: (fixer) => {\n const bodyText = sourceCode.getText(node.body);\n\n const rangeArgs =\n step === undefined\n ? `${begin}, ${end}`\n : `${begin}, ${end}, ${step}`;\n\n const replacement = `for (const ${varName} of range(${rangeArgs})) ${bodyText}`;\n\n const importFixes =\n index === 0 && !hasRangeImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, [\n 'range',\n ])\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,11 +1,8 @@
1
1
  export declare const tsDataForgeRules: {
2
2
  readonly 'prefer-canonical-array-slicing': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferCanonicalArraySlicing", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
3
- readonly 'prefer-arr-is-min-length-array': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsMinLengthArray", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
4
- readonly 'prefer-arr-is-max-length-array': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsMaxLengthArray", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
5
- readonly 'prefer-arr-is-bounded-length-array': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsBoundedLengthArray", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
6
- readonly 'prefer-arr-is-fixed-length-array': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsFixedLengthArray", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
3
+ readonly 'prefer-canonical-length-guard': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useCanonicalGuard" | "useIsBoundedLengthArray" | "useIsFixedLengthArray" | "useIsMaxLengthArray" | "useIsMinLengthArray" | "useIsNonEmpty", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
4
+ readonly 'prefer-canonical-length-cast': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useCanonicalCast", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
7
5
  readonly 'prefer-arr-is-array': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useArrIsArray", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
8
- readonly 'prefer-arr-is-non-empty': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsNonEmpty", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
9
6
  readonly 'prefer-arr-sum': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useArrSum" | "useArrSumBy", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
10
7
  readonly 'prefer-as-int': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useBrandedNumberCastFunction", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
11
8
  readonly 'prefer-is-non-null-object': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsNonNullObject", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../../src/rules/rules.mts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;CAiBa,CAAC"}
1
+ {"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../../src/rules/rules.mts"],"names":[],"mappings":"AAeA,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;CAca,CAAC"}
@@ -1,13 +1,10 @@
1
1
  import { noUnnecessaryTypeGuard } from './no-unnecessary-type-guard.mjs';
2
2
  import { preferArrIsArray } from './prefer-arr-is-array.mjs';
3
- import { preferArrIsBoundedLengthArray } from './prefer-arr-is-bounded-length-array.mjs';
4
- import { preferArrIsFixedLengthArray } from './prefer-arr-is-fixed-length-array.mjs';
5
- import { preferArrIsMaxLengthArray } from './prefer-arr-is-max-length-array.mjs';
6
- import { preferArrIsMinLengthArray } from './prefer-arr-is-min-length-array.mjs';
7
- import { preferArrIsNonEmpty } from './prefer-arr-is-non-empty.mjs';
8
3
  import { preferArrSum } from './prefer-arr-sum.mjs';
9
4
  import { preferAsInt } from './prefer-as-int.mjs';
10
5
  import { preferCanonicalArraySlicing } from './prefer-canonical-array-slicing.mjs';
6
+ import { preferCanonicalLengthCast } from './prefer-canonical-length-cast.mjs';
7
+ import { preferCanonicalLengthGuard } from './prefer-canonical-length-guard.mjs';
11
8
  import { preferComparisonOverNullishGuard } from './prefer-comparison-over-nullish-guard.mjs';
12
9
  import { preferIsNonNullObject } from './prefer-is-non-null-object.mjs';
13
10
  import { preferIsRecordAndHasKey } from './prefer-is-record-and-has-key.mjs';
@@ -17,12 +14,9 @@ import { preferRangeForLoop } from './prefer-range-for-loop.mjs';
17
14
 
18
15
  const tsDataForgeRules = {
19
16
  "prefer-canonical-array-slicing": preferCanonicalArraySlicing,
20
- "prefer-arr-is-min-length-array": preferArrIsMinLengthArray,
21
- "prefer-arr-is-max-length-array": preferArrIsMaxLengthArray,
22
- "prefer-arr-is-bounded-length-array": preferArrIsBoundedLengthArray,
23
- "prefer-arr-is-fixed-length-array": preferArrIsFixedLengthArray,
17
+ "prefer-canonical-length-guard": preferCanonicalLengthGuard,
18
+ "prefer-canonical-length-cast": preferCanonicalLengthCast,
24
19
  "prefer-arr-is-array": preferArrIsArray,
25
- "prefer-arr-is-non-empty": preferArrIsNonEmpty,
26
20
  "prefer-arr-sum": preferArrSum,
27
21
  "prefer-as-int": preferAsInt,
28
22
  "prefer-is-non-null-object": preferIsNonNullObject,
@@ -1 +1 @@
1
- {"version":3,"file":"rules.mjs","sources":["../../src/rules/rules.mts"],"sourcesContent":["import { type ESLintPlugin } from '../types.mjs';\nimport { noUnnecessaryTypeGuard } from './no-unnecessary-type-guard.mjs';\nimport { preferArrIsArray } from './prefer-arr-is-array.mjs';\nimport { preferArrIsBoundedLengthArray } from './prefer-arr-is-bounded-length-array.mjs';\nimport { preferArrIsFixedLengthArray } from './prefer-arr-is-fixed-length-array.mjs';\nimport { preferArrIsMaxLengthArray } from './prefer-arr-is-max-length-array.mjs';\nimport { preferArrIsMinLengthArray } from './prefer-arr-is-min-length-array.mjs';\nimport { preferArrIsNonEmpty } from './prefer-arr-is-non-empty.mjs';\nimport { preferArrSum } from './prefer-arr-sum.mjs';\nimport { preferAsInt } from './prefer-as-int.mjs';\nimport { preferCanonicalArraySlicing } from './prefer-canonical-array-slicing.mjs';\nimport { preferComparisonOverNullishGuard } from './prefer-comparison-over-nullish-guard.mjs';\nimport { preferIsNonNullObject } from './prefer-is-non-null-object.mjs';\nimport { preferIsRecordAndHasKey } from './prefer-is-record-and-has-key.mjs';\nimport { preferNumSafeParseFloat } from './prefer-num-safe-parse-float.mjs';\nimport { preferNumSafeParseInt } from './prefer-num-safe-parse-int.mjs';\nimport { preferRangeForLoop } from './prefer-range-for-loop.mjs';\n\nexport const tsDataForgeRules = {\n 'prefer-canonical-array-slicing': preferCanonicalArraySlicing,\n 'prefer-arr-is-min-length-array': preferArrIsMinLengthArray,\n 'prefer-arr-is-max-length-array': preferArrIsMaxLengthArray,\n 'prefer-arr-is-bounded-length-array': preferArrIsBoundedLengthArray,\n 'prefer-arr-is-fixed-length-array': preferArrIsFixedLengthArray,\n 'prefer-arr-is-array': preferArrIsArray,\n 'prefer-arr-is-non-empty': preferArrIsNonEmpty,\n 'prefer-arr-sum': preferArrSum,\n 'prefer-as-int': preferAsInt,\n 'prefer-is-non-null-object': preferIsNonNullObject,\n 'prefer-range-for-loop': preferRangeForLoop,\n 'prefer-is-record-and-has-key': preferIsRecordAndHasKey,\n 'prefer-num-safe-parse-int': preferNumSafeParseInt,\n 'prefer-num-safe-parse-float': preferNumSafeParseFloat,\n 'no-unnecessary-type-guard': noUnnecessaryTypeGuard,\n 'prefer-comparison-over-nullish-guard': preferComparisonOverNullishGuard,\n} as const satisfies ESLintPlugin['rules'];\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAkBO,MAAM,gBAAA,GAAmB;AAAA,EAC9B,gCAAA,EAAkC,2BAAA;AAAA,EAClC,gCAAA,EAAkC,yBAAA;AAAA,EAClC,gCAAA,EAAkC,yBAAA;AAAA,EAClC,oCAAA,EAAsC,6BAAA;AAAA,EACtC,kCAAA,EAAoC,2BAAA;AAAA,EACpC,qBAAA,EAAuB,gBAAA;AAAA,EACvB,yBAAA,EAA2B,mBAAA;AAAA,EAC3B,gBAAA,EAAkB,YAAA;AAAA,EAClB,eAAA,EAAiB,WAAA;AAAA,EACjB,2BAAA,EAA6B,qBAAA;AAAA,EAC7B,uBAAA,EAAyB,kBAAA;AAAA,EACzB,8BAAA,EAAgC,uBAAA;AAAA,EAChC,2BAAA,EAA6B,qBAAA;AAAA,EAC7B,6BAAA,EAA+B,uBAAA;AAAA,EAC/B,2BAAA,EAA6B,sBAAA;AAAA,EAC7B,sCAAA,EAAwC;AAC1C;;;;"}
1
+ {"version":3,"file":"rules.mjs","sources":["../../src/rules/rules.mts"],"sourcesContent":["import { type ESLintPlugin } from '../types.mjs';\nimport { noUnnecessaryTypeGuard } from './no-unnecessary-type-guard.mjs';\nimport { preferArrIsArray } from './prefer-arr-is-array.mjs';\nimport { preferArrSum } from './prefer-arr-sum.mjs';\nimport { preferAsInt } from './prefer-as-int.mjs';\nimport { preferCanonicalArraySlicing } from './prefer-canonical-array-slicing.mjs';\nimport { preferCanonicalLengthCast } from './prefer-canonical-length-cast.mjs';\nimport { preferCanonicalLengthGuard } from './prefer-canonical-length-guard.mjs';\nimport { preferComparisonOverNullishGuard } from './prefer-comparison-over-nullish-guard.mjs';\nimport { preferIsNonNullObject } from './prefer-is-non-null-object.mjs';\nimport { preferIsRecordAndHasKey } from './prefer-is-record-and-has-key.mjs';\nimport { preferNumSafeParseFloat } from './prefer-num-safe-parse-float.mjs';\nimport { preferNumSafeParseInt } from './prefer-num-safe-parse-int.mjs';\nimport { preferRangeForLoop } from './prefer-range-for-loop.mjs';\n\nexport const tsDataForgeRules = {\n 'prefer-canonical-array-slicing': preferCanonicalArraySlicing,\n 'prefer-canonical-length-guard': preferCanonicalLengthGuard,\n 'prefer-canonical-length-cast': preferCanonicalLengthCast,\n 'prefer-arr-is-array': preferArrIsArray,\n 'prefer-arr-sum': preferArrSum,\n 'prefer-as-int': preferAsInt,\n 'prefer-is-non-null-object': preferIsNonNullObject,\n 'prefer-range-for-loop': preferRangeForLoop,\n 'prefer-is-record-and-has-key': preferIsRecordAndHasKey,\n 'prefer-num-safe-parse-int': preferNumSafeParseInt,\n 'prefer-num-safe-parse-float': preferNumSafeParseFloat,\n 'no-unnecessary-type-guard': noUnnecessaryTypeGuard,\n 'prefer-comparison-over-nullish-guard': preferComparisonOverNullishGuard,\n} as const satisfies ESLintPlugin['rules'];\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAeO,MAAM,gBAAA,GAAmB;AAAA,EAC9B,gCAAA,EAAkC,2BAAA;AAAA,EAClC,+BAAA,EAAiC,0BAAA;AAAA,EACjC,8BAAA,EAAgC,yBAAA;AAAA,EAChC,qBAAA,EAAuB,gBAAA;AAAA,EACvB,gBAAA,EAAkB,YAAA;AAAA,EAClB,eAAA,EAAiB,WAAA;AAAA,EACjB,2BAAA,EAA6B,qBAAA;AAAA,EAC7B,uBAAA,EAAyB,kBAAA;AAAA,EACzB,8BAAA,EAAgC,uBAAA;AAAA,EAChC,2BAAA,EAA6B,qBAAA;AAAA,EAC7B,6BAAA,EAA+B,uBAAA;AAAA,EAC/B,2BAAA,EAA6B,sBAAA;AAAA,EAC7B,sCAAA,EAAwC;AAC1C;;;;"}
package/dist/types.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type TSESLint } from '@typescript-eslint/utils';
2
2
  import { type DeepReadonly } from 'ts-type-forge';
3
3
  export type ESLintPlugin = DeepReadonly<TSESLint.FlatConfig.Plugin>;
4
+ export type ESLintFlatConfig = DeepReadonly<TSESLint.FlatConfig.Config>;
4
5
  //# sourceMappingURL=types.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../src/types.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../src/types.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAEpE,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-ts-data-forge",
3
- "version": "0.1.7",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "ESLint rules that steer code toward ts-data-forge idioms.",
6
6
  "keywords": [
@@ -35,8 +35,8 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "@typescript-eslint/utils": "8.64.0",
38
- "ts-type-forge": "~7.2.1",
39
- "ts-data-forge": "12.2.2"
38
+ "ts-type-forge": "^9.1.1",
39
+ "ts-data-forge": "14.0.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@typescript-eslint/parser": "8.64.0",
@@ -51,6 +51,10 @@
51
51
  "node": ">=22.22.2",
52
52
  "pnpm": ">=9.0.0"
53
53
  },
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "provenance": true
57
+ },
54
58
  "scripts": {
55
59
  "build": "tsx ./scripts/cmd/build.mts",
56
60
  "build:min": "tsx ./scripts/cmd/build.mts --skip-check",
package/src/index.mts CHANGED
@@ -4,4 +4,4 @@ export type {
4
4
  EslintTsDataForgeRulesOption,
5
5
  } from './rule-types.mjs';
6
6
  export { tsDataForgeRules } from './rules/index.mjs';
7
- export { type ESLintPlugin } from './types.mjs';
7
+ export { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';
package/src/plugin.mts CHANGED
@@ -1,9 +1,53 @@
1
1
  import { tsDataForgeRules } from './rules/index.mjs';
2
- import { type ESLintPlugin } from './types.mjs';
2
+ import { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';
3
3
 
4
- export const eslintPluginTsDataForge: ESLintPlugin = {
4
+ /**
5
+ * Every rule this plugin ships, at `error`.
6
+ *
7
+ * The `satisfies` clause below is keyed off {@link tsDataForgeRules}, so adding
8
+ * a rule without listing it here fails to type-check.
9
+ */
10
+ const recommendedRules = {
11
+ 'ts-data-forge/prefer-canonical-array-slicing': 'error',
12
+ 'ts-data-forge/prefer-canonical-length-cast': 'error',
13
+ 'ts-data-forge/prefer-canonical-length-guard': 'error',
14
+ 'ts-data-forge/prefer-arr-is-array': 'error',
15
+ 'ts-data-forge/prefer-arr-sum': 'error',
16
+ 'ts-data-forge/prefer-as-int': 'error',
17
+ 'ts-data-forge/prefer-is-non-null-object': 'error',
18
+ 'ts-data-forge/prefer-range-for-loop': 'error',
19
+ 'ts-data-forge/prefer-is-record-and-has-key': 'error',
20
+ 'ts-data-forge/prefer-num-safe-parse-int': 'error',
21
+ 'ts-data-forge/prefer-num-safe-parse-float': 'error',
22
+ 'ts-data-forge/no-unnecessary-type-guard': 'error',
23
+ 'ts-data-forge/prefer-comparison-over-nullish-guard': 'error',
24
+ } as const satisfies Readonly<
25
+ Record<`ts-data-forge/${keyof typeof tsDataForgeRules}`, 'error'>
26
+ >;
27
+
28
+ const recommendedConfig = {
29
+ name: 'ts-data-forge/recommended',
30
+ plugins: {
31
+ // Resolved lazily so that this config registers the *same* object that
32
+ // `eslintPluginTsDataForge` refers to. ESLint rejects a plugin name that
33
+ // maps to two different objects with `Cannot redefine plugin`, which is
34
+ // what a user combining this preset with their own
35
+ // `plugins: { 'ts-data-forge': eslintPluginTsDataForge }` entry would hit
36
+ // if the preset embedded a separate copy of the plugin.
37
+ get 'ts-data-forge'(): ESLintPlugin {
38
+ return eslintPluginTsDataForge;
39
+ },
40
+ },
41
+ rules: recommendedRules,
42
+ } as const satisfies ESLintFlatConfig;
43
+
44
+ export const eslintPluginTsDataForge = {
5
45
  meta: {
6
46
  name: 'eslint-plugin-ts-data-forge',
7
47
  },
8
48
  rules: tsDataForgeRules,
9
- } as const;
49
+ configs: {
50
+ /** Enables every rule of this plugin at `error`. */
51
+ recommended: recommendedConfig,
52
+ },
53
+ } as const satisfies ESLintPlugin;
@@ -0,0 +1,24 @@
1
+ import { eslintPluginTsDataForge } from './plugin.mjs';
2
+ import { tsDataForgeRules } from './rules/index.mjs';
3
+
4
+ describe('eslintPluginTsDataForge.configs.recommended', () => {
5
+ const recommended = eslintPluginTsDataForge.configs.recommended;
6
+
7
+ test('enables every rule of the plugin, and nothing else, at "error"', () => {
8
+ assert.deepStrictEqual(
9
+ recommended.rules,
10
+ Object.fromEntries(
11
+ Object.keys(tsDataForgeRules).map((name) => [
12
+ `ts-data-forge/${name}`,
13
+ 'error',
14
+ ]),
15
+ ),
16
+ );
17
+ });
18
+
19
+ test('registers the exported plugin object itself', () => {
20
+ // Registering a *copy* would make `Cannot redefine plugin` errors possible
21
+ // for users who also list the plugin in their own `plugins` record.
22
+ expect(recommended.plugins['ts-data-forge']).toBe(eslintPluginTsDataForge);
23
+ });
24
+ });
@@ -43,7 +43,7 @@ namespace PreferCanonicalArraySlicing {
43
43
  }
44
44
 
45
45
  /**
46
- * @description Replace `xs.length >= n` with `Arr.isMinLengthArray(xs, n)` from ts-data-forge.
46
+ * @description Normalize array-length checks to their canonical `Arr` guard: `xs.length <op> n` becomes the matching `Arr.is*` guard, and a degenerate guard becomes the named one for its family (`Arr.isFixedLengthArray(0, xs)` → `Arr.isEmpty`, `Arr.isFixedLengthTuple(0, xs)` → `Arr.isEmptyTuple`).
47
47
  *
48
48
  * ```md
49
49
  * | key | value |
@@ -53,12 +53,12 @@ namespace PreferCanonicalArraySlicing {
53
53
  * | fixable | code |
54
54
  * ```
55
55
  */
56
- namespace PreferArrIsMinLengthArray {
56
+ namespace PreferCanonicalLengthGuard {
57
57
  export type RuleEntry = Linter.StringSeverity;
58
58
  }
59
59
 
60
60
  /**
61
- * @description Replace `xs.length <= n` with `Arr.isMaxLengthArray(xs, n)` from ts-data-forge.
61
+ * @description Normalize an `Arr` length cast whose bounds are degenerate (e.g. `Arr.asMinLengthArray(1, xs)`, `Arr.asFixedLengthTuple(0, xs)`) to the cast that names that constraint directly, staying within the branded `*Array` or structural `*Tuple` family.
62
62
  *
63
63
  * ```md
64
64
  * | key | value |
@@ -68,37 +68,7 @@ namespace PreferArrIsMinLengthArray {
68
68
  * | fixable | code |
69
69
  * ```
70
70
  */
71
- namespace PreferArrIsMaxLengthArray {
72
- export type RuleEntry = Linter.StringSeverity;
73
- }
74
-
75
- /**
76
- * @description Replace `xs.length >= min && xs.length <= max` with `Arr.isBoundedLengthArray(xs, min, max)` from ts-data-forge.
77
- *
78
- * ```md
79
- * | key | value |
80
- * | :--------- | :--------- |
81
- * | type | suggestion |
82
- * | deprecated | false |
83
- * | fixable | code |
84
- * ```
85
- */
86
- namespace PreferArrIsBoundedLengthArray {
87
- export type RuleEntry = Linter.StringSeverity;
88
- }
89
-
90
- /**
91
- * @description Replace `xs.length === n` with `Arr.isFixedLengthArray(xs, n)` from ts-data-forge.
92
- *
93
- * ```md
94
- * | key | value |
95
- * | :--------- | :--------- |
96
- * | type | suggestion |
97
- * | deprecated | false |
98
- * | fixable | code |
99
- * ```
100
- */
101
- namespace PreferArrIsFixedLengthArray {
71
+ namespace PreferCanonicalLengthCast {
102
72
  export type RuleEntry = Linter.StringSeverity;
103
73
  }
104
74
 
@@ -117,21 +87,6 @@ namespace PreferArrIsArray {
117
87
  export type RuleEntry = Linter.StringSeverity;
118
88
  }
119
89
 
120
- /**
121
- * @description Replace `xs.length > 0` with `Arr.isNonEmpty(xs)` from ts-data-forge.
122
- *
123
- * ```md
124
- * | key | value |
125
- * | :--------- | :--------- |
126
- * | type | suggestion |
127
- * | deprecated | false |
128
- * | fixable | code |
129
- * ```
130
- */
131
- namespace PreferArrIsNonEmpty {
132
- export type RuleEntry = Linter.StringSeverity;
133
- }
134
-
135
90
  /**
136
91
  * @description Replace `xs.reduce((a, b) => a + b, 0)` with `Arr.sum(xs)` or `Arr.sumBy(xs, fn)` from ts-data-forge.
137
92
  *
@@ -297,12 +252,9 @@ namespace PreferComparisonOverNullishGuard {
297
252
 
298
253
  export type EslintTsDataForgeRules = Readonly<{
299
254
  'ts-data-forge/prefer-canonical-array-slicing': PreferCanonicalArraySlicing.RuleEntry;
300
- 'ts-data-forge/prefer-arr-is-min-length-array': PreferArrIsMinLengthArray.RuleEntry;
301
- 'ts-data-forge/prefer-arr-is-max-length-array': PreferArrIsMaxLengthArray.RuleEntry;
302
- 'ts-data-forge/prefer-arr-is-bounded-length-array': PreferArrIsBoundedLengthArray.RuleEntry;
303
- 'ts-data-forge/prefer-arr-is-fixed-length-array': PreferArrIsFixedLengthArray.RuleEntry;
255
+ 'ts-data-forge/prefer-canonical-length-guard': PreferCanonicalLengthGuard.RuleEntry;
256
+ 'ts-data-forge/prefer-canonical-length-cast': PreferCanonicalLengthCast.RuleEntry;
304
257
  'ts-data-forge/prefer-arr-is-array': PreferArrIsArray.RuleEntry;
305
- 'ts-data-forge/prefer-arr-is-non-empty': PreferArrIsNonEmpty.RuleEntry;
306
258
  'ts-data-forge/prefer-arr-sum': PreferArrSum.RuleEntry;
307
259
  'ts-data-forge/prefer-as-int': PreferAsInt.RuleEntry;
308
260
  'ts-data-forge/prefer-is-non-null-object': PreferIsNonNullObject.RuleEntry;
@@ -149,4 +149,3 @@ export const buildImportFixes = (
149
149
  // (organize-imports will handle merging/deduplication)
150
150
  return [fixer.insertTextBefore(program, insertionText)];
151
151
  };
152
- /* eslint-enable @typescript-eslint/prefer-readonly-parameter-types */
@@ -125,7 +125,7 @@ export const preferArrIsBoundedLengthArray: TSESLint.RuleModule<
125
125
  max: maxText,
126
126
  },
127
127
  fix: (fixer) => {
128
- const replacement = `Arr.isBoundedLengthArray(${arrayText}, ${minText}, ${maxText})`;
128
+ const replacement = `Arr.isBoundedLengthArray(${minText}, ${maxText}, ${arrayText})`;
129
129
 
130
130
  const importFixes =
131
131
  index === 0 && !hasArrImport
@@ -85,7 +85,7 @@ describe('prefer-arr-is-bounded-length-array', () => {
85
85
  output: dedent`
86
86
  import { Arr } from 'ts-data-forge';
87
87
  const xs: readonly number[] = [1, 2, 3];
88
- const ok = Arr.isBoundedLengthArray(xs, 1, 5);
88
+ const ok = Arr.isBoundedLengthArray(1, 5, xs);
89
89
  `,
90
90
  errors: [{ messageId: 'useIsBoundedLengthArray' }],
91
91
  },
@@ -98,7 +98,7 @@ describe('prefer-arr-is-bounded-length-array', () => {
98
98
  output: dedent`
99
99
  import { Arr } from 'ts-data-forge';
100
100
  const xs: readonly number[] = [1, 2, 3];
101
- const ok = Arr.isBoundedLengthArray(xs, 1, 5);
101
+ const ok = Arr.isBoundedLengthArray(1, 5, xs);
102
102
  `,
103
103
  errors: [{ messageId: 'useIsBoundedLengthArray' }],
104
104
  },
@@ -111,7 +111,7 @@ describe('prefer-arr-is-bounded-length-array', () => {
111
111
  output: dedent`
112
112
  import { Arr } from 'ts-data-forge';
113
113
  const xs: readonly number[] = [1, 2, 3];
114
- const ok = Arr.isBoundedLengthArray(xs, 1, 5);
114
+ const ok = Arr.isBoundedLengthArray(1, 5, xs);
115
115
  `,
116
116
  errors: [{ messageId: 'useIsBoundedLengthArray' }],
117
117
  },
@@ -128,7 +128,7 @@ describe('prefer-arr-is-bounded-length-array', () => {
128
128
  const xs = [1, 2, 3];
129
129
  const lo = 1;
130
130
  const hi = 5;
131
- const ok = Arr.isBoundedLengthArray(xs, lo, hi);
131
+ const ok = Arr.isBoundedLengthArray(lo, hi, xs);
132
132
  `,
133
133
  errors: [{ messageId: 'useIsBoundedLengthArray' }],
134
134
  },
@@ -144,7 +144,7 @@ describe('prefer-arr-is-bounded-length-array', () => {
144
144
  import { Arr } from 'ts-data-forge';
145
145
 
146
146
  const xs = [1, 2, 3];
147
- const ok = Arr.isBoundedLengthArray(xs, 1, 5);
147
+ const ok = Arr.isBoundedLengthArray(1, 5, xs);
148
148
  `,
149
149
  errors: [{ messageId: 'useIsBoundedLengthArray' }],
150
150
  },
@@ -122,7 +122,7 @@ export const preferArrIsFixedLengthArray: TSESLint.RuleModule<
122
122
  length: lengthText,
123
123
  },
124
124
  fix: (fixer) => {
125
- const baseCall = `Arr.isFixedLengthArray(${arrayText}, ${lengthText})`;
125
+ const baseCall = `Arr.isFixedLengthArray(${lengthText}, ${arrayText})`;
126
126
 
127
127
  const replacement = isNegated ? `!${baseCall}` : baseCall;
128
128
 
@@ -76,7 +76,7 @@ describe('prefer-arr-is-fixed-length-array', () => {
76
76
  output: dedent`
77
77
  import { Arr } from 'ts-data-forge';
78
78
  const xs: readonly number[] = [1, 2, 3];
79
- const ok = Arr.isFixedLengthArray(xs, 3);
79
+ const ok = Arr.isFixedLengthArray(3, xs);
80
80
  `,
81
81
  errors: [{ messageId: 'useIsFixedLengthArray' }],
82
82
  },
@@ -89,7 +89,7 @@ describe('prefer-arr-is-fixed-length-array', () => {
89
89
  output: dedent`
90
90
  import { Arr } from 'ts-data-forge';
91
91
  const xs: readonly number[] = [1, 2, 3];
92
- const ok = Arr.isFixedLengthArray(xs, 3);
92
+ const ok = Arr.isFixedLengthArray(3, xs);
93
93
  `,
94
94
  errors: [{ messageId: 'useIsFixedLengthArray' }],
95
95
  },
@@ -102,7 +102,7 @@ describe('prefer-arr-is-fixed-length-array', () => {
102
102
  output: dedent`
103
103
  import { Arr } from 'ts-data-forge';
104
104
  const xs = [1, 2, 3];
105
- const ok = Arr.isFixedLengthArray(xs, 3);
105
+ const ok = Arr.isFixedLengthArray(3, xs);
106
106
  `,
107
107
  errors: [{ messageId: 'useIsFixedLengthArray' }],
108
108
  },
@@ -115,7 +115,7 @@ describe('prefer-arr-is-fixed-length-array', () => {
115
115
  output: dedent`
116
116
  import { Arr } from 'ts-data-forge';
117
117
  const xs = [1, 2, 3] as const;
118
- const ok = Arr.isFixedLengthArray(xs, 3);
118
+ const ok = Arr.isFixedLengthArray(3, xs);
119
119
  `,
120
120
  errors: [{ messageId: 'useIsFixedLengthArray' }],
121
121
  },
@@ -130,7 +130,7 @@ describe('prefer-arr-is-fixed-length-array', () => {
130
130
  import { Arr } from 'ts-data-forge';
131
131
  const xs = [1, 2, 3];
132
132
  const n = 3;
133
- const ok = Arr.isFixedLengthArray(xs, n);
133
+ const ok = Arr.isFixedLengthArray(n, xs);
134
134
  `,
135
135
  errors: [{ messageId: 'useIsFixedLengthArray' }],
136
136
  },
@@ -146,7 +146,7 @@ describe('prefer-arr-is-fixed-length-array', () => {
146
146
  import { Arr } from 'ts-data-forge';
147
147
 
148
148
  const xs = [1, 2, 3];
149
- const ok = Arr.isFixedLengthArray(xs, 3);
149
+ const ok = Arr.isFixedLengthArray(3, xs);
150
150
  `,
151
151
  errors: [{ messageId: 'useIsFixedLengthArray' }],
152
152
  },
@@ -162,8 +162,8 @@ describe('prefer-arr-is-fixed-length-array', () => {
162
162
  import { Arr } from 'ts-data-forge';
163
163
  const xs = [1, 2, 3];
164
164
  const ys = [4, 5];
165
- const ok1 = Arr.isFixedLengthArray(xs, 3);
166
- const ok2 = Arr.isFixedLengthArray(ys, 2);
165
+ const ok1 = Arr.isFixedLengthArray(3, xs);
166
+ const ok2 = Arr.isFixedLengthArray(2, ys);
167
167
  `,
168
168
  errors: [
169
169
  { messageId: 'useIsFixedLengthArray' },
@@ -179,7 +179,7 @@ describe('prefer-arr-is-fixed-length-array', () => {
179
179
  output: dedent`
180
180
  import { Arr } from 'ts-data-forge';
181
181
  const xs: readonly number[] = [1, 2, 3];
182
- const ok = !Arr.isFixedLengthArray(xs, 3);
182
+ const ok = !Arr.isFixedLengthArray(3, xs);
183
183
  `,
184
184
  errors: [{ messageId: 'useIsFixedLengthArray' }],
185
185
  },
@@ -192,7 +192,7 @@ describe('prefer-arr-is-fixed-length-array', () => {
192
192
  output: dedent`
193
193
  import { Arr } from 'ts-data-forge';
194
194
  const xs: readonly number[] = [1, 2, 3];
195
- const ok = !Arr.isFixedLengthArray(xs, 3);
195
+ const ok = !Arr.isFixedLengthArray(3, xs);
196
196
  `,
197
197
  errors: [{ messageId: 'useIsFixedLengthArray' }],
198
198
  },
@@ -205,7 +205,7 @@ describe('prefer-arr-is-fixed-length-array', () => {
205
205
  output: dedent`
206
206
  import { Arr } from 'ts-data-forge';
207
207
  const xs = [1, 2, 3];
208
- const ok = !Arr.isFixedLengthArray(xs, 5);
208
+ const ok = !Arr.isFixedLengthArray(5, xs);
209
209
  `,
210
210
  errors: [{ messageId: 'useIsFixedLengthArray' }],
211
211
  },
@@ -123,7 +123,7 @@ export const preferArrIsMaxLengthArray: TSESLint.RuleModule<
123
123
  length: lengthText,
124
124
  },
125
125
  fix: (fixer) => {
126
- const replacement = `Arr.isMaxLengthArray(${arrayText}, ${lengthText})`;
126
+ const replacement = `Arr.isMaxLengthArray(${lengthText}, ${arrayText})`;
127
127
 
128
128
  const importFixes =
129
129
  index === 0 && !hasArrImport
@@ -89,7 +89,7 @@ describe('prefer-arr-is-max-length-array', () => {
89
89
  output: dedent`
90
90
  import { Arr } from 'ts-data-forge';
91
91
  const xs: readonly number[] = [1, 2, 3];
92
- const ok = Arr.isMaxLengthArray(xs, 3);
92
+ const ok = Arr.isMaxLengthArray(3, xs);
93
93
  `,
94
94
  errors: [{ messageId: 'useIsMaxLengthArray' }],
95
95
  },
@@ -102,7 +102,7 @@ describe('prefer-arr-is-max-length-array', () => {
102
102
  output: dedent`
103
103
  import { Arr } from 'ts-data-forge';
104
104
  const xs: readonly number[] = [1, 2, 3];
105
- const ok = Arr.isMaxLengthArray(xs, 3);
105
+ const ok = Arr.isMaxLengthArray(3, xs);
106
106
  `,
107
107
  errors: [{ messageId: 'useIsMaxLengthArray' }],
108
108
  },
@@ -115,7 +115,7 @@ describe('prefer-arr-is-max-length-array', () => {
115
115
  output: dedent`
116
116
  import { Arr } from 'ts-data-forge';
117
117
  const xs = [1, 2, 3];
118
- const ok = Arr.isMaxLengthArray(xs, 1);
118
+ const ok = Arr.isMaxLengthArray(1, xs);
119
119
  `,
120
120
  errors: [{ messageId: 'useIsMaxLengthArray' }],
121
121
  },
@@ -128,7 +128,7 @@ describe('prefer-arr-is-max-length-array', () => {
128
128
  output: dedent`
129
129
  import { Arr } from 'ts-data-forge';
130
130
  const xs = [1, 2, 3] as const;
131
- const ok = Arr.isMaxLengthArray(xs, 2);
131
+ const ok = Arr.isMaxLengthArray(2, xs);
132
132
  `,
133
133
  errors: [{ messageId: 'useIsMaxLengthArray' }],
134
134
  },
@@ -143,7 +143,7 @@ describe('prefer-arr-is-max-length-array', () => {
143
143
  import { Arr } from 'ts-data-forge';
144
144
  const xs = [1, 2, 3];
145
145
  const n = 2;
146
- const ok = Arr.isMaxLengthArray(xs, n);
146
+ const ok = Arr.isMaxLengthArray(n, xs);
147
147
  `,
148
148
  errors: [{ messageId: 'useIsMaxLengthArray' }],
149
149
  },
@@ -159,7 +159,7 @@ describe('prefer-arr-is-max-length-array', () => {
159
159
  import { Arr } from 'ts-data-forge';
160
160
 
161
161
  const xs = [1, 2, 3];
162
- const ok = Arr.isMaxLengthArray(xs, 1);
162
+ const ok = Arr.isMaxLengthArray(1, xs);
163
163
  `,
164
164
  errors: [{ messageId: 'useIsMaxLengthArray' }],
165
165
  },
@@ -175,8 +175,8 @@ describe('prefer-arr-is-max-length-array', () => {
175
175
  import { Arr } from 'ts-data-forge';
176
176
  const xs = [1, 2, 3];
177
177
  const ys = [4, 5];
178
- const ok1 = Arr.isMaxLengthArray(xs, 2);
179
- const ok2 = Arr.isMaxLengthArray(ys, 1);
178
+ const ok1 = Arr.isMaxLengthArray(2, xs);
179
+ const ok2 = Arr.isMaxLengthArray(1, ys);
180
180
  `,
181
181
  errors: [
182
182
  { messageId: 'useIsMaxLengthArray' },