eslint-plugin-ts-data-forge 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +1 -0
  2. package/dist/plugin.d.mts +2 -0
  3. package/dist/plugin.d.mts.map +1 -1
  4. package/dist/plugin.mjs +1 -0
  5. package/dist/plugin.mjs.map +1 -1
  6. package/dist/rule-types.d.mts +15 -0
  7. package/dist/rule-types.d.mts.map +1 -1
  8. package/dist/rules/ast-utils.mjs.map +1 -1
  9. package/dist/rules/branded-number-types.mjs.map +1 -1
  10. package/dist/rules/constants.mjs.map +1 -1
  11. package/dist/rules/import-utils.mjs.map +1 -1
  12. package/dist/rules/no-side-effect-import.d.mts +17 -0
  13. package/dist/rules/no-side-effect-import.d.mts.map +1 -0
  14. package/dist/rules/no-side-effect-import.mjs +41 -0
  15. package/dist/rules/no-side-effect-import.mjs.map +1 -0
  16. package/dist/rules/no-unnecessary-type-guard.mjs.map +1 -1
  17. package/dist/rules/prefer-arr-is-array.mjs.map +1 -1
  18. package/dist/rules/prefer-arr-is-bounded-length-array.mjs.map +1 -1
  19. package/dist/rules/prefer-arr-is-fixed-length-array.mjs.map +1 -1
  20. package/dist/rules/prefer-arr-is-max-length-array.mjs.map +1 -1
  21. package/dist/rules/prefer-arr-is-min-length-array.mjs.map +1 -1
  22. package/dist/rules/prefer-arr-is-non-empty.mjs.map +1 -1
  23. package/dist/rules/prefer-arr-sum.mjs.map +1 -1
  24. package/dist/rules/prefer-as-int.mjs.map +1 -1
  25. package/dist/rules/prefer-canonical-array-slicing.mjs.map +1 -1
  26. package/dist/rules/prefer-canonical-length-cast.mjs +1 -1
  27. package/dist/rules/prefer-canonical-length-cast.mjs.map +1 -1
  28. package/dist/rules/prefer-canonical-length-guard.mjs.map +1 -1
  29. package/dist/rules/prefer-comparison-over-nullish-guard.mjs.map +1 -1
  30. package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
  31. package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
  32. package/dist/rules/prefer-num-safe-parse-float.mjs.map +1 -1
  33. package/dist/rules/prefer-num-safe-parse-int.mjs.map +1 -1
  34. package/dist/rules/prefer-obj-over-entries-round-trip.mjs.map +1 -1
  35. package/dist/rules/prefer-range-for-loop.mjs +1 -1
  36. package/dist/rules/prefer-range-for-loop.mjs.map +1 -1
  37. package/dist/rules/rules.d.mts +1 -0
  38. package/dist/rules/rules.d.mts.map +1 -1
  39. package/dist/rules/rules.mjs +2 -0
  40. package/dist/rules/rules.mjs.map +1 -1
  41. package/package.json +5 -5
  42. package/src/plugin.mts +1 -0
  43. package/src/rule-types.mts +16 -0
  44. package/src/rules/no-side-effect-import.mts +71 -0
  45. package/src/rules/no-side-effect-import.test.mts +85 -0
  46. package/src/rules/prefer-canonical-length-cast.mts +9 -2
  47. package/src/rules/prefer-range-for-loop.mts +1 -1
  48. package/src/rules/rules.mts +2 -0
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-is-non-null-object.mjs","sources":["../../src/rules/prefer-is-non-null-object.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useIsNonNullObject';\n\nexport const preferIsNonNullObject: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `typeof u === \"object\" && u !== null` with `isNonNullObject(u)` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useIsNonNullObject:\n 'Replace the object/null check with `isNonNullObject()` from ts-data-forge.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const mut_nodesToFix: {\n node: TSESTree.LogicalExpression;\n identifierName: string;\n }[] = [];\n\n return {\n LogicalExpression: (node) => {\n const identifierName = getNonNullObjectIdentifierName(node);\n\n if (identifierName === undefined) return;\n\n mut_nodesToFix.push({ node, identifierName });\n },\n 'Program:exit': () => {\n // Check if isNonNullObject is already imported\n const hasIsNonNullObjectImport =\n getNamedImports(tsDataForgeImport).includes('isNonNullObject');\n\n // Note: We add import only for the first node to avoid conflicts when\n // multiple fixes try to insert at the same position. This means if the\n // first node is disabled via eslint-disable comment, no import will be\n // added.\n for (const [\n index,\n { node, identifierName },\n ] of mut_nodesToFix.entries()) {\n context.report({\n node,\n messageId: 'useIsNonNullObject',\n fix: (fixer) => {\n const replacement = `isNonNullObject(${identifierName})`;\n\n // Add import only for the first node and only if not already imported\n const importFixes =\n index === 0 && !hasIsNonNullObjectImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, [\n 'isNonNullObject',\n ])\n : [];\n\n return [...importFixes, fixer.replaceText(node, replacement)];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n} as const;\n\nconst getNonNullObjectIdentifierName = (\n node: DeepReadonly<TSESTree.LogicalExpression>,\n): string | undefined => {\n if (node.operator !== '&&') return undefined;\n\n const leftIdentifierName = getTypeofObjectIdentifierName(node.left);\n\n const rightIdentifierName = getNonNullCheckIdentifierName(node.right);\n\n if (leftIdentifierName === undefined) return undefined;\n\n if (rightIdentifierName === undefined) return undefined;\n\n if (leftIdentifierName !== rightIdentifierName) return undefined;\n\n return leftIdentifierName;\n};\n\nconst getTypeofObjectIdentifierName = (\n node: DeepReadonly<TSESTree.Expression>,\n): string | undefined => {\n if (node.type !== AST_NODE_TYPES.BinaryExpression) return undefined;\n\n if (node.operator !== '===') return undefined;\n\n const { left, right } = node;\n\n if (left.type !== AST_NODE_TYPES.UnaryExpression) return undefined;\n\n if (left.operator !== 'typeof') return undefined;\n\n if (right.type !== AST_NODE_TYPES.Literal) return undefined;\n\n if (right.value !== 'object') return undefined;\n\n if (left.argument.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n return left.argument.name;\n};\n\nconst getNonNullCheckIdentifierName = (\n node: DeepReadonly<TSESTree.Expression>,\n): string | undefined => {\n if (node.type !== AST_NODE_TYPES.BinaryExpression) return undefined;\n\n if (node.operator !== '!==') return undefined;\n\n const { left, right } = node;\n\n if (left.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n if (right.type !== AST_NODE_TYPES.Literal) return undefined;\n\n if (right.value !== null) return undefined;\n\n return left.name;\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prefer-is-non-null-object.mjs","sources":["../../src/rules/prefer-is-non-null-object.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { type DeepReadonly } from 'ts-type-forge';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useIsNonNullObject';\n\nexport const preferIsNonNullObject: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `typeof u === \"object\" && u !== null` with `isNonNullObject(u)` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useIsNonNullObject:\n 'Replace the object/null check with `isNonNullObject()` from ts-data-forge.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const mut_nodesToFix: {\n node: TSESTree.LogicalExpression;\n identifierName: string;\n }[] = [];\n\n return {\n LogicalExpression: (node) => {\n const identifierName = getNonNullObjectIdentifierName(node);\n\n if (identifierName === undefined) return;\n\n mut_nodesToFix.push({ node, identifierName });\n },\n 'Program:exit': () => {\n // Check if isNonNullObject is already imported\n const hasIsNonNullObjectImport =\n getNamedImports(tsDataForgeImport).includes('isNonNullObject');\n\n // Note: We add import only for the first node to avoid conflicts when\n // multiple fixes try to insert at the same position. This means if the\n // first node is disabled via eslint-disable comment, no import will be\n // added.\n for (const [\n index,\n { node, identifierName },\n ] of mut_nodesToFix.entries()) {\n context.report({\n node,\n messageId: 'useIsNonNullObject',\n fix: (fixer) => {\n const replacement = `isNonNullObject(${identifierName})`;\n\n // Add import only for the first node and only if not already imported\n const importFixes =\n index === 0 && !hasIsNonNullObjectImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, [\n 'isNonNullObject',\n ])\n : [];\n\n return [...importFixes, fixer.replaceText(node, replacement)];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n} as const;\n\nconst getNonNullObjectIdentifierName = (\n node: DeepReadonly<TSESTree.LogicalExpression>,\n): string | undefined => {\n if (node.operator !== '&&') return undefined;\n\n const leftIdentifierName = getTypeofObjectIdentifierName(node.left);\n\n const rightIdentifierName = getNonNullCheckIdentifierName(node.right);\n\n if (leftIdentifierName === undefined) return undefined;\n\n if (rightIdentifierName === undefined) return undefined;\n\n if (leftIdentifierName !== rightIdentifierName) return undefined;\n\n return leftIdentifierName;\n};\n\nconst getTypeofObjectIdentifierName = (\n node: DeepReadonly<TSESTree.Expression>,\n): string | undefined => {\n if (node.type !== AST_NODE_TYPES.BinaryExpression) return undefined;\n\n if (node.operator !== '===') return undefined;\n\n const { left, right } = node;\n\n if (left.type !== AST_NODE_TYPES.UnaryExpression) return undefined;\n\n if (left.operator !== 'typeof') return undefined;\n\n if (right.type !== AST_NODE_TYPES.Literal) return undefined;\n\n if (right.value !== 'object') return undefined;\n\n if (left.argument.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n return left.argument.name;\n};\n\nconst getNonNullCheckIdentifierName = (\n node: DeepReadonly<TSESTree.Expression>,\n): string | undefined => {\n if (node.type !== AST_NODE_TYPES.BinaryExpression) return undefined;\n\n if (node.operator !== '!==') return undefined;\n\n const { left, right } = node;\n\n if (left.type !== AST_NODE_TYPES.Identifier) return undefined;\n\n if (right.type !== AST_NODE_TYPES.Literal) return undefined;\n\n if (right.value !== null) return undefined;\n\n return left.name;\n};\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-is-record-and-has-key.mjs","sources":["../../src/rules/prefer-is-record-and-has-key.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { Arr } from 'ts-data-forge';\nimport * as ts from 'typescript';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useIsRecordAndHasKey' | 'useHasKey';\n\nexport const preferIsRecordAndHasKey: TSESLint.RuleModule<MessageIds, Options> =\n {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `Object.hasOwn(obj, key)` or `key in obj` with `isRecord(obj) && hasKey(obj, key)` from ts-data-forge',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useIsRecordAndHasKey:\n 'Replace `{{original}}` with `isRecord({{objName}}) && hasKey({{objName}}, {{keyName}})` from ts-data-forge.',\n useHasKey:\n 'Replace `{{original}}` with `hasKey({{objName}}, {{keyName}})` 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 = sourceCode.parserServices;\n\n const checker = services?.program?.getTypeChecker();\n\n /**\n * Whether the `isRecord(...)` half of the rewrite would be dead code\n * because the object already satisfies `hasKey`'s\n * `R extends UnknownRecord` constraint. Without type information nothing\n * is known, so the guard is kept.\n */\n const isKnownRecord = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n expression: TSESTree.Expression,\n ): boolean => {\n if (checker === undefined) return false;\n\n const tsNode = services?.esTreeNodeToTSNodeMap?.get(expression);\n\n return (\n tsNode !== undefined &&\n isAlreadyRecordType(checker, checker.getTypeAtLocation(tsNode))\n );\n };\n\n const mut_nodesToFix: {\n node: TSESTree.CallExpression | TSESTree.BinaryExpression;\n objExpression: TSESTree.Expression;\n keyExpression: TSESTree.Expression;\n }[] = [];\n\n return {\n // Handle Object.hasOwn(obj, key)\n CallExpression: (node) => {\n if (\n node.callee.type !== AST_NODE_TYPES.MemberExpression ||\n node.callee.object.type !== AST_NODE_TYPES.Identifier ||\n node.callee.object.name !== 'Object' ||\n node.callee.property.type !== AST_NODE_TYPES.Identifier ||\n node.callee.property.name !== 'hasOwn'\n ) {\n return;\n }\n\n // Check arguments: Object.hasOwn(obj, key)\n if (node.arguments.length !== 2) return;\n\n const objArg = node.arguments[0];\n\n const keyArg = node.arguments[1];\n\n if (objArg === undefined || keyArg === undefined) return;\n\n // Type guard: ensure arguments are Expressions (not SpreadElement)\n if (\n objArg.type === AST_NODE_TYPES.SpreadElement ||\n keyArg.type === AST_NODE_TYPES.SpreadElement\n ) {\n return;\n }\n\n mut_nodesToFix.push({\n node,\n objExpression: objArg,\n keyExpression: keyArg,\n });\n },\n\n // Handle key in obj\n BinaryExpression: (node) => {\n if (node.operator !== 'in') return;\n\n const keyExpression = node.left;\n\n const objExpression = node.right;\n\n // Type guard: ensure keyExpression is Expression (not PrivateIdentifier)\n if (keyExpression.type === AST_NODE_TYPES.PrivateIdentifier) return;\n\n mut_nodesToFix.push({\n node,\n objExpression,\n keyExpression,\n });\n },\n\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const rewrites = mut_nodesToFix.map((entry) => ({\n ...entry,\n // A record-typed object makes the `isRecord(...)` conjunct always\n // true, so it is left out of the replacement entirely.\n needsIsRecord: !isKnownRecord(entry.objExpression),\n }));\n\n const importsToAdd: readonly string[] = [\n ...(rewrites.some(({ needsIsRecord }) => needsIsRecord) &&\n !namedImports.includes('isRecord')\n ? ['isRecord']\n : []),\n ...(namedImports.includes('hasKey') ? [] : ['hasKey']),\n ];\n\n for (const [\n index,\n { node, objExpression, keyExpression, needsIsRecord },\n ] of rewrites.entries()) {\n const objText = sourceCode.getText(objExpression);\n\n const keyText = sourceCode.getText(keyExpression);\n\n const originalText = sourceCode.getText(node);\n\n context.report({\n node,\n messageId: needsIsRecord ? 'useIsRecordAndHasKey' : 'useHasKey',\n data: {\n original: originalText,\n objName: objText,\n keyName: keyText,\n },\n fix: (fixer) => {\n const replacement = needsIsRecord\n ? `(isRecord(${objText}) && hasKey(${objText}, ${keyText}))`\n : `hasKey(${objText}, ${keyText})`;\n\n // All import fixes have to live on the first report:\n // `insertTextBefore(program, …)` produces overlapping ranges\n // otherwise and only one of them would survive a pass.\n const importFixes =\n index === 0 && importsToAdd.length > 0\n ? buildImportFixes(\n fixer,\n program,\n tsDataForgeImport,\n importsToAdd,\n )\n : [];\n\n return [...importFixes, fixer.replaceText(node, replacement)];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n } as const;\n\n/**\n * Whether `type` already satisfies `hasKey`'s `R extends UnknownRecord`\n * constraint, so that guarding the call with `isRecord` adds nothing.\n *\n * `UnknownRecord` is `ReadonlyRecord<string, unknown>`, so a string index\n * signature is exactly what it asks for and its value type is `unknown`, which\n * anything satisfies. Callables and arrays are excluded because `isRecord`\n * rejects them at runtime (`typeof fn === 'object'` is false, `Array.isArray`\n * is true), so dropping the guard there would change behavior.\n *\n * Deliberately conservative: a type TypeScript would accept through an\n * *implicit* index signature (an object type alias without one of its own)\n * keeps the guard, because the checker does not report such a signature here.\n */\nconst isAlreadyRecordType = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n checker: ts.TypeChecker,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n type: ts.Type,\n): boolean =>\n type.isUnion()\n ? type.types.every((member) => isAlreadyRecordType(checker, member))\n : checker.getIndexInfoOfType(type, ts.IndexKind.String) !== undefined &&\n Arr.isEmpty(checker.getSignaturesOfType(type, ts.SignatureKind.Call)) &&\n Arr.isEmpty(\n checker.getSignaturesOfType(type, ts.SignatureKind.Construct),\n ) &&\n !checker.isArrayType(type) &&\n !checker.isTupleType(type);\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prefer-is-record-and-has-key.mjs","sources":["../../src/rules/prefer-is-record-and-has-key.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { Arr } from 'ts-data-forge';\nimport * as ts from 'typescript';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useIsRecordAndHasKey' | 'useHasKey';\n\nexport const preferIsRecordAndHasKey: TSESLint.RuleModule<MessageIds, Options> =\n {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `Object.hasOwn(obj, key)` or `key in obj` with `isRecord(obj) && hasKey(obj, key)` from ts-data-forge',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useIsRecordAndHasKey:\n 'Replace `{{original}}` with `isRecord({{objName}}) && hasKey({{objName}}, {{keyName}})` from ts-data-forge.',\n useHasKey:\n 'Replace `{{original}}` with `hasKey({{objName}}, {{keyName}})` 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 = sourceCode.parserServices;\n\n const checker = services?.program?.getTypeChecker();\n\n /**\n * Whether the `isRecord(...)` half of the rewrite would be dead code\n * because the object already satisfies `hasKey`'s\n * `R extends UnknownRecord` constraint. Without type information nothing\n * is known, so the guard is kept.\n */\n const isKnownRecord = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n expression: TSESTree.Expression,\n ): boolean => {\n if (checker === undefined) return false;\n\n const tsNode = services?.esTreeNodeToTSNodeMap?.get(expression);\n\n return (\n tsNode !== undefined &&\n isAlreadyRecordType(checker, checker.getTypeAtLocation(tsNode))\n );\n };\n\n const mut_nodesToFix: {\n node: TSESTree.CallExpression | TSESTree.BinaryExpression;\n objExpression: TSESTree.Expression;\n keyExpression: TSESTree.Expression;\n }[] = [];\n\n return {\n // Handle Object.hasOwn(obj, key)\n CallExpression: (node) => {\n if (\n node.callee.type !== AST_NODE_TYPES.MemberExpression ||\n node.callee.object.type !== AST_NODE_TYPES.Identifier ||\n node.callee.object.name !== 'Object' ||\n node.callee.property.type !== AST_NODE_TYPES.Identifier ||\n node.callee.property.name !== 'hasOwn'\n ) {\n return;\n }\n\n // Check arguments: Object.hasOwn(obj, key)\n if (node.arguments.length !== 2) return;\n\n const objArg = node.arguments[0];\n\n const keyArg = node.arguments[1];\n\n if (objArg === undefined || keyArg === undefined) return;\n\n // Type guard: ensure arguments are Expressions (not SpreadElement)\n if (\n objArg.type === AST_NODE_TYPES.SpreadElement ||\n keyArg.type === AST_NODE_TYPES.SpreadElement\n ) {\n return;\n }\n\n mut_nodesToFix.push({\n node,\n objExpression: objArg,\n keyExpression: keyArg,\n });\n },\n\n // Handle key in obj\n BinaryExpression: (node) => {\n if (node.operator !== 'in') return;\n\n const keyExpression = node.left;\n\n const objExpression = node.right;\n\n // Type guard: ensure keyExpression is Expression (not PrivateIdentifier)\n if (keyExpression.type === AST_NODE_TYPES.PrivateIdentifier) return;\n\n mut_nodesToFix.push({\n node,\n objExpression,\n keyExpression,\n });\n },\n\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const rewrites = mut_nodesToFix.map((entry) => ({\n ...entry,\n // A record-typed object makes the `isRecord(...)` conjunct always\n // true, so it is left out of the replacement entirely.\n needsIsRecord: !isKnownRecord(entry.objExpression),\n }));\n\n const importsToAdd: readonly string[] = [\n ...(rewrites.some(({ needsIsRecord }) => needsIsRecord) &&\n !namedImports.includes('isRecord')\n ? ['isRecord']\n : []),\n ...(namedImports.includes('hasKey') ? [] : ['hasKey']),\n ];\n\n for (const [\n index,\n { node, objExpression, keyExpression, needsIsRecord },\n ] of rewrites.entries()) {\n const objText = sourceCode.getText(objExpression);\n\n const keyText = sourceCode.getText(keyExpression);\n\n const originalText = sourceCode.getText(node);\n\n context.report({\n node,\n messageId: needsIsRecord ? 'useIsRecordAndHasKey' : 'useHasKey',\n data: {\n original: originalText,\n objName: objText,\n keyName: keyText,\n },\n fix: (fixer) => {\n const replacement = needsIsRecord\n ? `(isRecord(${objText}) && hasKey(${objText}, ${keyText}))`\n : `hasKey(${objText}, ${keyText})`;\n\n // All import fixes have to live on the first report:\n // `insertTextBefore(program, …)` produces overlapping ranges\n // otherwise and only one of them would survive a pass.\n const importFixes =\n index === 0 && importsToAdd.length > 0\n ? buildImportFixes(\n fixer,\n program,\n tsDataForgeImport,\n importsToAdd,\n )\n : [];\n\n return [...importFixes, fixer.replaceText(node, replacement)];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n } as const;\n\n/**\n * Whether `type` already satisfies `hasKey`'s `R extends UnknownRecord`\n * constraint, so that guarding the call with `isRecord` adds nothing.\n *\n * `UnknownRecord` is `ReadonlyRecord<string, unknown>`, so a string index\n * signature is exactly what it asks for and its value type is `unknown`, which\n * anything satisfies. Callables and arrays are excluded because `isRecord`\n * rejects them at runtime (`typeof fn === 'object'` is false, `Array.isArray`\n * is true), so dropping the guard there would change behavior.\n *\n * Deliberately conservative: a type TypeScript would accept through an\n * *implicit* index signature (an object type alias without one of its own)\n * keeps the guard, because the checker does not report such a signature here.\n */\nconst isAlreadyRecordType = (\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n checker: ts.TypeChecker,\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n type: ts.Type,\n): boolean =>\n type.isUnion()\n ? type.types.every((member) => isAlreadyRecordType(checker, member))\n : checker.getIndexInfoOfType(type, ts.IndexKind.String) !== undefined &&\n Arr.isEmpty(checker.getSignaturesOfType(type, ts.SignatureKind.Call)) &&\n Arr.isEmpty(\n checker.getSignaturesOfType(type, ts.SignatureKind.Construct),\n ) &&\n !checker.isArrayType(type) &&\n !checker.isTupleType(type);\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-num-safe-parse-float.mjs","sources":["../../src/rules/prefer-num-safe-parse-float.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport * as ts from 'typescript';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useSafeParseFloat';\n\n/**\n * Returns `true` when every constituent of `type` is string-like, i.e. the\n * type is assignable to the `string` parameter of `Num.safeParseFloat`. A\n * union such as `string | undefined` is rejected so the autofix never\n * produces a type error.\n */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst isStringType = (type: ts.Type): boolean => {\n const parts = type.isUnion() ? type.types : ([type] as const);\n\n return (\n parts.length > 0 &&\n parts.every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0)\n );\n};\n\n/**\n * Detects `parseFloat(x)`, `Number.parseFloat(x)`, and `Number(x)` (when x\n * is typed as `string`) and replaces them with\n * `Result.unwrapOkOr(Num.safeParseFloat(x), Number.NaN)` (ts-data-forge).\n *\n * `Number(x)` is only flagged when x is purely a string type to avoid false\n * positives for `Number(someBoolean)` or `Number(someNumber)` uses.\n */\nexport const preferNumSafeParseFloat: TSESLint.RuleModule<MessageIds, Options> =\n {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `parseFloat(x)`, `Number.parseFloat(x)`, or `Number(x)` (when x is a string) with `Result.unwrapOkOr(Num.safeParseFloat(x), Number.NaN)` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useSafeParseFloat:\n 'Replace `{{original}}` with `Result.unwrapOkOr(Num.safeParseFloat({{argName}}), Number.NaN)` 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 argExpression: TSESTree.Expression;\n }[] = [];\n\n return {\n CallExpression: (node) => {\n const { callee } = node;\n\n // Match `parseFloat(...)` (global function).\n const isGlobalParseFloat =\n callee.type === AST_NODE_TYPES.Identifier &&\n callee.name === 'parseFloat';\n\n // Match `Number.parseFloat(...)`.\n const isNumberParseFloat =\n callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n callee.object.name === 'Number' &&\n callee.property.type === AST_NODE_TYPES.Identifier &&\n callee.property.name === 'parseFloat';\n\n // Match `Number(...)` called as a function (not `new Number(...)`).\n const isNumberCall =\n callee.type === AST_NODE_TYPES.Identifier &&\n callee.name === 'Number';\n\n if (!isGlobalParseFloat && !isNumberParseFloat && !isNumberCall)\n return;\n\n const args = node.arguments;\n\n const firstArg = args[0];\n\n if (firstArg === undefined) return;\n\n // Spread argument (`parseFloat(...rest)`) cannot be rewritten safely.\n if (firstArg.type === AST_NODE_TYPES.SpreadElement) return;\n\n // The argument must be purely `string` so the autofix is type-safe\n // against `Num.safeParseFloat(s: string)`. Without type information,\n // skip.\n if (services?.program == null) return;\n\n const checker = services.program.getTypeChecker();\n\n const tsNode = services.esTreeNodeToTSNodeMap?.get(firstArg);\n\n if (tsNode === undefined) return;\n\n const argType = checker.getTypeAtLocation(tsNode);\n\n if (!isStringType(argType)) return;\n\n mut_nodesToFix.push({ node, argExpression: firstArg });\n },\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const missingImports = (['Num', 'Result'] as const).filter(\n (name) => !namedImports.includes(name),\n );\n\n for (const [\n index,\n { node, argExpression },\n ] of mut_nodesToFix.entries()) {\n const argText = sourceCode.getText(argExpression);\n\n const originalText = sourceCode.getText(node);\n\n context.report({\n node,\n messageId: 'useSafeParseFloat',\n data: {\n original: originalText,\n argName: argText,\n },\n fix: (fixer) => {\n const core = `Result.unwrapOkOr(Num.safeParseFloat(${argText}), Number.NaN)`;\n\n const importFixes =\n index === 0 && missingImports.length > 0\n ? buildImportFixes(\n fixer,\n program,\n tsDataForgeImport,\n missingImports,\n )\n : [];\n\n return [...importFixes, fixer.replaceText(node, core)];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n } as const;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prefer-num-safe-parse-float.mjs","sources":["../../src/rules/prefer-num-safe-parse-float.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport * as ts from 'typescript';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useSafeParseFloat';\n\n/**\n * Returns `true` when every constituent of `type` is string-like, i.e. the\n * type is assignable to the `string` parameter of `Num.safeParseFloat`. A\n * union such as `string | undefined` is rejected so the autofix never\n * produces a type error.\n */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst isStringType = (type: ts.Type): boolean => {\n const parts = type.isUnion() ? type.types : ([type] as const);\n\n return (\n parts.length > 0 &&\n parts.every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0)\n );\n};\n\n/**\n * Detects `parseFloat(x)`, `Number.parseFloat(x)`, and `Number(x)` (when x\n * is typed as `string`) and replaces them with\n * `Result.unwrapOkOr(Num.safeParseFloat(x), Number.NaN)` (ts-data-forge).\n *\n * `Number(x)` is only flagged when x is purely a string type to avoid false\n * positives for `Number(someBoolean)` or `Number(someNumber)` uses.\n */\nexport const preferNumSafeParseFloat: TSESLint.RuleModule<MessageIds, Options> =\n {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `parseFloat(x)`, `Number.parseFloat(x)`, or `Number(x)` (when x is a string) with `Result.unwrapOkOr(Num.safeParseFloat(x), Number.NaN)` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useSafeParseFloat:\n 'Replace `{{original}}` with `Result.unwrapOkOr(Num.safeParseFloat({{argName}}), Number.NaN)` 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 argExpression: TSESTree.Expression;\n }[] = [];\n\n return {\n CallExpression: (node) => {\n const { callee } = node;\n\n // Match `parseFloat(...)` (global function).\n const isGlobalParseFloat =\n callee.type === AST_NODE_TYPES.Identifier &&\n callee.name === 'parseFloat';\n\n // Match `Number.parseFloat(...)`.\n const isNumberParseFloat =\n callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n callee.object.name === 'Number' &&\n callee.property.type === AST_NODE_TYPES.Identifier &&\n callee.property.name === 'parseFloat';\n\n // Match `Number(...)` called as a function (not `new Number(...)`).\n const isNumberCall =\n callee.type === AST_NODE_TYPES.Identifier &&\n callee.name === 'Number';\n\n if (!isGlobalParseFloat && !isNumberParseFloat && !isNumberCall)\n return;\n\n const args = node.arguments;\n\n const firstArg = args[0];\n\n if (firstArg === undefined) return;\n\n // Spread argument (`parseFloat(...rest)`) cannot be rewritten safely.\n if (firstArg.type === AST_NODE_TYPES.SpreadElement) return;\n\n // The argument must be purely `string` so the autofix is type-safe\n // against `Num.safeParseFloat(s: string)`. Without type information,\n // skip.\n if (services?.program == null) return;\n\n const checker = services.program.getTypeChecker();\n\n const tsNode = services.esTreeNodeToTSNodeMap?.get(firstArg);\n\n if (tsNode === undefined) return;\n\n const argType = checker.getTypeAtLocation(tsNode);\n\n if (!isStringType(argType)) return;\n\n mut_nodesToFix.push({ node, argExpression: firstArg });\n },\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const missingImports = (['Num', 'Result'] as const).filter(\n (name) => !namedImports.includes(name),\n );\n\n for (const [\n index,\n { node, argExpression },\n ] of mut_nodesToFix.entries()) {\n const argText = sourceCode.getText(argExpression);\n\n const originalText = sourceCode.getText(node);\n\n context.report({\n node,\n messageId: 'useSafeParseFloat',\n data: {\n original: originalText,\n argName: argText,\n },\n fix: (fixer) => {\n const core = `Result.unwrapOkOr(Num.safeParseFloat(${argText}), Number.NaN)`;\n\n const importFixes =\n index === 0 && missingImports.length > 0\n ? buildImportFixes(\n fixer,\n program,\n tsDataForgeImport,\n missingImports,\n )\n : [];\n\n return [...importFixes, fixer.replaceText(node, core)];\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-num-safe-parse-int.mjs","sources":["../../src/rules/prefer-num-safe-parse-int.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport * as ts from 'typescript';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useSafeParseInt';\n\n/**\n * Returns `true` when every constituent of `type` is string-like, i.e. the type\n * is assignable to the `string` parameter of `Num.safeParseInt`. A union such\n * as `string | undefined` is rejected so the autofix never produces a type\n * error.\n */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst isStringType = (type: ts.Type): boolean => {\n const parts = type.isUnion() ? type.types : ([type] as const);\n\n return (\n parts.length > 0 &&\n parts.every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0)\n );\n};\n\n/**\n * Detects the base-10 forms of `parseInt` / `Number.parseInt` (`parseInt(x)` or\n * `parseInt(x, 10)`) and replaces them with\n * `Result.unwrapOkOr(Num.safeParseInt(x), Number.NaN)` (ts-data-forge).\n *\n * Other radixes (e.g. `parseInt(x, 16)`) are intentionally left untouched, as\n * `Num.safeParseInt` only supports base 10.\n */\nexport const preferNumSafeParseInt: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `parseInt(x, 10)` with `Result.unwrapOkOr(Num.safeParseInt(x), Number.NaN)` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useSafeParseInt:\n 'Replace `{{original}}` with `Result.unwrapOkOr(Num.safeParseInt({{argName}}), Number.NaN)` 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 argExpression: TSESTree.Expression;\n }[] = [];\n\n return {\n CallExpression: (node) => {\n const { callee } = node;\n\n // Match `parseInt(...)` or `Number.parseInt(...)`.\n const isGlobalParseInt =\n callee.type === AST_NODE_TYPES.Identifier &&\n callee.name === 'parseInt';\n\n const isNumberParseInt =\n callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n callee.object.name === 'Number' &&\n callee.property.type === AST_NODE_TYPES.Identifier &&\n callee.property.name === 'parseInt';\n\n if (!isGlobalParseInt && !isNumberParseInt) return;\n\n const args = node.arguments;\n\n const firstArg = args[0];\n\n if (firstArg === undefined) return;\n\n // Spread argument (`parseInt(...rest)`) cannot be rewritten safely.\n if (firstArg.type === AST_NODE_TYPES.SpreadElement) return;\n\n // Only base 10: accept a missing radix or an explicit literal `10`.\n if (args.length >= 2) {\n const radix = args[1];\n\n if (radix?.type !== AST_NODE_TYPES.Literal || radix.value !== 10) {\n return;\n }\n }\n\n // The argument must be a `string` so the autofix is type-safe against\n // `Num.safeParseInt(s: string)`. Without type information, skip.\n if (services?.program == null) return;\n\n const checker = services.program.getTypeChecker();\n\n const tsNode = services.esTreeNodeToTSNodeMap?.get(firstArg);\n\n if (tsNode === undefined) return;\n\n const argType = checker.getTypeAtLocation(tsNode);\n\n if (!isStringType(argType)) return;\n\n mut_nodesToFix.push({ node, argExpression: firstArg });\n },\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const missingImports = (['Num', 'Result'] as const).filter(\n (name) => !namedImports.includes(name),\n );\n\n for (const [\n index,\n { node, argExpression },\n ] of mut_nodesToFix.entries()) {\n const argText = sourceCode.getText(argExpression);\n\n const originalText = sourceCode.getText(node);\n\n context.report({\n node,\n messageId: 'useSafeParseInt',\n data: {\n original: originalText,\n argName: argText,\n },\n fix: (fixer) => {\n const core = `Result.unwrapOkOr(Num.safeParseInt(${argText}), Number.NaN)`;\n\n const importFixes =\n index === 0 && missingImports.length > 0\n ? buildImportFixes(\n fixer,\n program,\n tsDataForgeImport,\n missingImports,\n )\n : [];\n\n return [...importFixes, fixer.replaceText(node, core)];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n} as const;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prefer-num-safe-parse-int.mjs","sources":["../../src/rules/prefer-num-safe-parse-int.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport * as ts from 'typescript';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useSafeParseInt';\n\n/**\n * Returns `true` when every constituent of `type` is string-like, i.e. the type\n * is assignable to the `string` parameter of `Num.safeParseInt`. A union such\n * as `string | undefined` is rejected so the autofix never produces a type\n * error.\n */\n// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\nconst isStringType = (type: ts.Type): boolean => {\n const parts = type.isUnion() ? type.types : ([type] as const);\n\n return (\n parts.length > 0 &&\n parts.every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0)\n );\n};\n\n/**\n * Detects the base-10 forms of `parseInt` / `Number.parseInt` (`parseInt(x)` or\n * `parseInt(x, 10)`) and replaces them with\n * `Result.unwrapOkOr(Num.safeParseInt(x), Number.NaN)` (ts-data-forge).\n *\n * Other radixes (e.g. `parseInt(x, 16)`) are intentionally left untouched, as\n * `Num.safeParseInt` only supports base 10.\n */\nexport const preferNumSafeParseInt: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `parseInt(x, 10)` with `Result.unwrapOkOr(Num.safeParseInt(x), Number.NaN)` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useSafeParseInt:\n 'Replace `{{original}}` with `Result.unwrapOkOr(Num.safeParseInt({{argName}}), Number.NaN)` 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 argExpression: TSESTree.Expression;\n }[] = [];\n\n return {\n CallExpression: (node) => {\n const { callee } = node;\n\n // Match `parseInt(...)` or `Number.parseInt(...)`.\n const isGlobalParseInt =\n callee.type === AST_NODE_TYPES.Identifier &&\n callee.name === 'parseInt';\n\n const isNumberParseInt =\n callee.type === AST_NODE_TYPES.MemberExpression &&\n !callee.computed &&\n callee.object.type === AST_NODE_TYPES.Identifier &&\n callee.object.name === 'Number' &&\n callee.property.type === AST_NODE_TYPES.Identifier &&\n callee.property.name === 'parseInt';\n\n if (!isGlobalParseInt && !isNumberParseInt) return;\n\n const args = node.arguments;\n\n const firstArg = args[0];\n\n if (firstArg === undefined) return;\n\n // Spread argument (`parseInt(...rest)`) cannot be rewritten safely.\n if (firstArg.type === AST_NODE_TYPES.SpreadElement) return;\n\n // Only base 10: accept a missing radix or an explicit literal `10`.\n if (args.length >= 2) {\n const radix = args[1];\n\n if (radix?.type !== AST_NODE_TYPES.Literal || radix.value !== 10) {\n return;\n }\n }\n\n // The argument must be a `string` so the autofix is type-safe against\n // `Num.safeParseInt(s: string)`. Without type information, skip.\n if (services?.program == null) return;\n\n const checker = services.program.getTypeChecker();\n\n const tsNode = services.esTreeNodeToTSNodeMap?.get(firstArg);\n\n if (tsNode === undefined) return;\n\n const argType = checker.getTypeAtLocation(tsNode);\n\n if (!isStringType(argType)) return;\n\n mut_nodesToFix.push({ node, argExpression: firstArg });\n },\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const missingImports = (['Num', 'Result'] as const).filter(\n (name) => !namedImports.includes(name),\n );\n\n for (const [\n index,\n { node, argExpression },\n ] of mut_nodesToFix.entries()) {\n const argText = sourceCode.getText(argExpression);\n\n const originalText = sourceCode.getText(node);\n\n context.report({\n node,\n messageId: 'useSafeParseInt',\n data: {\n original: originalText,\n argName: argText,\n },\n fix: (fixer) => {\n const core = `Result.unwrapOkOr(Num.safeParseInt(${argText}), Number.NaN)`;\n\n const importFixes =\n index === 0 && missingImports.length > 0\n ? buildImportFixes(\n fixer,\n program,\n tsDataForgeImport,\n missingImports,\n )\n : [];\n\n return [...importFixes, fixer.replaceText(node, core)];\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-obj-over-entries-round-trip.mjs","sources":["../../src/rules/prefer-obj-over-entries-round-trip.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { Arr } from 'ts-data-forge';\nimport { type ReadonlyRecord } from 'ts-type-forge';\nimport {\n buildImportFixes,\n getImportedLocalName,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\ntype Options = readonly [];\n\ntype MessageIds = 'useObjFilter' | 'useObjFilterMap' | 'useObjMap';\n\n/** The `Obj` function an entries round trip is rewritten to. */\ntype ObjMethod = 'filter' | 'filterMap' | 'map';\n\nconst MESSAGE_ID_OF = {\n map: 'useObjMap',\n filter: 'useObjFilter',\n filterMap: 'useObjFilterMap',\n} as const satisfies ReadonlyRecord<ObjMethod, MessageIds>;\n\n/**\n * The body the rewritten callback gets:\n *\n * - `expression` — the mapped value (`Obj.map`) or the predicate (`Obj.filter`),\n * used verbatim.\n * - `optional` — the two branches of the `flatMap` callback's conditional,\n * rebuilt as `Optional.some(...)` / `Optional.none` (`Obj.filterMap`).\n */\ntype CallbackBody = Readonly<\n | { kind: 'expression'; expression: TSESTree.Expression }\n | {\n kind: 'optional';\n test: TSESTree.Expression;\n kept: TSESTree.Expression;\n keepOnTrue: boolean;\n }\n>;\n\ntype RoundTrip = Readonly<{\n method: ObjMethod;\n /** The record `Object.entries` was called on. */\n record: TSESTree.Expression;\n /** The callback given to `map` / `filter` / `flatMap`. */\n callback: TSESTree.ArrowFunctionExpression;\n /** `k` in `([k, v]) => …`, absent when the entry pattern elides it. */\n keyParam: TSESTree.Identifier | undefined;\n /** `v` in `([k, v]) => …`, absent when the entry pattern elides it. */\n valueParam: TSESTree.Identifier | undefined;\n /**\n * The key identifiers the rewrite drops — the ones that only rebuilt the\n * entry. They are excluded when deciding whether the callback still needs a\n * key parameter.\n */\n dropped: readonly TSESTree.Identifier[];\n body: CallbackBody;\n}>;\n\nexport const preferObjOverEntriesRoundTrip: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace an `Object.fromEntries(Object.entries(record).map/filter/flatMap(...))` round trip with `Obj.map` / `Obj.filter` / `Obj.filterMap` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useObjMap:\n 'Replace this `Object.fromEntries(Object.entries(…).map(…))` round trip with `Obj.map` from ts-data-forge, which keeps the key set of the record in the result type.',\n useObjFilter:\n 'Replace this `Object.fromEntries(Object.entries(…).filter(…))` round trip with `Obj.filter` from ts-data-forge, which keeps the key and value types of the record in the result type.',\n useObjFilterMap:\n 'Replace this `Object.fromEntries(Object.entries(…).flatMap(…))` round trip with `Obj.filterMap` from ts-data-forge, which keeps the key types of the record in the result type.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const mut_roundTrips: {\n node: TSESTree.CallExpression;\n roundTrip: RoundTrip;\n }[] = [];\n\n /**\n * Rebuilds the callback's parameter list. The entry pattern binds\n * `[key, value]`; `Obj` passes `(value, key)`, and either parameter is\n * dropped when the rewritten body no longer refers to it.\n */\n const buildParams = (roundTrip: RoundTrip): string => {\n const scope = sourceCode.getScope(roundTrip.callback);\n\n const dropped = new Set<TSESTree.Node>(roundTrip.dropped);\n\n const keyName =\n roundTrip.keyParam !== undefined &&\n isReferencedOutside(scope, roundTrip.keyParam.name, dropped)\n ? roundTrip.keyParam.name\n : undefined;\n\n const valueName = roundTrip.valueParam?.name;\n\n if (keyName !== undefined) {\n // `Obj` puts the value first, so a callback that only needs the key\n // still has to name a value parameter.\n return `(${valueName ?? unusedValueParamName(scope)}, ${keyName})`;\n }\n\n return valueName !== undefined &&\n isReferencedOutside(scope, valueName, dropped)\n ? `(${valueName})`\n : '()';\n };\n\n const buildCallbackBody = (\n roundTrip: RoundTrip,\n optionalName: string,\n ): string => {\n const { body } = roundTrip;\n\n if (body.kind === 'expression') {\n return arrowBodyText(\n sourceCode.getText(body.expression),\n body.expression,\n );\n }\n\n const testText = conditionalTestText(\n sourceCode.getText(body.test),\n body.test,\n );\n\n const someText = `${optionalName}.some(${argumentText(sourceCode.getText(body.kept), body.kept)})`;\n\n const noneText = `${optionalName}.none`;\n\n return body.keepOnTrue\n ? `${testText} ? ${someText} : ${noneText}`\n : `${testText} ? ${noneText} : ${someText}`;\n };\n\n return {\n CallExpression: (node) => {\n const roundTrip = parseEntriesRoundTrip(node);\n\n if (roundTrip === undefined) return;\n\n mut_roundTrips.push({ node, roundTrip });\n },\n\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const objName = getImportedLocalName(tsDataForgeImport, 'Obj') ?? 'Obj';\n\n const optionalName =\n getImportedLocalName(tsDataForgeImport, 'Optional') ?? 'Optional';\n\n const needsOptional = mut_roundTrips.some(\n ({ roundTrip }) => roundTrip.body.kind === 'optional',\n );\n\n const missingImports = (['Obj', 'Optional'] as const).filter(\n (name) =>\n (name !== 'Optional' || needsOptional) &&\n !namedImports.includes(name),\n );\n\n // Note: the import is added by the first fix only, so that several\n // fixes in one file do not each insert it at the same position.\n for (const [index, { node, roundTrip }] of mut_roundTrips.entries()) {\n context.report({\n node,\n messageId: MESSAGE_ID_OF[roundTrip.method],\n fix: (fixer) => {\n const recordText = argumentText(\n sourceCode.getText(roundTrip.record),\n roundTrip.record,\n );\n\n const replacement = `${objName}.${roundTrip.method}(${recordText}, ${buildParams(roundTrip)} => ${buildCallbackBody(roundTrip, optionalName)})`;\n\n const importFixes =\n index === 0 && missingImports.length > 0\n ? buildImportFixes(\n fixer,\n program,\n tsDataForgeImport,\n missingImports,\n )\n : [];\n\n return [...importFixes, fixer.replaceText(node, replacement)];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n} as const;\n\n/**\n * Matches `Object.fromEntries(Object.entries(record).map|filter|flatMap(cb))`\n * and reports which `Obj` function replaces it, or `undefined` when the call is\n * not such a round trip or `cb` has a shape `Obj` cannot express (a key\n * transform, a block body, an entry-index parameter, …).\n */\nconst parseEntriesRoundTrip = (\n node: TSESTree.CallExpression,\n): RoundTrip | undefined => {\n if (!isObjectStaticCall(node, 'fromEntries')) return undefined;\n\n if (!Arr.isFixedLengthTuple(1, node.arguments)) return undefined;\n\n const [transform] = node.arguments;\n\n if (transform.type !== AST_NODE_TYPES.CallExpression) return undefined;\n\n const { callee } = transform;\n\n if (\n callee.type !== AST_NODE_TYPES.MemberExpression ||\n callee.computed ||\n callee.property.type !== AST_NODE_TYPES.Identifier\n ) {\n return undefined;\n }\n\n const arrayMethod = callee.property.name;\n\n if (\n arrayMethod !== 'map' &&\n arrayMethod !== 'filter' &&\n arrayMethod !== 'flatMap'\n ) {\n return undefined;\n }\n\n const entriesCall = callee.object;\n\n if (\n entriesCall.type !== AST_NODE_TYPES.CallExpression ||\n !isObjectStaticCall(entriesCall, 'entries') ||\n !Arr.isFixedLengthTuple(1, entriesCall.arguments)\n ) {\n return undefined;\n }\n\n const [record] = entriesCall.arguments;\n\n if (record.type === AST_NODE_TYPES.SpreadElement) return undefined;\n\n // A second argument (`thisArg`) or a callback that also takes the entry index\n // has no counterpart in `Obj`.\n if (!Arr.isFixedLengthTuple(1, transform.arguments)) return undefined;\n\n const [callback] = transform.arguments;\n\n if (\n callback.type !== AST_NODE_TYPES.ArrowFunctionExpression ||\n callback.async ||\n // An explicit return type is either redundant or a type predicate; the\n // latter would be lost by the rewrite.\n callback.returnType !== undefined ||\n !Arr.isFixedLengthTuple(1, callback.params) ||\n callback.body.type === AST_NODE_TYPES.BlockStatement\n ) {\n return undefined;\n }\n\n const [entryPattern] = callback.params;\n\n if (entryPattern.type !== AST_NODE_TYPES.ArrayPattern) return undefined;\n\n const entryParams = parseEntryPattern(entryPattern);\n\n if (entryParams === undefined) return undefined;\n\n const body = callback.body;\n\n const common = { record, callback, ...entryParams } as const;\n\n switch (arrayMethod) {\n case 'filter':\n return {\n ...common,\n method: 'filter',\n dropped: [],\n body: { kind: 'expression', expression: body },\n };\n\n case 'map': {\n const entry = parseKeptEntry(body, entryParams.keyParam);\n\n return entry === undefined\n ? undefined\n : {\n ...common,\n method: 'map',\n dropped: [entry.key],\n body: { kind: 'expression', expression: entry.value },\n };\n }\n\n case 'flatMap': {\n if (body.type !== AST_NODE_TYPES.ConditionalExpression) return undefined;\n\n const onTrue = parseSingletonEntry(body.consequent, entryParams.keyParam);\n\n const onFalse = parseSingletonEntry(body.alternate, entryParams.keyParam);\n\n const keepOnTrue = onTrue !== undefined && isEmptyArray(body.alternate);\n\n const kept = keepOnTrue\n ? onTrue\n : onFalse !== undefined && isEmptyArray(body.consequent)\n ? onFalse\n : undefined;\n\n return kept === undefined\n ? undefined\n : {\n ...common,\n method: 'filterMap',\n dropped: [kept.key],\n body: {\n kind: 'optional',\n test: body.test,\n kept: kept.value,\n keepOnTrue,\n },\n };\n }\n }\n};\n\ntype EntryParams = Readonly<{\n keyParam: TSESTree.Identifier | undefined;\n valueParam: TSESTree.Identifier | undefined;\n}>;\n\n/**\n * Reads the `[key, value]` destructuring the entry callback binds. Anything\n * beyond a plain binding — a default, a rest element, a nested pattern — is\n * rejected, because the rewritten callback receives the value and the key\n * directly rather than an entry tuple.\n */\nconst parseEntryPattern = (\n pattern: TSESTree.ArrayPattern,\n): EntryParams | undefined => {\n if (pattern.elements.length > 2) return undefined;\n\n const [keyElement, valueElement] = pattern.elements;\n\n return isElidedOrIdentifier(keyElement) && isElidedOrIdentifier(valueElement)\n ? {\n keyParam: keyElement ?? undefined,\n valueParam: valueElement ?? undefined,\n }\n : undefined;\n};\n\nconst isElidedOrIdentifier = (\n element: TSESTree.DestructuringPattern | null | undefined,\n): element is TSESTree.Identifier | null | undefined =>\n element === null ||\n element === undefined ||\n element.type === AST_NODE_TYPES.Identifier;\n\ntype KeptEntry = Readonly<{\n /** The key identifier that only rebuilds the entry, dropped by the rewrite. */\n key: TSESTree.Identifier;\n value: TSESTree.Expression;\n}>;\n\n/**\n * Matches `[k, value]` — an entry rebuilt under the very key it came from,\n * which is the only shape `Obj.map` / `Obj.filterMap` can express. An entry\n * built under a different key is left alone.\n */\nconst parseKeptEntry = (\n node: TSESTree.Expression,\n keyParam: TSESTree.Identifier | undefined,\n): KeptEntry | undefined => {\n if (keyParam === undefined) return undefined;\n\n const tuple = stripTypeWrappers(node);\n\n if (\n tuple.type !== AST_NODE_TYPES.ArrayExpression ||\n !Arr.isFixedLengthTuple(2, tuple.elements)\n ) {\n return undefined;\n }\n\n const [key, value] = tuple.elements;\n\n return key !== null &&\n value !== null &&\n key.type === AST_NODE_TYPES.Identifier &&\n value.type !== AST_NODE_TYPES.SpreadElement &&\n key.name === keyParam.name\n ? { key, value }\n : undefined;\n};\n\n/** Matches `[[k, value]]` — the `flatMap` branch that keeps an entry. */\nconst parseSingletonEntry = (\n node: TSESTree.Expression,\n keyParam: TSESTree.Identifier | undefined,\n): KeptEntry | undefined => {\n const array = stripTypeWrappers(node);\n\n if (\n array.type !== AST_NODE_TYPES.ArrayExpression ||\n !Arr.isFixedLengthTuple(1, array.elements)\n ) {\n return undefined;\n }\n\n const [entry] = array.elements;\n\n return entry === null || entry.type === AST_NODE_TYPES.SpreadElement\n ? undefined\n : parseKeptEntry(entry, keyParam);\n};\n\n/** Matches `[]` — the `flatMap` branch that drops an entry. */\nconst isEmptyArray = (node: TSESTree.Expression): boolean => {\n const array = stripTypeWrappers(node);\n\n return (\n array.type === AST_NODE_TYPES.ArrayExpression && array.elements.length === 0\n );\n};\n\n/**\n * Looks through the `as const` / `satisfies` an entry tuple usually carries.\n * Any other assertion is left in place, so that a tuple asserted to a wider\n * type than the callback returns is not reported: `Obj` would infer the\n * narrower one and the rewrite would not type-check.\n */\nconst stripTypeWrappers = (node: TSESTree.Expression): TSESTree.Expression =>\n node.type === AST_NODE_TYPES.TSSatisfiesExpression ||\n (node.type === AST_NODE_TYPES.TSAsExpression && isConstAssertion(node))\n ? stripTypeWrappers(node.expression)\n : node;\n\nconst isConstAssertion = (node: TSESTree.TSAsExpression): boolean =>\n node.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference &&\n node.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier &&\n node.typeAnnotation.typeName.name === 'const';\n\nconst isObjectStaticCall = (\n node: TSESTree.CallExpression,\n name: 'entries' | 'fromEntries',\n): boolean =>\n node.callee.type === AST_NODE_TYPES.MemberExpression &&\n !node.callee.computed &&\n node.callee.object.type === AST_NODE_TYPES.Identifier &&\n node.callee.object.name === 'Object' &&\n node.callee.property.type === AST_NODE_TYPES.Identifier &&\n node.callee.property.name === name;\n\n/**\n * `true` when the parameter is still referenced once the identifiers in\n * `dropped` — the ones that only rebuilt the entry key — are discounted.\n * References from nested functions resolve to the same variable, so they count.\n */\nconst isReferencedOutside = (\n scope: TSESLint.Scope.Scope,\n name: string,\n dropped: ReadonlySet<TSESTree.Node>,\n): boolean =>\n scope.variables\n .find((variable) => variable.name === name)\n ?.references.some((reference) => !dropped.has(reference.identifier)) ??\n false;\n\n/**\n * A name for the value parameter of a callback that only uses the key. `_value`\n * is what the `Obj` documentation uses; a suffix is added if that name is\n * already referenced inside the callback, so the new parameter shadows nothing.\n */\nconst unusedValueParamName = (scope: TSESLint.Scope.Scope): string => {\n const taken = new Set<string>(\n scope.through\n .map((reference) => reference.identifier.name)\n .concat(scope.variables.map((variable) => variable.name)),\n );\n\n const firstFree = (candidate: string, suffix: number): string =>\n taken.has(candidate)\n ? firstFree(`_value${suffix + 1}`, suffix + 1)\n : candidate;\n\n return firstFree('_value', 1);\n};\n\n/** Expressions that bind looser than a conditional's test position. */\nconst NEEDS_PARENS_AS_TEST: ReadonlySet<string> = new Set<string>([\n AST_NODE_TYPES.ArrowFunctionExpression,\n AST_NODE_TYPES.AssignmentExpression,\n AST_NODE_TYPES.ConditionalExpression,\n AST_NODE_TYPES.SequenceExpression,\n AST_NODE_TYPES.YieldExpression,\n]);\n\nconst conditionalTestText = (\n text: string,\n node: TSESTree.Expression,\n): string =>\n NEEDS_PARENS_AS_TEST.has(node.type) ? (`(${text})` as const) : text;\n\n/** A comma expression would read as another argument without parentheses. */\nconst argumentText = (text: string, node: TSESTree.Expression): string =>\n node.type === AST_NODE_TYPES.SequenceExpression\n ? (`(${text})` as const)\n : text;\n\n/**\n * A concise arrow body cannot start with `{`, nor hold a bare comma. The text\n * is what decides the first case rather than the node type, because an object\n * literal keeps its leading brace under an `as const`.\n */\nconst arrowBodyText = (text: string, node: TSESTree.Expression): string =>\n text.startsWith('{') || node.type === AST_NODE_TYPES.SequenceExpression\n ? (`(${text})` as const)\n : text;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prefer-obj-over-entries-round-trip.mjs","sources":["../../src/rules/prefer-obj-over-entries-round-trip.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { Arr } from 'ts-data-forge';\nimport { type ReadonlyRecord } from 'ts-type-forge';\nimport {\n buildImportFixes,\n getImportedLocalName,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\ntype Options = readonly [];\n\ntype MessageIds = 'useObjFilter' | 'useObjFilterMap' | 'useObjMap';\n\n/** The `Obj` function an entries round trip is rewritten to. */\ntype ObjMethod = 'filter' | 'filterMap' | 'map';\n\nconst MESSAGE_ID_OF = {\n map: 'useObjMap',\n filter: 'useObjFilter',\n filterMap: 'useObjFilterMap',\n} as const satisfies ReadonlyRecord<ObjMethod, MessageIds>;\n\n/**\n * The body the rewritten callback gets:\n *\n * - `expression` — the mapped value (`Obj.map`) or the predicate (`Obj.filter`),\n * used verbatim.\n * - `optional` — the two branches of the `flatMap` callback's conditional,\n * rebuilt as `Optional.some(...)` / `Optional.none` (`Obj.filterMap`).\n */\ntype CallbackBody = Readonly<\n | { kind: 'expression'; expression: TSESTree.Expression }\n | {\n kind: 'optional';\n test: TSESTree.Expression;\n kept: TSESTree.Expression;\n keepOnTrue: boolean;\n }\n>;\n\ntype RoundTrip = Readonly<{\n method: ObjMethod;\n /** The record `Object.entries` was called on. */\n record: TSESTree.Expression;\n /** The callback given to `map` / `filter` / `flatMap`. */\n callback: TSESTree.ArrowFunctionExpression;\n /** `k` in `([k, v]) => …`, absent when the entry pattern elides it. */\n keyParam: TSESTree.Identifier | undefined;\n /** `v` in `([k, v]) => …`, absent when the entry pattern elides it. */\n valueParam: TSESTree.Identifier | undefined;\n /**\n * The key identifiers the rewrite drops — the ones that only rebuilt the\n * entry. They are excluded when deciding whether the callback still needs a\n * key parameter.\n */\n dropped: readonly TSESTree.Identifier[];\n body: CallbackBody;\n}>;\n\nexport const preferObjOverEntriesRoundTrip: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace an `Object.fromEntries(Object.entries(record).map/filter/flatMap(...))` round trip with `Obj.map` / `Obj.filter` / `Obj.filterMap` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useObjMap:\n 'Replace this `Object.fromEntries(Object.entries(…).map(…))` round trip with `Obj.map` from ts-data-forge, which keeps the key set of the record in the result type.',\n useObjFilter:\n 'Replace this `Object.fromEntries(Object.entries(…).filter(…))` round trip with `Obj.filter` from ts-data-forge, which keeps the key and value types of the record in the result type.',\n useObjFilterMap:\n 'Replace this `Object.fromEntries(Object.entries(…).flatMap(…))` round trip with `Obj.filterMap` from ts-data-forge, which keeps the key types of the record in the result type.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const mut_roundTrips: {\n node: TSESTree.CallExpression;\n roundTrip: RoundTrip;\n }[] = [];\n\n /**\n * Rebuilds the callback's parameter list. The entry pattern binds\n * `[key, value]`; `Obj` passes `(value, key)`, and either parameter is\n * dropped when the rewritten body no longer refers to it.\n */\n const buildParams = (roundTrip: RoundTrip): string => {\n const scope = sourceCode.getScope(roundTrip.callback);\n\n const dropped = new Set<TSESTree.Node>(roundTrip.dropped);\n\n const keyName =\n roundTrip.keyParam !== undefined &&\n isReferencedOutside(scope, roundTrip.keyParam.name, dropped)\n ? roundTrip.keyParam.name\n : undefined;\n\n const valueName = roundTrip.valueParam?.name;\n\n if (keyName !== undefined) {\n // `Obj` puts the value first, so a callback that only needs the key\n // still has to name a value parameter.\n return `(${valueName ?? unusedValueParamName(scope)}, ${keyName})`;\n }\n\n return valueName !== undefined &&\n isReferencedOutside(scope, valueName, dropped)\n ? `(${valueName})`\n : '()';\n };\n\n const buildCallbackBody = (\n roundTrip: RoundTrip,\n optionalName: string,\n ): string => {\n const { body } = roundTrip;\n\n if (body.kind === 'expression') {\n return arrowBodyText(\n sourceCode.getText(body.expression),\n body.expression,\n );\n }\n\n const testText = conditionalTestText(\n sourceCode.getText(body.test),\n body.test,\n );\n\n const someText = `${optionalName}.some(${argumentText(sourceCode.getText(body.kept), body.kept)})`;\n\n const noneText = `${optionalName}.none`;\n\n return body.keepOnTrue\n ? `${testText} ? ${someText} : ${noneText}`\n : `${testText} ? ${noneText} : ${someText}`;\n };\n\n return {\n CallExpression: (node) => {\n const roundTrip = parseEntriesRoundTrip(node);\n\n if (roundTrip === undefined) return;\n\n mut_roundTrips.push({ node, roundTrip });\n },\n\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const objName = getImportedLocalName(tsDataForgeImport, 'Obj') ?? 'Obj';\n\n const optionalName =\n getImportedLocalName(tsDataForgeImport, 'Optional') ?? 'Optional';\n\n const needsOptional = mut_roundTrips.some(\n ({ roundTrip }) => roundTrip.body.kind === 'optional',\n );\n\n const missingImports = (['Obj', 'Optional'] as const).filter(\n (name) =>\n (name !== 'Optional' || needsOptional) &&\n !namedImports.includes(name),\n );\n\n // Note: the import is added by the first fix only, so that several\n // fixes in one file do not each insert it at the same position.\n for (const [index, { node, roundTrip }] of mut_roundTrips.entries()) {\n context.report({\n node,\n messageId: MESSAGE_ID_OF[roundTrip.method],\n fix: (fixer) => {\n const recordText = argumentText(\n sourceCode.getText(roundTrip.record),\n roundTrip.record,\n );\n\n const replacement = `${objName}.${roundTrip.method}(${recordText}, ${buildParams(roundTrip)} => ${buildCallbackBody(roundTrip, optionalName)})`;\n\n const importFixes =\n index === 0 && missingImports.length > 0\n ? buildImportFixes(\n fixer,\n program,\n tsDataForgeImport,\n missingImports,\n )\n : [];\n\n return [...importFixes, fixer.replaceText(node, replacement)];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n} as const;\n\n/**\n * Matches `Object.fromEntries(Object.entries(record).map|filter|flatMap(cb))`\n * and reports which `Obj` function replaces it, or `undefined` when the call is\n * not such a round trip or `cb` has a shape `Obj` cannot express (a key\n * transform, a block body, an entry-index parameter, …).\n */\nconst parseEntriesRoundTrip = (\n node: TSESTree.CallExpression,\n): RoundTrip | undefined => {\n if (!isObjectStaticCall(node, 'fromEntries')) return undefined;\n\n if (!Arr.isFixedLengthTuple(1, node.arguments)) return undefined;\n\n const [transform] = node.arguments;\n\n if (transform.type !== AST_NODE_TYPES.CallExpression) return undefined;\n\n const { callee } = transform;\n\n if (\n callee.type !== AST_NODE_TYPES.MemberExpression ||\n callee.computed ||\n callee.property.type !== AST_NODE_TYPES.Identifier\n ) {\n return undefined;\n }\n\n const arrayMethod = callee.property.name;\n\n if (\n arrayMethod !== 'map' &&\n arrayMethod !== 'filter' &&\n arrayMethod !== 'flatMap'\n ) {\n return undefined;\n }\n\n const entriesCall = callee.object;\n\n if (\n entriesCall.type !== AST_NODE_TYPES.CallExpression ||\n !isObjectStaticCall(entriesCall, 'entries') ||\n !Arr.isFixedLengthTuple(1, entriesCall.arguments)\n ) {\n return undefined;\n }\n\n const [record] = entriesCall.arguments;\n\n if (record.type === AST_NODE_TYPES.SpreadElement) return undefined;\n\n // A second argument (`thisArg`) or a callback that also takes the entry index\n // has no counterpart in `Obj`.\n if (!Arr.isFixedLengthTuple(1, transform.arguments)) return undefined;\n\n const [callback] = transform.arguments;\n\n if (\n callback.type !== AST_NODE_TYPES.ArrowFunctionExpression ||\n callback.async ||\n // An explicit return type is either redundant or a type predicate; the\n // latter would be lost by the rewrite.\n callback.returnType !== undefined ||\n !Arr.isFixedLengthTuple(1, callback.params) ||\n callback.body.type === AST_NODE_TYPES.BlockStatement\n ) {\n return undefined;\n }\n\n const [entryPattern] = callback.params;\n\n if (entryPattern.type !== AST_NODE_TYPES.ArrayPattern) return undefined;\n\n const entryParams = parseEntryPattern(entryPattern);\n\n if (entryParams === undefined) return undefined;\n\n const body = callback.body;\n\n const common = { record, callback, ...entryParams } as const;\n\n switch (arrayMethod) {\n case 'filter':\n return {\n ...common,\n method: 'filter',\n dropped: [],\n body: { kind: 'expression', expression: body },\n };\n\n case 'map': {\n const entry = parseKeptEntry(body, entryParams.keyParam);\n\n return entry === undefined\n ? undefined\n : {\n ...common,\n method: 'map',\n dropped: [entry.key],\n body: { kind: 'expression', expression: entry.value },\n };\n }\n\n case 'flatMap': {\n if (body.type !== AST_NODE_TYPES.ConditionalExpression) return undefined;\n\n const onTrue = parseSingletonEntry(body.consequent, entryParams.keyParam);\n\n const onFalse = parseSingletonEntry(body.alternate, entryParams.keyParam);\n\n const keepOnTrue = onTrue !== undefined && isEmptyArray(body.alternate);\n\n const kept = keepOnTrue\n ? onTrue\n : onFalse !== undefined && isEmptyArray(body.consequent)\n ? onFalse\n : undefined;\n\n return kept === undefined\n ? undefined\n : {\n ...common,\n method: 'filterMap',\n dropped: [kept.key],\n body: {\n kind: 'optional',\n test: body.test,\n kept: kept.value,\n keepOnTrue,\n },\n };\n }\n }\n};\n\ntype EntryParams = Readonly<{\n keyParam: TSESTree.Identifier | undefined;\n valueParam: TSESTree.Identifier | undefined;\n}>;\n\n/**\n * Reads the `[key, value]` destructuring the entry callback binds. Anything\n * beyond a plain binding — a default, a rest element, a nested pattern — is\n * rejected, because the rewritten callback receives the value and the key\n * directly rather than an entry tuple.\n */\nconst parseEntryPattern = (\n pattern: TSESTree.ArrayPattern,\n): EntryParams | undefined => {\n if (pattern.elements.length > 2) return undefined;\n\n const [keyElement, valueElement] = pattern.elements;\n\n return isElidedOrIdentifier(keyElement) && isElidedOrIdentifier(valueElement)\n ? {\n keyParam: keyElement ?? undefined,\n valueParam: valueElement ?? undefined,\n }\n : undefined;\n};\n\nconst isElidedOrIdentifier = (\n element: TSESTree.DestructuringPattern | null | undefined,\n): element is TSESTree.Identifier | null | undefined =>\n element === null ||\n element === undefined ||\n element.type === AST_NODE_TYPES.Identifier;\n\ntype KeptEntry = Readonly<{\n /** The key identifier that only rebuilds the entry, dropped by the rewrite. */\n key: TSESTree.Identifier;\n value: TSESTree.Expression;\n}>;\n\n/**\n * Matches `[k, value]` — an entry rebuilt under the very key it came from,\n * which is the only shape `Obj.map` / `Obj.filterMap` can express. An entry\n * built under a different key is left alone.\n */\nconst parseKeptEntry = (\n node: TSESTree.Expression,\n keyParam: TSESTree.Identifier | undefined,\n): KeptEntry | undefined => {\n if (keyParam === undefined) return undefined;\n\n const tuple = stripTypeWrappers(node);\n\n if (\n tuple.type !== AST_NODE_TYPES.ArrayExpression ||\n !Arr.isFixedLengthTuple(2, tuple.elements)\n ) {\n return undefined;\n }\n\n const [key, value] = tuple.elements;\n\n return key !== null &&\n value !== null &&\n key.type === AST_NODE_TYPES.Identifier &&\n value.type !== AST_NODE_TYPES.SpreadElement &&\n key.name === keyParam.name\n ? { key, value }\n : undefined;\n};\n\n/** Matches `[[k, value]]` — the `flatMap` branch that keeps an entry. */\nconst parseSingletonEntry = (\n node: TSESTree.Expression,\n keyParam: TSESTree.Identifier | undefined,\n): KeptEntry | undefined => {\n const array = stripTypeWrappers(node);\n\n if (\n array.type !== AST_NODE_TYPES.ArrayExpression ||\n !Arr.isFixedLengthTuple(1, array.elements)\n ) {\n return undefined;\n }\n\n const [entry] = array.elements;\n\n return entry === null || entry.type === AST_NODE_TYPES.SpreadElement\n ? undefined\n : parseKeptEntry(entry, keyParam);\n};\n\n/** Matches `[]` — the `flatMap` branch that drops an entry. */\nconst isEmptyArray = (node: TSESTree.Expression): boolean => {\n const array = stripTypeWrappers(node);\n\n return (\n array.type === AST_NODE_TYPES.ArrayExpression && array.elements.length === 0\n );\n};\n\n/**\n * Looks through the `as const` / `satisfies` an entry tuple usually carries.\n * Any other assertion is left in place, so that a tuple asserted to a wider\n * type than the callback returns is not reported: `Obj` would infer the\n * narrower one and the rewrite would not type-check.\n */\nconst stripTypeWrappers = (node: TSESTree.Expression): TSESTree.Expression =>\n node.type === AST_NODE_TYPES.TSSatisfiesExpression ||\n (node.type === AST_NODE_TYPES.TSAsExpression && isConstAssertion(node))\n ? stripTypeWrappers(node.expression)\n : node;\n\nconst isConstAssertion = (node: TSESTree.TSAsExpression): boolean =>\n node.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference &&\n node.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier &&\n node.typeAnnotation.typeName.name === 'const';\n\nconst isObjectStaticCall = (\n node: TSESTree.CallExpression,\n name: 'entries' | 'fromEntries',\n): boolean =>\n node.callee.type === AST_NODE_TYPES.MemberExpression &&\n !node.callee.computed &&\n node.callee.object.type === AST_NODE_TYPES.Identifier &&\n node.callee.object.name === 'Object' &&\n node.callee.property.type === AST_NODE_TYPES.Identifier &&\n node.callee.property.name === name;\n\n/**\n * `true` when the parameter is still referenced once the identifiers in\n * `dropped` — the ones that only rebuilt the entry key — are discounted.\n * References from nested functions resolve to the same variable, so they count.\n */\nconst isReferencedOutside = (\n scope: TSESLint.Scope.Scope,\n name: string,\n dropped: ReadonlySet<TSESTree.Node>,\n): boolean =>\n scope.variables\n .find((variable) => variable.name === name)\n ?.references.some((reference) => !dropped.has(reference.identifier)) ??\n false;\n\n/**\n * A name for the value parameter of a callback that only uses the key. `_value`\n * is what the `Obj` documentation uses; a suffix is added if that name is\n * already referenced inside the callback, so the new parameter shadows nothing.\n */\nconst unusedValueParamName = (scope: TSESLint.Scope.Scope): string => {\n const taken = new Set<string>(\n scope.through\n .map((reference) => reference.identifier.name)\n .concat(scope.variables.map((variable) => variable.name)),\n );\n\n const firstFree = (candidate: string, suffix: number): string =>\n taken.has(candidate)\n ? firstFree(`_value${suffix + 1}`, suffix + 1)\n : candidate;\n\n return firstFree('_value', 1);\n};\n\n/** Expressions that bind looser than a conditional's test position. */\nconst NEEDS_PARENS_AS_TEST: ReadonlySet<string> = new Set<string>([\n AST_NODE_TYPES.ArrowFunctionExpression,\n AST_NODE_TYPES.AssignmentExpression,\n AST_NODE_TYPES.ConditionalExpression,\n AST_NODE_TYPES.SequenceExpression,\n AST_NODE_TYPES.YieldExpression,\n]);\n\nconst conditionalTestText = (\n text: string,\n node: TSESTree.Expression,\n): string =>\n NEEDS_PARENS_AS_TEST.has(node.type) ? (`(${text})` as const) : text;\n\n/** A comma expression would read as another argument without parentheses. */\nconst argumentText = (text: string, node: TSESTree.Expression): string =>\n node.type === AST_NODE_TYPES.SequenceExpression\n ? (`(${text})` as const)\n : text;\n\n/**\n * A concise arrow body cannot start with `{`, nor hold a bare comma. The text\n * is what decides the first case rather than the node type, because an object\n * literal keeps its leading brace under an `as const`.\n */\nconst arrowBodyText = (text: string, node: TSESTree.Expression): string =>\n text.startsWith('{') || node.type === AST_NODE_TYPES.SequenceExpression\n ? (`(${text})` as const)\n : text;\n"],"names":[],"mappings":";;;;AAuBA,MAAM,aAAA,GAAgB;AAAA,EACpB,GAAA,EAAK,WAAA;AAAA,EACL,MAAA,EAAQ,cAAA;AAAA,EACR,SAAA,EAAW;AACb,CAAA;AAuCO,MAAM,6BAAA,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,SAAA,EACE,+KAAA;AAAA,MACF,YAAA,EACE,iMAAA;AAAA,MACF,eAAA,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,iBAGA,EAAC;AAOP,IAAA,MAAM,WAAA,GAAc,CAAC,SAAA,KAAiC;AACpD,MAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,QAAA,CAAS,SAAA,CAAU,QAAQ,CAAA;AAEpD,MAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAmB,SAAA,CAAU,OAAO,CAAA;AAExD,MAAA,MAAM,OAAA,GACJ,SAAA,CAAU,QAAA,KAAa,MAAA,IACvB,mBAAA,CAAoB,KAAA,EAAO,SAAA,CAAU,QAAA,CAAS,IAAA,EAAM,OAAO,CAAA,GACvD,SAAA,CAAU,SAAS,IAAA,GACnB,MAAA;AAEN,MAAA,MAAM,SAAA,GAAY,UAAU,UAAA,EAAY,IAAA;AAExC,MAAA,IAAI,YAAY,MAAA,EAAW;AAGzB,QAAA,OAAO,IAAI,SAAA,IAAa,oBAAA,CAAqB,KAAK,CAAC,KAAK,OAAO,CAAA,CAAA,CAAA;AAAA,MACjE;AAEA,MAAA,OAAO,SAAA,KAAc,UACnB,mBAAA,CAAoB,KAAA,EAAO,WAAW,OAAO,CAAA,GAC3C,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA,CAAA,GACb,IAAA;AAAA,IACN,CAAA;AAEA,IAAA,MAAM,iBAAA,GAAoB,CACxB,SAAA,EACA,YAAA,KACW;AACX,MAAA,MAAM,EAAE,MAAK,GAAI,SAAA;AAEjB,MAAA,IAAI,IAAA,CAAK,SAAS,YAAA,EAAc;AAC9B,QAAA,OAAO,aAAA;AAAA,UACL,UAAA,CAAW,OAAA,CAAQ,IAAA,CAAK,UAAU,CAAA;AAAA,UAClC,IAAA,CAAK;AAAA,SACP;AAAA,MACF;AAEA,MAAA,MAAM,QAAA,GAAW,mBAAA;AAAA,QACf,UAAA,CAAW,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA;AAAA,QAC5B,IAAA,CAAK;AAAA,OACP;AAEA,MAAA,MAAM,QAAA,GAAW,CAAA,EAAG,YAAY,CAAA,MAAA,EAAS,YAAA,CAAa,UAAA,CAAW,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA,EAAG,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,CAAA;AAE/F,MAAA,MAAM,QAAA,GAAW,GAAG,YAAY,CAAA,KAAA,CAAA;AAEhC,MAAA,OAAO,IAAA,CAAK,UAAA,GACR,CAAA,EAAG,QAAQ,MAAM,QAAQ,CAAA,GAAA,EAAM,QAAQ,CAAA,CAAA,GACvC,CAAA,EAAG,QAAQ,CAAA,GAAA,EAAM,QAAQ,MAAM,QAAQ,CAAA,CAAA;AAAA,IAC7C,CAAA;AAEA,IAAA,OAAO;AAAA,MACL,cAAA,EAAgB,CAAC,IAAA,KAAS;AACxB,QAAA,MAAM,SAAA,GAAY,sBAAsB,IAAI,CAAA;AAE5C,QAAA,IAAI,cAAc,MAAA,EAAW;AAE7B,QAAA,cAAA,CAAe,IAAA,CAAK,EAAE,IAAA,EAAM,SAAA,EAAW,CAAA;AAAA,MACzC,CAAA;AAAA,MAEA,gBAAgB,MAAM;AACpB,QAAA,MAAM,YAAA,GAAe,gBAAgB,iBAAiB,CAAA;AAEtD,QAAA,MAAM,OAAA,GAAU,oBAAA,CAAqB,iBAAA,EAAmB,KAAK,CAAA,IAAK,KAAA;AAElE,QAAA,MAAM,YAAA,GACJ,oBAAA,CAAqB,iBAAA,EAAmB,UAAU,CAAA,IAAK,UAAA;AAEzD,QAAA,MAAM,gBAAgB,cAAA,CAAe,IAAA;AAAA,UACnC,CAAC,EAAE,SAAA,EAAU,KAAM,SAAA,CAAU,KAAK,IAAA,KAAS;AAAA,SAC7C;AAEA,QAAA,MAAM,cAAA,GAAkB,CAAC,KAAA,EAAO,UAAU,CAAA,CAAY,MAAA;AAAA,UACpD,CAAC,UACE,IAAA,KAAS,UAAA,IAAc,kBACxB,CAAC,YAAA,CAAa,SAAS,IAAI;AAAA,SAC/B;AAIA,QAAA,KAAA,MAAW,CAAC,OAAO,EAAE,IAAA,EAAM,WAAW,CAAA,IAAK,cAAA,CAAe,OAAA,EAAQ,EAAG;AACnE,UAAA,OAAA,CAAQ,MAAA,CAAO;AAAA,YACb,IAAA;AAAA,YACA,SAAA,EAAW,aAAA,CAAc,SAAA,CAAU,MAAM,CAAA;AAAA,YACzC,GAAA,EAAK,CAAC,KAAA,KAAU;AACd,cAAA,MAAM,UAAA,GAAa,YAAA;AAAA,gBACjB,UAAA,CAAW,OAAA,CAAQ,SAAA,CAAU,MAAM,CAAA;AAAA,gBACnC,SAAA,CAAU;AAAA,eACZ;AAEA,cAAA,MAAM,cAAc,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,SAAA,CAAU,MAAM,CAAA,CAAA,EAAI,UAAU,CAAA,EAAA,EAAK,WAAA,CAAY,SAAS,CAAC,CAAA,IAAA,EAAO,iBAAA,CAAkB,SAAA,EAAW,YAAY,CAAC,CAAA,CAAA,CAAA;AAE5I,cAAA,MAAM,WAAA,GACJ,KAAA,KAAU,CAAA,IAAK,cAAA,CAAe,SAAS,CAAA,GACnC,gBAAA;AAAA,gBACE,KAAA;AAAA,gBACA,OAAA;AAAA,gBACA,iBAAA;AAAA,gBACA;AAAA,kBAEF,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;AAQA,MAAM,qBAAA,GAAwB,CAC5B,IAAA,KAC0B;AAC1B,EAAA,IAAI,CAAC,kBAAA,CAAmB,IAAA,EAAM,aAAa,GAAG,OAAO,MAAA;AAErD,EAAA,IAAI,CAAC,GAAA,CAAI,kBAAA,CAAmB,GAAG,IAAA,CAAK,SAAS,GAAG,OAAO,MAAA;AAEvD,EAAA,MAAM,CAAC,SAAS,CAAA,GAAI,IAAA,CAAK,SAAA;AAEzB,EAAA,IAAI,SAAA,CAAU,IAAA,KAAS,cAAA,CAAe,cAAA,EAAgB,OAAO,MAAA;AAE7D,EAAA,MAAM,EAAE,QAAO,GAAI,SAAA;AAEnB,EAAA,IACE,MAAA,CAAO,IAAA,KAAS,cAAA,CAAe,gBAAA,IAC/B,MAAA,CAAO,YACP,MAAA,CAAO,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,UAAA,EACxC;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,WAAA,GAAc,OAAO,QAAA,CAAS,IAAA;AAEpC,EAAA,IACE,WAAA,KAAgB,KAAA,IAChB,WAAA,KAAgB,QAAA,IAChB,gBAAgB,SAAA,EAChB;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,cAAc,MAAA,CAAO,MAAA;AAE3B,EAAA,IACE,WAAA,CAAY,IAAA,KAAS,cAAA,CAAe,cAAA,IACpC,CAAC,kBAAA,CAAmB,WAAA,EAAa,SAAS,CAAA,IAC1C,CAAC,GAAA,CAAI,kBAAA,CAAmB,CAAA,EAAG,WAAA,CAAY,SAAS,CAAA,EAChD;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,CAAC,MAAM,CAAA,GAAI,WAAA,CAAY,SAAA;AAE7B,EAAA,IAAI,MAAA,CAAO,IAAA,KAAS,cAAA,CAAe,aAAA,EAAe,OAAO,MAAA;AAIzD,EAAA,IAAI,CAAC,GAAA,CAAI,kBAAA,CAAmB,GAAG,SAAA,CAAU,SAAS,GAAG,OAAO,MAAA;AAE5D,EAAA,MAAM,CAAC,QAAQ,CAAA,GAAI,SAAA,CAAU,SAAA;AAE7B,EAAA,IACE,QAAA,CAAS,IAAA,KAAS,cAAA,CAAe,uBAAA,IACjC,QAAA,CAAS,KAAA;AAAA;AAAA,EAGT,QAAA,CAAS,UAAA,KAAe,MAAA,IACxB,CAAC,IAAI,kBAAA,CAAmB,CAAA,EAAG,QAAA,CAAS,MAAM,CAAA,IAC1C,QAAA,CAAS,IAAA,CAAK,IAAA,KAAS,eAAe,cAAA,EACtC;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,CAAC,YAAY,CAAA,GAAI,QAAA,CAAS,MAAA;AAEhC,EAAA,IAAI,YAAA,CAAa,IAAA,KAAS,cAAA,CAAe,YAAA,EAAc,OAAO,MAAA;AAE9D,EAAA,MAAM,WAAA,GAAc,kBAAkB,YAAY,CAAA;AAElD,EAAA,IAAI,WAAA,KAAgB,QAAW,OAAO,MAAA;AAEtC,EAAA,MAAM,OAAO,QAAA,CAAS,IAAA;AAEtB,EAAA,MAAM,MAAA,GAAS,EAAE,MAAA,EAAQ,QAAA,EAAU,GAAG,WAAA,EAAY;AAElD,EAAA,QAAQ,WAAA;AAAa,IACnB,KAAK,QAAA;AACH,MAAA,OAAO;AAAA,QACL,GAAG,MAAA;AAAA,QACH,MAAA,EAAQ,QAAA;AAAA,QACR,SAAS,EAAC;AAAA,QACV,IAAA,EAAM,EAAE,IAAA,EAAM,YAAA,EAAc,YAAY,IAAA;AAAK,OAC/C;AAAA,IAEF,KAAK,KAAA,EAAO;AACV,MAAA,MAAM,KAAA,GAAQ,cAAA,CAAe,IAAA,EAAM,WAAA,CAAY,QAAQ,CAAA;AAEvD,MAAA,OAAO,KAAA,KAAU,SACb,MAAA,GACA;AAAA,QACE,GAAG,MAAA;AAAA,QACH,MAAA,EAAQ,KAAA;AAAA,QACR,OAAA,EAAS,CAAC,KAAA,CAAM,GAAG,CAAA;AAAA,QACnB,MAAM,EAAE,IAAA,EAAM,YAAA,EAAc,UAAA,EAAY,MAAM,KAAA;AAAM,OACtD;AAAA,IACN;AAAA,IAEA,KAAK,SAAA,EAAW;AACd,MAAA,IAAI,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,qBAAA,EAAuB,OAAO,MAAA;AAE/D,MAAA,MAAM,MAAA,GAAS,mBAAA,CAAoB,IAAA,CAAK,UAAA,EAAY,YAAY,QAAQ,CAAA;AAExE,MAAA,MAAM,OAAA,GAAU,mBAAA,CAAoB,IAAA,CAAK,SAAA,EAAW,YAAY,QAAQ,CAAA;AAExE,MAAA,MAAM,UAAA,GAAa,MAAA,KAAW,MAAA,IAAa,YAAA,CAAa,KAAK,SAAS,CAAA;AAEtE,MAAA,MAAM,IAAA,GAAO,aACT,MAAA,GACA,OAAA,KAAY,UAAa,YAAA,CAAa,IAAA,CAAK,UAAU,CAAA,GACnD,OAAA,GACA,MAAA;AAEN,MAAA,OAAO,IAAA,KAAS,SACZ,MAAA,GACA;AAAA,QACE,GAAG,MAAA;AAAA,QACH,MAAA,EAAQ,WAAA;AAAA,QACR,OAAA,EAAS,CAAC,IAAA,CAAK,GAAG,CAAA;AAAA,QAClB,IAAA,EAAM;AAAA,UACJ,IAAA,EAAM,UAAA;AAAA,UACN,MAAM,IAAA,CAAK,IAAA;AAAA,UACX,MAAM,IAAA,CAAK,KAAA;AAAA,UACX;AAAA;AACF,OACF;AAAA,IACN;AAAA;AAEJ,CAAA;AAaA,MAAM,iBAAA,GAAoB,CACxB,OAAA,KAC4B;AAC5B,EAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,MAAA,GAAS,CAAA,EAAG,OAAO,MAAA;AAExC,EAAA,MAAM,CAAC,UAAA,EAAY,YAAY,CAAA,GAAI,OAAA,CAAQ,QAAA;AAE3C,EAAA,OAAO,oBAAA,CAAqB,UAAU,CAAA,IAAK,oBAAA,CAAqB,YAAY,CAAA,GACxE;AAAA,IACE,UAAU,UAAA,IAAc,MAAA;AAAA,IACxB,YAAY,YAAA,IAAgB;AAAA,GAC9B,GACA,MAAA;AACN,CAAA;AAEA,MAAM,oBAAA,GAAuB,CAC3B,OAAA,KAEA,OAAA,KAAY,QACZ,OAAA,KAAY,MAAA,IACZ,OAAA,CAAQ,IAAA,KAAS,cAAA,CAAe,UAAA;AAalC,MAAM,cAAA,GAAiB,CACrB,IAAA,EACA,QAAA,KAC0B;AAC1B,EAAA,IAAI,QAAA,KAAa,QAAW,OAAO,MAAA;AAEnC,EAAA,MAAM,KAAA,GAAQ,kBAAkB,IAAI,CAAA;AAEpC,EAAA,IACE,KAAA,CAAM,IAAA,KAAS,cAAA,CAAe,eAAA,IAC9B,CAAC,IAAI,kBAAA,CAAmB,CAAA,EAAG,KAAA,CAAM,QAAQ,CAAA,EACzC;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,CAAC,GAAA,EAAK,KAAK,CAAA,GAAI,KAAA,CAAM,QAAA;AAE3B,EAAA,OAAO,QAAQ,IAAA,IACb,KAAA,KAAU,QACV,GAAA,CAAI,IAAA,KAAS,eAAe,UAAA,IAC5B,KAAA,CAAM,SAAS,cAAA,CAAe,aAAA,IAC9B,IAAI,IAAA,KAAS,QAAA,CAAS,OACpB,EAAE,GAAA,EAAK,OAAM,GACb,MAAA;AACN,CAAA;AAGA,MAAM,mBAAA,GAAsB,CAC1B,IAAA,EACA,QAAA,KAC0B;AAC1B,EAAA,MAAM,KAAA,GAAQ,kBAAkB,IAAI,CAAA;AAEpC,EAAA,IACE,KAAA,CAAM,IAAA,KAAS,cAAA,CAAe,eAAA,IAC9B,CAAC,IAAI,kBAAA,CAAmB,CAAA,EAAG,KAAA,CAAM,QAAQ,CAAA,EACzC;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,CAAC,KAAK,CAAA,GAAI,KAAA,CAAM,QAAA;AAEtB,EAAA,OAAO,KAAA,KAAU,QAAQ,KAAA,CAAM,IAAA,KAAS,eAAe,aAAA,GACnD,MAAA,GACA,cAAA,CAAe,KAAA,EAAO,QAAQ,CAAA;AACpC,CAAA;AAGA,MAAM,YAAA,GAAe,CAAC,IAAA,KAAuC;AAC3D,EAAA,MAAM,KAAA,GAAQ,kBAAkB,IAAI,CAAA;AAEpC,EAAA,OACE,MAAM,IAAA,KAAS,cAAA,CAAe,eAAA,IAAmB,KAAA,CAAM,SAAS,MAAA,KAAW,CAAA;AAE/E,CAAA;AAQA,MAAM,oBAAoB,CAAC,IAAA,KACzB,IAAA,CAAK,IAAA,KAAS,eAAe,qBAAA,IAC5B,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,kBAAkB,gBAAA,CAAiB,IAAI,IACjE,iBAAA,CAAkB,IAAA,CAAK,UAAU,CAAA,GACjC,IAAA;AAEN,MAAM,mBAAmB,CAAC,IAAA,KACxB,IAAA,CAAK,cAAA,CAAe,SAAS,cAAA,CAAe,eAAA,IAC5C,IAAA,CAAK,cAAA,CAAe,SAAS,IAAA,KAAS,cAAA,CAAe,cACrD,IAAA,CAAK,cAAA,CAAe,SAAS,IAAA,KAAS,OAAA;AAExC,MAAM,kBAAA,GAAqB,CACzB,IAAA,EACA,IAAA,KAEA,KAAK,MAAA,CAAO,IAAA,KAAS,cAAA,CAAe,gBAAA,IACpC,CAAC,IAAA,CAAK,MAAA,CAAO,QAAA,IACb,KAAK,MAAA,CAAO,MAAA,CAAO,IAAA,KAAS,cAAA,CAAe,UAAA,IAC3C,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,SAAS,QAAA,IAC5B,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,SAAS,cAAA,CAAe,UAAA,IAC7C,IAAA,CAAK,MAAA,CAAO,SAAS,IAAA,KAAS,IAAA;AAOhC,MAAM,mBAAA,GAAsB,CAC1B,KAAA,EACA,IAAA,EACA,OAAA,KAEA,MAAM,SAAA,CACH,IAAA,CAAK,CAAC,QAAA,KAAa,QAAA,CAAS,IAAA,KAAS,IAAI,CAAA,EACxC,UAAA,CAAW,IAAA,CAAK,CAAC,SAAA,KAAc,CAAC,QAAQ,GAAA,CAAI,SAAA,CAAU,UAAU,CAAC,CAAA,IACrE,KAAA;AAOF,MAAM,oBAAA,GAAuB,CAAC,KAAA,KAAwC;AACpE,EAAA,MAAM,QAAQ,IAAI,GAAA;AAAA,IAChB,MAAM,OAAA,CACH,GAAA,CAAI,CAAC,SAAA,KAAc,UAAU,UAAA,CAAW,IAAI,CAAA,CAC5C,MAAA,CAAO,MAAM,SAAA,CAAU,GAAA,CAAI,CAAC,QAAA,KAAa,QAAA,CAAS,IAAI,CAAC;AAAA,GAC5D;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,SAAA,EAAmB,MAAA,KACpC,MAAM,GAAA,CAAI,SAAS,CAAA,GACf,SAAA,CAAU,SAAS,MAAA,GAAS,CAAC,CAAA,CAAA,EAAI,MAAA,GAAS,CAAC,CAAA,GAC3C,SAAA;AAEN,EAAA,OAAO,SAAA,CAAU,UAAU,CAAC,CAAA;AAC9B,CAAA;AAGA,MAAM,oBAAA,uBAAgD,GAAA,CAAY;AAAA,EAChE,cAAA,CAAe,uBAAA;AAAA,EACf,cAAA,CAAe,oBAAA;AAAA,EACf,cAAA,CAAe,qBAAA;AAAA,EACf,cAAA,CAAe,kBAAA;AAAA,EACf,cAAA,CAAe;AACjB,CAAC,CAAA;AAED,MAAM,mBAAA,GAAsB,CAC1B,IAAA,EACA,IAAA,KAEA,oBAAA,CAAqB,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,GAAK,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,CAAA,GAAgB,IAAA;AAGjE,MAAM,YAAA,GAAe,CAAC,IAAA,EAAc,IAAA,KAClC,IAAA,CAAK,SAAS,cAAA,CAAe,kBAAA,GACxB,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,CAAA,GACT,IAAA;AAON,MAAM,aAAA,GAAgB,CAAC,IAAA,EAAc,IAAA,KACnC,KAAK,UAAA,CAAW,GAAG,CAAA,IAAK,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,kBAAA,GAChD,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,CAAA,GACT,IAAA;;"}
@@ -47,7 +47,7 @@ const preferRangeForLoop = {
47
47
  if (stepValue.type === AST_NODE_TYPES.Literal) {
48
48
  const stepNum = stepValue.value;
49
49
  if (typeof stepNum === "number" && stepNum > 0) {
50
- mut_step = stepNum === 1 ? void 0 : String(stepNum);
50
+ mut_step = stepNum === 1 ? void 0 : stepNum.toString();
51
51
  } else {
52
52
  return;
53
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-range-for-loop.mjs","sources":["../../src/rules/prefer-range-for-loop.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { Arr } from 'ts-data-forge';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useRangeForLoop';\n\nexport const preferRangeForLoop: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `for (let i = begin; i < end; ++i)` with `for (const i of range(begin, end))` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useRangeForLoop:\n 'Replace with `for (const {{varName}} of range({{begin}}, {{end}}))` from ts-data-forge.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const mut_nodesToFix: {\n node: TSESTree.ForStatement;\n varName: string;\n begin: string;\n end: string;\n step?: string;\n }[] = [];\n\n return {\n ForStatement: (node) => {\n // Check init: let i = begin\n if (\n node.init?.type !== AST_NODE_TYPES.VariableDeclaration ||\n node.init.kind !== 'let' ||\n !Arr.isFixedLengthTuple(1, node.init.declarations)\n ) {\n return;\n }\n\n const declaration = node.init.declarations[0];\n\n if (\n declaration.id.type !== AST_NODE_TYPES.Identifier ||\n declaration.init === null\n ) {\n return;\n }\n\n const varName = declaration.id.name;\n\n const beginExpr = declaration.init;\n\n // Check test: i < end\n if (\n node.test?.type !== AST_NODE_TYPES.BinaryExpression ||\n node.test.operator !== '<' ||\n node.test.left.type !== AST_NODE_TYPES.Identifier ||\n node.test.left.name !== varName\n ) {\n return;\n }\n\n const endExpr = node.test.right;\n\n // Check update: ++i, i++, i += 1, or i += step\n if (node.update === null) return;\n\n let mut_step: string | undefined;\n\n if (node.update.type === AST_NODE_TYPES.UpdateExpression) {\n // ++i or i++\n if (\n node.update.operator === '++' &&\n node.update.argument.type === AST_NODE_TYPES.Identifier &&\n node.update.argument.name === varName\n ) {\n mut_step = undefined; // step defaults to 1\n } else {\n return;\n }\n } else if (\n node.update.type === AST_NODE_TYPES.AssignmentExpression &&\n node.update.operator === '+=' &&\n node.update.left.type === AST_NODE_TYPES.Identifier &&\n node.update.left.name === varName\n ) {\n // i += step\n const stepValue = node.update.right;\n\n if (stepValue.type === AST_NODE_TYPES.Literal) {\n const stepNum = stepValue.value;\n\n // Only support positive integer steps\n if (typeof stepNum === 'number' && stepNum > 0) {\n mut_step = stepNum === 1 ? undefined : String(stepNum);\n } else {\n return;\n }\n } else {\n // Variable step like i += step\n mut_step = sourceCode.getText(stepValue);\n }\n } else {\n return;\n }\n\n const beginText = sourceCode.getText(beginExpr);\n\n const endText = sourceCode.getText(endExpr);\n\n mut_nodesToFix.push({\n node,\n varName,\n begin: beginText,\n end: endText,\n step: mut_step,\n });\n },\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const hasRangeImport = namedImports.includes('range');\n\n for (const [\n index,\n { node, varName, begin, end, step },\n ] of mut_nodesToFix.entries()) {\n context.report({\n node: node.init ?? node,\n messageId: 'useRangeForLoop',\n data: {\n varName,\n begin,\n end,\n },\n fix: (fixer) => {\n const bodyText = sourceCode.getText(node.body);\n\n const rangeArgs =\n step === undefined\n ? `${begin}, ${end}`\n : `${begin}, ${end}, ${step}`;\n\n const replacement = `for (const ${varName} of range(${rangeArgs})) ${bodyText}`;\n\n const importFixes =\n index === 0 && !hasRangeImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, [\n 'range',\n ])\n : [];\n\n return [...importFixes, fixer.replaceText(node, replacement)];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n} as const;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prefer-range-for-loop.mjs","sources":["../../src/rules/prefer-range-for-loop.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { Arr } from 'ts-data-forge';\nimport {\n buildImportFixes,\n getNamedImports,\n getTsDataForgeImport,\n} from './import-utils.mjs';\n\ntype Options = readonly [];\n\ntype MessageIds = 'useRangeForLoop';\n\nexport const preferRangeForLoop: TSESLint.RuleModule<MessageIds, Options> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace `for (let i = begin; i < end; ++i)` with `for (const i of range(begin, end))` from ts-data-forge.',\n },\n fixable: 'code',\n schema: [],\n messages: {\n useRangeForLoop:\n 'Replace with `for (const {{varName}} of range({{begin}}, {{end}}))` from ts-data-forge.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const program = sourceCode.ast;\n\n const tsDataForgeImport = getTsDataForgeImport(program);\n\n const mut_nodesToFix: {\n node: TSESTree.ForStatement;\n varName: string;\n begin: string;\n end: string;\n step?: string;\n }[] = [];\n\n return {\n ForStatement: (node) => {\n // Check init: let i = begin\n if (\n node.init?.type !== AST_NODE_TYPES.VariableDeclaration ||\n node.init.kind !== 'let' ||\n !Arr.isFixedLengthTuple(1, node.init.declarations)\n ) {\n return;\n }\n\n const declaration = node.init.declarations[0];\n\n if (\n declaration.id.type !== AST_NODE_TYPES.Identifier ||\n declaration.init === null\n ) {\n return;\n }\n\n const varName = declaration.id.name;\n\n const beginExpr = declaration.init;\n\n // Check test: i < end\n if (\n node.test?.type !== AST_NODE_TYPES.BinaryExpression ||\n node.test.operator !== '<' ||\n node.test.left.type !== AST_NODE_TYPES.Identifier ||\n node.test.left.name !== varName\n ) {\n return;\n }\n\n const endExpr = node.test.right;\n\n // Check update: ++i, i++, i += 1, or i += step\n if (node.update === null) return;\n\n let mut_step: string | undefined;\n\n if (node.update.type === AST_NODE_TYPES.UpdateExpression) {\n // ++i or i++\n if (\n node.update.operator === '++' &&\n node.update.argument.type === AST_NODE_TYPES.Identifier &&\n node.update.argument.name === varName\n ) {\n mut_step = undefined; // step defaults to 1\n } else {\n return;\n }\n } else if (\n node.update.type === AST_NODE_TYPES.AssignmentExpression &&\n node.update.operator === '+=' &&\n node.update.left.type === AST_NODE_TYPES.Identifier &&\n node.update.left.name === varName\n ) {\n // i += step\n const stepValue = node.update.right;\n\n if (stepValue.type === AST_NODE_TYPES.Literal) {\n const stepNum = stepValue.value;\n\n // Only support positive integer steps\n if (typeof stepNum === 'number' && stepNum > 0) {\n mut_step = stepNum === 1 ? undefined : stepNum.toString();\n } else {\n return;\n }\n } else {\n // Variable step like i += step\n mut_step = sourceCode.getText(stepValue);\n }\n } else {\n return;\n }\n\n const beginText = sourceCode.getText(beginExpr);\n\n const endText = sourceCode.getText(endExpr);\n\n mut_nodesToFix.push({\n node,\n varName,\n begin: beginText,\n end: endText,\n step: mut_step,\n });\n },\n 'Program:exit': () => {\n const namedImports = getNamedImports(tsDataForgeImport);\n\n const hasRangeImport = namedImports.includes('range');\n\n for (const [\n index,\n { node, varName, begin, end, step },\n ] of mut_nodesToFix.entries()) {\n context.report({\n node: node.init ?? node,\n messageId: 'useRangeForLoop',\n data: {\n varName,\n begin,\n end,\n },\n fix: (fixer) => {\n const bodyText = sourceCode.getText(node.body);\n\n const rangeArgs =\n step === undefined\n ? `${begin}, ${end}`\n : `${begin}, ${end}, ${step}`;\n\n const replacement = `for (const ${varName} of range(${rangeArgs})) ${bodyText}`;\n\n const importFixes =\n index === 0 && !hasRangeImport\n ? buildImportFixes(fixer, program, tsDataForgeImport, [\n 'range',\n ])\n : [];\n\n return [...importFixes, fixer.replaceText(node, replacement)];\n },\n });\n }\n },\n };\n },\n defaultOptions: [],\n} as const;\n"],"names":[],"mappings":"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"}
@@ -10,6 +10,7 @@ export declare const tsDataForgeRules: {
10
10
  readonly 'prefer-is-record-and-has-key': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useHasKey" | "useIsRecordAndHasKey", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
11
11
  readonly 'prefer-num-safe-parse-int': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseInt", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
12
12
  readonly 'prefer-num-safe-parse-float': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useSafeParseFloat", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
13
+ readonly 'no-side-effect-import': import("@typescript-eslint/utils/ts-eslint").RuleModule<"removeSideEffectImport", readonly [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
13
14
  readonly 'no-unnecessary-type-guard': import("@typescript-eslint/utils/ts-eslint").RuleModule<"alwaysFalse" | "alwaysTrue" | "replaceTypeGuard", readonly [(Readonly<{
14
15
  ignore?: readonly string[];
15
16
  }> | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../../src/rules/rules.mts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;CAea,CAAC"}
1
+ {"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../../src/rules/rules.mts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;CAgBa,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { noSideEffectImport } from './no-side-effect-import.mjs';
1
2
  import { noUnnecessaryTypeGuard } from './no-unnecessary-type-guard.mjs';
2
3
  import { preferArrIsArray } from './prefer-arr-is-array.mjs';
3
4
  import { preferArrSum } from './prefer-arr-sum.mjs';
@@ -25,6 +26,7 @@ const tsDataForgeRules = {
25
26
  "prefer-is-record-and-has-key": preferIsRecordAndHasKey,
26
27
  "prefer-num-safe-parse-int": preferNumSafeParseInt,
27
28
  "prefer-num-safe-parse-float": preferNumSafeParseFloat,
29
+ "no-side-effect-import": noSideEffectImport,
28
30
  "no-unnecessary-type-guard": noUnnecessaryTypeGuard,
29
31
  "prefer-comparison-over-nullish-guard": preferComparisonOverNullishGuard,
30
32
  "prefer-obj-over-entries-round-trip": preferObjOverEntriesRoundTrip
@@ -1 +1 @@
1
- {"version":3,"file":"rules.mjs","sources":["../../src/rules/rules.mts"],"sourcesContent":["import { type ESLintPlugin } from '../types.mjs';\nimport { noUnnecessaryTypeGuard } from './no-unnecessary-type-guard.mjs';\nimport { preferArrIsArray } from './prefer-arr-is-array.mjs';\nimport { preferArrSum } from './prefer-arr-sum.mjs';\nimport { preferAsInt } from './prefer-as-int.mjs';\nimport { preferCanonicalArraySlicing } from './prefer-canonical-array-slicing.mjs';\nimport { preferCanonicalLengthCast } from './prefer-canonical-length-cast.mjs';\nimport { preferCanonicalLengthGuard } from './prefer-canonical-length-guard.mjs';\nimport { preferComparisonOverNullishGuard } from './prefer-comparison-over-nullish-guard.mjs';\nimport { preferIsNonNullObject } from './prefer-is-non-null-object.mjs';\nimport { preferIsRecordAndHasKey } from './prefer-is-record-and-has-key.mjs';\nimport { preferNumSafeParseFloat } from './prefer-num-safe-parse-float.mjs';\nimport { preferNumSafeParseInt } from './prefer-num-safe-parse-int.mjs';\nimport { preferObjOverEntriesRoundTrip } from './prefer-obj-over-entries-round-trip.mjs';\nimport { preferRangeForLoop } from './prefer-range-for-loop.mjs';\n\nexport const tsDataForgeRules = {\n 'prefer-canonical-array-slicing': preferCanonicalArraySlicing,\n 'prefer-canonical-length-guard': preferCanonicalLengthGuard,\n 'prefer-canonical-length-cast': preferCanonicalLengthCast,\n 'prefer-arr-is-array': preferArrIsArray,\n 'prefer-arr-sum': preferArrSum,\n 'prefer-as-int': preferAsInt,\n 'prefer-is-non-null-object': preferIsNonNullObject,\n 'prefer-range-for-loop': preferRangeForLoop,\n 'prefer-is-record-and-has-key': preferIsRecordAndHasKey,\n 'prefer-num-safe-parse-int': preferNumSafeParseInt,\n 'prefer-num-safe-parse-float': preferNumSafeParseFloat,\n 'no-unnecessary-type-guard': noUnnecessaryTypeGuard,\n 'prefer-comparison-over-nullish-guard': preferComparisonOverNullishGuard,\n 'prefer-obj-over-entries-round-trip': preferObjOverEntriesRoundTrip,\n} as const satisfies ESLintPlugin['rules'];\n"],"names":[],"mappings":";;;;;;;;;;;;;;;AAgBO,MAAM,gBAAA,GAAmB;AAAA,EAC9B,gCAAA,EAAkC,2BAAA;AAAA,EAClC,+BAAA,EAAiC,0BAAA;AAAA,EACjC,8BAAA,EAAgC,yBAAA;AAAA,EAChC,qBAAA,EAAuB,gBAAA;AAAA,EACvB,gBAAA,EAAkB,YAAA;AAAA,EAClB,eAAA,EAAiB,WAAA;AAAA,EACjB,2BAAA,EAA6B,qBAAA;AAAA,EAC7B,uBAAA,EAAyB,kBAAA;AAAA,EACzB,8BAAA,EAAgC,uBAAA;AAAA,EAChC,2BAAA,EAA6B,qBAAA;AAAA,EAC7B,6BAAA,EAA+B,uBAAA;AAAA,EAC/B,2BAAA,EAA6B,sBAAA;AAAA,EAC7B,sCAAA,EAAwC,gCAAA;AAAA,EACxC,oCAAA,EAAsC;AACxC;;;;"}
1
+ {"version":3,"file":"rules.mjs","sources":["../../src/rules/rules.mts"],"sourcesContent":["import { type ESLintPlugin } from '../types.mjs';\nimport { noSideEffectImport } from './no-side-effect-import.mjs';\nimport { noUnnecessaryTypeGuard } from './no-unnecessary-type-guard.mjs';\nimport { preferArrIsArray } from './prefer-arr-is-array.mjs';\nimport { preferArrSum } from './prefer-arr-sum.mjs';\nimport { preferAsInt } from './prefer-as-int.mjs';\nimport { preferCanonicalArraySlicing } from './prefer-canonical-array-slicing.mjs';\nimport { preferCanonicalLengthCast } from './prefer-canonical-length-cast.mjs';\nimport { preferCanonicalLengthGuard } from './prefer-canonical-length-guard.mjs';\nimport { preferComparisonOverNullishGuard } from './prefer-comparison-over-nullish-guard.mjs';\nimport { preferIsNonNullObject } from './prefer-is-non-null-object.mjs';\nimport { preferIsRecordAndHasKey } from './prefer-is-record-and-has-key.mjs';\nimport { preferNumSafeParseFloat } from './prefer-num-safe-parse-float.mjs';\nimport { preferNumSafeParseInt } from './prefer-num-safe-parse-int.mjs';\nimport { preferObjOverEntriesRoundTrip } from './prefer-obj-over-entries-round-trip.mjs';\nimport { preferRangeForLoop } from './prefer-range-for-loop.mjs';\n\nexport const tsDataForgeRules = {\n 'prefer-canonical-array-slicing': preferCanonicalArraySlicing,\n 'prefer-canonical-length-guard': preferCanonicalLengthGuard,\n 'prefer-canonical-length-cast': preferCanonicalLengthCast,\n 'prefer-arr-is-array': preferArrIsArray,\n 'prefer-arr-sum': preferArrSum,\n 'prefer-as-int': preferAsInt,\n 'prefer-is-non-null-object': preferIsNonNullObject,\n 'prefer-range-for-loop': preferRangeForLoop,\n 'prefer-is-record-and-has-key': preferIsRecordAndHasKey,\n 'prefer-num-safe-parse-int': preferNumSafeParseInt,\n 'prefer-num-safe-parse-float': preferNumSafeParseFloat,\n 'no-side-effect-import': noSideEffectImport,\n 'no-unnecessary-type-guard': noUnnecessaryTypeGuard,\n 'prefer-comparison-over-nullish-guard': preferComparisonOverNullishGuard,\n 'prefer-obj-over-entries-round-trip': preferObjOverEntriesRoundTrip,\n} as const satisfies ESLintPlugin['rules'];\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAiBO,MAAM,gBAAA,GAAmB;AAAA,EAC9B,gCAAA,EAAkC,2BAAA;AAAA,EAClC,+BAAA,EAAiC,0BAAA;AAAA,EACjC,8BAAA,EAAgC,yBAAA;AAAA,EAChC,qBAAA,EAAuB,gBAAA;AAAA,EACvB,gBAAA,EAAkB,YAAA;AAAA,EAClB,eAAA,EAAiB,WAAA;AAAA,EACjB,2BAAA,EAA6B,qBAAA;AAAA,EAC7B,uBAAA,EAAyB,kBAAA;AAAA,EACzB,8BAAA,EAAgC,uBAAA;AAAA,EAChC,2BAAA,EAA6B,qBAAA;AAAA,EAC7B,6BAAA,EAA+B,uBAAA;AAAA,EAC/B,uBAAA,EAAyB,kBAAA;AAAA,EACzB,2BAAA,EAA6B,sBAAA;AAAA,EAC7B,sCAAA,EAAwC,gCAAA;AAAA,EACxC,oCAAA,EAAsC;AACxC;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-ts-data-forge",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "description": "ESLint rules that steer code toward ts-data-forge idioms.",
6
6
  "keywords": [
@@ -42,13 +42,13 @@
42
42
  "@typescript-eslint/parser": "8.68.0",
43
43
  "@typescript-eslint/rule-tester": "8.68.0",
44
44
  "dedent": "1.7.2",
45
- "eslint-config-typed": "5.10.2",
46
- "eslint-plugin-ts-fortress": "1.0.7",
47
- "eslint-plugin-ts-type-forge": "0.7.3",
45
+ "eslint-config-typed": "5.10.4",
46
+ "eslint-plugin-ts-fortress": "1.1.0",
47
+ "eslint-plugin-ts-type-forge": "0.8.0",
48
48
  "prettier": "3.9.6",
49
49
  "prettier-plugin-organize-imports": "4.3.0",
50
50
  "prettier-plugin-packagejson": "3.0.2",
51
- "rollup": "4.62.5",
51
+ "rollup": "4.63.0",
52
52
  "ts-repo-utils": "10.4.0"
53
53
  },
54
54
  "peerDependencies": {
package/src/plugin.mts CHANGED
@@ -20,6 +20,7 @@ const recommendedRules = {
20
20
  'ts-data-forge/prefer-is-record-and-has-key': 'error',
21
21
  'ts-data-forge/prefer-num-safe-parse-int': 'error',
22
22
  'ts-data-forge/prefer-num-safe-parse-float': 'error',
23
+ 'ts-data-forge/no-side-effect-import': 'error',
23
24
  'ts-data-forge/no-unnecessary-type-guard': 'error',
24
25
  'ts-data-forge/prefer-comparison-over-nullish-guard': 'error',
25
26
  'ts-data-forge/prefer-obj-over-entries-round-trip': 'error',
@@ -192,6 +192,21 @@ namespace PreferNumSafeParseFloat {
192
192
  export type RuleEntry = Linter.StringSeverity;
193
193
  }
194
194
 
195
+ /**
196
+ * @description Remove a side-effect-only `import 'ts-data-forge';`, which binds no name and, the package being `sideEffects: false`, does nothing.
197
+ *
198
+ * ```md
199
+ * | key | value |
200
+ * | :--------- | :--------- |
201
+ * | type | suggestion |
202
+ * | deprecated | false |
203
+ * | fixable | code |
204
+ * ```
205
+ */
206
+ namespace NoSideEffectImport {
207
+ export type RuleEntry = Linter.StringSeverity;
208
+ }
209
+
195
210
  /**
196
211
  * @description Detect ts-data-forge type guard calls that perform no narrowing (the argument type already satisfies, or can never satisfy, the guard).
197
212
  *
@@ -277,6 +292,7 @@ export type EslintTsDataForgeRules = Readonly<{
277
292
  'ts-data-forge/prefer-is-record-and-has-key': PreferIsRecordAndHasKey.RuleEntry;
278
293
  'ts-data-forge/prefer-num-safe-parse-int': PreferNumSafeParseInt.RuleEntry;
279
294
  'ts-data-forge/prefer-num-safe-parse-float': PreferNumSafeParseFloat.RuleEntry;
295
+ 'ts-data-forge/no-side-effect-import': NoSideEffectImport.RuleEntry;
280
296
  'ts-data-forge/no-unnecessary-type-guard': NoUnnecessaryTypeGuard.RuleEntry;
281
297
  'ts-data-forge/prefer-comparison-over-nullish-guard': PreferComparisonOverNullishGuard.RuleEntry;
282
298
  'ts-data-forge/prefer-obj-over-entries-round-trip': PreferObjOverEntriesRoundTrip.RuleEntry;
@@ -0,0 +1,71 @@
1
+ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
+ import { Arr } from 'ts-data-forge';
3
+ import { TS_DATA_FORGE_MODULE } from './constants.mjs';
4
+
5
+ type Options = readonly [];
6
+
7
+ type MessageIds = 'removeSideEffectImport';
8
+
9
+ /**
10
+ * `import 'ts-data-forge';` binds nothing and runs nothing: every export is a
11
+ * pure value or function, and the package declares `sideEffects: false`, which
12
+ * is a promise that evaluating it changes nothing observable. TypeScript keeps
13
+ * a side-effect import in the emitted JavaScript — that is the one kind it
14
+ * never elides — so what is left is a module loaded for no reason, and a
15
+ * bundler that trusts the flag drops it anyway.
16
+ *
17
+ * The rule matches the bare specifier exactly; an import of some other module
18
+ * is none of its business.
19
+ */
20
+ export const noSideEffectImport: TSESLint.RuleModule<MessageIds, Options> = {
21
+ meta: {
22
+ type: 'suggestion',
23
+ docs: {
24
+ description: `Remove a side-effect-only \`import '${TS_DATA_FORGE_MODULE}';\`, which binds no name and, the package being \`sideEffects: false\`, does nothing.`,
25
+ },
26
+ fixable: 'code',
27
+ schema: [],
28
+ messages: {
29
+ removeSideEffectImport: `\`import '${TS_DATA_FORGE_MODULE}';\` binds no name, and \`${TS_DATA_FORGE_MODULE}\` declares \`sideEffects: false\`, so loading it does nothing. Remove it, and import what you need by name.`,
30
+ },
31
+ },
32
+
33
+ create: (context) => ({
34
+ ImportDeclaration: (node) => {
35
+ if (
36
+ node.source.value !== TS_DATA_FORGE_MODULE ||
37
+ !Arr.isEmpty(node.specifiers)
38
+ ) {
39
+ return;
40
+ }
41
+
42
+ context.report({
43
+ node,
44
+ messageId: 'removeSideEffectImport',
45
+ fix: (fixer) => removeStatement(fixer, context.sourceCode, node),
46
+ });
47
+ },
48
+ }),
49
+ defaultOptions: [],
50
+ } as const;
51
+
52
+ /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
53
+
54
+ /**
55
+ * Removes a statement along with the rest of its line, so that deleting it does
56
+ * not leave a blank line where it was.
57
+ */
58
+ const removeStatement = (
59
+ fixer: TSESLint.RuleFixer,
60
+ sourceCode: TSESLint.SourceCode,
61
+ node: TSESTree.Node,
62
+ ): TSESLint.RuleFix => {
63
+ const restOfLine = /^[^\S\n]*\n/u.exec(
64
+ sourceCode.getText().slice(node.range[1]),
65
+ );
66
+
67
+ return fixer.removeRange([
68
+ node.range[0],
69
+ node.range[1] + (restOfLine?.[0].length ?? 0),
70
+ ]);
71
+ };