eslint-plugin-ts-data-forge 0.2.0 → 0.4.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.
- package/README.md +16 -14
- package/dist/plugin.d.mts +3 -1
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +1 -0
- package/dist/plugin.mjs.map +1 -1
- package/dist/rule-types.d.mts +16 -1
- package/dist/rule-types.d.mts.map +1 -1
- package/dist/rules/import-utils.mjs.map +1 -1
- package/dist/rules/no-unnecessary-type-guard.d.mts.map +1 -1
- package/dist/rules/no-unnecessary-type-guard.mjs +13 -1
- package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-bounded-length-array.mjs +1 -1
- package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-fixed-length-array.mjs +1 -1
- package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-max-length-array.mjs +1 -1
- package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-is-min-length-array.mjs +1 -1
- package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -1
- package/dist/rules/prefer-arr-sum.mjs +2 -2
- package/dist/rules/prefer-arr-sum.mjs.map +1 -1
- package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
- package/dist/rules/prefer-canonical-length-cast.d.mts +6 -0
- package/dist/rules/prefer-canonical-length-cast.d.mts.map +1 -0
- package/dist/rules/prefer-canonical-length-cast.mjs +155 -0
- package/dist/rules/prefer-canonical-length-cast.mjs.map +1 -0
- package/dist/rules/prefer-canonical-length-guard.d.mts.map +1 -1
- package/dist/rules/prefer-canonical-length-guard.mjs +12 -8
- package/dist/rules/prefer-canonical-length-guard.mjs.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.d.mts +1 -1
- package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.mjs +30 -16
- package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
- package/dist/rules/prefer-range-for-loop.mjs +1 -1
- package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
- package/dist/rules/rules.d.mts +2 -1
- package/dist/rules/rules.d.mts.map +1 -1
- package/dist/rules/rules.mjs +2 -0
- package/dist/rules/rules.mjs.map +1 -1
- package/package.json +6 -6
- package/src/plugin.mts +1 -0
- package/src/rule-types.mts +17 -1
- package/src/rules/import-utils.mts +0 -1
- package/src/rules/no-unnecessary-type-guard.mts +65 -0
- package/src/rules/no-unnecessary-type-guard.test.mts +94 -0
- package/src/rules/prefer-arr-is-bounded-length-array.mts +1 -1
- package/src/rules/prefer-arr-is-bounded-length-array.test.mts +5 -5
- package/src/rules/prefer-arr-is-fixed-length-array.mts +1 -1
- package/src/rules/prefer-arr-is-fixed-length-array.test.mts +11 -11
- package/src/rules/prefer-arr-is-max-length-array.mts +1 -1
- package/src/rules/prefer-arr-is-max-length-array.test.mts +8 -8
- package/src/rules/prefer-arr-is-min-length-array.mts +1 -1
- package/src/rules/prefer-arr-is-min-length-array.test.mts +9 -9
- package/src/rules/prefer-arr-sum.mts +2 -2
- package/src/rules/prefer-canonical-array-slicing.mts +0 -2
- package/src/rules/prefer-canonical-length-cast.mts +290 -0
- package/src/rules/prefer-canonical-length-cast.test.mts +305 -0
- package/src/rules/prefer-canonical-length-guard.mts +40 -21
- package/src/rules/prefer-canonical-length-guard.test.mts +32 -32
- package/src/rules/prefer-is-record-and-has-key.mts +84 -20
- package/src/rules/prefer-is-record-and-has-key.test.mts +74 -0
- package/src/rules/prefer-range-for-loop.mts +1 -1
- package/src/rules/rules.mts +2 -0
|
@@ -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(${
|
|
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":";;;AAgBO,MAAM,yBAAA,GAGT;AAAA,EACF,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,YAAA;AAAA,IACN,IAAA,EAAM;AAAA,MACJ,WAAA,EACE;AAAA,KACJ;AAAA,IACA,OAAA,EAAS,MAAA;AAAA,IACT,QAAQ,EAAC;AAAA,IACT,QAAA,EAAU;AAAA,MACR,mBAAA,EACE;AAAA;AACJ,GACF;AAAA,EAEA,MAAA,EAAQ,CAAC,OAAA,KAAY;AACnB,IAAA,MAAM,aAAa,OAAA,CAAQ,UAAA;AAE3B,IAAA,MAAM,UAAU,UAAA,CAAW,GAAA;AAE3B,IAAA,MAAM,iBAAA,GAAoB,qBAAqB,OAAO,CAAA;AAEtD,IAAA,MAAM,QAAA,GAAW,QAAQ,UAAA,CAAW,cAAA;AAEpC,IAAA,MAAM,iBAIA,EAAC;AAEP,IAAA,OAAO;AAAA,MACL,gBAAA,EAAkB,CAAC,IAAA,KAAS;AAE1B,QAAA,IAAI,IAAA,CAAK,QAAA,KAAa,IAAA,IAAQ,IAAA,CAAK,aAAa,IAAA,EAAM;AAItD,QAAA,IAAI,0BAAA,CAA2B,IAAA,EAAM,UAAU,CAAA,EAAG;AAGlD,QAAA,MAAM,cAAA,GAAiB,KAAK,QAAA,KAAa,IAAA;AAEzC,QAAA,MAAM,UAAA,GAAa,IAAA,CAAK,cAAA,GAAiB,MAAA,GAAS,OAAO,CAAA;AAEzD,QAAA,MAAM,SAAA,GAAY,IAAA,CAAK,cAAA,GAAiB,OAAA,GAAU,MAAM,CAAA;AAGxD,QAAA,IAAI,CAAC,cAAA,CAAe,UAAU,CAAA,EAAG;AAGjC,QAAA,IAAI,CAAC,0BAAA,CAA2B,SAAA,EAAW,UAAU,CAAA,EAAG;AAGxD,QAAA,MAAM,kBAAkB,UAAA,CAAW,MAAA;AAGnC,QAAA,IAAI,QAAA,EAAU,OAAA,KAAY,MAAA,IAAa,QAAA,CAAS,YAAY,IAAA,EAAM;AAChE,UAAA,MAAM,OAAA,GAAU,QAAA,CAAS,OAAA,CAAQ,cAAA,EAAe;AAEhD,UAAA,MAAM,MAAA,GAAS,QAAA,CAAS,qBAAA,EAAuB,GAAA,CAAI,eAAe,CAAA;AAElE,UAAA,IAAI,WAAW,MAAA,EAAW;AACxB,YAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,iBAAA,CAAkB,MAAM,CAAA;AAG7C,YAAA,MAAM,cACJ,OAAA,CAAQ,WAAA,CAAY,IAAI,CAAA,IAAK,OAAA,CAAQ,YAAY,IAAI,CAAA;AAEvD,YAAA,IAAI,CAAC,WAAA,EAAa;AAAA,UACpB,CAAA,MAAO;AACL,YAAA;AAAA,UACF;AAAA,QACF,CAAA,MAAO;AACL,UAAA;AAAA,QACF;AAEA,QAAA,cAAA,CAAe,IAAA,CAAK;AAAA,UAClB,IAAA;AAAA,UACA,eAAA;AAAA,UACA,gBAAA,EAAkB;AAAA,SACnB,CAAA;AAAA,MACH,CAAA;AAAA,MACA,gBAAgB,MAAM;AACpB,QAAA,MAAM,YAAA,GAAe,gBAAgB,iBAAiB,CAAA;AAEtD,QAAA,MAAM,YAAA,GAAe,YAAA,CAAa,QAAA,CAAS,KAAK,CAAA;AAEhD,QAAA,KAAA,MAAW;AAAA,UACT,KAAA;AAAA,UACA,EAAE,IAAA,EAAM,eAAA,EAAiB,gBAAA;AAAiB,SAC5C,IAAK,cAAA,CAAe,OAAA,EAAQ,EAAG;AAC7B,UAAA,MAAM,SAAA,GAAY,UAAA,CAAW,OAAA,CAAQ,eAAe,CAAA;AAEpD,UAAA,MAAM,UAAA,GAAa,UAAA,CAAW,OAAA,CAAQ,gBAAgB,CAAA;AAEtD,UAAA,MAAM,YAAA,GAAe,UAAA,CAAW,OAAA,CAAQ,IAAI,CAAA;AAE5C,UAAA,OAAA,CAAQ,MAAA,CAAO;AAAA,YACb,IAAA;AAAA,YACA,SAAA,EAAW,qBAAA;AAAA,YACX,IAAA,EAAM;AAAA,cACJ,QAAA,EAAU,YAAA;AAAA,cACV,SAAA,EAAW,SAAA;AAAA,cACX,MAAA,EAAQ;AAAA,aACV;AAAA,YACA,GAAA,EAAK,CAAC,KAAA,KAAU;AACd,cAAA,MAAM,WAAA,GAAc,CAAA,qBAAA,EAAwB,UAAU,CAAA,EAAA,EAAK,SAAS,CAAA,CAAA,CAAA;AAEpE,cAAA,MAAM,WAAA,GACJ,KAAA,KAAU,CAAA,IAAK,CAAC,YAAA,GACZ,gBAAA,CAAiB,KAAA,EAAO,OAAA,EAAS,iBAAA,EAAmB,CAAC,KAAK,CAAC,IAC3D,EAAC;AAEP,cAAA,OAAO,CAAC,GAAG,WAAA,EAAa,MAAM,WAAA,CAAY,IAAA,EAAM,WAAW,CAAC,CAAA;AAAA,YAC9D;AAAA,WACD,CAAA;AAAA,QACH;AAAA,MACF;AAAA,KACF;AAAA,EACF,CAAA;AAAA,EACA,gBAAgB;AAClB;;;;"}
|
|
@@ -27,13 +27,13 @@ const preferArrSum = {
|
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
const arrayExpression = node.callee.object;
|
|
30
|
-
if (!Arr.isFixedLengthTuple(node.arguments
|
|
30
|
+
if (!Arr.isFixedLengthTuple(2, node.arguments)) return;
|
|
31
31
|
const reducer = node.arguments[0];
|
|
32
32
|
const initialValue = node.arguments[1];
|
|
33
33
|
if (initialValue.type !== AST_NODE_TYPES.Literal || initialValue.value !== 0) {
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
if (reducer.type !== AST_NODE_TYPES.ArrowFunctionExpression || !Arr.isFixedLengthTuple(reducer.params
|
|
36
|
+
if (reducer.type !== AST_NODE_TYPES.ArrowFunctionExpression || !Arr.isFixedLengthTuple(2, reducer.params)) {
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
39
|
const [param1, param2] = reducer.params;
|
|
@@ -1 +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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"}
|
|
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(2, node.arguments)) 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(2, reducer.params)\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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-canonical-array-slicing.mjs","sources":["../../src/rules/prefer-canonical-array-slicing.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n ESLintUtils,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport * as ts from 'typescript';\nimport {\n buildImportFixes,\n getImportedLocalName,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\ntype Options = readonly [];\n\ntype MessageIds = 'preferCanonicalArraySlicing';\n\n/**\n * A canonical ts-data-forge `Arr` operation a detected pattern is rewritten to:\n *\n * - `tail` / `butLast` — drop the first / last element\n * - `skip N` / `take N` — drop / keep the first `N` elements\n * - `skipLast N` / `takeLast N` — drop / keep the last `N` elements\n * - `toUnshifted item` / `toPushed item` — prepend / append one element\n * (`toUnshifted` is emitted in its curried form `toUnshifted(item)(xs)` so\n * the new element keeps the leading position it had in `[item, ...xs]`)\n */\ntype CopyOp = Readonly<\n | { fn: 'butLast' | 'tail' }\n | { fn: 'skip' | 'skipLast' | 'take' | 'takeLast'; count: number }\n | { fn: 'toPushed' | 'toUnshifted'; itemText: string }\n>;\n\n/** `skip 1` / `skipLast 1` have dedicated single-element names. */\nconst normalizeCountedOp = (\n fn: 'skip' | 'skipLast' | 'take' | 'takeLast',\n count: number,\n): CopyOp | undefined =>\n count < 1\n ? undefined\n : fn === 'skip' && count === 1\n ? ({ fn: 'tail' } as const)\n : fn === 'skipLast' && count === 1\n ? ({ fn: 'butLast' } as const)\n : ({ fn, count } as const);\n\nconst asNonNegativeIntegerLiteral = (\n node: TSESTree.Node,\n): number | undefined =>\n node.type === AST_NODE_TYPES.Literal &&\n typeof node.value === 'number' &&\n Number.isInteger(node.value) &&\n node.value >= 0\n ? node.value\n : undefined;\n\n/** Matches `-N` (unary minus of a positive integer literal) and returns `N`. */\nconst asNegatedPositiveIntegerLiteral = (\n node: TSESTree.Node,\n): number | undefined => {\n if (\n node.type !== AST_NODE_TYPES.UnaryExpression ||\n node.operator !== '-' ||\n node.argument.type !== AST_NODE_TYPES.Literal\n ) {\n return undefined;\n }\n\n const n = asNonNegativeIntegerLiteral(node.argument);\n\n return n !== undefined && n >= 1 ? n : undefined;\n};\n\n/**\n * Whether re-reading the expression is free of side effects (a plain variable\n * or a non-computed property chain). Required for patterns that reference the\n * receiver twice, such as `xs.filter((_, i) => i < xs.length - 1)`.\n */\nconst isSimpleReference = (node: TSESTree.Node): boolean =>\n node.type === AST_NODE_TYPES.Identifier ||\n node.type === AST_NODE_TYPES.ThisExpression ||\n (node.type === AST_NODE_TYPES.MemberExpression &&\n !node.computed &&\n node.property.type === AST_NODE_TYPES.Identifier &&\n isSimpleReference(node.object));\n\nconst rootIdentifierName = (node: TSESTree.Node): string | undefined =>\n node.type === AST_NODE_TYPES.Identifier\n ? node.name\n : node.type === AST_NODE_TYPES.MemberExpression\n ? rootIdentifierName(node.object)\n : undefined;\n\ntype ComparisonOperator = '!==' | '<' | '<=' | '>' | '>=';\n\nconst COMPARISON_OPERATORS: ReadonlySet<string> = new Set([\n '!==',\n '<',\n '<=',\n '>',\n '>=',\n] as const satisfies readonly ComparisonOperator[]);\n\n/** Mirror of each comparison when its operands are swapped. */\nconst FLIPPED_OPERATORS = {\n '!==': '!==',\n '<': '>',\n '<=': '>=',\n '>': '<',\n '>=': '<=',\n} as const satisfies Record<ComparisonOperator, ComparisonOperator>;\n\n/** Classifies `index OP N` (a comparison against an integer literal). */\nconst classifyLiteralComparison = (\n op: ComparisonOperator,\n n: number,\n): CopyOp | undefined => {\n switch (op) {\n case '>=':\n return normalizeCountedOp('skip', n);\n\n case '>':\n return normalizeCountedOp('skip', n + 1);\n\n case '!==':\n return n === 0 ? { fn: 'tail' } : undefined;\n\n case '<':\n return normalizeCountedOp('take', n);\n\n case '<=':\n return normalizeCountedOp('take', n + 1);\n }\n};\n\n/** Classifies `index OP <xs>.length - M`. */\nconst classifyLengthComparison = (\n op: ComparisonOperator,\n m: number,\n): CopyOp | undefined => {\n switch (op) {\n case '<':\n return normalizeCountedOp('skipLast', m);\n\n case '<=':\n return normalizeCountedOp('skipLast', m - 1);\n\n case '!==':\n return m === 1 ? { fn: 'butLast' } : undefined;\n\n case '>=':\n return normalizeCountedOp('takeLast', m);\n\n case '>':\n return normalizeCountedOp('takeLast', m - 1);\n }\n};\n\nexport const preferCanonicalArraySlicing: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Unify non-mutating array element addition/removal patterns (`slice` / `toSpliced` / `filter` / `concat` / spread) into the corresponding ts-data-forge functions: `Arr.tail`, `Arr.skip`, `Arr.take`, `Arr.butLast`, `Arr.skipLast`, `Arr.takeLast`, `Arr.toUnshifted`, `Arr.toPushed`.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n preferCanonicalArraySlicing:\n 'Replace with `{{replacement}}` 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 parserServices = ESLintUtils.getParserServices(context);\n\n const checker = parserServices.program.getTypeChecker();\n\n /**\n * The in-scope way to reference the `Arr` namespace: a named import\n * (possibly aliased), a namespace import member, or the canonical `Arr`\n * (added by the import fix when nothing is imported yet).\n */\n const arrLocalName = getImportedLocalName(tsDataForgeImport, 'Arr');\n\n const namespaceLocalName = tsDataForgeImport?.specifiers.find(\n (specifier) => specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier,\n )?.local.name;\n\n const arrRef =\n arrLocalName ??\n (namespaceLocalName === undefined ? 'Arr' : `${namespaceLocalName}.Arr`);\n\n const needsArrImport =\n arrLocalName === undefined && namespaceLocalName === undefined;\n\n /**\n * Deferred (non-concrete) types are reduced to their base constraint\n * before checking; `any` / `unknown` are conservatively not array-like.\n */\n const isArrayOrTupleType = (type: ts.Type): boolean => {\n if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) {\n return false;\n }\n\n if (type.isUnion()) {\n return type.types.every(isArrayOrTupleType);\n }\n\n if (\n (type.flags &\n (ts.TypeFlags.TypeParameter |\n ts.TypeFlags.IndexedAccess |\n ts.TypeFlags.Conditional |\n ts.TypeFlags.Substitution)) !==\n 0\n ) {\n const constraint = checker.getBaseConstraintOfType(type);\n\n return constraint !== undefined && isArrayOrTupleType(constraint);\n }\n\n return checker.isArrayType(type) || checker.isTupleType(type);\n };\n\n const getTypeOf = (node: TSESTree.Node): ts.Type =>\n checker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(node));\n\n /**\n * `true` only when the type can never be an array in any branch, so\n * `xs.concat(v)` is guaranteed to append `v` as one element rather than\n * flattening it.\n */\n const isDefinitelyNotArrayType = (type: ts.Type): boolean => {\n if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) {\n return false;\n }\n\n if (type.isUnion()) {\n return type.types.every(isDefinitelyNotArrayType);\n }\n\n if (\n (type.flags &\n (ts.TypeFlags.TypeParameter |\n ts.TypeFlags.IndexedAccess |\n ts.TypeFlags.Conditional |\n ts.TypeFlags.Substitution)) !==\n 0\n ) {\n const constraint = checker.getBaseConstraintOfType(type);\n\n return constraint !== undefined && isDefinitelyNotArrayType(constraint);\n }\n\n return !checker.isArrayType(type) && !checker.isTupleType(type);\n };\n\n /** Argument-position text; a comma (sequence) expression needs parens. */\n const toArgText = (node: TSESTree.Node): string => {\n const text = sourceCode.getText(node);\n\n return node.type === AST_NODE_TYPES.SequenceExpression\n ? `(${text})`\n : text;\n };\n\n const toReplacementText = (op: CopyOp, xsText: string): string => {\n switch (op.fn) {\n case 'tail':\n case 'butLast':\n return `${arrRef}.${op.fn}(${xsText})`;\n\n case 'skip':\n case 'take':\n case 'skipLast':\n case 'takeLast':\n return `${arrRef}.${op.fn}(${xsText}, ${op.count})`;\n\n // Emit the curried form for `toUnshifted` so the new element stays in\n // the same textual position as in the `[item, ...xs]` source, keeping\n // the rewrite easy to read.\n case 'toUnshifted':\n return `${arrRef}.toUnshifted(${op.itemText})(${xsText})`;\n\n case 'toPushed':\n return `${arrRef}.toPushed(${xsText}, ${op.itemText})`;\n }\n };\n\n const mut_nodesToFix: {\n node: TSESTree.Node;\n replacement: string;\n }[] = [];\n\n const report = (node: TSESTree.Node, op: CopyOp, xsText: string): void => {\n mut_nodesToFix.push({\n node,\n replacement: toReplacementText(op, xsText),\n });\n };\n\n /** Matches `<xsText>.length` (non-computed). */\n const isReceiverLengthAccess = (\n node: TSESTree.Node,\n receiverText: string,\n ): boolean =>\n node.type === AST_NODE_TYPES.MemberExpression &&\n !node.computed &&\n node.property.type === AST_NODE_TYPES.Identifier &&\n node.property.name === 'length' &&\n sourceCode.getText(node.object) === receiverText;\n\n /** Matches `<xsText>.length - M` (M ≥ 1) and returns `M`. */\n const matchLengthMinus = (\n node: TSESTree.Node,\n receiverText: string,\n ): number | undefined => {\n if (\n node.type !== AST_NODE_TYPES.BinaryExpression ||\n node.operator !== '-' ||\n !isReceiverLengthAccess(node.left, receiverText)\n ) {\n return undefined;\n }\n\n const m = asNonNegativeIntegerLiteral(node.right);\n\n return m !== undefined && m >= 1 ? m : undefined;\n };\n\n /**\n * Matches an index-only filter callback (`(_, i) => <comparison>`) and\n * classifies the comparison. Both a leading index (`i >= 1`) and a\n * trailing index (`1 <= i`) operand order are accepted.\n */\n const classifyFilterCallback = (\n callback: TSESTree.CallExpressionArgument,\n receiver: TSESTree.Expression,\n receiverText: string,\n ): CopyOp | undefined => {\n if (\n callback.type !== AST_NODE_TYPES.ArrowFunctionExpression ||\n callback.async ||\n callback.body.type === AST_NODE_TYPES.BlockStatement ||\n callback.params.length !== 2\n ) {\n return undefined;\n }\n\n const [elementParam, indexParam] = callback.params;\n\n if (\n elementParam === undefined ||\n indexParam === undefined ||\n elementParam.type !== AST_NODE_TYPES.Identifier ||\n indexParam.type !== AST_NODE_TYPES.Identifier\n ) {\n return undefined;\n }\n\n // A callback parameter shadowing the receiver's root binding (e.g.\n // `xs.filter((xs, i) => i < xs.length - 1)`) would make the textual\n // `xs.length` comparison refer to the element, not the receiver.\n const receiverRoot = rootIdentifierName(receiver);\n\n if (\n receiverRoot !== undefined &&\n (elementParam.name === receiverRoot || indexParam.name === receiverRoot)\n ) {\n return undefined;\n }\n\n const body = callback.body;\n\n if (\n body.type !== AST_NODE_TYPES.BinaryExpression ||\n !COMPARISON_OPERATORS.has(body.operator)\n ) {\n return undefined;\n }\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n const rawOperator = body.operator as ComparisonOperator;\n\n const isIndex = (side: TSESTree.Node): boolean =>\n side.type === AST_NODE_TYPES.Identifier &&\n side.name === indexParam.name;\n\n const [op, other] = isIndex(body.left)\n ? ([rawOperator, body.right] as const)\n : isIndex(body.right)\n ? ([FLIPPED_OPERATORS[rawOperator], body.left] as const)\n : [undefined, undefined];\n\n // `op` and `other` are set together (both from the same tuple), so\n // checking `op` also guarantees `other` is defined.\n if (op === undefined || isIndex(other)) {\n return undefined;\n }\n\n // The element parameter must not participate in the comparison (only\n // the index side and a receiver-independent bound are recognized, so\n // reaching here with an element reference is impossible for literals,\n // but `xs.length - N` needs a stable receiver).\n const n = asNonNegativeIntegerLiteral(other);\n\n if (n !== undefined) {\n return classifyLiteralComparison(op, n);\n }\n\n if (!isSimpleReference(receiver)) return undefined;\n\n const m = matchLengthMinus(other, receiverText);\n\n return m === undefined ? undefined : classifyLengthComparison(op, m);\n };\n\n const classifySliceArgs = (\n args: readonly TSESTree.CallExpressionArgument[],\n receiver: TSESTree.Expression,\n ): CopyOp | undefined => {\n if (args.length === 1) {\n const [start] = args;\n\n if (start === undefined) return undefined;\n\n const n = asNonNegativeIntegerLiteral(start);\n\n if (n !== undefined) return normalizeCountedOp('skip', n);\n\n const negated = asNegatedPositiveIntegerLiteral(start);\n\n return negated === undefined\n ? undefined\n : normalizeCountedOp('takeLast', negated);\n }\n\n if (args.length === 2) {\n const [start, end] = args;\n\n if (\n start === undefined ||\n end === undefined ||\n asNonNegativeIntegerLiteral(start) !== 0\n ) {\n return undefined;\n }\n\n const n = asNonNegativeIntegerLiteral(end);\n\n if (n !== undefined) return normalizeCountedOp('take', n);\n\n const negated = asNegatedPositiveIntegerLiteral(end);\n\n if (negated !== undefined) {\n return normalizeCountedOp('skipLast', negated);\n }\n\n if (!isSimpleReference(receiver)) return undefined;\n\n const m = matchLengthMinus(end, sourceCode.getText(receiver));\n\n return m === undefined ? undefined : normalizeCountedOp('skipLast', m);\n }\n\n return undefined;\n };\n\n const classifyToSplicedArgs = (\n args: readonly TSESTree.CallExpressionArgument[],\n receiver: TSESTree.Expression,\n receiverText: string,\n ): CopyOp | undefined => {\n if (args.length === 1) {\n const [start] = args;\n\n if (start === undefined) return undefined;\n\n const n = asNonNegativeIntegerLiteral(start);\n\n // `xs.toSpliced(N)` removes everything from index N on → take N.\n if (n !== undefined) return normalizeCountedOp('take', n);\n\n const negated = asNegatedPositiveIntegerLiteral(start);\n\n // `xs.toSpliced(-N)` removes the last N elements → skipLast N.\n return negated === undefined\n ? undefined\n : normalizeCountedOp('skipLast', negated);\n }\n\n if (args.length === 2) {\n const [start, deleteCount] = args;\n\n if (\n start === undefined ||\n deleteCount === undefined ||\n asNonNegativeIntegerLiteral(start) !== 0\n ) {\n return undefined;\n }\n\n // `xs.toSpliced(0, N)` removes the first N elements → skip N.\n const n = asNonNegativeIntegerLiteral(deleteCount);\n\n if (n !== undefined) return normalizeCountedOp('skip', n);\n\n // `xs.toSpliced(0, xs.length - N)` keeps the last N → takeLast N.\n // (Note: `xs.toSpliced(0, -N)` clamps the delete count to 0 and is a\n // plain copy, so it is deliberately NOT treated as takeLast.)\n if (!isSimpleReference(receiver)) return undefined;\n\n const m = matchLengthMinus(deleteCount, receiverText);\n\n return m === undefined ? undefined : normalizeCountedOp('takeLast', m);\n }\n\n if (args.length === 3) {\n const [start, deleteCount, item] = args;\n\n if (\n start === undefined ||\n deleteCount === undefined ||\n item === undefined ||\n item.type === AST_NODE_TYPES.SpreadElement ||\n asNonNegativeIntegerLiteral(deleteCount) !== 0\n ) {\n return undefined;\n }\n\n // `xs.toSpliced(0, 0, item)` prepends → toUnshifted.\n if (asNonNegativeIntegerLiteral(start) === 0) {\n return { fn: 'toUnshifted', itemText: toArgText(item) };\n }\n\n // `xs.toSpliced(xs.length, 0, item)` appends → toPushed.\n if (\n isSimpleReference(receiver) &&\n isReceiverLengthAccess(start, receiverText)\n ) {\n return { fn: 'toPushed', itemText: toArgText(item) };\n }\n\n return undefined;\n }\n\n return undefined;\n };\n\n return {\n CallExpression: (node) => {\n const callee = node.callee;\n\n if (\n callee.type !== AST_NODE_TYPES.MemberExpression ||\n callee.computed ||\n callee.property.type !== AST_NODE_TYPES.Identifier\n ) {\n return;\n }\n\n const methodName = callee.property.name;\n\n const receiver = callee.object;\n\n // `Arr.skip(xs, 1)` → `Arr.tail(xs)`, `Arr.skipLast(xs, 1)` →\n // `Arr.butLast(xs)` (also via a namespace import or an alias).\n if (methodName === 'skip' || methodName === 'skipLast') {\n const isArrObject =\n (receiver.type === AST_NODE_TYPES.Identifier &&\n receiver.name === arrLocalName) ||\n (receiver.type === AST_NODE_TYPES.MemberExpression &&\n !receiver.computed &&\n receiver.object.type === AST_NODE_TYPES.Identifier &&\n receiver.object.name === namespaceLocalName &&\n receiver.property.type === AST_NODE_TYPES.Identifier &&\n receiver.property.name === 'Arr');\n\n const [arrayArg, countArg] = node.arguments;\n\n if (\n isArrObject &&\n node.arguments.length === 2 &&\n arrayArg !== undefined &&\n arrayArg.type !== AST_NODE_TYPES.SpreadElement &&\n countArg !== undefined &&\n asNonNegativeIntegerLiteral(countArg) === 1\n ) {\n report(\n node,\n { fn: methodName === 'skip' ? 'tail' : 'butLast' },\n toArgText(arrayArg),\n );\n }\n\n return;\n }\n\n // `[item].concat(xs)` → `Arr.toUnshifted(xs, item)`.\n if (\n methodName === 'concat' &&\n receiver.type === AST_NODE_TYPES.ArrayExpression\n ) {\n const [element] = receiver.elements;\n\n const [arg] = node.arguments;\n\n if (\n receiver.elements.length === 1 &&\n element !== null &&\n element !== undefined &&\n element.type !== AST_NODE_TYPES.SpreadElement &&\n node.arguments.length === 1 &&\n arg !== undefined &&\n arg.type !== AST_NODE_TYPES.SpreadElement &&\n isArrayOrTupleType(getTypeOf(arg))\n ) {\n report(\n node,\n { fn: 'toUnshifted', itemText: toArgText(element) },\n toArgText(arg),\n );\n }\n\n return;\n }\n\n if (\n methodName !== 'slice' &&\n methodName !== 'toSpliced' &&\n methodName !== 'filter' &&\n methodName !== 'concat'\n ) {\n return;\n }\n\n // The receiver must be an array or tuple (not a string / typed array\n // / other object that happens to share the method name).\n if (!isArrayOrTupleType(getTypeOf(receiver))) return;\n\n const receiverText = sourceCode.getText(receiver);\n\n switch (methodName) {\n case 'slice': {\n const op = classifySliceArgs(node.arguments, receiver);\n\n if (op !== undefined) report(node, op, toArgText(receiver));\n\n return;\n }\n\n case 'toSpliced': {\n const op = classifyToSplicedArgs(\n node.arguments,\n receiver,\n receiverText,\n );\n\n if (op !== undefined) report(node, op, toArgText(receiver));\n\n return;\n }\n\n case 'filter': {\n const [callback] = node.arguments;\n\n if (callback === undefined || node.arguments.length !== 1) {\n return;\n }\n\n const op = classifyFilterCallback(callback, receiver, receiverText);\n\n if (op !== undefined) report(node, op, toArgText(receiver));\n\n return;\n }\n\n case 'concat': {\n const [arg] = node.arguments;\n\n if (\n node.arguments.length !== 1 ||\n arg === undefined ||\n arg.type === AST_NODE_TYPES.SpreadElement\n ) {\n return;\n }\n\n // `xs.concat([item])` → `Arr.toPushed(xs, item)`.\n if (arg.type === AST_NODE_TYPES.ArrayExpression) {\n const [element] = arg.elements;\n\n if (\n arg.elements.length === 1 &&\n element !== null &&\n element !== undefined &&\n element.type !== AST_NODE_TYPES.SpreadElement\n ) {\n report(\n node,\n { fn: 'toPushed', itemText: toArgText(element) },\n toArgText(receiver),\n );\n }\n\n return;\n }\n\n // `xs.concat(item)` appends only when `item` can never be an\n // array (otherwise `concat` would flatten it).\n if (isDefinitelyNotArrayType(getTypeOf(arg))) {\n report(\n node,\n { fn: 'toPushed', itemText: toArgText(arg) },\n toArgText(receiver),\n );\n }\n }\n }\n },\n\n ArrayExpression: (node) => {\n // `[item, ...xs]` → `Arr.toUnshifted(xs, item)`,\n // `[...xs, item]` → `Arr.toPushed(xs, item)`.\n if (node.elements.length !== 2) return;\n\n const [first, second] = node.elements;\n\n if (\n first === null ||\n first === undefined ||\n second === null ||\n second === undefined\n ) {\n return;\n }\n\n const [spread, item, fn] =\n first.type === AST_NODE_TYPES.SpreadElement &&\n second.type !== AST_NODE_TYPES.SpreadElement\n ? ([first, second, 'toPushed'] as const)\n : first.type !== AST_NODE_TYPES.SpreadElement &&\n second.type === AST_NODE_TYPES.SpreadElement\n ? ([second, first, 'toUnshifted'] as const)\n : [undefined, undefined, undefined];\n\n // `spread`, `item`, and `fn` are set together (all from the same\n // tuple), so checking `spread` also guarantees the others are\n // defined.\n if (spread === undefined) return;\n\n // Spreading a non-array iterable (`[v, ...someSet]`) is a genuine\n // conversion, not an element addition to an array.\n if (!isArrayOrTupleType(getTypeOf(spread.argument))) return;\n\n report(\n node,\n { fn, itemText: toArgText(item) },\n toArgText(spread.argument),\n );\n },\n\n 'Program:exit': () => {\n for (const [index, nodeInfo] of mut_nodesToFix.entries()) {\n context.report({\n node: nodeInfo.node,\n messageId: 'preferCanonicalArraySlicing',\n data: { replacement: nodeInfo.replacement },\n fix: (fixer) => {\n const importFixes =\n index === 0 && needsArrImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])\n : [];\n\n return [\n ...importFixes,\n fixer.replaceText(nodeInfo.node, nodeInfo.replacement),\n ];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n} as const;\n\n/* eslint-enable @typescript-eslint/prefer-readonly-parameter-types */\n"],"names":[],"mappings":";;;;AAoCA,MAAM,kBAAA,GAAqB,CACzB,EAAA,EACA,KAAA,KAEA,KAAA,GAAQ,IACJ,MAAA,GACA,EAAA,KAAO,MAAA,IAAU,KAAA,KAAU,CAAA,GACxB,EAAE,IAAI,MAAA,EAAO,GACd,EAAA,KAAO,UAAA,IAAc,KAAA,KAAU,CAAA,GAC5B,EAAE,EAAA,EAAI,SAAA,EAAU,GAChB,EAAE,EAAA,EAAI,KAAA,EAAM;AAEvB,MAAM,2BAAA,GAA8B,CAClC,IAAA,KAEA,IAAA,CAAK,SAAS,cAAA,CAAe,OAAA,IAC7B,OAAO,IAAA,CAAK,KAAA,KAAU,YACtB,MAAA,CAAO,SAAA,CAAU,KAAK,KAAK,CAAA,IAC3B,KAAK,KAAA,IAAS,CAAA,GACV,KAAK,KAAA,GACL,MAAA;AAGN,MAAM,+BAAA,GAAkC,CACtC,IAAA,KACuB;AACvB,EAAA,IACE,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,eAAA,IAC7B,IAAA,CAAK,QAAA,KAAa,GAAA,IAClB,IAAA,CAAK,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,OAAA,EACtC;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,CAAA,GAAI,2BAAA,CAA4B,IAAA,CAAK,QAAQ,CAAA;AAEnD,EAAA,OAAO,CAAA,KAAM,MAAA,IAAa,CAAA,IAAK,CAAA,GAAI,CAAA,GAAI,MAAA;AACzC,CAAA;AAOA,MAAM,iBAAA,GAAoB,CAAC,IAAA,KACzB,IAAA,CAAK,IAAA,KAAS,eAAe,UAAA,IAC7B,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,cAAA,IAC5B,IAAA,CAAK,SAAS,cAAA,CAAe,gBAAA,IAC5B,CAAC,IAAA,CAAK,QAAA,IACN,IAAA,CAAK,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,UAAA,IACtC,iBAAA,CAAkB,IAAA,CAAK,MAAM,CAAA;AAEjC,MAAM,qBAAqB,CAAC,IAAA,KAC1B,IAAA,CAAK,IAAA,KAAS,eAAe,UAAA,GACzB,IAAA,CAAK,IAAA,GACL,IAAA,CAAK,SAAS,cAAA,CAAe,gBAAA,GAC3B,kBAAA,CAAmB,IAAA,CAAK,MAAM,CAAA,GAC9B,MAAA;AAIR,MAAM,oBAAA,uBAAgD,GAAA,CAAI;AAAA,EACxD,KAAA;AAAA,EACA,GAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA;AACF,CAAkD,CAAA;AAGlD,MAAM,iBAAA,GAAoB;AAAA,EACxB,KAAA,EAAO,KAAA;AAAA,EACP,GAAA,EAAK,GAAA;AAAA,EACL,IAAA,EAAM,IAAA;AAAA,EACN,GAAA,EAAK,GAAA;AAAA,EACL,IAAA,EAAM;AACR,CAAA;AAGA,MAAM,yBAAA,GAA4B,CAChC,EAAA,EACA,CAAA,KACuB;AACvB,EAAA,QAAQ,EAAA;AAAI,IACV,KAAK,IAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAAA,IAErC,KAAK,GAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,MAAA,EAAQ,CAAA,GAAI,CAAC,CAAA;AAAA,IAEzC,KAAK,KAAA;AACH,MAAA,OAAO,CAAA,KAAM,CAAA,GAAI,EAAE,EAAA,EAAI,QAAO,GAAI,MAAA;AAAA,IAEpC,KAAK,GAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAAA,IAErC,KAAK,IAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,MAAA,EAAQ,CAAA,GAAI,CAAC,CAAA;AAAA;AAE7C,CAAA;AAGA,MAAM,wBAAA,GAA2B,CAC/B,EAAA,EACA,CAAA,KACuB;AACvB,EAAA,QAAQ,EAAA;AAAI,IACV,KAAK,GAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,YAAY,CAAC,CAAA;AAAA,IAEzC,KAAK,IAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,UAAA,EAAY,CAAA,GAAI,CAAC,CAAA;AAAA,IAE7C,KAAK,KAAA;AACH,MAAA,OAAO,CAAA,KAAM,CAAA,GAAI,EAAE,EAAA,EAAI,WAAU,GAAI,MAAA;AAAA,IAEvC,KAAK,IAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,YAAY,CAAC,CAAA;AAAA,IAEzC,KAAK,GAAA;AACH,MAAA,OAAO,kBAAA,CAAmB,UAAA,EAAY,CAAA,GAAI,CAAC,CAAA;AAAA;AAEjD,CAAA;AAEO,MAAM,2BAAA,GAGT;AAAA,EACF,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,YAAA;AAAA,IACN,IAAA,EAAM;AAAA,MACJ,WAAA,EACE;AAAA,KACJ;AAAA,IACA,OAAA,EAAS,MAAA;AAAA,IACT,QAAQ,EAAC;AAAA,IACT,QAAA,EAAU;AAAA,MACR,2BAAA,EACE;AAAA;AACJ,GACF;AAAA,EAEA,MAAA,EAAQ,CAAC,OAAA,KAAY;AACnB,IAAA,MAAM,aAAa,OAAA,CAAQ,UAAA;AAE3B,IAAA,MAAM,UAAU,UAAA,CAAW,GAAA;AAE3B,IAAA,MAAM,iBAAA,GAAoB,qBAAqB,OAAO,CAAA;AAEtD,IAAA,MAAM,cAAA,GAAiB,WAAA,CAAY,iBAAA,CAAkB,OAAO,CAAA;AAE5D,IAAA,MAAM,OAAA,GAAU,cAAA,CAAe,OAAA,CAAQ,cAAA,EAAe;AAOtD,IAAA,MAAM,YAAA,GAAe,oBAAA,CAAqB,iBAAA,EAAmB,KAAK,CAAA;AAElE,IAAA,MAAM,kBAAA,GAAqB,mBAAmB,UAAA,CAAW,IAAA;AAAA,MACvD,CAAC,SAAA,KAAc,SAAA,CAAU,IAAA,KAAS,cAAA,CAAe;AAAA,OAChD,KAAA,CAAM,IAAA;AAET,IAAA,MAAM,SACJ,YAAA,KACC,kBAAA,KAAuB,MAAA,GAAY,KAAA,GAAQ,GAAG,kBAAkB,CAAA,IAAA,CAAA,CAAA;AAEnE,IAAA,MAAM,cAAA,GACJ,YAAA,KAAiB,MAAA,IAAa,kBAAA,KAAuB,MAAA;AAMvD,IAAA,MAAM,kBAAA,GAAqB,CAAC,IAAA,KAA2B;AACrD,MAAA,IAAA,CAAK,IAAA,CAAK,SAAS,EAAA,CAAG,SAAA,CAAU,MAAM,EAAA,CAAG,SAAA,CAAU,cAAc,CAAA,EAAG;AAClE,QAAA,OAAO,KAAA;AAAA,MACT;AAEA,MAAA,IAAI,IAAA,CAAK,SAAQ,EAAG;AAClB,QAAA,OAAO,IAAA,CAAK,KAAA,CAAM,KAAA,CAAM,kBAAkB,CAAA;AAAA,MAC5C;AAEA,MAAA,IAAA,CACG,IAAA,CAAK,KAAA,IACH,EAAA,CAAG,SAAA,CAAU,gBACZ,EAAA,CAAG,SAAA,CAAU,aAAA,GACb,EAAA,CAAG,SAAA,CAAU,WAAA,GACb,EAAA,CAAG,SAAA,CAAU,mBACjB,CAAA,EACA;AACA,QAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,uBAAA,CAAwB,IAAI,CAAA;AAEvD,QAAA,OAAO,UAAA,KAAe,MAAA,IAAa,kBAAA,CAAmB,UAAU,CAAA;AAAA,MAClE;AAEA,MAAA,OAAO,QAAQ,WAAA,CAAY,IAAI,CAAA,IAAK,OAAA,CAAQ,YAAY,IAAI,CAAA;AAAA,IAC9D,CAAA;AAEA,IAAA,MAAM,SAAA,GAAY,CAAC,IAAA,KACjB,OAAA,CAAQ,kBAAkB,cAAA,CAAe,qBAAA,CAAsB,GAAA,CAAI,IAAI,CAAC,CAAA;AAO1E,IAAA,MAAM,wBAAA,GAA2B,CAAC,IAAA,KAA2B;AAC3D,MAAA,IAAA,CAAK,IAAA,CAAK,SAAS,EAAA,CAAG,SAAA,CAAU,MAAM,EAAA,CAAG,SAAA,CAAU,cAAc,CAAA,EAAG;AAClE,QAAA,OAAO,KAAA;AAAA,MACT;AAEA,MAAA,IAAI,IAAA,CAAK,SAAQ,EAAG;AAClB,QAAA,OAAO,IAAA,CAAK,KAAA,CAAM,KAAA,CAAM,wBAAwB,CAAA;AAAA,MAClD;AAEA,MAAA,IAAA,CACG,IAAA,CAAK,KAAA,IACH,EAAA,CAAG,SAAA,CAAU,gBACZ,EAAA,CAAG,SAAA,CAAU,aAAA,GACb,EAAA,CAAG,SAAA,CAAU,WAAA,GACb,EAAA,CAAG,SAAA,CAAU,mBACjB,CAAA,EACA;AACA,QAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,uBAAA,CAAwB,IAAI,CAAA;AAEvD,QAAA,OAAO,UAAA,KAAe,MAAA,IAAa,wBAAA,CAAyB,UAAU,CAAA;AAAA,MACxE;AAEA,MAAA,OAAO,CAAC,QAAQ,WAAA,CAAY,IAAI,KAAK,CAAC,OAAA,CAAQ,YAAY,IAAI,CAAA;AAAA,IAChE,CAAA;AAGA,IAAA,MAAM,SAAA,GAAY,CAAC,IAAA,KAAgC;AACjD,MAAA,MAAM,IAAA,GAAO,UAAA,CAAW,OAAA,CAAQ,IAAI,CAAA;AAEpC,MAAA,OAAO,KAAK,IAAA,KAAS,cAAA,CAAe,kBAAA,GAChC,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,CAAA,GACR,IAAA;AAAA,IACN,CAAA;AAEA,IAAA,MAAM,iBAAA,GAAoB,CAAC,EAAA,EAAY,MAAA,KAA2B;AAChE,MAAA,QAAQ,GAAG,EAAA;AAAI,QACb,KAAK,MAAA;AAAA,QACL,KAAK,SAAA;AACH,UAAA,OAAO,GAAG,MAAM,CAAA,CAAA,EAAI,EAAA,CAAG,EAAE,IAAI,MAAM,CAAA,CAAA,CAAA;AAAA,QAErC,KAAK,MAAA;AAAA,QACL,KAAK,MAAA;AAAA,QACL,KAAK,UAAA;AAAA,QACL,KAAK,UAAA;AACH,UAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,EAAA,CAAG,EAAE,CAAA,CAAA,EAAI,MAAM,CAAA,EAAA,EAAK,EAAA,CAAG,KAAK,CAAA,CAAA,CAAA;AAAA;AAAA;AAAA;AAAA,QAKlD,KAAK,aAAA;AACH,UAAA,OAAO,GAAG,MAAM,CAAA,aAAA,EAAgB,EAAA,CAAG,QAAQ,KAAK,MAAM,CAAA,CAAA,CAAA;AAAA,QAExD,KAAK,UAAA;AACH,UAAA,OAAO,GAAG,MAAM,CAAA,UAAA,EAAa,MAAM,CAAA,EAAA,EAAK,GAAG,QAAQ,CAAA,CAAA,CAAA;AAAA;AACvD,IACF,CAAA;AAEA,IAAA,MAAM,iBAGA,EAAC;AAEP,IAAA,MAAM,MAAA,GAAS,CAAC,IAAA,EAAqB,EAAA,EAAY,MAAA,KAAyB;AACxE,MAAA,cAAA,CAAe,IAAA,CAAK;AAAA,QAClB,IAAA;AAAA,QACA,WAAA,EAAa,iBAAA,CAAkB,EAAA,EAAI,MAAM;AAAA,OAC1C,CAAA;AAAA,IACH,CAAA;AAGA,IAAA,MAAM,sBAAA,GAAyB,CAC7B,IAAA,EACA,YAAA,KAEA,IAAA,CAAK,SAAS,cAAA,CAAe,gBAAA,IAC7B,CAAC,IAAA,CAAK,QAAA,IACN,IAAA,CAAK,SAAS,IAAA,KAAS,cAAA,CAAe,UAAA,IACtC,IAAA,CAAK,QAAA,CAAS,IAAA,KAAS,YACvB,UAAA,CAAW,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA,KAAM,YAAA;AAGtC,IAAA,MAAM,gBAAA,GAAmB,CACvB,IAAA,EACA,YAAA,KACuB;AACvB,MAAA,IACE,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,gBAAA,IAC7B,IAAA,CAAK,QAAA,KAAa,GAAA,IAClB,CAAC,sBAAA,CAAuB,IAAA,CAAK,IAAA,EAAM,YAAY,CAAA,EAC/C;AACA,QAAA,OAAO,MAAA;AAAA,MACT;AAEA,MAAA,MAAM,CAAA,GAAI,2BAAA,CAA4B,IAAA,CAAK,KAAK,CAAA;AAEhD,MAAA,OAAO,CAAA,KAAM,MAAA,IAAa,CAAA,IAAK,CAAA,GAAI,CAAA,GAAI,MAAA;AAAA,IACzC,CAAA;AAOA,IAAA,MAAM,sBAAA,GAAyB,CAC7B,QAAA,EACA,QAAA,EACA,YAAA,KACuB;AACvB,MAAA,IACE,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,uBAAA,IACjC,SAAS,KAAA,IACT,QAAA,CAAS,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,cAAA,IACtC,QAAA,CAAS,MAAA,CAAO,WAAW,CAAA,EAC3B;AACA,QAAA,OAAO,MAAA;AAAA,MACT;AAEA,MAAA,MAAM,CAAC,YAAA,EAAc,UAAU,CAAA,GAAI,QAAA,CAAS,MAAA;AAE5C,MAAA,IACE,YAAA,KAAiB,MAAA,IACjB,UAAA,KAAe,MAAA,IACf,YAAA,CAAa,IAAA,KAAS,cAAA,CAAe,UAAA,IACrC,UAAA,CAAW,IAAA,KAAS,cAAA,CAAe,UAAA,EACnC;AACA,QAAA,OAAO,MAAA;AAAA,MACT;AAKA,MAAA,MAAM,YAAA,GAAe,mBAAmB,QAAQ,CAAA;AAEhD,MAAA,IACE,iBAAiB,MAAA,KAChB,YAAA,CAAa,SAAS,YAAA,IAAgB,UAAA,CAAW,SAAS,YAAA,CAAA,EAC3D;AACA,QAAA,OAAO,MAAA;AAAA,MACT;AAEA,MAAA,MAAM,OAAO,QAAA,CAAS,IAAA;AAEtB,MAAA,IACE,IAAA,CAAK,SAAS,cAAA,CAAe,gBAAA,IAC7B,CAAC,oBAAA,CAAqB,GAAA,CAAI,IAAA,CAAK,QAAQ,CAAA,EACvC;AACA,QAAA,OAAO,MAAA;AAAA,MACT;AAGA,MAAA,MAAM,cAAc,IAAA,CAAK,QAAA;AAEzB,MAAA,MAAM,OAAA,GAAU,CAAC,IAAA,KACf,IAAA,CAAK,SAAS,cAAA,CAAe,UAAA,IAC7B,IAAA,CAAK,IAAA,KAAS,UAAA,CAAW,IAAA;AAE3B,MAAA,MAAM,CAAC,EAAA,EAAI,KAAK,CAAA,GAAI,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA,GAChC,CAAC,WAAA,EAAa,IAAA,CAAK,KAAK,CAAA,GACzB,QAAQ,IAAA,CAAK,KAAK,CAAA,GACf,CAAC,iBAAA,CAAkB,WAAW,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,GAC3C,CAAC,MAAA,EAAW,MAAS,CAAA;AAI3B,MAAA,IAAI,EAAA,KAAO,MAAA,IAAa,OAAA,CAAQ,KAAK,CAAA,EAAG;AACtC,QAAA,OAAO,MAAA;AAAA,MACT;AAMA,MAAA,MAAM,CAAA,GAAI,4BAA4B,KAAK,CAAA;AAE3C,MAAA,IAAI,MAAM,MAAA,EAAW;AACnB,QAAA,OAAO,yBAAA,CAA0B,IAAI,CAAC,CAAA;AAAA,MACxC;AAEA,MAAA,IAAI,CAAC,iBAAA,CAAkB,QAAQ,CAAA,EAAG,OAAO,MAAA;AAEzC,MAAA,MAAM,CAAA,GAAI,gBAAA,CAAiB,KAAA,EAAO,YAAY,CAAA;AAE9C,MAAA,OAAO,CAAA,KAAM,MAAA,GAAY,MAAA,GAAY,wBAAA,CAAyB,IAAI,CAAC,CAAA;AAAA,IACrE,CAAA;AAEA,IAAA,MAAM,iBAAA,GAAoB,CACxB,IAAA,EACA,QAAA,KACuB;AACvB,MAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,QAAA,MAAM,CAAC,KAAK,CAAA,GAAI,IAAA;AAEhB,QAAA,IAAI,KAAA,KAAU,QAAW,OAAO,MAAA;AAEhC,QAAA,MAAM,CAAA,GAAI,4BAA4B,KAAK,CAAA;AAE3C,QAAA,IAAI,CAAA,KAAM,MAAA,EAAW,OAAO,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAExD,QAAA,MAAM,OAAA,GAAU,gCAAgC,KAAK,CAAA;AAErD,QAAA,OAAO,OAAA,KAAY,MAAA,GACf,MAAA,GACA,kBAAA,CAAmB,YAAY,OAAO,CAAA;AAAA,MAC5C;AAEA,MAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,QAAA,MAAM,CAAC,KAAA,EAAO,GAAG,CAAA,GAAI,IAAA;AAErB,QAAA,IACE,UAAU,MAAA,IACV,GAAA,KAAQ,UACR,2BAAA,CAA4B,KAAK,MAAM,CAAA,EACvC;AACA,UAAA,OAAO,MAAA;AAAA,QACT;AAEA,QAAA,MAAM,CAAA,GAAI,4BAA4B,GAAG,CAAA;AAEzC,QAAA,IAAI,CAAA,KAAM,MAAA,EAAW,OAAO,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAExD,QAAA,MAAM,OAAA,GAAU,gCAAgC,GAAG,CAAA;AAEnD,QAAA,IAAI,YAAY,MAAA,EAAW;AACzB,UAAA,OAAO,kBAAA,CAAmB,YAAY,OAAO,CAAA;AAAA,QAC/C;AAEA,QAAA,IAAI,CAAC,iBAAA,CAAkB,QAAQ,CAAA,EAAG,OAAO,MAAA;AAEzC,QAAA,MAAM,IAAI,gBAAA,CAAiB,GAAA,EAAK,UAAA,CAAW,OAAA,CAAQ,QAAQ,CAAC,CAAA;AAE5D,QAAA,OAAO,CAAA,KAAM,MAAA,GAAY,MAAA,GAAY,kBAAA,CAAmB,YAAY,CAAC,CAAA;AAAA,MACvE;AAEA,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAEA,IAAA,MAAM,qBAAA,GAAwB,CAC5B,IAAA,EACA,QAAA,EACA,YAAA,KACuB;AACvB,MAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,QAAA,MAAM,CAAC,KAAK,CAAA,GAAI,IAAA;AAEhB,QAAA,IAAI,KAAA,KAAU,QAAW,OAAO,MAAA;AAEhC,QAAA,MAAM,CAAA,GAAI,4BAA4B,KAAK,CAAA;AAG3C,QAAA,IAAI,CAAA,KAAM,MAAA,EAAW,OAAO,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAExD,QAAA,MAAM,OAAA,GAAU,gCAAgC,KAAK,CAAA;AAGrD,QAAA,OAAO,OAAA,KAAY,MAAA,GACf,MAAA,GACA,kBAAA,CAAmB,YAAY,OAAO,CAAA;AAAA,MAC5C;AAEA,MAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,QAAA,MAAM,CAAC,KAAA,EAAO,WAAW,CAAA,GAAI,IAAA;AAE7B,QAAA,IACE,UAAU,MAAA,IACV,WAAA,KAAgB,UAChB,2BAAA,CAA4B,KAAK,MAAM,CAAA,EACvC;AACA,UAAA,OAAO,MAAA;AAAA,QACT;AAGA,QAAA,MAAM,CAAA,GAAI,4BAA4B,WAAW,CAAA;AAEjD,QAAA,IAAI,CAAA,KAAM,MAAA,EAAW,OAAO,kBAAA,CAAmB,QAAQ,CAAC,CAAA;AAKxD,QAAA,IAAI,CAAC,iBAAA,CAAkB,QAAQ,CAAA,EAAG,OAAO,MAAA;AAEzC,QAAA,MAAM,CAAA,GAAI,gBAAA,CAAiB,WAAA,EAAa,YAAY,CAAA;AAEpD,QAAA,OAAO,CAAA,KAAM,MAAA,GAAY,MAAA,GAAY,kBAAA,CAAmB,YAAY,CAAC,CAAA;AAAA,MACvE;AAEA,MAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,QAAA,MAAM,CAAC,KAAA,EAAO,WAAA,EAAa,IAAI,CAAA,GAAI,IAAA;AAEnC,QAAA,IACE,KAAA,KAAU,MAAA,IACV,WAAA,KAAgB,MAAA,IAChB,IAAA,KAAS,MAAA,IACT,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,aAAA,IAC7B,2BAAA,CAA4B,WAAW,MAAM,CAAA,EAC7C;AACA,UAAA,OAAO,MAAA;AAAA,QACT;AAGA,QAAA,IAAI,2BAAA,CAA4B,KAAK,CAAA,KAAM,CAAA,EAAG;AAC5C,UAAA,OAAO,EAAE,EAAA,EAAI,aAAA,EAAe,QAAA,EAAU,SAAA,CAAU,IAAI,CAAA,EAAE;AAAA,QACxD;AAGA,QAAA,IACE,kBAAkB,QAAQ,CAAA,IAC1B,sBAAA,CAAuB,KAAA,EAAO,YAAY,CAAA,EAC1C;AACA,UAAA,OAAO,EAAE,EAAA,EAAI,UAAA,EAAY,QAAA,EAAU,SAAA,CAAU,IAAI,CAAA,EAAE;AAAA,QACrD;AAEA,QAAA,OAAO,MAAA;AAAA,MACT;AAEA,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAEA,IAAA,OAAO;AAAA,MACL,cAAA,EAAgB,CAAC,IAAA,KAAS;AACxB,QAAA,MAAM,SAAS,IAAA,CAAK,MAAA;AAEpB,QAAA,IACE,MAAA,CAAO,IAAA,KAAS,cAAA,CAAe,gBAAA,IAC/B,MAAA,CAAO,YACP,MAAA,CAAO,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,UAAA,EACxC;AACA,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,UAAA,GAAa,OAAO,QAAA,CAAS,IAAA;AAEnC,QAAA,MAAM,WAAW,MAAA,CAAO,MAAA;AAIxB,QAAA,IAAI,UAAA,KAAe,MAAA,IAAU,UAAA,KAAe,UAAA,EAAY;AACtD,UAAA,MAAM,WAAA,GACH,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,UAAA,IAChC,QAAA,CAAS,IAAA,KAAS,YAAA,IACnB,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,gBAAA,IAChC,CAAC,QAAA,CAAS,QAAA,IACV,QAAA,CAAS,MAAA,CAAO,IAAA,KAAS,cAAA,CAAe,UAAA,IACxC,QAAA,CAAS,OAAO,IAAA,KAAS,kBAAA,IACzB,QAAA,CAAS,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,UAAA,IAC1C,QAAA,CAAS,SAAS,IAAA,KAAS,KAAA;AAE/B,UAAA,MAAM,CAAC,QAAA,EAAU,QAAQ,CAAA,GAAI,IAAA,CAAK,SAAA;AAElC,UAAA,IACE,eACA,IAAA,CAAK,SAAA,CAAU,MAAA,KAAW,CAAA,IAC1B,aAAa,MAAA,IACb,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,iBACjC,QAAA,KAAa,MAAA,IACb,2BAAA,CAA4B,QAAQ,MAAM,CAAA,EAC1C;AACA,YAAA,MAAA;AAAA,cACE,IAAA;AAAA,cACA,EAAE,EAAA,EAAI,UAAA,KAAe,MAAA,GAAS,SAAS,SAAA,EAAU;AAAA,cACjD,UAAU,QAAQ;AAAA,aACpB;AAAA,UACF;AAEA,UAAA;AAAA,QACF;AAGA,QAAA,IACE,UAAA,KAAe,QAAA,IACf,QAAA,CAAS,IAAA,KAAS,eAAe,eAAA,EACjC;AACA,UAAA,MAAM,CAAC,OAAO,CAAA,GAAI,QAAA,CAAS,QAAA;AAE3B,UAAA,MAAM,CAAC,GAAG,CAAA,GAAI,IAAA,CAAK,SAAA;AAEnB,UAAA,IACE,QAAA,CAAS,QAAA,CAAS,MAAA,KAAW,CAAA,IAC7B,OAAA,KAAY,IAAA,IACZ,OAAA,KAAY,MAAA,IACZ,OAAA,CAAQ,IAAA,KAAS,cAAA,CAAe,aAAA,IAChC,IAAA,CAAK,UAAU,MAAA,KAAW,CAAA,IAC1B,GAAA,KAAQ,MAAA,IACR,GAAA,CAAI,IAAA,KAAS,cAAA,CAAe,aAAA,IAC5B,kBAAA,CAAmB,SAAA,CAAU,GAAG,CAAC,CAAA,EACjC;AACA,YAAA,MAAA;AAAA,cACE,IAAA;AAAA,cACA,EAAE,EAAA,EAAI,aAAA,EAAe,QAAA,EAAU,SAAA,CAAU,OAAO,CAAA,EAAE;AAAA,cAClD,UAAU,GAAG;AAAA,aACf;AAAA,UACF;AAEA,UAAA;AAAA,QACF;AAEA,QAAA,IACE,eAAe,OAAA,IACf,UAAA,KAAe,eACf,UAAA,KAAe,QAAA,IACf,eAAe,QAAA,EACf;AACA,UAAA;AAAA,QACF;AAIA,QAAA,IAAI,CAAC,kBAAA,CAAmB,SAAA,CAAU,QAAQ,CAAC,CAAA,EAAG;AAE9C,QAAA,MAAM,YAAA,GAAe,UAAA,CAAW,OAAA,CAAQ,QAAQ,CAAA;AAEhD,QAAA,QAAQ,UAAA;AAAY,UAClB,KAAK,OAAA,EAAS;AACZ,YAAA,MAAM,EAAA,GAAK,iBAAA,CAAkB,IAAA,CAAK,SAAA,EAAW,QAAQ,CAAA;AAErD,YAAA,IAAI,OAAO,MAAA,EAAW,MAAA,CAAO,MAAM,EAAA,EAAI,SAAA,CAAU,QAAQ,CAAC,CAAA;AAE1D,YAAA;AAAA,UACF;AAAA,UAEA,KAAK,WAAA,EAAa;AAChB,YAAA,MAAM,EAAA,GAAK,qBAAA;AAAA,cACT,IAAA,CAAK,SAAA;AAAA,cACL,QAAA;AAAA,cACA;AAAA,aACF;AAEA,YAAA,IAAI,OAAO,MAAA,EAAW,MAAA,CAAO,MAAM,EAAA,EAAI,SAAA,CAAU,QAAQ,CAAC,CAAA;AAE1D,YAAA;AAAA,UACF;AAAA,UAEA,KAAK,QAAA,EAAU;AACb,YAAA,MAAM,CAAC,QAAQ,CAAA,GAAI,IAAA,CAAK,SAAA;AAExB,YAAA,IAAI,QAAA,KAAa,MAAA,IAAa,IAAA,CAAK,SAAA,CAAU,WAAW,CAAA,EAAG;AACzD,cAAA;AAAA,YACF;AAEA,YAAA,MAAM,EAAA,GAAK,sBAAA,CAAuB,QAAA,EAAU,QAAA,EAAU,YAAY,CAAA;AAElE,YAAA,IAAI,OAAO,MAAA,EAAW,MAAA,CAAO,MAAM,EAAA,EAAI,SAAA,CAAU,QAAQ,CAAC,CAAA;AAE1D,YAAA;AAAA,UACF;AAAA,UAEA,KAAK,QAAA,EAAU;AACb,YAAA,MAAM,CAAC,GAAG,CAAA,GAAI,IAAA,CAAK,SAAA;AAEnB,YAAA,IACE,IAAA,CAAK,UAAU,MAAA,KAAW,CAAA,IAC1B,QAAQ,MAAA,IACR,GAAA,CAAI,IAAA,KAAS,cAAA,CAAe,aAAA,EAC5B;AACA,cAAA;AAAA,YACF;AAGA,YAAA,IAAI,GAAA,CAAI,IAAA,KAAS,cAAA,CAAe,eAAA,EAAiB;AAC/C,cAAA,MAAM,CAAC,OAAO,CAAA,GAAI,GAAA,CAAI,QAAA;AAEtB,cAAA,IACE,GAAA,CAAI,QAAA,CAAS,MAAA,KAAW,CAAA,IACxB,OAAA,KAAY,IAAA,IACZ,OAAA,KAAY,MAAA,IACZ,OAAA,CAAQ,IAAA,KAAS,cAAA,CAAe,aAAA,EAChC;AACA,gBAAA,MAAA;AAAA,kBACE,IAAA;AAAA,kBACA,EAAE,EAAA,EAAI,UAAA,EAAY,QAAA,EAAU,SAAA,CAAU,OAAO,CAAA,EAAE;AAAA,kBAC/C,UAAU,QAAQ;AAAA,iBACpB;AAAA,cACF;AAEA,cAAA;AAAA,YACF;AAIA,YAAA,IAAI,wBAAA,CAAyB,SAAA,CAAU,GAAG,CAAC,CAAA,EAAG;AAC5C,cAAA,MAAA;AAAA,gBACE,IAAA;AAAA,gBACA,EAAE,EAAA,EAAI,UAAA,EAAY,QAAA,EAAU,SAAA,CAAU,GAAG,CAAA,EAAE;AAAA,gBAC3C,UAAU,QAAQ;AAAA,eACpB;AAAA,YACF;AAAA,UACF;AAAA;AACF,MACF,CAAA;AAAA,MAEA,eAAA,EAAiB,CAAC,IAAA,KAAS;AAGzB,QAAA,IAAI,IAAA,CAAK,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG;AAEhC,QAAA,MAAM,CAAC,KAAA,EAAO,MAAM,CAAA,GAAI,IAAA,CAAK,QAAA;AAE7B,QAAA,IACE,UAAU,IAAA,IACV,KAAA,KAAU,UACV,MAAA,KAAW,IAAA,IACX,WAAW,MAAA,EACX;AACA,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,CAAC,MAAA,EAAQ,IAAA,EAAM,EAAE,IACrB,KAAA,CAAM,IAAA,KAAS,cAAA,CAAe,aAAA,IAC9B,OAAO,IAAA,KAAS,cAAA,CAAe,aAAA,GAC1B,CAAC,OAAO,MAAA,EAAQ,UAAU,CAAA,GAC3B,KAAA,CAAM,IAAA,KAAS,cAAA,CAAe,aAAA,IAC5B,MAAA,CAAO,SAAS,cAAA,CAAe,aAAA,GAC9B,CAAC,MAAA,EAAQ,OAAO,aAAa,CAAA,GAC9B,CAAC,MAAA,EAAW,QAAW,MAAS,CAAA;AAKxC,QAAA,IAAI,WAAW,MAAA,EAAW;AAI1B,QAAA,IAAI,CAAC,kBAAA,CAAmB,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAC,CAAA,EAAG;AAErD,QAAA,MAAA;AAAA,UACE,IAAA;AAAA,UACA,EAAE,EAAA,EAAI,QAAA,EAAU,SAAA,CAAU,IAAI,CAAA,EAAE;AAAA,UAChC,SAAA,CAAU,OAAO,QAAQ;AAAA,SAC3B;AAAA,MACF,CAAA;AAAA,MAEA,gBAAgB,MAAM;AACpB,QAAA,KAAA,MAAW,CAAC,KAAA,EAAO,QAAQ,CAAA,IAAK,cAAA,CAAe,SAAQ,EAAG;AACxD,UAAA,OAAA,CAAQ,MAAA,CAAO;AAAA,YACb,MAAM,QAAA,CAAS,IAAA;AAAA,YACf,SAAA,EAAW,6BAAA;AAAA,YACX,IAAA,EAAM,EAAE,WAAA,EAAa,QAAA,CAAS,WAAA,EAAY;AAAA,YAC1C,GAAA,EAAK,CAAC,KAAA,KAAU;AACd,cAAA,MAAM,WAAA,GACJ,KAAA,KAAU,CAAA,IAAK,cAAA,GACX,gBAAA,CAAiB,KAAA,EAAO,OAAA,EAAS,iBAAA,EAAmB,CAAC,KAAK,CAAC,CAAA,GAC3D,EAAC;AAEP,cAAA,OAAO;AAAA,gBACL,GAAG,WAAA;AAAA,gBACH,KAAA,CAAM,WAAA,CAAY,QAAA,CAAS,IAAA,EAAM,SAAS,WAAW;AAAA,eACvD;AAAA,YACF;AAAA,WACD,CAAA;AAAA,QACH;AAAA,MACF;AAAA,KACF;AAAA,EACF,CAAA;AAAA,EACA,gBAAgB;AAClB;;;;"}
|
|
1
|
+
{"version":3,"file":"prefer-canonical-array-slicing.mjs","sources":["../../src/rules/prefer-canonical-array-slicing.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n ESLintUtils,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport * as ts from 'typescript';\nimport {\n buildImportFixes,\n getImportedLocalName,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\ntype Options = readonly [];\n\ntype MessageIds = 'preferCanonicalArraySlicing';\n\n/**\n * A canonical ts-data-forge `Arr` operation a detected pattern is rewritten to:\n *\n * - `tail` / `butLast` — drop the first / last element\n * - `skip N` / `take N` — drop / keep the first `N` elements\n * - `skipLast N` / `takeLast N` — drop / keep the last `N` elements\n * - `toUnshifted item` / `toPushed item` — prepend / append one element\n * (`toUnshifted` is emitted in its curried form `toUnshifted(item)(xs)` so\n * the new element keeps the leading position it had in `[item, ...xs]`)\n */\ntype CopyOp = Readonly<\n | { fn: 'butLast' | 'tail' }\n | { fn: 'skip' | 'skipLast' | 'take' | 'takeLast'; count: number }\n | { fn: 'toPushed' | 'toUnshifted'; itemText: string }\n>;\n\n/** `skip 1` / `skipLast 1` have dedicated single-element names. */\nconst normalizeCountedOp = (\n fn: 'skip' | 'skipLast' | 'take' | 'takeLast',\n count: number,\n): CopyOp | undefined =>\n count < 1\n ? undefined\n : fn === 'skip' && count === 1\n ? ({ fn: 'tail' } as const)\n : fn === 'skipLast' && count === 1\n ? ({ fn: 'butLast' } as const)\n : ({ fn, count } as const);\n\nconst asNonNegativeIntegerLiteral = (\n node: TSESTree.Node,\n): number | undefined =>\n node.type === AST_NODE_TYPES.Literal &&\n typeof node.value === 'number' &&\n Number.isInteger(node.value) &&\n node.value >= 0\n ? node.value\n : undefined;\n\n/** Matches `-N` (unary minus of a positive integer literal) and returns `N`. */\nconst asNegatedPositiveIntegerLiteral = (\n node: TSESTree.Node,\n): number | undefined => {\n if (\n node.type !== AST_NODE_TYPES.UnaryExpression ||\n node.operator !== '-' ||\n node.argument.type !== AST_NODE_TYPES.Literal\n ) {\n return undefined;\n }\n\n const n = asNonNegativeIntegerLiteral(node.argument);\n\n return n !== undefined && n >= 1 ? n : undefined;\n};\n\n/**\n * Whether re-reading the expression is free of side effects (a plain variable\n * or a non-computed property chain). Required for patterns that reference the\n * receiver twice, such as `xs.filter((_, i) => i < xs.length - 1)`.\n */\nconst isSimpleReference = (node: TSESTree.Node): boolean =>\n node.type === AST_NODE_TYPES.Identifier ||\n node.type === AST_NODE_TYPES.ThisExpression ||\n (node.type === AST_NODE_TYPES.MemberExpression &&\n !node.computed &&\n node.property.type === AST_NODE_TYPES.Identifier &&\n isSimpleReference(node.object));\n\nconst rootIdentifierName = (node: TSESTree.Node): string | undefined =>\n node.type === AST_NODE_TYPES.Identifier\n ? node.name\n : node.type === AST_NODE_TYPES.MemberExpression\n ? rootIdentifierName(node.object)\n : undefined;\n\ntype ComparisonOperator = '!==' | '<' | '<=' | '>' | '>=';\n\nconst COMPARISON_OPERATORS: ReadonlySet<string> = new Set([\n '!==',\n '<',\n '<=',\n '>',\n '>=',\n] as const satisfies readonly ComparisonOperator[]);\n\n/** Mirror of each comparison when its operands are swapped. */\nconst FLIPPED_OPERATORS = {\n '!==': '!==',\n '<': '>',\n '<=': '>=',\n '>': '<',\n '>=': '<=',\n} as const satisfies Record<ComparisonOperator, ComparisonOperator>;\n\n/** Classifies `index OP N` (a comparison against an integer literal). */\nconst classifyLiteralComparison = (\n op: ComparisonOperator,\n n: number,\n): CopyOp | undefined => {\n switch (op) {\n case '>=':\n return normalizeCountedOp('skip', n);\n\n case '>':\n return normalizeCountedOp('skip', n + 1);\n\n case '!==':\n return n === 0 ? { fn: 'tail' } : undefined;\n\n case '<':\n return normalizeCountedOp('take', n);\n\n case '<=':\n return normalizeCountedOp('take', n + 1);\n }\n};\n\n/** Classifies `index OP <xs>.length - M`. */\nconst classifyLengthComparison = (\n op: ComparisonOperator,\n m: number,\n): CopyOp | undefined => {\n switch (op) {\n case '<':\n return normalizeCountedOp('skipLast', m);\n\n case '<=':\n return normalizeCountedOp('skipLast', m - 1);\n\n case '!==':\n return m === 1 ? { fn: 'butLast' } : undefined;\n\n case '>=':\n return normalizeCountedOp('takeLast', m);\n\n case '>':\n return normalizeCountedOp('takeLast', m - 1);\n }\n};\n\nexport const preferCanonicalArraySlicing: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Unify non-mutating array element addition/removal patterns (`slice` / `toSpliced` / `filter` / `concat` / spread) into the corresponding ts-data-forge functions: `Arr.tail`, `Arr.skip`, `Arr.take`, `Arr.butLast`, `Arr.skipLast`, `Arr.takeLast`, `Arr.toUnshifted`, `Arr.toPushed`.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n preferCanonicalArraySlicing:\n 'Replace with `{{replacement}}` 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 parserServices = ESLintUtils.getParserServices(context);\n\n const checker = parserServices.program.getTypeChecker();\n\n /**\n * The in-scope way to reference the `Arr` namespace: a named import\n * (possibly aliased), a namespace import member, or the canonical `Arr`\n * (added by the import fix when nothing is imported yet).\n */\n const arrLocalName = getImportedLocalName(tsDataForgeImport, 'Arr');\n\n const namespaceLocalName = tsDataForgeImport?.specifiers.find(\n (specifier) => specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier,\n )?.local.name;\n\n const arrRef =\n arrLocalName ??\n (namespaceLocalName === undefined ? 'Arr' : `${namespaceLocalName}.Arr`);\n\n const needsArrImport =\n arrLocalName === undefined && namespaceLocalName === undefined;\n\n /**\n * Deferred (non-concrete) types are reduced to their base constraint\n * before checking; `any` / `unknown` are conservatively not array-like.\n */\n const isArrayOrTupleType = (type: ts.Type): boolean => {\n if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) {\n return false;\n }\n\n if (type.isUnion()) {\n return type.types.every(isArrayOrTupleType);\n }\n\n if (\n (type.flags &\n (ts.TypeFlags.TypeParameter |\n ts.TypeFlags.IndexedAccess |\n ts.TypeFlags.Conditional |\n ts.TypeFlags.Substitution)) !==\n 0\n ) {\n const constraint = checker.getBaseConstraintOfType(type);\n\n return constraint !== undefined && isArrayOrTupleType(constraint);\n }\n\n return checker.isArrayType(type) || checker.isTupleType(type);\n };\n\n const getTypeOf = (node: TSESTree.Node): ts.Type =>\n checker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(node));\n\n /**\n * `true` only when the type can never be an array in any branch, so\n * `xs.concat(v)` is guaranteed to append `v` as one element rather than\n * flattening it.\n */\n const isDefinitelyNotArrayType = (type: ts.Type): boolean => {\n if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) {\n return false;\n }\n\n if (type.isUnion()) {\n return type.types.every(isDefinitelyNotArrayType);\n }\n\n if (\n (type.flags &\n (ts.TypeFlags.TypeParameter |\n ts.TypeFlags.IndexedAccess |\n ts.TypeFlags.Conditional |\n ts.TypeFlags.Substitution)) !==\n 0\n ) {\n const constraint = checker.getBaseConstraintOfType(type);\n\n return constraint !== undefined && isDefinitelyNotArrayType(constraint);\n }\n\n return !checker.isArrayType(type) && !checker.isTupleType(type);\n };\n\n /** Argument-position text; a comma (sequence) expression needs parens. */\n const toArgText = (node: TSESTree.Node): string => {\n const text = sourceCode.getText(node);\n\n return node.type === AST_NODE_TYPES.SequenceExpression\n ? `(${text})`\n : text;\n };\n\n const toReplacementText = (op: CopyOp, xsText: string): string => {\n switch (op.fn) {\n case 'tail':\n case 'butLast':\n return `${arrRef}.${op.fn}(${xsText})`;\n\n case 'skip':\n case 'take':\n case 'skipLast':\n case 'takeLast':\n return `${arrRef}.${op.fn}(${xsText}, ${op.count})`;\n\n // Emit the curried form for `toUnshifted` so the new element stays in\n // the same textual position as in the `[item, ...xs]` source, keeping\n // the rewrite easy to read.\n case 'toUnshifted':\n return `${arrRef}.toUnshifted(${op.itemText})(${xsText})`;\n\n case 'toPushed':\n return `${arrRef}.toPushed(${xsText}, ${op.itemText})`;\n }\n };\n\n const mut_nodesToFix: {\n node: TSESTree.Node;\n replacement: string;\n }[] = [];\n\n const report = (node: TSESTree.Node, op: CopyOp, xsText: string): void => {\n mut_nodesToFix.push({\n node,\n replacement: toReplacementText(op, xsText),\n });\n };\n\n /** Matches `<xsText>.length` (non-computed). */\n const isReceiverLengthAccess = (\n node: TSESTree.Node,\n receiverText: string,\n ): boolean =>\n node.type === AST_NODE_TYPES.MemberExpression &&\n !node.computed &&\n node.property.type === AST_NODE_TYPES.Identifier &&\n node.property.name === 'length' &&\n sourceCode.getText(node.object) === receiverText;\n\n /** Matches `<xsText>.length - M` (M ≥ 1) and returns `M`. */\n const matchLengthMinus = (\n node: TSESTree.Node,\n receiverText: string,\n ): number | undefined => {\n if (\n node.type !== AST_NODE_TYPES.BinaryExpression ||\n node.operator !== '-' ||\n !isReceiverLengthAccess(node.left, receiverText)\n ) {\n return undefined;\n }\n\n const m = asNonNegativeIntegerLiteral(node.right);\n\n return m !== undefined && m >= 1 ? m : undefined;\n };\n\n /**\n * Matches an index-only filter callback (`(_, i) => <comparison>`) and\n * classifies the comparison. Both a leading index (`i >= 1`) and a\n * trailing index (`1 <= i`) operand order are accepted.\n */\n const classifyFilterCallback = (\n callback: TSESTree.CallExpressionArgument,\n receiver: TSESTree.Expression,\n receiverText: string,\n ): CopyOp | undefined => {\n if (\n callback.type !== AST_NODE_TYPES.ArrowFunctionExpression ||\n callback.async ||\n callback.body.type === AST_NODE_TYPES.BlockStatement ||\n callback.params.length !== 2\n ) {\n return undefined;\n }\n\n const [elementParam, indexParam] = callback.params;\n\n if (\n elementParam === undefined ||\n indexParam === undefined ||\n elementParam.type !== AST_NODE_TYPES.Identifier ||\n indexParam.type !== AST_NODE_TYPES.Identifier\n ) {\n return undefined;\n }\n\n // A callback parameter shadowing the receiver's root binding (e.g.\n // `xs.filter((xs, i) => i < xs.length - 1)`) would make the textual\n // `xs.length` comparison refer to the element, not the receiver.\n const receiverRoot = rootIdentifierName(receiver);\n\n if (\n receiverRoot !== undefined &&\n (elementParam.name === receiverRoot || indexParam.name === receiverRoot)\n ) {\n return undefined;\n }\n\n const body = callback.body;\n\n if (\n body.type !== AST_NODE_TYPES.BinaryExpression ||\n !COMPARISON_OPERATORS.has(body.operator)\n ) {\n return undefined;\n }\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n const rawOperator = body.operator as ComparisonOperator;\n\n const isIndex = (side: TSESTree.Node): boolean =>\n side.type === AST_NODE_TYPES.Identifier &&\n side.name === indexParam.name;\n\n const [op, other] = isIndex(body.left)\n ? ([rawOperator, body.right] as const)\n : isIndex(body.right)\n ? ([FLIPPED_OPERATORS[rawOperator], body.left] as const)\n : [undefined, undefined];\n\n // `op` and `other` are set together (both from the same tuple), so\n // checking `op` also guarantees `other` is defined.\n if (op === undefined || isIndex(other)) {\n return undefined;\n }\n\n // The element parameter must not participate in the comparison (only\n // the index side and a receiver-independent bound are recognized, so\n // reaching here with an element reference is impossible for literals,\n // but `xs.length - N` needs a stable receiver).\n const n = asNonNegativeIntegerLiteral(other);\n\n if (n !== undefined) {\n return classifyLiteralComparison(op, n);\n }\n\n if (!isSimpleReference(receiver)) return undefined;\n\n const m = matchLengthMinus(other, receiverText);\n\n return m === undefined ? undefined : classifyLengthComparison(op, m);\n };\n\n const classifySliceArgs = (\n args: readonly TSESTree.CallExpressionArgument[],\n receiver: TSESTree.Expression,\n ): CopyOp | undefined => {\n if (args.length === 1) {\n const [start] = args;\n\n if (start === undefined) return undefined;\n\n const n = asNonNegativeIntegerLiteral(start);\n\n if (n !== undefined) return normalizeCountedOp('skip', n);\n\n const negated = asNegatedPositiveIntegerLiteral(start);\n\n return negated === undefined\n ? undefined\n : normalizeCountedOp('takeLast', negated);\n }\n\n if (args.length === 2) {\n const [start, end] = args;\n\n if (\n start === undefined ||\n end === undefined ||\n asNonNegativeIntegerLiteral(start) !== 0\n ) {\n return undefined;\n }\n\n const n = asNonNegativeIntegerLiteral(end);\n\n if (n !== undefined) return normalizeCountedOp('take', n);\n\n const negated = asNegatedPositiveIntegerLiteral(end);\n\n if (negated !== undefined) {\n return normalizeCountedOp('skipLast', negated);\n }\n\n if (!isSimpleReference(receiver)) return undefined;\n\n const m = matchLengthMinus(end, sourceCode.getText(receiver));\n\n return m === undefined ? undefined : normalizeCountedOp('skipLast', m);\n }\n\n return undefined;\n };\n\n const classifyToSplicedArgs = (\n args: readonly TSESTree.CallExpressionArgument[],\n receiver: TSESTree.Expression,\n receiverText: string,\n ): CopyOp | undefined => {\n if (args.length === 1) {\n const [start] = args;\n\n if (start === undefined) return undefined;\n\n const n = asNonNegativeIntegerLiteral(start);\n\n // `xs.toSpliced(N)` removes everything from index N on → take N.\n if (n !== undefined) return normalizeCountedOp('take', n);\n\n const negated = asNegatedPositiveIntegerLiteral(start);\n\n // `xs.toSpliced(-N)` removes the last N elements → skipLast N.\n return negated === undefined\n ? undefined\n : normalizeCountedOp('skipLast', negated);\n }\n\n if (args.length === 2) {\n const [start, deleteCount] = args;\n\n if (\n start === undefined ||\n deleteCount === undefined ||\n asNonNegativeIntegerLiteral(start) !== 0\n ) {\n return undefined;\n }\n\n // `xs.toSpliced(0, N)` removes the first N elements → skip N.\n const n = asNonNegativeIntegerLiteral(deleteCount);\n\n if (n !== undefined) return normalizeCountedOp('skip', n);\n\n // `xs.toSpliced(0, xs.length - N)` keeps the last N → takeLast N.\n // (Note: `xs.toSpliced(0, -N)` clamps the delete count to 0 and is a\n // plain copy, so it is deliberately NOT treated as takeLast.)\n if (!isSimpleReference(receiver)) return undefined;\n\n const m = matchLengthMinus(deleteCount, receiverText);\n\n return m === undefined ? undefined : normalizeCountedOp('takeLast', m);\n }\n\n if (args.length === 3) {\n const [start, deleteCount, item] = args;\n\n if (\n start === undefined ||\n deleteCount === undefined ||\n item === undefined ||\n item.type === AST_NODE_TYPES.SpreadElement ||\n asNonNegativeIntegerLiteral(deleteCount) !== 0\n ) {\n return undefined;\n }\n\n // `xs.toSpliced(0, 0, item)` prepends → toUnshifted.\n if (asNonNegativeIntegerLiteral(start) === 0) {\n return { fn: 'toUnshifted', itemText: toArgText(item) };\n }\n\n // `xs.toSpliced(xs.length, 0, item)` appends → toPushed.\n if (\n isSimpleReference(receiver) &&\n isReceiverLengthAccess(start, receiverText)\n ) {\n return { fn: 'toPushed', itemText: toArgText(item) };\n }\n\n return undefined;\n }\n\n return undefined;\n };\n\n return {\n CallExpression: (node) => {\n const callee = node.callee;\n\n if (\n callee.type !== AST_NODE_TYPES.MemberExpression ||\n callee.computed ||\n callee.property.type !== AST_NODE_TYPES.Identifier\n ) {\n return;\n }\n\n const methodName = callee.property.name;\n\n const receiver = callee.object;\n\n // `Arr.skip(xs, 1)` → `Arr.tail(xs)`, `Arr.skipLast(xs, 1)` →\n // `Arr.butLast(xs)` (also via a namespace import or an alias).\n if (methodName === 'skip' || methodName === 'skipLast') {\n const isArrObject =\n (receiver.type === AST_NODE_TYPES.Identifier &&\n receiver.name === arrLocalName) ||\n (receiver.type === AST_NODE_TYPES.MemberExpression &&\n !receiver.computed &&\n receiver.object.type === AST_NODE_TYPES.Identifier &&\n receiver.object.name === namespaceLocalName &&\n receiver.property.type === AST_NODE_TYPES.Identifier &&\n receiver.property.name === 'Arr');\n\n const [arrayArg, countArg] = node.arguments;\n\n if (\n isArrObject &&\n node.arguments.length === 2 &&\n arrayArg !== undefined &&\n arrayArg.type !== AST_NODE_TYPES.SpreadElement &&\n countArg !== undefined &&\n asNonNegativeIntegerLiteral(countArg) === 1\n ) {\n report(\n node,\n { fn: methodName === 'skip' ? 'tail' : 'butLast' },\n toArgText(arrayArg),\n );\n }\n\n return;\n }\n\n // `[item].concat(xs)` → `Arr.toUnshifted(xs, item)`.\n if (\n methodName === 'concat' &&\n receiver.type === AST_NODE_TYPES.ArrayExpression\n ) {\n const [element] = receiver.elements;\n\n const [arg] = node.arguments;\n\n if (\n receiver.elements.length === 1 &&\n element !== null &&\n element !== undefined &&\n element.type !== AST_NODE_TYPES.SpreadElement &&\n node.arguments.length === 1 &&\n arg !== undefined &&\n arg.type !== AST_NODE_TYPES.SpreadElement &&\n isArrayOrTupleType(getTypeOf(arg))\n ) {\n report(\n node,\n { fn: 'toUnshifted', itemText: toArgText(element) },\n toArgText(arg),\n );\n }\n\n return;\n }\n\n if (\n methodName !== 'slice' &&\n methodName !== 'toSpliced' &&\n methodName !== 'filter' &&\n methodName !== 'concat'\n ) {\n return;\n }\n\n // The receiver must be an array or tuple (not a string / typed array\n // / other object that happens to share the method name).\n if (!isArrayOrTupleType(getTypeOf(receiver))) return;\n\n const receiverText = sourceCode.getText(receiver);\n\n switch (methodName) {\n case 'slice': {\n const op = classifySliceArgs(node.arguments, receiver);\n\n if (op !== undefined) report(node, op, toArgText(receiver));\n\n return;\n }\n\n case 'toSpliced': {\n const op = classifyToSplicedArgs(\n node.arguments,\n receiver,\n receiverText,\n );\n\n if (op !== undefined) report(node, op, toArgText(receiver));\n\n return;\n }\n\n case 'filter': {\n const [callback] = node.arguments;\n\n if (callback === undefined || node.arguments.length !== 1) {\n return;\n }\n\n const op = classifyFilterCallback(callback, receiver, receiverText);\n\n if (op !== undefined) report(node, op, toArgText(receiver));\n\n return;\n }\n\n case 'concat': {\n const [arg] = node.arguments;\n\n if (\n node.arguments.length !== 1 ||\n arg === undefined ||\n arg.type === AST_NODE_TYPES.SpreadElement\n ) {\n return;\n }\n\n // `xs.concat([item])` → `Arr.toPushed(xs, item)`.\n if (arg.type === AST_NODE_TYPES.ArrayExpression) {\n const [element] = arg.elements;\n\n if (\n arg.elements.length === 1 &&\n element !== null &&\n element !== undefined &&\n element.type !== AST_NODE_TYPES.SpreadElement\n ) {\n report(\n node,\n { fn: 'toPushed', itemText: toArgText(element) },\n toArgText(receiver),\n );\n }\n\n return;\n }\n\n // `xs.concat(item)` appends only when `item` can never be an\n // array (otherwise `concat` would flatten it).\n if (isDefinitelyNotArrayType(getTypeOf(arg))) {\n report(\n node,\n { fn: 'toPushed', itemText: toArgText(arg) },\n toArgText(receiver),\n );\n }\n }\n }\n },\n\n ArrayExpression: (node) => {\n // `[item, ...xs]` → `Arr.toUnshifted(xs, item)`,\n // `[...xs, item]` → `Arr.toPushed(xs, item)`.\n if (node.elements.length !== 2) return;\n\n const [first, second] = node.elements;\n\n if (\n first === null ||\n first === undefined ||\n second === null ||\n second === undefined\n ) {\n return;\n }\n\n const [spread, item, fn] =\n first.type === AST_NODE_TYPES.SpreadElement &&\n second.type !== AST_NODE_TYPES.SpreadElement\n ? ([first, second, 'toPushed'] as const)\n : first.type !== AST_NODE_TYPES.SpreadElement &&\n second.type === AST_NODE_TYPES.SpreadElement\n ? ([second, first, 'toUnshifted'] as const)\n : [undefined, undefined, undefined];\n\n // `spread`, `item`, and `fn` are set together (all from the same\n // tuple), so checking `spread` also guarantees the others are\n // defined.\n if (spread === undefined) return;\n\n // Spreading a non-array iterable (`[v, ...someSet]`) is a genuine\n // conversion, not an element addition to an array.\n if (!isArrayOrTupleType(getTypeOf(spread.argument))) return;\n\n report(\n node,\n { fn, itemText: toArgText(item) },\n toArgText(spread.argument),\n );\n },\n\n 'Program:exit': () => {\n for (const [index, nodeInfo] of mut_nodesToFix.entries()) {\n context.report({\n node: nodeInfo.node,\n messageId: 'preferCanonicalArraySlicing',\n data: { replacement: nodeInfo.replacement },\n fix: (fixer) => {\n const importFixes =\n index === 0 && needsArrImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, ['Arr'])\n : [];\n\n return [\n ...importFixes,\n fixer.replaceText(nodeInfo.node, nodeInfo.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 = 'useCanonicalCast';
|
|
4
|
+
export declare const preferCanonicalLengthCast: TSESLint.RuleModule<MessageIds, Options>;
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=prefer-canonical-length-cast.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-canonical-length-cast.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-canonical-length-cast.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAGlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GAAG,kBAAkB,CAAC;AAgHrC,eAAO,MAAM,yBAAyB,EAAE,QAAQ,CAAC,UAAU,CACzD,UAAU,EACV,OAAO,CAiEC,CAAC"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
2
|
+
import { getImportedLocalName, getTsDataForgeImport } from './import-utils.mjs';
|
|
3
|
+
|
|
4
|
+
const CAST_REWRITES = [
|
|
5
|
+
{
|
|
6
|
+
cast: "asMinLengthArray",
|
|
7
|
+
bounds: [1],
|
|
8
|
+
replacement: "asNonEmptyArray",
|
|
9
|
+
keep: []
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
cast: "asFixedLengthArray",
|
|
13
|
+
bounds: [0],
|
|
14
|
+
replacement: "asEmptyArray",
|
|
15
|
+
keep: []
|
|
16
|
+
},
|
|
17
|
+
// Listed before the `anyLength, sameAsFirst` entry so that
|
|
18
|
+
// `asBoundedLengthArray(0, 0, xs)` lands on `asEmptyArray` in one step.
|
|
19
|
+
{
|
|
20
|
+
cast: "asBoundedLengthArray",
|
|
21
|
+
bounds: [0, 0],
|
|
22
|
+
replacement: "asEmptyArray",
|
|
23
|
+
keep: []
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
cast: "asBoundedLengthArray",
|
|
27
|
+
bounds: ["anyLength", "sameAsFirst"],
|
|
28
|
+
replacement: "asFixedLengthArray",
|
|
29
|
+
keep: [0]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
cast: "asMaxLengthArray",
|
|
33
|
+
bounds: [0],
|
|
34
|
+
replacement: "asEmptyArray",
|
|
35
|
+
keep: []
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
cast: "asMinLengthTuple",
|
|
39
|
+
bounds: [1],
|
|
40
|
+
replacement: "asNonEmptyTuple",
|
|
41
|
+
keep: []
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
cast: "asFixedLengthTuple",
|
|
45
|
+
bounds: [0],
|
|
46
|
+
replacement: "asEmptyTuple",
|
|
47
|
+
keep: []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
cast: "asBoundedLengthTuple",
|
|
51
|
+
bounds: [0, 0],
|
|
52
|
+
replacement: "asEmptyTuple",
|
|
53
|
+
keep: []
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
cast: "asBoundedLengthTuple",
|
|
57
|
+
bounds: ["anyLength", "sameAsFirst"],
|
|
58
|
+
replacement: "asFixedLengthTuple",
|
|
59
|
+
keep: [0]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
cast: "asMaxLengthTuple",
|
|
63
|
+
bounds: [0],
|
|
64
|
+
replacement: "asEmptyTuple",
|
|
65
|
+
keep: []
|
|
66
|
+
}
|
|
67
|
+
];
|
|
68
|
+
const preferCanonicalLengthCast = {
|
|
69
|
+
meta: {
|
|
70
|
+
type: "suggestion",
|
|
71
|
+
docs: {
|
|
72
|
+
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."
|
|
73
|
+
},
|
|
74
|
+
fixable: "code",
|
|
75
|
+
schema: [],
|
|
76
|
+
messages: {
|
|
77
|
+
useCanonicalCast: "Use `{{arrName}}.{{replacement}}(...)` instead of `{{arrName}}.{{cast}}({{boundsText}})`: with these bounds the result type is the same (or stricter), and `{{replacement}}` names the constraint directly."
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
create: (context) => {
|
|
81
|
+
const sourceCode = context.sourceCode;
|
|
82
|
+
const arrLocalName = getImportedLocalName(
|
|
83
|
+
getTsDataForgeImport(sourceCode.ast),
|
|
84
|
+
"Arr"
|
|
85
|
+
);
|
|
86
|
+
if (arrLocalName === void 0) return {};
|
|
87
|
+
return {
|
|
88
|
+
CallExpression: (node) => {
|
|
89
|
+
const castName = getCastName(node, arrLocalName);
|
|
90
|
+
if (castName === void 0) return;
|
|
91
|
+
const rewrite = CAST_REWRITES.find(
|
|
92
|
+
(candidate) => candidate.cast === castName && matchesBounds(node, candidate)
|
|
93
|
+
);
|
|
94
|
+
if (rewrite === void 0) return;
|
|
95
|
+
const boundsText = node.arguments.slice(0, rewrite.bounds.length).map((arg) => sourceCode.getText(arg)).join(", ");
|
|
96
|
+
context.report({
|
|
97
|
+
node,
|
|
98
|
+
messageId: "useCanonicalCast",
|
|
99
|
+
data: {
|
|
100
|
+
arrName: arrLocalName,
|
|
101
|
+
cast: castName,
|
|
102
|
+
boundsText: [boundsText, "\u2026"].join(", "),
|
|
103
|
+
replacement: rewrite.replacement
|
|
104
|
+
},
|
|
105
|
+
fix: (fixer) => [
|
|
106
|
+
fixer.replaceText(
|
|
107
|
+
node.callee,
|
|
108
|
+
`${arrLocalName}.${rewrite.replacement}`
|
|
109
|
+
),
|
|
110
|
+
...dropBoundsFix(fixer, node, rewrite)
|
|
111
|
+
]
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
defaultOptions: []
|
|
117
|
+
};
|
|
118
|
+
const getCastName = (node, arrLocalName) => {
|
|
119
|
+
const { callee } = node;
|
|
120
|
+
return callee.type === AST_NODE_TYPES.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES.Identifier && callee.object.name === arrLocalName && callee.property.type === AST_NODE_TYPES.Identifier ? callee.property.name : void 0;
|
|
121
|
+
};
|
|
122
|
+
const matchesBounds = (node, rewrite) => {
|
|
123
|
+
const args = node.arguments;
|
|
124
|
+
const boundCount = rewrite.bounds.length;
|
|
125
|
+
if (args.length !== boundCount + 1 || node.typeArguments !== void 0) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (args.some((arg) => arg.type === AST_NODE_TYPES.SpreadElement)) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
const bounds = args.slice(0, boundCount).map(getLengthLiteral);
|
|
132
|
+
const [first] = bounds;
|
|
133
|
+
return rewrite.bounds.every((matcher, index) => {
|
|
134
|
+
const value = bounds[index];
|
|
135
|
+
if (value === void 0) return false;
|
|
136
|
+
switch (matcher) {
|
|
137
|
+
case "anyLength":
|
|
138
|
+
return true;
|
|
139
|
+
case "sameAsFirst":
|
|
140
|
+
return value === first;
|
|
141
|
+
default:
|
|
142
|
+
return value === matcher;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
const dropBoundsFix = (fixer, node, rewrite) => rewrite.bounds.flatMap((_, index) => {
|
|
147
|
+
if (rewrite.keep.includes(index)) return [];
|
|
148
|
+
const dropped = node.arguments[index];
|
|
149
|
+
const next = node.arguments[index + 1];
|
|
150
|
+
return dropped === void 0 || next === void 0 ? [] : [fixer.removeRange([dropped.range[0], next.range[0]])];
|
|
151
|
+
});
|
|
152
|
+
const getLengthLiteral = (node) => node.type === AST_NODE_TYPES.Literal && typeof node.value === "number" && Number.isSafeInteger(node.value) && node.value >= 0 ? node.value : void 0;
|
|
153
|
+
|
|
154
|
+
export { preferCanonicalLengthCast };
|
|
155
|
+
//# sourceMappingURL=prefer-canonical-length-cast.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-canonical-length-cast.mjs","sources":["../../src/rules/prefer-canonical-length-cast.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';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useCanonicalCast';\n\n/** How one leading length argument of a cast call must look. */\ntype BoundMatcher = number | 'anyLength' | 'sameAsFirst';\n\ntype CastRewrite = Readonly<{\n /** The `Arr` cast the call resolves to. */\n cast: string;\n /** One matcher per leading length argument the call must have. */\n bounds: readonly BoundMatcher[];\n /** The cast the call is rewritten to. */\n replacement: string;\n /** Indices into `bounds` of the length arguments the rewrite keeps, in order. */\n keep: readonly number[];\n}>;\n\n/**\n * `Arr.as*` casts whose length arguments make them a longhand spelling of\n * another cast.\n *\n * Unlike a `Type<T>` wrapper, a cast only ever *returns* the narrowed value, so\n * a rewrite that strengthens the result type stays safe — the result is\n * assignable everywhere the old one was:\n *\n * | call | canonical form | relation |\n * | :----------------------------------- | :------------------------------ | :------- |\n * | `Arr.asMinLengthArray(1, xs)` | `Arr.asNonEmptyArray(xs)` | type-identical — `NonEmptyArray<A>` is defined as `MinLengthArray<1, A>` |\n * | `Arr.asFixedLengthArray(0, xs)` | `Arr.asEmptyArray(xs)` | type-identical — `asEmptyArray` is the length-0 specialization |\n * | `Arr.asBoundedLengthArray(n, n, xs)` | `Arr.asFixedLengthArray(n, xs)` | strengthens — `FixedLengthArray<N, E>` is `BoundedLengthArray<N, N, E>` intersected with the exact tuple for `N <= 10` |\n * | `Arr.asMaxLengthArray(0, xs)` | `Arr.asEmptyArray(xs)` | strengthens — an array of at most `0` elements is an array of exactly `0` |\n * | `Arr.asMinLengthTuple(1, xs)` | `Arr.asNonEmptyTuple(xs)` | type-identical — both are `MinLengthTuple<1, E>` |\n * | `Arr.asFixedLengthTuple(0, xs)` | `Arr.asEmptyTuple(xs)` | type-identical — both are `readonly []` |\n * | `Arr.asBoundedLengthTuple(n, n, xs)` | `Arr.asFixedLengthTuple(n, xs)` | type-identical — `BoundedLengthTuple<N, N, E>` *is* `FixedLengthTuple<N, E>` |\n * | `Arr.asMaxLengthTuple(0, xs)` | `Arr.asEmptyTuple(xs)` | type-identical — `MaxLengthTuple<0, E>` is `readonly []` |\n *\n * Each family is rewritten within its own lane — the branded `*Array` casts to\n * `asEmptyArray` / `asNonEmptyArray`, the structural `*Tuple` casts to\n * `asEmptyTuple` / `asNonEmptyTuple` — so a rewrite never moves a value across\n * the branded/structural divide, matching `prefer-canonical-length-guard`.\n *\n * `Arr.asBoundedLengthArray(0, n, xs)` → `Arr.asMaxLengthArray(n, xs)` is\n * deliberately absent: it would *drop* the `MinLengthArray<0, E>` brand, which\n * is a widening rather than a rename.\n *\n * Only the runtime `TypeError` message changes, to the one that names the\n * constraint actually being checked.\n */\nconst CAST_REWRITES = [\n {\n cast: 'asMinLengthArray',\n bounds: [1],\n replacement: 'asNonEmptyArray',\n keep: [],\n },\n {\n cast: 'asFixedLengthArray',\n bounds: [0],\n replacement: 'asEmptyArray',\n keep: [],\n },\n // Listed before the `anyLength, sameAsFirst` entry so that\n // `asBoundedLengthArray(0, 0, xs)` lands on `asEmptyArray` in one step.\n {\n cast: 'asBoundedLengthArray',\n bounds: [0, 0],\n replacement: 'asEmptyArray',\n keep: [],\n },\n {\n cast: 'asBoundedLengthArray',\n bounds: ['anyLength', 'sameAsFirst'],\n replacement: 'asFixedLengthArray',\n keep: [0],\n },\n {\n cast: 'asMaxLengthArray',\n bounds: [0],\n replacement: 'asEmptyArray',\n keep: [],\n },\n {\n cast: 'asMinLengthTuple',\n bounds: [1],\n replacement: 'asNonEmptyTuple',\n keep: [],\n },\n {\n cast: 'asFixedLengthTuple',\n bounds: [0],\n replacement: 'asEmptyTuple',\n keep: [],\n },\n {\n cast: 'asBoundedLengthTuple',\n bounds: [0, 0],\n replacement: 'asEmptyTuple',\n keep: [],\n },\n {\n cast: 'asBoundedLengthTuple',\n bounds: ['anyLength', 'sameAsFirst'],\n replacement: 'asFixedLengthTuple',\n keep: [0],\n },\n {\n cast: 'asMaxLengthTuple',\n bounds: [0],\n replacement: 'asEmptyTuple',\n keep: [],\n },\n] as const satisfies readonly CastRewrite[];\n\nexport const preferCanonicalLengthCast: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n '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.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useCanonicalCast:\n 'Use `{{arrName}}.{{replacement}}(...)` instead of `{{arrName}}.{{cast}}({{boundsText}})`: with these bounds the result type is the same (or stricter), and `{{replacement}}` names the constraint directly.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const arrLocalName = getImportedLocalName(\n getTsDataForgeImport(sourceCode.ast),\n 'Arr',\n );\n\n if (arrLocalName === undefined) return {};\n\n return {\n CallExpression: (node) => {\n const castName = getCastName(node, arrLocalName);\n\n if (castName === undefined) return;\n\n const rewrite = CAST_REWRITES.find(\n (candidate) =>\n candidate.cast === castName && matchesBounds(node, candidate),\n );\n\n if (rewrite === undefined) return;\n\n const boundsText = node.arguments\n .slice(0, rewrite.bounds.length)\n .map((arg) => sourceCode.getText(arg))\n .join(', ');\n\n context.report({\n node,\n messageId: 'useCanonicalCast',\n data: {\n arrName: arrLocalName,\n cast: castName,\n boundsText: [boundsText, '…'].join(', '),\n replacement: rewrite.replacement,\n },\n fix: (fixer) => [\n fixer.replaceText(\n node.callee,\n `${arrLocalName}.${rewrite.replacement}`,\n ),\n ...dropBoundsFix(fixer, node, rewrite),\n ],\n });\n },\n };\n },\n defaultOptions: [],\n} as const;\n\n/** The `Arr.<cast>` method name of `node`, or `undefined` for other calls. */\nconst getCastName = (\n // AST nodes hold mutable child arrays, so they are not deeply readonly.\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 `cast(...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 (`cast(...bounds)`):\n * `Arr.asNonEmptyArray` has no curried counterpart, and a curried\n * `asFixedLengthArray` would need the bound kept in a different position.\n */\nconst matchesBounds = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n node: TSESTree.CallExpression,\n rewrite: CastRewrite,\n): boolean => {\n const args = node.arguments;\n\n const boundCount = rewrite.bounds.length;\n\n if (args.length !== boundCount + 1 || node.typeArguments !== undefined) {\n return false;\n }\n\n if (args.some((arg) => arg.type === AST_NODE_TYPES.SpreadElement)) {\n return false;\n }\n\n const bounds = args.slice(0, boundCount).map(getLengthLiteral);\n\n const [first] = bounds;\n\n return rewrite.bounds.every((matcher, index) => {\n const value = bounds[index];\n\n if (value === undefined) return false;\n\n switch (matcher) {\n case 'anyLength':\n return true;\n\n case 'sameAsFirst':\n return value === first;\n\n default:\n return value === matcher;\n }\n });\n};\n\n/**\n * Removes the length arguments the rewrite drops. Each removal runs from the\n * dropped argument up to the start of the next one, so the comma, whitespace\n * and any comments in between go with it; the ranges never overlap.\n */\nconst dropBoundsFix = (\n fixer: TSESLint.RuleFixer,\n // AST nodes hold mutable child arrays, so they are not deeply readonly.\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n node: TSESTree.CallExpression,\n rewrite: CastRewrite,\n): readonly TSESLint.RuleFix[] =>\n rewrite.bounds.flatMap((_, index) => {\n if (rewrite.keep.includes(index)) return [];\n\n const dropped = node.arguments[index];\n\n const next = node.arguments[index + 1];\n\n return dropped === undefined || next === undefined\n ? []\n : [fixer.removeRange([dropped.range[0], next.range[0]])];\n });\n\n/** A non-negative integer literal's value, or `undefined` for anything else. */\nconst getLengthLiteral = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n node: TSESTree.Node,\n): number | undefined =>\n node.type === AST_NODE_TYPES.Literal &&\n typeof node.value === 'number' &&\n Number.isSafeInteger(node.value) &&\n node.value >= 0\n ? node.value\n : undefined;\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-canonical-length-guard.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-canonical-length-guard.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAQlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GACX,mBAAmB,GACnB,yBAAyB,GACzB,uBAAuB,GACvB,qBAAqB,GACrB,qBAAqB,GACrB,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"prefer-canonical-length-guard.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-canonical-length-guard.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAQlC,KAAK,OAAO,GAAG,SAAS,EAAE,CAAC;AAE3B,KAAK,UAAU,GACX,mBAAmB,GACnB,yBAAyB,GACzB,uBAAuB,GACvB,qBAAqB,GACrB,qBAAqB,GACrB,eAAe,CAAC;AA8EpB,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,UAAU,CAC1D,UAAU,EACV,OAAO,CAsEC,CAAC"}
|
|
@@ -23,19 +23,23 @@ const MESSAGES = {
|
|
|
23
23
|
};
|
|
24
24
|
const GUARD_REWRITES = [
|
|
25
25
|
{ guard: "isFixedLengthArray", bounds: [0], replacement: "isEmpty" },
|
|
26
|
-
{ guard: "isFixedLengthTuple", bounds: [0], replacement: "isEmpty" },
|
|
27
|
-
{ guard: "isMaxLengthTuple", bounds: [0], replacement: "isEmpty" },
|
|
28
26
|
{ guard: "isMaxLengthArray", bounds: [0], replacement: "isEmpty" },
|
|
29
|
-
{ guard: "isBoundedLengthTuple", bounds: [0, 0], replacement: "isEmpty" },
|
|
30
27
|
{ guard: "isBoundedLengthArray", bounds: [0, 0], replacement: "isEmpty" },
|
|
31
28
|
{ guard: "isMinLengthArray", bounds: [1], replacement: "isNonEmpty" },
|
|
32
|
-
{ guard: "
|
|
29
|
+
{ guard: "isFixedLengthTuple", bounds: [0], replacement: "isEmptyTuple" },
|
|
30
|
+
{ guard: "isMaxLengthTuple", bounds: [0], replacement: "isEmptyTuple" },
|
|
31
|
+
{
|
|
32
|
+
guard: "isBoundedLengthTuple",
|
|
33
|
+
bounds: [0, 0],
|
|
34
|
+
replacement: "isEmptyTuple"
|
|
35
|
+
},
|
|
36
|
+
{ guard: "isMinLengthTuple", bounds: [1], replacement: "isNonEmptyTuple" }
|
|
33
37
|
];
|
|
34
38
|
const preferCanonicalLengthGuard = {
|
|
35
39
|
meta: {
|
|
36
40
|
type: "suggestion",
|
|
37
41
|
docs: {
|
|
38
|
-
description: "Normalize array-length checks to their canonical `Arr` guard: `xs.length <op> n` becomes the matching `Arr.is*` guard, and degenerate
|
|
42
|
+
description: "Normalize array-length checks to their canonical `Arr` guard: `xs.length <op> n` becomes the matching `Arr.is*` guard, and a degenerate guard becomes the named one for its family (`Arr.isFixedLengthArray(0, xs)` \u2192 `Arr.isEmpty`, `Arr.isFixedLengthTuple(0, xs)` \u2192 `Arr.isEmptyTuple`)."
|
|
39
43
|
},
|
|
40
44
|
fixable: "code",
|
|
41
45
|
schema: [],
|
|
@@ -63,7 +67,7 @@ const preferCanonicalLengthGuard = {
|
|
|
63
67
|
if (rewrite === void 0 || !matchesBounds(node, rewrite.bounds)) {
|
|
64
68
|
return;
|
|
65
69
|
}
|
|
66
|
-
const
|
|
70
|
+
const array = node.arguments[rewrite.bounds.length];
|
|
67
71
|
if (array === void 0) return;
|
|
68
72
|
const arrayText = sourceCode.getText(array);
|
|
69
73
|
context.report({
|
|
@@ -72,7 +76,7 @@ const preferCanonicalLengthGuard = {
|
|
|
72
76
|
data: {
|
|
73
77
|
arrName: arrLocalName,
|
|
74
78
|
guard: guardName,
|
|
75
|
-
boundsText: [
|
|
79
|
+
boundsText: [...rewrite.bounds.map(String), "\u2026"].join(", "),
|
|
76
80
|
replacement: rewrite.replacement
|
|
77
81
|
},
|
|
78
82
|
fix: (fixer) => fixer.replaceText(
|
|
@@ -117,7 +121,7 @@ const matchesBounds = (node, bounds) => {
|
|
|
117
121
|
return false;
|
|
118
122
|
}
|
|
119
123
|
return args.every(
|
|
120
|
-
(arg, index) => arg.type !== AST_NODE_TYPES.SpreadElement && (index ===
|
|
124
|
+
(arg, index) => arg.type !== AST_NODE_TYPES.SpreadElement && (index === bounds.length || arg.type === AST_NODE_TYPES.Literal && arg.value === bounds[index])
|
|
121
125
|
);
|
|
122
126
|
};
|
|
123
127
|
|