@trackunit/eslint-plugin-trackunit 0.3.2 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/package.json +8 -4
- package/src/lib/config/index.d.ts +1 -1
- package/src/lib/config/plugins.d.ts +1 -2
- package/src/lib/config/plugins.js +1 -3
- package/src/lib/config/presets/react.d.ts +1 -1
- package/src/lib/config/presets/react.js +0 -1
- package/src/lib/utils/file-utils.d.ts +6 -0
- package/src/lib/utils/file-utils.js +0 -67
- package/src/index.js.map +0 -1
- package/src/lib/config/fragments/ignores.js.map +0 -1
- package/src/lib/config/fragments/import-rules.js.map +0 -1
- package/src/lib/config/fragments/jest-overrides.js.map +0 -1
- package/src/lib/config/fragments/jsdoc-rules.js.map +0 -1
- package/src/lib/config/fragments/module-boundaries.js.map +0 -1
- package/src/lib/config/fragments/react-rules.js.map +0 -1
- package/src/lib/config/fragments/restricted-imports.js.map +0 -1
- package/src/lib/config/fragments/testing-library.js.map +0 -1
- package/src/lib/config/fragments/typescript-rules.js.map +0 -1
- package/src/lib/config/index.js.map +0 -1
- package/src/lib/config/plugins.js.map +0 -1
- package/src/lib/config/presets/base.js.map +0 -1
- package/src/lib/config/presets/e2e.js.map +0 -1
- package/src/lib/config/presets/public-api.d.ts +0 -147
- package/src/lib/config/presets/public-api.js +0 -62
- package/src/lib/config/presets/public-api.js.map +0 -1
- package/src/lib/config/presets/react.js.map +0 -1
- package/src/lib/config/presets/server.js.map +0 -1
- package/src/lib/config/utils.js.map +0 -1
- package/src/lib/config-helpers/create-skip-when.js.map +0 -1
- package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.js.map +0 -1
- package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.js.map +0 -1
- package/src/lib/rules/no-internal-barrel-files/examples.js.map +0 -1
- package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.js.map +0 -1
- package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.js.map +0 -1
- package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.js.map +0 -1
- package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.js.map +0 -1
- package/src/lib/rules/no-typescript-assertion/examples.js.map +0 -1
- package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.js.map +0 -1
- package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.js.map +0 -1
- package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.js.map +0 -1
- package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.js.map +0 -1
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.js.map +0 -1
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.js.map +0 -1
- package/src/lib/rules/prefer-event-specific-callback-naming/utils.js.map +0 -1
- package/src/lib/rules/prefer-field-components/prefer-field-components.js.map +0 -1
- package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.js.map +0 -1
- package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.js.map +0 -1
- package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.js.map +0 -1
- package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.js.map +0 -1
- package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.js.map +0 -1
- package/src/lib/rules-map.js.map +0 -1
- package/src/lib/utils/ast-utils.js.map +0 -1
- package/src/lib/utils/classname-utils.js.map +0 -1
- package/src/lib/utils/file-utils.js.map +0 -1
- package/src/lib/utils/import-utils.js.map +0 -1
- package/src/lib/utils/nx-utils.js.map +0 -1
- package/src/lib/utils/package-utils.js.map +0 -1
- package/src/lib/utils/typescript-utils.js.map +0 -1
package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cva-merge-base-classes-as-array.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.ts"],"names":[],"mappings":"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","sourcesContent":["import { AST_NODE_TYPES, ESLintUtils, TSESTree } from \"@typescript-eslint/utils\";\nimport { formatAsArray, getCalleeName, splitClasses } from \"../../utils/classname-utils\";\n\nconst createRule = ESLintUtils.RuleCreator(\n name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}.ts`\n);\n\ntype MessageIds = \"stringNeedsArray\" | \"arrayNeedsSplit\";\ntype Options = [];\n\n/**\n * ESLint rule that converts cvaMerge base styles to arrays if more than one class is used.\n *\n * @example\n * Invalid:\n * const cvaMyComponent = cvaMerge(\"text-danger-600 mb-4 font-semibold\");\n * const cvaMyComponent = cvaMerge([\"text-danger-600 mb-4\",\"font-semibold\"]);\n * Valid:\n * const cvaMyComponent = cvaMerge([\"text-danger-600\", \"mb-4\", \"font-semibold\"]);\n */\nexport const cvaMergeBaseClassesAsArray = createRule<Options, MessageIds>({\n name: \"cva-merge-base-classes-as-array\",\n meta: {\n type: \"layout\",\n docs: {\n description: \"Convert cvaMerge base styles to arrays if more than one class is used.\",\n },\n fixable: \"code\",\n messages: {\n stringNeedsArray: \"[cvaMerge] The first argument of cvaMerge should be an array if multiple classes are used.\",\n arrayNeedsSplit: \"[cvaMerge] Only one class per array item is allowed.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n return {\n CallExpression(node: TSESTree.CallExpression) {\n const calleeName = getCalleeName(node.callee);\n\n if (calleeName !== \"cvaMerge\" || node.arguments.length === 0) {\n return;\n }\n\n const firstArg = node.arguments[0];\n if (!firstArg) {\n return;\n }\n\n // Handle string literal: cvaMerge(\"class1 class2\")\n if (firstArg.type === AST_NODE_TYPES.Literal && typeof firstArg.value === \"string\") {\n const value = firstArg.value;\n if (value.includes(\" \")) {\n context.report({\n node,\n messageId: \"stringNeedsArray\",\n fix(fixer) {\n return fixer.replaceText(firstArg, formatAsArray(splitClasses(value)));\n },\n });\n }\n return;\n }\n\n // Handle array: cvaMerge([\"class1 class2\", \"class3\"])\n if (firstArg.type === AST_NODE_TYPES.ArrayExpression) {\n const hasMultiClassElement = firstArg.elements.some(\n element =>\n element?.type === AST_NODE_TYPES.Literal &&\n typeof element.value === \"string\" &&\n element.value.includes(\" \")\n );\n\n if (hasMultiClassElement) {\n context.report({\n node,\n messageId: \"arrayNeedsSplit\",\n fix(fixer) {\n // Extract all string values and split them into individual classes\n const allClasses: Array<string> = [];\n for (const element of firstArg.elements) {\n if (element?.type === AST_NODE_TYPES.Literal && typeof element.value === \"string\") {\n allClasses.push(...splitClasses(element.value));\n }\n }\n return fixer.replaceText(firstArg, formatAsArray(allClasses));\n },\n });\n }\n }\n },\n };\n },\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"design-guideline-button-icon-size-match.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.ts"],"names":[],"mappings":"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","sourcesContent":["import { ESLintUtils, type TSESLint, type TSESTree } from \"@typescript-eslint/utils\";\nimport * as ts from \"typescript\";\n\nconst createRule = ESLintUtils.RuleCreator(\n name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/docs/rules/${name}.md`\n);\n\n// Helper to get possible string literal values from a variable's type\nfunction getPossibleStringLiterals(\n context: TSESLint.RuleContext<string, ReadonlyArray<unknown>>,\n identifier: TSESTree.Identifier\n): Array<string> | null {\n try {\n const services = ESLintUtils.getParserServices(context);\n const checker = services.program.getTypeChecker();\n const tsNode = services.esTreeNodeToTSNodeMap.get(identifier);\n const type = checker.getTypeAtLocation(tsNode);\n\n const literalValues: Array<string> = [];\n\n // Type guard for string literal types\n const isStringLiteralType = (t: ts.Type): t is ts.Type & { value: string } => {\n if ((t.flags & ts.TypeFlags.StringLiteral) === 0) {\n return false;\n }\n // Check if the type has a value property and it's a string\n // eslint-disable-next-line @trackunit/no-typescript-assertion\n const typeWithValue = t as { value?: unknown };\n return \"value\" in typeWithValue && typeof typeWithValue.value === \"string\";\n };\n\n if (type.isUnion()) {\n for (const unionType of type.types) {\n if (isStringLiteralType(unionType)) {\n literalValues.push(unionType.value);\n }\n }\n } else if (isStringLiteralType(type)) {\n literalValues.push(type.value);\n }\n\n return literalValues.length > 0 ? literalValues : null;\n } catch {\n return null;\n }\n}\n\n// Helper to check if a conditional expression matches the expected button-icon size pattern\nfunction isCorrectConditionalExpression(expression: TSESTree.Expression, buttonVariableName: string): boolean {\n if (expression.type !== \"ConditionalExpression\") {\n return false;\n }\n\n const { test, consequent, alternate } = expression;\n\n // Check if test is: variable === \"large\"\n if (\n test.type === \"BinaryExpression\" &&\n test.operator === \"===\" &&\n test.left.type === \"Identifier\" &&\n test.left.name === buttonVariableName &&\n test.right.type === \"Literal\" &&\n test.right.value === \"large\"\n ) {\n // Check if consequent is \"medium\" and alternate is \"small\"\n return (\n consequent.type === \"Literal\" &&\n consequent.value === \"medium\" &&\n alternate.type === \"Literal\" &&\n alternate.value === \"small\"\n );\n }\n\n return false;\n}\n\nexport const designGuidelineButtonIconSizeMatch = createRule({\n name: \"design-guideline-button-icon-size-match\",\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Ensure Icon components within Button have the correct size prop based on the Button's size.\",\n },\n fixable: \"code\",\n schema: [],\n messages: {\n incorrectIconSize:\n \"Expected Icon size to be '{{expectedSize}}' for Button size '{{buttonSize}}', found '{{iconSize}}'.\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n JSXOpeningElement(node: TSESTree.JSXOpeningElement) {\n if (node.name.type === \"JSXIdentifier\" && node.name.name === \"Button\") {\n const sizeAttr = node.attributes.find(\n (attr): attr is TSESTree.JSXAttribute =>\n attr.type === \"JSXAttribute\" && attr.name.type === \"JSXIdentifier\" && attr.name.name === \"size\"\n );\n\n const buttonSizeInfo = (() => {\n if (!sizeAttr?.value) return { type: \"implicit\", value: null };\n\n if (sizeAttr.value.type === \"Literal\" && typeof sizeAttr.value.value === \"string\") {\n return { type: \"literal\", value: sizeAttr.value.value };\n }\n\n if (sizeAttr.value.type === \"JSXExpressionContainer\") {\n const expr = sizeAttr.value.expression;\n if (expr.type === \"Identifier\") {\n return { type: \"variable\", value: expr.name, expression: expr };\n }\n return { type: \"expression\", value: null, expression: expr };\n }\n\n return { type: \"implicit\", value: null };\n })();\n\n node.attributes.forEach(attr => {\n if (\n attr.type === \"JSXAttribute\" &&\n attr.name.type === \"JSXIdentifier\" &&\n (attr.name.name === \"prefix\" || attr.name.name === \"suffix\") &&\n attr.value?.type === \"JSXExpressionContainer\"\n ) {\n // Find Icon element - could be direct or inside conditional\n const findIconElement = (expression: TSESTree.Expression): TSESTree.JSXOpeningElement | null => {\n if (\n expression.type === \"JSXElement\" &&\n expression.openingElement.name.type === \"JSXIdentifier\" &&\n expression.openingElement.name.name === \"Icon\"\n ) {\n return expression.openingElement;\n }\n\n if (expression.type === \"ConditionalExpression\") {\n // Check both consequent and alternate\n const consequent = findIconElement(expression.consequent);\n if (consequent) return consequent;\n\n const alternate = findIconElement(expression.alternate);\n if (alternate) return alternate;\n }\n\n return null;\n };\n\n // Skip empty JSX expressions like {}\n if (attr.value.expression.type === \"JSXEmptyExpression\") return;\n\n const iconElement = findIconElement(attr.value.expression);\n if (!iconElement) return;\n const iconSizeAttr = iconElement.attributes.find(\n (iconAttr): iconAttr is TSESTree.JSXAttribute =>\n iconAttr.type === \"JSXAttribute\" &&\n iconAttr.name.type === \"JSXIdentifier\" &&\n iconAttr.name.name === \"size\"\n );\n\n const iconSizeInfo = (() => {\n if (!iconSizeAttr?.value) return { type: \"implicit\", value: null };\n\n if (iconSizeAttr.value.type === \"Literal\" && typeof iconSizeAttr.value.value === \"string\") {\n return { type: \"literal\", value: iconSizeAttr.value.value };\n }\n\n if (iconSizeAttr.value.type === \"JSXExpressionContainer\") {\n const expr = iconSizeAttr.value.expression;\n if (expr.type === \"Identifier\") {\n return { type: \"variable\", value: expr.name, expression: expr };\n }\n return { type: \"expression\", value: null, expression: expr };\n }\n\n return { type: \"implicit\", value: null };\n })();\n\n // Rule: large buttons → medium icons, small/medium buttons → small icons\n const getExpectedIconSize = (buttonSize: string): string => {\n if (buttonSize === \"large\") return \"medium\";\n return \"small\"; // both \"small\" and \"medium\" buttons get \"small\" icons\n };\n\n const shouldReport = (() => {\n // For complex expressions, always warn but don't autofix\n if (buttonSizeInfo.type === \"expression\") {\n return true;\n }\n\n // For variables, check if icon already has the correct size\n if (buttonSizeInfo.type === \"variable\" && buttonSizeInfo.expression?.type === \"Identifier\") {\n const possibleSizes = getPossibleStringLiterals(context, buttonSizeInfo.expression);\n\n if (possibleSizes) {\n // Check if all possible button sizes map to the same icon size\n const iconSizes = possibleSizes.map(getExpectedIconSize);\n const uniqueIconSizes = [...new Set(iconSizes)];\n\n if (uniqueIconSizes.length === 1) {\n // All map to same icon size - check if icon already has correct literal\n const variableExpectedIconSize = uniqueIconSizes[0];\n const variableIconEffectiveSize = iconSizeInfo.value || \"medium\";\n\n if (variableIconEffectiveSize === variableExpectedIconSize) {\n return false; // Icon already has correct literal size\n }\n } else {\n // Mixed mapping - check if icon has correct conditional expression\n if (\n iconSizeInfo.type === \"expression\" &&\n iconSizeInfo.expression &&\n iconSizeInfo.expression.type !== \"JSXEmptyExpression\" &&\n buttonSizeInfo.value &&\n isCorrectConditionalExpression(iconSizeInfo.expression, buttonSizeInfo.value)\n ) {\n return false; // Icon already has correct conditional\n }\n }\n }\n\n // Mixed mapping or can't determine - always warn\n return true;\n }\n\n // For literals and implicit, check the mapping\n const buttonEffectiveSize = buttonSizeInfo.value || \"medium\";\n const iconEffectiveSize = iconSizeInfo.value || \"medium\";\n const expectedIconSize = getExpectedIconSize(buttonEffectiveSize);\n\n return iconEffectiveSize !== expectedIconSize;\n })();\n\n if (shouldReport) {\n context.report({\n node: iconElement,\n messageId: \"incorrectIconSize\",\n data: {\n expectedSize: (() => {\n if (buttonSizeInfo.type === \"variable\" && buttonSizeInfo.expression?.type === \"Identifier\") {\n // Analyze the variable's type to determine expected message\n const possibleSizes = getPossibleStringLiterals(context, buttonSizeInfo.expression);\n\n if (possibleSizes) {\n const iconSizes = possibleSizes.map(getExpectedIconSize);\n const uniqueIconSizes = [...new Set(iconSizes)];\n\n if (uniqueIconSizes.length === 1) {\n return uniqueIconSizes[0];\n }\n }\n\n return `{${buttonSizeInfo.value} === \"large\" ? \"medium\" : \"small\"}`;\n }\n if (buttonSizeInfo.type === \"expression\") {\n return \"small (for small/medium buttons) or medium (for large buttons)\";\n }\n const buttonEffectiveSize = buttonSizeInfo.value || \"medium\";\n return getExpectedIconSize(buttonEffectiveSize);\n })(),\n buttonSize:\n buttonSizeInfo.type === \"implicit\"\n ? \"medium (default)\"\n : buttonSizeInfo.type === \"literal\"\n ? buttonSizeInfo.value\n : buttonSizeInfo.type === \"variable\"\n ? (() => {\n if (buttonSizeInfo.expression?.type === \"Identifier\") {\n const possibleSizes = getPossibleStringLiterals(context, buttonSizeInfo.expression);\n if (possibleSizes && possibleSizes.length > 0) {\n const sizeList = possibleSizes.map(s => `\"${s}\"`).join(\" | \");\n return `{${buttonSizeInfo.value}} (of type ${sizeList})`;\n }\n }\n return `{${buttonSizeInfo.value}}`;\n })()\n : \"expression\",\n iconSize:\n iconSizeInfo.type === \"implicit\"\n ? \"medium (default)\"\n : iconSizeInfo.type === \"literal\"\n ? iconSizeInfo.value\n : iconSizeInfo.type === \"variable\"\n ? `{${iconSizeInfo.value}}`\n : \"expression\",\n },\n fix(fixer) {\n // Don't autofix complex expressions, only variables and literals\n if (buttonSizeInfo.type === \"expression\") {\n return null;\n }\n\n let targetSizeValue: string;\n\n if (buttonSizeInfo.type === \"variable\" && buttonSizeInfo.expression?.type === \"Identifier\") {\n // Analyze the variable's type to determine optimal autofix\n const possibleSizes = getPossibleStringLiterals(context, buttonSizeInfo.expression);\n\n if (possibleSizes) {\n // Check if all possible button sizes map to the same icon size\n const iconSizes = possibleSizes.map(getExpectedIconSize);\n const uniqueIconSizes = [...new Set(iconSizes)];\n\n if (uniqueIconSizes.length === 1) {\n // All possible button sizes map to the same icon size\n targetSizeValue = `\"${uniqueIconSizes[0]}\"`;\n } else {\n // Mixed mapping, use conditional\n targetSizeValue = `{${buttonSizeInfo.value} === \"large\" ? \"medium\" : \"small\"}`;\n }\n } else {\n // Can't determine type, use conditional\n targetSizeValue = `{${buttonSizeInfo.value} === \"large\" ? \"medium\" : \"small\"}`;\n }\n } else {\n // Calculate target icon size based on button size (literal or implicit)\n const buttonEffectiveSize = buttonSizeInfo.value || \"medium\";\n const targetIconSize = getExpectedIconSize(buttonEffectiveSize);\n targetSizeValue = `\"${targetIconSize}\"`;\n }\n\n if (iconSizeAttr) {\n // Replace existing size attribute\n if (!iconSizeAttr.value) return null;\n return fixer.replaceText(iconSizeAttr.value, targetSizeValue);\n } else {\n // Add size attribute\n const lastAttr = iconElement.attributes[iconElement.attributes.length - 1];\n if (lastAttr) {\n return fixer.insertTextAfter(lastAttr, ` size=${targetSizeValue}`);\n } else {\n return fixer.insertTextAfter(iconElement.name, ` size=${targetSizeValue}`);\n }\n }\n },\n });\n }\n }\n });\n }\n },\n };\n },\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"examples.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/no-internal-barrel-files/examples.ts"],"names":[],"mappings":";AAAA,4EAA4E;AAC5E,kFAAkF;AAClF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG","sourcesContent":["// To test this rule: create an index.ts file in a subdirectory and run lint\n// Note: eslint-disable is not needed here because this file is not named index.ts\n/**\n * This file documents the no-internal-barrel-files rule.\n *\n * Unlike other ESLint rules that check code patterns, this rule checks FILE LOCATION.\n * The rule triggers on any index.ts or index.tsx file that is NOT the root barrel\n * of an NX project (i.e., not at src/index.ts or src/index.tsx).\n *\n * This examples.ts file itself will NOT trigger the rule because it's not named index.ts.\n *\n * ## ✅ VALID - Root barrel file (allowed)\n *\n * ```\n * libs/my-lib/\n * ├── project.json\n * └── src/\n * └── index.ts ← This is the root barrel, allowed\n * ```\n *\n * Content of src/index.ts:\n * ```ts\n * export { Button } from \"./components/Button\";\n * export { useHook } from \"./hooks/useHook\";\n * export type { ButtonProps } from \"./components/Button\";\n * ```\n *\n * ## ❌ INVALID - Internal barrel files (SHOULD be flagged)\n *\n * ```\n * libs/my-lib/\n * ├── project.json\n * └── src/\n * ├── index.ts ← Root barrel (allowed)\n * ├── components/\n * │ ├── index.ts ← VIOLATION: Internal barrel\n * │ └── Button.tsx\n * └── utils/\n * ├── index.ts ← VIOLATION: Internal barrel\n * └── helpers.ts\n * ```\n *\n * ## ❌ INVALID - Deeply nested barrel files (SHOULD be flagged)\n *\n * ```\n * libs/my-lib/\n * ├── project.json\n * └── src/\n * └── features/\n * └── auth/\n * └── index.ts ← VIOLATION: Internal barrel\n * ```\n *\n * ## How to fix violations\n *\n * Option 1: Use the flatten-barrel-exports tool\n * ```bash\n * yarn ts-node devtools/flatten-barrel-exports/flatten-barrel-exports.ts --project=my-lib\n * ```\n *\n * Option 2: Manually move exports to the root barrel\n *\n * Before (with internal barrel):\n * ```ts\n * // src/components/index.ts\n * export { Button } from \"./Button\";\n * export { Input } from \"./Input\";\n *\n * // src/index.ts\n * export * from \"./components\"; // Re-exports the internal barrel\n * ```\n *\n * After (flattened):\n * ```ts\n * // src/index.ts\n * export { Button } from \"./components/Button\";\n * export { Input } from \"./components/Input\";\n *\n * // src/components/index.ts - DELETE THIS FILE\n * ```\n */\n\nexport {};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"no-internal-barrel-files.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESLint rule to disallow internal barrel files.\n *\n * Internal barrel files (any index.ts or index.tsx that is not at the library root) cause:\n * - Multiple import paths to the same module\n * - Slower builds (bundlers must traverse more barrel files)\n * - Confusion about canonical import paths\n * - Potential bundle size issues\n * - Module identity problems\n *\n * EXCEPTION: Secondary entry points configured in tsconfig.base.json are allowed.\n * These are intentional barrel files that provide separate package imports\n * (e.g., @trackunit/react-core-contexts-host-test pointing to src/test/index.ts).\n *\n * @example\n * // Valid - Root barrel file\n * // libs/my-lib/src/index.ts ✅\n *\n * // Valid - Secondary entry point configured in tsconfig.base.json\n * // libs/my-lib/src/test/index.ts ✅ (if configured as @trackunit/my-lib-test)\n *\n * // Invalid - Internal barrel file\n * // libs/my-lib/src/components/index.ts ❌\n * // libs/my-lib/src/utils/helpers/index.ts ❌\n */\nimport { ESLintUtils } from \"@typescript-eslint/utils\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { z } from \"zod\";\nimport { findNearestFile } from \"../../utils/file-utils\";\n\nconst projectJsonSchema = z.object({\n sourceRoot: z.string().optional(),\n});\n\nconst tsconfigSchema = z.object({\n compilerOptions: z\n .object({\n paths: z.record(z.array(z.string())).optional(),\n })\n .optional(),\n});\n\n/**\n * Cache for tsconfig.base.json secondary entry points.\n * Key: workspace root path\n * Value: Set of absolute paths to allowed secondary entry points\n */\nconst secondaryEntryPointsCache = new Map<string, Set<string>>();\n\n/**\n * Find the workspace root by looking for nx.json\n */\nconst findWorkspaceRoot = (startPath: string): string | null => {\n const nxJsonPath = findNearestFile(startPath, \"nx.json\");\n return nxJsonPath ? path.dirname(nxJsonPath) : null;\n};\n\n/**\n * Get secondary entry points from tsconfig.base.json.\n * These are path mappings that point to index.ts files inside src/ subdirectories.\n * Results are cached per workspace root.\n */\nconst getSecondaryEntryPoints = (workspaceRoot: string): Set<string> => {\n const cached = secondaryEntryPointsCache.get(workspaceRoot);\n if (cached) {\n return cached;\n }\n\n const entryPoints = new Set<string>();\n const tsconfigPath = path.join(workspaceRoot, \"tsconfig.base.json\");\n\n try {\n const tsconfigContent = fs.readFileSync(tsconfigPath, \"utf-8\");\n const parsed = tsconfigSchema.safeParse(JSON.parse(tsconfigContent));\n\n if (parsed.success && parsed.data.compilerOptions?.paths) {\n const paths = parsed.data.compilerOptions.paths;\n\n for (const pathMappings of Object.values(paths)) {\n for (const mapping of pathMappings) {\n // Only consider mappings that point to index.ts files\n if (mapping.endsWith(\"/index.ts\") || mapping.endsWith(\"/index.tsx\")) {\n const absolutePath = path.normalize(path.join(workspaceRoot, mapping));\n entryPoints.add(absolutePath);\n }\n }\n }\n }\n } catch {\n // If we can't read tsconfig.base.json, return empty set\n }\n\n secondaryEntryPointsCache.set(workspaceRoot, entryPoints);\n return entryPoints;\n};\n\nconst createRule = ESLintUtils.RuleCreator(\n name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}.ts`\n);\n\ntype MessageIds = \"noInternalBarrel\";\ntype Options = [];\n\n/**\n * Check if a file is a barrel file (index.ts or index.tsx).\n */\nconst isBarrelFile = (filePath: string): boolean => {\n const basename = path.basename(filePath);\n return basename === \"index.ts\" || basename === \"index.tsx\";\n};\n\n/**\n * Normalize file path for comparison (handles potential trailing slashes, etc.)\n */\nconst normalizePath = (filePath: string): string => {\n return path.normalize(filePath);\n};\n\nexport const noInternalBarrelFiles = createRule<Options, MessageIds>({\n name: \"no-internal-barrel-files\",\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Disallow internal barrel files (index.ts) that are not at the library root or configured as secondary entry points\",\n },\n schema: [],\n messages: {\n noInternalBarrel:\n \"Internal barrel files are not allowed. Move exports to the root barrel file (src/index.ts) instead. Use the flatten-barrel-exports tool: yarn ts-node devtools/flatten-barrel-exports/flatten-barrel-exports.ts\",\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n Program(node) {\n const currentFile = context.filename;\n\n // Only analyze barrel files (index.ts/tsx)\n if (!isBarrelFile(currentFile)) {\n return;\n }\n\n // Find the project root by looking for project.json\n const projectJsonPath = findNearestFile(currentFile, \"project.json\");\n if (projectJsonPath === null) {\n // Not in an NX project, skip\n return;\n }\n\n const projectRoot = path.dirname(projectJsonPath);\n const workspaceRoot = findWorkspaceRoot(currentFile);\n\n // Read sourceRoot from project.json, defaulting to \"src\" if not specified\n // sourceRoot in project.json is relative to workspace root (e.g., \"libs/my-lib/src\")\n let sourceRootAbsolute = path.join(projectRoot, \"src\");\n try {\n const projectJsonContent = fs.readFileSync(projectJsonPath, \"utf-8\");\n const parsed = projectJsonSchema.safeParse(JSON.parse(projectJsonContent));\n if (parsed.success && parsed.data.sourceRoot && workspaceRoot) {\n // sourceRoot is relative to workspace root, so resolve it\n sourceRootAbsolute = path.join(workspaceRoot, parsed.data.sourceRoot);\n }\n } catch {\n // If we can't read project.json, fall back to projectRoot/src\n }\n\n // The root barrel is at {sourceRoot}/index.ts or {sourceRoot}/index.tsx\n const rootBarrelTs = normalizePath(path.join(sourceRootAbsolute, \"index.ts\"));\n const rootBarrelTsx = normalizePath(path.join(sourceRootAbsolute, \"index.tsx\"));\n const normalizedCurrentFile = normalizePath(currentFile);\n\n // If this IS the root barrel, it's allowed\n if (normalizedCurrentFile === rootBarrelTs || normalizedCurrentFile === rootBarrelTsx) {\n return;\n }\n\n // Check if this is a secondary entry point configured in tsconfig.base.json\n if (workspaceRoot) {\n const secondaryEntryPoints = getSecondaryEntryPoints(workspaceRoot);\n if (secondaryEntryPoints.has(normalizedCurrentFile)) {\n // This is a configured secondary entry point, allow it\n return;\n }\n }\n\n // This is an internal barrel file - flag it\n // Note: No autofix or suggestion is provided because:\n // 1. The exports need to be moved to the root barrel\n // 2. Imports throughout the codebase may need updating\n // 3. The flatten-barrel-exports tool handles this properly\n context.report({\n node,\n messageId: \"noInternalBarrel\",\n });\n },\n };\n },\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"no-internal-graphql-when-tagged-with-gql-public.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.ts"],"names":[],"mappings":";;;;AACA,+CAAyB;AACzB,mDAA6B;AAE7B,MAAM,IAAI,GAAsB;IAC9B,IAAI,EAAE,SAAS;IACf,IAAI,EAAE;QACJ,WAAW,EAAE,yEAAyE;KACvF;IACD,QAAQ,EAAE;QACR,wBAAwB,EAAE,kEAAkE;KAC7F;IACD,MAAM,EAAE,EAAE;CACX,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,OAAyB,EAAqB,EAAE;IAC9D,MAAM,4BAA4B,GAAG,CAAC,OAAe,EAAE,QAAgB,EAAE,EAAE;QACzE,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC;QAC1B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;QACpD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC,CAAC;IAEF,MAAM,qCAAqC,GAAG,CAAC,SAAiB,EAAE,WAAmB,EAAE,EAAE;QACvF,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QACxC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAE5C,0DAA0D;YAC1D,IAAI,IAAc,CAAC;YACnB,IAAI,CAAC;gBACH,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACP,+EAA+E;gBAC/E,SAAS;YACX,CAAC;YAED,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;gBACvB,qCAAqC,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YAC/D,CAAC;iBAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;gBAC9C,OAAO,CAAC,MAAM,CAAC;oBACb,OAAO,EAAE,uEAAuE,QAAQ,EAAE;oBAC1F,GAAG,EAAE,4BAA4B,CAAC,WAAW,EAAE,WAAW,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;iBAClF,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,EAAE;YACf,MAAM,cAAc,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;YAC7C,IAAI,cAAc,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;gBAC7C,MAAM,iBAAiB,GAAG,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;gBAClE,MAAM,WAAW,GAA6B,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;gBAC5E,IAAI,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;oBAC/E,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;oBAChD,qCAAqC,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;gBACvE,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEW,QAAA,wCAAwC,GAAG;IACtD,IAAI;IACJ,MAAM;CACP,CAAC","sourcesContent":["import { Rule } from \"eslint\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\nconst meta: Rule.RuleMetaData = {\n type: \"problem\",\n docs: {\n description: \"Disallow '-internal.graphql' files in projects tagged with 'gql:public'\",\n },\n messages: {\n internalGraphqlFileFound: \"'-internal.graphql' file found in a 'gql:public' tagged project.\",\n },\n schema: [],\n};\n\nconst create = (context: Rule.RuleContext): Rule.RuleListener => {\n const getLineAndColumnFromPosition = (content: string, position: number) => {\n const lines = content.substring(0, position).split(\"\\n\");\n const line = lines.length;\n const column = lines[lines.length - 1]?.length ?? 0;\n return { line, column };\n };\n\n const checkDirectoryForInternalGraphQLFiles = (directory: string, projectJson: string) => {\n const files = fs.readdirSync(directory);\n for (const file of files) {\n const fullPath = path.join(directory, file);\n\n // Handle broken symlinks or inaccessible paths gracefully\n let stat: fs.Stats;\n try {\n stat = fs.statSync(fullPath);\n } catch {\n // Skip paths that can't be accessed (broken symlinks, permission issues, etc.)\n continue;\n }\n\n if (stat.isDirectory()) {\n checkDirectoryForInternalGraphQLFiles(fullPath, projectJson);\n } else if (file.endsWith(\"-internal.graphql\")) {\n context.report({\n message: `'*-internal.graphql' file found in a 'gql:public' tagged project. - ${fullPath}`,\n loc: getLineAndColumnFromPosition(projectJson, projectJson.indexOf(\"gql:public\")),\n });\n return;\n }\n }\n };\n return {\n Program: _node => {\n const sourceFilePath = context.getFilename();\n if (sourceFilePath.endsWith(\"/project.json\")) {\n const projectJsonString = fs.readFileSync(sourceFilePath, \"utf8\");\n const projectJson: { tags?: Array<string> } = JSON.parse(projectJsonString);\n if (Array.isArray(projectJson.tags) && projectJson.tags.includes(\"gql:public\")) {\n const projectDir = path.dirname(sourceFilePath);\n checkDirectoryForInternalGraphQLFiles(projectDir, projectJsonString);\n }\n }\n },\n };\n};\n\nexport const noInternalGraphqlWhenTaggedWithGqlPublic = {\n meta,\n create,\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"no-jest-mock-trackunit-react-core-hooks.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.ts"],"names":[],"mappings":";;;AAEa,QAAA,iCAAiC,GAAoB;IAChE,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,4DAA4D;SAC1E;QACD,QAAQ,EAAE;YACR,YAAY,EACV,oKAAoK;SACvK;QACD,MAAM,EAAE,EAAE;KACX;IACD,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,cAAc,CAAC,IAAI;gBACjB,IACE,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,kBAAkB;oBACvC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY;oBACxC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM;oBAClC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY;oBAC1C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,MAAM;oBACpC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,SAAS;oBACrC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,6BAA6B,EACzD,CAAC;oBACD,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,SAAS,EAAE,cAAc;qBAC1B,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import type { Rule } from \"eslint\";\n\nexport const noJestMockTrackunitReactCoreHooks: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description: \"Disallow direct jest.mock() of @trackunit/react-core-hooks\",\n },\n messages: {\n noDirectMock:\n 'Don\\'t use jest.mock(\"@trackunit/react-core-hooks\"). Use mock builder from trackunitProviders(), trackunitProvidersInternal() or trackunitProvidersHost() instead.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n if (\n node.callee.type === \"MemberExpression\" &&\n node.callee.object.type === \"Identifier\" &&\n node.callee.object.name === \"jest\" &&\n node.callee.property.type === \"Identifier\" &&\n node.callee.property.name === \"mock\" &&\n node.arguments[0]?.type === \"Literal\" &&\n node.arguments[0].value === \"@trackunit/react-core-hooks\"\n ) {\n context.report({\n node,\n messageId: \"noDirectMock\",\n });\n }\n },\n };\n },\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"no-template-strings-in-classname-prop.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.ts"],"names":[],"mappings":";;;AAAA,oDAAiF;AACjF,iEAAmE;AAEnE,MAAM,UAAU,GAAG,mBAAW,CAAC,WAAW,CACxC,IAAI,CAAC,EAAE,CAAC,6FAA6F,IAAI,KAAK,CAC/G,CAAC;AAKF;;;;;;;;;;;GAWG;AACU,QAAA,4BAA4B,GAAG,UAAU,CAAsB;IAC1E,IAAI,EAAE,uCAAuC;IAC7C,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EAAE,8FAA8F;SAC5G;QACD,QAAQ,EAAE;YACR,yBAAyB,EACvB,+LAA+L;SAClM;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,OAAO;YACL;;;;;;eAMG;YACH,YAAY,CAAC,IAA2B;gBACtC,8DAA8D;gBAC9D,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,sBAAc,CAAC,aAAa,IAAI,CAAC,IAAA,sCAAoB,EAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC7F,OAAO;gBACT,CAAC;gBAED,yEAAyE;gBACzE,IACE,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,sBAAc,CAAC,sBAAsB;oBAC1D,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,sBAAc,CAAC,eAAe,EAC7D,CAAC;oBACD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC,CAAC;gBACnE,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { AST_NODE_TYPES, ESLintUtils, TSESTree } from \"@typescript-eslint/utils\";\nimport { isClassnameAttribute } from \"../../utils/classname-utils\";\n\nconst createRule = ESLintUtils.RuleCreator(\n name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}.ts`\n);\n\ntype MessageIds = \"templateStringInClassName\";\ntype Options = [];\n\n/**\n * ESLint rule to disallow template strings in className prop.\n *\n * @example\n * // Bad\n * <div className={`${dynamicClass} staticClass`}>Content</div>\n *\n * // Good\n * <div className={twMerge(dynamicClassName, \"staticClassName\")}>Content</div>\n * // or\n * <div className={yourCvaMergeFunction({ yourVariantProp, className: \"extraClassName\" })}>Content</div>\n */\nexport const noTemplateStringsInClassName = createRule<Options, MessageIds>({\n name: \"no-template-strings-in-classname-prop\",\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Disallow the use of template strings in className prop to encourage better class management.\",\n },\n messages: {\n templateStringInClassName:\n \"Do not use template strings in className prop. If you wish to merge classes from different sources, use twMerge() for simple inline merging or make a cvaMerge() function if it has variants.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n return {\n /**\n * Handle JSXAttribute with className prop that uses template strings.\n *\n * @example\n * // Bad\n * <div className={`${dynamicClass} staticClass`}>Content</div>\n */\n JSXAttribute(node: TSESTree.JSXAttribute) {\n // Check if this is a classname attribute (className or class)\n if (node.name.type !== AST_NODE_TYPES.JSXIdentifier || !isClassnameAttribute(node.name.name)) {\n return;\n }\n\n // Check if the value is a template literal in a JSX expression container\n if (\n node.value?.type === AST_NODE_TYPES.JSXExpressionContainer &&\n node.value.expression.type === AST_NODE_TYPES.TemplateLiteral\n ) {\n context.report({ node, messageId: \"templateStringInClassName\" });\n }\n },\n };\n },\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"examples.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/no-typescript-assertion/examples.ts"],"names":[],"mappings":";AAAA,iHAAiH;AACjH,uDAAuD;AACvD,0DAA0D;;AAuB1D,+CAA+C;AAC/C,MAAM,OAAO,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAW,CAAC;AACtD,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAElD,wFAAwF;AACxF,4EAA4E;AAC5E,MAAM,KAAK,GAAG,SAAoB,CAAC;AAEnC,8BAA8B;AAC9B,SAAS,QAAQ,CAAC,GAAY;IAC5B,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC;AACjC,CAAC;AACD,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;AAEhE,6CAA6C;AAC7C,MAAM,cAAc,GAAG,UAAU,IAAI,SAAS,CAAC;AAE/C,0DAA0D;AAC1D,MAAM,aAAa,GAAW,SAAS,CAAC;AAExC,yDAAyD;AACzD,MAAM,UAAU,GAAG,SAAmB,CAAC;AACvC,MAAM,SAAS,GAAG,SAAmB,CAAC;AACtC,MAAM,SAAS,GAAG,QAAwC,CAAC;AAE3D,qDAAqD;AACrD,yEAAyE;AACzE,oEAAoE;AACpE,MAAM,eAAe,GAAG,SAAU,CAAC;AACnC,sHAAsH;AACtH,MAAM,eAAe,GAAG,GAAI,CAAC,IAAK,CAAC,KAAM,CAAC;AAC1C,sHAAsH;AACtH,MAAM,aAAa,GAAG,GAAI,CAAC,CAAC,CAAC,CAAC;AAE9B,mDAAmD;AACnD,MAAM,OAAO,GAAG,KAAiB,CAAC;AAClC,oEAAoE;AACpE,MAAM,OAAO,GAAG,YAAa,CAAC;AAC9B,MAAM,OAAO,GAAc,eAAe,CAAC;AAE3C,4DAA4D;AAC5D,4EAA4E;AAC5E,MAAM,YAAY,GAAG,KAAK,CAAC,WAAW,CAAsB,CAAC;AAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC;AACjD,oEAAoE;AACpE,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAE,CAAC;AAClD,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAqB,CAAC","sourcesContent":["// To test this rule: comment out the eslint-disable line below and run: yarn nx run eslint-plugin-trackunit:lint\n/* eslint-disable @trackunit/no-typescript-assertion */\n// This file demonstrates the no-typescript-assertion rule\n\n// Declare variables used in examples\ndeclare const someValue: unknown;\ndeclare const something: unknown;\ndeclare const maybeValue: unknown;\ndeclare const userInput: unknown;\ndeclare const response: unknown;\ndeclare const maybeNull: unknown;\ndeclare const obj: { prop?: { value?: unknown } };\ndeclare const arr: Array<unknown>;\ndeclare const value: unknown;\ndeclare const anotherValue: unknown;\ndeclare const yetAnotherValue: unknown;\ndeclare const str: string;\ndeclare const collection: Iterable<unknown>;\n\ntype SomeType = Record<string, unknown>;\ntype MyInterface = { id: number; name: string };\ntype _ApiData = { data: unknown };\ntype ThirdType = Record<string, unknown>;\ntype TypedItem = { id: string };\n\n// ✅ VALID - Using as const (allowed exception)\nconst _config = { name: \"test\", value: 123 } as const;\nconst _colors = [\"red\", \"green\", \"blue\"] as const;\n\n// ✅ VALID - Using as unknown as intermediate (allowed exception, but still discouraged)\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\nconst _data = someValue as unknown;\n\n// ✅ VALID - Proper type guard\nfunction isString(val: unknown): val is string {\n return typeof val === \"string\";\n}\nconst _validValue = isString(something) ? something : \"default\";\n\n// ✅ VALID - Explicit null/undefined handling\nconst _validOptional = maybeValue ?? \"default\";\n\n// ❌ INVALID - Angle bracket assertion (SHOULD be flagged)\nconst _angleExample = <string>someValue;\n\n// ❌ INVALID - 'as' keyword assertion (SHOULD be flagged)\nconst _asExample = someValue as string;\nconst _asNumber = userInput as number;\nconst _asObject = response as { id: number; name: string };\n\n// ❌ INVALID - Non-null assertion (SHOULD be flagged)\n// These are intentionally invalid examples - the rule should catch these\n// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\nconst _nonNullExample = maybeNull!;\n// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion\nconst _chainedNonNull = obj!.prop!.value!;\n// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion\nconst _arrayNonNull = arr![0];\n\n// ❌ INVALID - Multiple assertion types in one file\nconst _mixed1 = value as SomeType;\n// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\nconst _mixed2 = anotherValue!;\nconst _mixed3 = <ThirdType>yetAnotherValue;\n\n// ❌ INVALID - Common real-world cases that should be caught\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\nconst _apiResponse = fetch(\"/api/data\") as Promise<Response>;\nconst _jsonData = JSON.parse(str) as MyInterface;\n// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\nconst _element = document.getElementById(\"root\")!;\nconst _typedArray = Array.from(collection) as Array<TypedItem>;\n\nexport {};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"no-typescript-assertion.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/no-typescript-assertion/no-typescript-assertion.ts"],"names":[],"mappings":";;;AAkBA,MAAM,IAAI,GAAsB;IAC9B,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE;QACJ,WAAW,EAAE,qEAAqE;KACnF;IACD,QAAQ,EAAE;QACR,qBAAqB,EAAE,wEAAwE;QAC/F,WAAW,EAAE,2EAA2E;QACxF,gBAAgB,EAAE,iFAAiF;KACpG;IACD,MAAM,EAAE,EAAE;CACX,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,GAAG,CAAC,OAAyB,EAAqB,EAAE;IAC9D,OAAO;QACL;;;;;;;WAOG;QACH,eAAe,CAAC,IAAS;YACvB,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;YAEhC,IAAI,cAAc,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;gBAC/C,OAAO;YACT,CAAC;YAED,IAAI,cAAc,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBAC9C,MAAM,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC;gBACpC,IAAI,QAAQ,CAAC,IAAI,KAAK,YAAY,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAChE,OAAO;gBACT,CAAC;YACH,CAAC;YAED,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,uBAAuB,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD;;;;;;;WAOG;QACH,cAAc,CAAC,IAAS;YACtB,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;YAEhC,IAAI,cAAc,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;gBAC/C,OAAO;YACT,CAAC;YAED,IAAI,cAAc,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBAC9C,MAAM,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC;gBACpC,IAAI,QAAQ,CAAC,IAAI,KAAK,YAAY,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAChE,OAAO;gBACT,CAAC;YACH,CAAC;YAED,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC;QACrD,CAAC;QACD;;;;;;;WAOG;QACH,mBAAmB,CAAC,IAAS;YAC3B,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC1D,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEW,QAAA,qBAAqB,GAAG;IACnC,IAAI;IACJ,MAAM;CACP,CAAC","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * ESLint rule to disallow type assertions in TypeScript.\n * Initial implementation based on eslint-plugin-no-type-assertion\n * https://github.com/Dremora/eslint-plugin-no-type-assertion/blob/master/lib/rules/no-type-assertion.js\n *\n * @example\n * // Bad - type assertions bypass type checking\n * const foo = <string> bar;\n * const baz = bar as string;\n * const qux = bar!;\n *\n * // Good - proper typing or const assertions\n * const foo = bar;\n * const options = [\"a\", \"b\"] as const;\n */\nimport { Rule } from \"eslint\";\n\nconst meta: Rule.RuleMetaData = {\n type: \"suggestion\",\n docs: {\n description: \"Prevent the use of type assertions to encourage better type safety.\",\n },\n messages: {\n angleBracketAssertion: \"Avoid using angle bracket for type casting. It bypasses type checking.\",\n asAssertion: \"Avoid using the `as` keyword for type casting. It bypasses type checking.\",\n nonNullAssertion: \"Avoid using the non-null assertion operator (!). It can lead to runtime errors.\",\n },\n schema: [],\n};\n\n/**\n * Create ESLint rule listener.\n *\n * @param {Rule.RuleContext} context - The ESLint rule context.\n * @returns {Rule.RuleListener} - The listener object for this rule.\n */\nconst create = (context: Rule.RuleContext): Rule.RuleListener => {\n return {\n /**\n * Handle TypeScript type assertions using angle brackets.\n *\n * @example\n * // Bad\n * const foo = <string> bar;\n * @param {any} node - The AST node.\n */\n TSTypeAssertion(node: any) {\n const { typeAnnotation } = node;\n\n if (typeAnnotation.type === \"TSUnknownKeyword\") {\n return;\n }\n\n if (typeAnnotation.type === \"TSTypeReference\") {\n const { typeName } = typeAnnotation;\n if (typeName.type === \"Identifier\" && typeName.name === \"const\") {\n return;\n }\n }\n\n context.report({ node, messageId: \"angleBracketAssertion\" });\n },\n /**\n * Handle TypeScript 'as' expressions for type assertions.\n *\n * @example\n * // Bad\n * const baz = bar as string;\n * @param {any} node - The AST node.\n */\n TSAsExpression(node: any) {\n const { typeAnnotation } = node;\n\n if (typeAnnotation.type === \"TSUnknownKeyword\") {\n return;\n }\n\n if (typeAnnotation.type === \"TSTypeReference\") {\n const { typeName } = typeAnnotation;\n if (typeName.type === \"Identifier\" && typeName.name === \"const\") {\n return;\n }\n }\n\n context.report({ node, messageId: \"asAssertion\" });\n },\n /**\n * Handle TypeScript non-null expressions using the postfix expression '!'.\n *\n * @example\n * // Bad\n * const qux = bar!;\n * @param {any} node - The AST node.\n */\n TSNonNullExpression(node: any) {\n context.report({ node, messageId: \"nonNullAssertion\" });\n },\n };\n};\n\nexport const noTypescriptAssertion = {\n meta,\n create,\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-destructured-imports.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAEH,oDAAiF;AACjF,2DAAiG;AAEjG,MAAM,UAAU,GAAG,mBAAW,CAAC,WAAW,CACxC,IAAI,CAAC,EAAE,CAAC,6FAA6F,IAAI,KAAK,CAC/G,CAAC;AAWF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACU,QAAA,yBAAyB,GAAG,UAAU,CAAsB;IACvE,IAAI,EAAE,6BAA6B;IACnC,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EAAE,+EAA+E;SAC7F;QACD,OAAO,EAAE,MAAM;QACf,MAAM,EAAE;YACN;gBACE,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,QAAQ,EAAE;wBACR,IAAI,EAAE,QAAQ;wBACd,WAAW,EAAE,0FAA0F;wBACvG,oBAAoB,EAAE;4BACpB,IAAI,EAAE,QAAQ;yBACf;qBACF;iBACF;gBACD,oBAAoB,EAAE,KAAK;aAC5B;SACF;QACD,QAAQ,EAAE;YACR,kBAAkB,EAAE,0DAA0D;YAC9E,wBAAwB,EACtB,iGAAiG;SACpG;KACF;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACtC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAEnC,6CAA6C;QAC7C,IAAI,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxE,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC;QAC7C,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAExD,iEAAiE;QACjE,MAAM,kBAAkB,GAAG,CAAC,KAAa,EAAW,EAAE;YACpD,OAAO,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC,CAAC;QAEF,kFAAkF;QAClF,MAAM,yBAAyB,GAAG,IAAI,GAAG,EAAU,CAAC;QAEpD,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAiF,CAAC;QAClH,MAAM,cAAc,GAAG,IAAI,GAAG,EAAqE,CAAC;QAEpG,6GAA6G;QAC7G,MAAM,YAAY,GAAG,IAAI,GAAG,EAAsF,CAAC;QAEnH,OAAO;YACL,iBAAiB,CAAC,IAAgC;gBAChD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;gBACtC,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;oBACvE,MAAM,UAAU,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;oBAEpD,wCAAwC;oBACxC,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAChC,IAAI,CAAC,UAAU;yBACZ,MAAM,CAAC,CAAC,CAAC,EAAiC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,sBAAc,CAAC,eAAe,CAAC;yBACvF,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAC1B,CAAC;oBAEF,oDAAoD;oBACpD,MAAM,2BAA2B,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CACtD,CAAC,CAAC,EAA4E,EAAE,CAC9E,CAAC,CAAC,IAAI,KAAK,sBAAc,CAAC,sBAAsB,IAAI,CAAC,CAAC,IAAI,KAAK,sBAAc,CAAC,wBAAwB,CACzG,CAAC;oBAEF,IAAI,2BAA2B,EAAE,CAAC;wBAChC,MAAM,UAAU,GAAG,2BAA2B,CAAC,KAAK,CAAC,IAAI,CAAC;wBAC1D,+EAA+E;wBAC/E,+EAA+E;wBAC/E,IAAI,UAAU,KAAK,UAAU,EAAE,CAAC;4BAC9B,yBAAyB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;wBAC5C,CAAC;wBAED,oEAAoE;wBACpE,IAAI,kBAAkB,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;4BAChC,gBAAgB,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC;wBACrE,CAAC;wBACD,gBAAgB,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC;wBAC/D,cAAc,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;oBAC5C,CAAC;yBAAM,IAAI,kBAAkB,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;wBACvC,kCAAkC;wBAClC,gBAAgB,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC;wBACnE,oGAAoG;oBACtG,CAAC;gBACH,CAAC;YACH,CAAC;YAED,yCAAyC;YACzC,gBAAgB,CAAC,IAA+B;gBAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;gBAE/B,IAAI,UAAU,CAAC,IAAI,KAAK,sBAAc,CAAC,UAAU,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,sBAAc,CAAC,UAAU,EAAE,CAAC;oBACtG,8CAA8C;oBAC9C,IAAI,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;wBAC1C,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;wBACnD,IAAI,MAAM,EAAE,CAAC;4BACX,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;gCACxB,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI;gCACxB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;gCACpB,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;6BACnB,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;oBACD,uFAAuF;yBAClF,IAAI,CAAC,yBAAyB,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;wBACzD,qEAAqE;wBACrE,KAAK,MAAM,WAAW,IAAI,cAAc,EAAE,CAAC;4BACzC,MAAM,UAAU,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;4BACpD,IAAI,UAAU,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gCACnC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;oCAClC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE;wCAC3B,WAAW,EAAE,WAAW;wCACxB,OAAO,EAAE,IAAI,GAAG,EAAE;wCAClB,KAAK,EAAE,EAAE;qCACV,CAAC,CAAC;gCACL,CAAC;gCACD,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gCAC3C,IAAI,KAAK,EAAE,CAAC;oCACV,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;oCACtC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gCACzB,CAAC;gCACD,MAAM;4BACR,CAAC;wBACH,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAED,6CAA6C;YAC7C,eAAe,CAAC,IAA8B;gBAC5C,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,sBAAc,CAAC,UAAU,EAAE,CAAC;oBACjD,8CAA8C;oBAC9C,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;wBACzC,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;wBAClD,IAAI,MAAM,EAAE,CAAC;4BACX,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;gCACxB,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI;gCACrB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;gCACpB,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;6BACnB,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;oBACD,6CAA6C;yBACxC,IAAI,CAAC,yBAAyB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;wBACxD,KAAK,MAAM,WAAW,IAAI,cAAc,EAAE,CAAC;4BACzC,MAAM,UAAU,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;4BACpD,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gCAClC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;oCAClC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE;wCAC3B,WAAW,EAAE,WAAW;wCACxB,OAAO,EAAE,IAAI,GAAG,EAAE;wCAClB,KAAK,EAAE,EAAE;qCACV,CAAC,CAAC;gCACL,CAAC;gCACD,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gCAC3C,IAAI,KAAK,EAAE,CAAC;oCACV,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oCACnC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gCACzB,CAAC;gCACD,MAAM;4BACR,CAAC;wBACH,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAED,kFAAkF;YAClF,iFAAiF;YACjF,mDAAmD;YACnD,eAAe,CAAC,KAA+B;gBAC7C,6EAA6E;gBAC7E,wBAAwB;YAC1B,CAAC;YAED,cAAc;gBACZ,sCAAsC;gBACtC,KAAK,MAAM,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,IAAI,gBAAgB,EAAE,CAAC;oBAClE,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;oBAC9C,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;wBACjC,SAAS;oBACX,CAAC;oBAED,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC;oBAE5C,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;wBACxE,SAAS;oBACX,CAAC;oBAED,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI,EAAE,UAAU;wBAChB,SAAS,EAAE,oBAAoB;wBAC/B,GAAG,CAAC,KAAK;4BACP,MAAM,KAAK,GAAG,EAAE,CAAC;4BAEjB,mHAAmH;4BACnH,MAAM,oBAAoB,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;4BAElF,gEAAgE;4BAChE,MAAM,oBAAoB,GAAG,gBAAgB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;4BAClE,MAAM,uBAAuB,GAC3B,oBAAoB,IAAI,oBAAoB,CAAC,IAAI,KAAK,UAAU;gCAC9D,CAAC,CAAC,oBAAoB,CAAC,IAAI;gCAC3B,CAAC,CAAC,SAAS,CAAC;4BAEhB,yFAAyF;4BACzF,KAAK,CAAC,IAAI,CACR,GAAG,IAAA,+CAAgC,EAAC;gCAClC,UAAU;gCACV,KAAK;gCACL,WAAW,EAAE,WAAW;gCACxB,wBAAwB,EAAE,UAAU;gCACpC,eAAe,EAAE,oBAAoB;gCACrC,eAAe,EAAE,uBAAuB;6BACzC,CAAC,CACH,CAAC;4BAEF,kDAAkD;4BAClD,KAAK,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;gCAC7C,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gCACpD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,UAAU,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;gCAClE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;4BAC5D,CAAC;4BAED,OAAO,KAAK,CAAC;wBACf,CAAC;qBACF,CAAC,CAAC;gBACL,CAAC;gBAED,yFAAyF;gBACzF,KAAK,MAAM,CAAC,UAAU,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,IAAI,YAAY,EAAE,CAAC;oBACzE,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBAC7C,SAAS;oBACX,CAAC;oBAED,iCAAiC;oBACjC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBAC3B,IAAI,CAAC,SAAS,EAAE,CAAC;wBACf,SAAS;oBACX,CAAC;oBAED,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBACxC,MAAM,oBAAoB,GAAG,gBAAgB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;oBAElE,2EAA2E;oBAC3E,MAAM,YAAY,GAAG,oBAAoB;wBACvC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,oBAAoB,CAAC,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;wBACpF,CAAC,CAAC,WAAW,CAAC;oBAEhB,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI,EAAE,SAAS;wBACf,SAAS,EAAE,0BAA0B;wBACrC,IAAI,EAAE;4BACJ,UAAU;4BACV,WAAW;4BACX,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,yCAAyC;yBAClF;wBACD,GAAG,CAAC,KAAK;4BACP,MAAM,KAAK,GAAG,EAAE,CAAC;4BAEjB,sDAAsD;4BACtD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gCAC5B,KAAK,CAAC,IAAI,CACR,GAAG,IAAA,kCAAmB,EAAC;oCACrB,UAAU;oCACV,KAAK;oCACL,WAAW;oCACX,UAAU,EAAE,YAAY;oCACxB,cAAc,EAAE,oBAAoB,EAAE,IAAI;iCAC3C,CAAC,CACH,CAAC;4BACJ,CAAC;4BAED,0CAA0C;4BAC1C,wFAAwF;4BACxF,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAyB,CAAC;4BAC5D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gCACzB,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;gCACrD,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;oCACtC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;gCACzC,CAAC;4BACH,CAAC;4BAED,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;4BAEpG,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;gCAC/B,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gCAC1C,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,UAAU,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;gCACtE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;4BAC/C,CAAC;4BAED,OAAO,KAAK,CAAC;wBACf,CAAC;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["/**\n * @fileoverview Enforce destructured imports instead of namespace imports for configured packages.\n * This rule helps maintain consistent import styles by converting namespace imports and detecting global UMD usage.\n * @example\n * // ❌ Bad (namespace import)\n * import React from \"react\";\n * const Component = () => {\n * React.useEffect(() => {}, []);\n * return <div />;\n * };\n *\n * // ✅ Good (destructured import)\n * import { useEffect } from \"react\";\n * const Component = () => {\n * useEffect(() => {}, []);\n * return <div />;\n * };\n * @example\n * // ❌ Bad (global UMD usage without import)\n * interface Props {\n * children: React.ReactNode;\n * }\n *\n * // ✅ Good (explicit import)\n * import { ReactNode } from \"react\";\n * interface Props {\n * children: ReactNode;\n * }\n */\n\nimport { AST_NODE_TYPES, ESLintUtils, TSESTree } from \"@typescript-eslint/utils\";\nimport { addImportSpecifiers, replaceNamespaceWithDestructured } from \"../../utils/import-utils\";\n\nconst createRule = ESLintUtils.RuleCreator(\n name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}.ts`\n);\n\ntype MessageIds = \"preferDestructured\" | \"preferDestructuredGlobal\";\n\ntype PackageConfig = {\n /** Mapping of package names to their global UMD variable names (e.g., { \"react\": \"React\", \"lodash\": \"_\" }) */\n packages: Record<string, string>;\n};\n\ntype Options = [PackageConfig?];\n\n/**\n * ESLint rule: prefer-destructured-imports\n *\n * ## Configuration\n *\n * This rule requires explicit configuration to specify which packages to enforce.\n * Without configuration, the rule does nothing.\n *\n * @example\n * // .eslintrc.js - Enforce for React\n * {\n * \"rules\": {\n * \"@trackunit/prefer-destructured-imports\": [\"error\", {\n * \"packages\": {\n * \"react\": \"React\"\n * }\n * }]\n * }\n * }\n * @example\n * // .eslintrc.js - Multiple packages\n * {\n * \"rules\": {\n * \"@trackunit/prefer-destructured-imports\": [\"error\", {\n * \"packages\": {\n * \"react\": \"React\",\n * \"lodash\": \"_\",\n * \"jquery\": \"$\"\n * }\n * }]\n * }\n * }\n */\nexport const preferDestructuredImports = createRule<Options, MessageIds>({\n name: \"prefer-destructured-imports\",\n meta: {\n type: \"suggestion\",\n docs: {\n description: \"Prefer destructured imports instead of namespace imports for certain packages\",\n },\n fixable: \"code\",\n schema: [\n {\n type: \"object\",\n properties: {\n packages: {\n type: \"object\",\n description: \"Mapping of package names to their global UMD variable names (e.g., { 'react': 'React' })\",\n additionalProperties: {\n type: \"string\",\n },\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferDestructured: \"Prefer destructured imports instead of namespace imports\",\n preferDestructuredGlobal:\n \"Import '{{members}}' from '{{packageName}}' instead of using the global {{globalName}} variable\",\n },\n },\n defaultOptions: [],\n create(context) {\n const sourceCode = context.sourceCode;\n const options = context.options[0];\n\n // Return early if no packages are configured\n if (options === undefined || Object.keys(options.packages).length === 0) {\n return {};\n }\n\n const packageToGlobalName = options.packages;\n const targetPackages = Object.keys(packageToGlobalName);\n\n // Helper to check if a string is a valid/configured package name\n const isValidPackageName = (value: string): boolean => {\n return targetPackages.includes(value);\n };\n\n // Track which global identifiers are actually imported (to avoid false positives)\n const importedGlobalIdentifiers = new Set<string>();\n\n const namespaceImports = new Map<string, { node: TSESTree.ImportDeclaration; existingSpecifiers: Set<string> }>();\n const usagesByImport = new Map<string, Map<number, { name: string; start: number; end: number }>>();\n\n // Track usages of global identifiers that aren't imported (e.g., React.ReactElement without importing React)\n const globalUsages = new Map<string, { packageName: string; members: Set<string>; nodes: Array<TSESTree.Node> }>();\n\n return {\n ImportDeclaration(node: TSESTree.ImportDeclaration) {\n const sourceValue = node.source.value;\n if (typeof sourceValue === \"string\" && isValidPackageName(sourceValue)) {\n const globalName = packageToGlobalName[sourceValue];\n\n // Get any existing destructured imports\n const existingSpecifiers = new Set(\n node.specifiers\n .filter((s): s is TSESTree.ImportSpecifier => s.type === AST_NODE_TYPES.ImportSpecifier)\n .map(s => s.local.name)\n );\n\n // Find the namespace or default import if it exists\n const namespaceOrDefaultSpecifier = node.specifiers.find(\n (s): s is TSESTree.ImportDefaultSpecifier | TSESTree.ImportNamespaceSpecifier =>\n s.type === AST_NODE_TYPES.ImportDefaultSpecifier || s.type === AST_NODE_TYPES.ImportNamespaceSpecifier\n );\n\n if (namespaceOrDefaultSpecifier) {\n const importName = namespaceOrDefaultSpecifier.local.name;\n // Only mark this global as imported if the import name matches the global name\n // This prevents false positives when someone imports React as a different name\n if (importName === globalName) {\n importedGlobalIdentifiers.add(globalName);\n }\n\n // If this import also has destructured specifiers, store it as both\n if (existingSpecifiers.size > 0) {\n namespaceImports.set(\"destructured\", { node, existingSpecifiers });\n }\n namespaceImports.set(importName, { node, existingSpecifiers });\n usagesByImport.set(importName, new Map());\n } else if (existingSpecifiers.size > 0) {\n // Store pure destructured imports\n namespaceImports.set(\"destructured\", { node, existingSpecifiers });\n // Do NOT mark the global as imported - we want to catch global usage even with destructured imports\n }\n }\n },\n\n // Handle React.something in regular code\n MemberExpression(node: TSESTree.MemberExpression) {\n const objectNode = node.object;\n\n if (objectNode.type === AST_NODE_TYPES.Identifier && node.property.type === AST_NODE_TYPES.Identifier) {\n // Check if this is a tracked namespace import\n if (namespaceImports.has(objectNode.name)) {\n const usages = usagesByImport.get(objectNode.name);\n if (usages) {\n usages.set(node.range[0], {\n name: node.property.name,\n start: node.range[0],\n end: node.range[1],\n });\n }\n }\n // Check if this is a global identifier usage (e.g., React.ReactElement without import)\n else if (!importedGlobalIdentifiers.has(objectNode.name)) {\n // Find if this identifier matches any of our configured global names\n for (const packageName of targetPackages) {\n const globalName = packageToGlobalName[packageName];\n if (objectNode.name === globalName) {\n if (!globalUsages.has(globalName)) {\n globalUsages.set(globalName, {\n packageName: packageName,\n members: new Set(),\n nodes: [],\n });\n }\n const usage = globalUsages.get(globalName);\n if (usage) {\n usage.members.add(node.property.name);\n usage.nodes.push(node);\n }\n break;\n }\n }\n }\n }\n },\n\n // Handle React.ReactNode in type annotations\n TSQualifiedName(node: TSESTree.TSQualifiedName) {\n if (node.left.type === AST_NODE_TYPES.Identifier) {\n // Check if this is a tracked namespace import\n if (namespaceImports.has(node.left.name)) {\n const usages = usagesByImport.get(node.left.name);\n if (usages) {\n usages.set(node.range[0], {\n name: node.right.name,\n start: node.range[0],\n end: node.range[1],\n });\n }\n }\n // Check if this is a global identifier usage\n else if (!importedGlobalIdentifiers.has(node.left.name)) {\n for (const packageName of targetPackages) {\n const globalName = packageToGlobalName[packageName];\n if (node.left.name === globalName) {\n if (!globalUsages.has(globalName)) {\n globalUsages.set(globalName, {\n packageName: packageName,\n members: new Set(),\n nodes: [],\n });\n }\n const usage = globalUsages.get(globalName);\n if (usage) {\n usage.members.add(node.right.name);\n usage.nodes.push(node);\n }\n break;\n }\n }\n }\n }\n },\n\n // Note: TSQualifiedName already handles React.ReactElement, React.RefObject, etc.\n // This visitor is kept for potential future expansion but currently does nothing\n // to avoid duplicate tracking with TSQualifiedName\n TSTypeReference(_node: TSESTree.TSTypeReference) {\n // TSQualifiedName visitor handles all qualified type names (React.Something)\n // No action needed here\n },\n\n \"Program:exit\"() {\n // Report errors for namespace imports\n for (const [importName, { node: importNode }] of namespaceImports) {\n const usages = usagesByImport.get(importName);\n if (!usages || usages.size === 0) {\n continue;\n }\n\n const sourceValue = importNode.source.value;\n\n if (typeof sourceValue !== \"string\" || !isValidPackageName(sourceValue)) {\n continue;\n }\n\n context.report({\n node: importNode,\n messageId: \"preferDestructured\",\n fix(fixer) {\n const fixes = [];\n\n // Collect all specifiers needed from usages (e.g., [\"useState\", \"useEffect\"] from React.useState, React.useEffect)\n const specifiersFromUsages = Array.from(usages.values()).map(usage => usage.name);\n\n // Check if there's a separate destructured import to merge with\n const existingDestructured = namespaceImports.get(\"destructured\");\n const otherDestructuredImport =\n existingDestructured && existingDestructured.node !== importNode\n ? existingDestructured.node\n : undefined;\n\n // Replace the namespace import with destructured imports (merges if other import exists)\n fixes.push(\n ...replaceNamespaceWithDestructured({\n sourceCode,\n fixer,\n packageName: sourceValue,\n namespaceImportToReplace: importNode,\n specifiersToAdd: specifiersFromUsages,\n mergeIntoImport: otherDestructuredImport,\n })\n );\n\n // Remove namespace/default prefix from all usages\n for (const { start, end } of usages.values()) {\n const text = sourceCode.getText().slice(start, end);\n const newText = text.replace(new RegExp(`^${importName}\\\\.`), \"\");\n fixes.push(fixer.replaceTextRange([start, end], newText));\n }\n\n return fixes;\n },\n });\n }\n\n // Report errors for global UMD variable usages (e.g., React.ReactElement without import)\n for (const [globalName, { packageName, members, nodes }] of globalUsages) {\n if (members.size === 0 || nodes.length === 0) {\n continue;\n }\n\n // Report on the first usage node\n const firstNode = nodes[0];\n if (!firstNode) {\n continue;\n }\n\n const membersList = Array.from(members);\n const existingDestructured = namespaceImports.get(\"destructured\");\n\n // Filter out members that are already imported (no need to add them again)\n const membersToAdd = existingDestructured\n ? membersList.filter(member => !existingDestructured.existingSpecifiers.has(member))\n : membersList;\n\n context.report({\n node: firstNode,\n messageId: \"preferDestructuredGlobal\",\n data: {\n globalName,\n packageName,\n members: membersList.sort().join(\", \"), // Sort only for display in error message\n },\n fix(fixer) {\n const fixes = [];\n\n // Only update imports if there are new members to add\n if (membersToAdd.length > 0) {\n fixes.push(\n ...addImportSpecifiers({\n sourceCode,\n fixer,\n packageName,\n specifiers: membersToAdd,\n existingImport: existingDestructured?.node,\n })\n );\n }\n\n // Replace all global usages (React.X → X)\n // Deduplicate nodes by range and sort by position (reverse order to maintain positions)\n const uniqueNodesByRange = new Map<string, TSESTree.Node>();\n for (const node of nodes) {\n const rangeKey = `${node.range[0]}-${node.range[1]}`;\n if (!uniqueNodesByRange.has(rangeKey)) {\n uniqueNodesByRange.set(rangeKey, node);\n }\n }\n\n const sortedNodes = Array.from(uniqueNodesByRange.values()).sort((a, b) => b.range[0] - a.range[0]);\n\n for (const node of sortedNodes) {\n const nodeText = sourceCode.getText(node);\n const newText = nodeText.replace(new RegExp(`^${globalName}\\\\.`), \"\");\n fixes.push(fixer.replaceText(node, newText));\n }\n\n return fixes;\n },\n });\n }\n },\n };\n },\n});\n"]}
|
package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"name-suggestion-strategies.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Strategy-based name suggestion system for the prefer-event-specific-callback-naming rule.\n *\n * This module provides a structured, extensible approach to generating suggested names\n * for callback functions based on different naming patterns. Each strategy handles a\n * specific type of problematic name and knows how to transform it correctly.\n *\n * Strategies are evaluated in order - the first matching strategy wins.\n */\n\n/**\n * Context passed to name suggestion strategies.\n */\nexport type NameSuggestionContext = {\n /** The current callback name (e.g., \"onPrimary\", \"onClose\", \"handleClick\") */\n callbackName: string;\n /** The event handler prop name (e.g., \"onClick\", \"primaryAction\", \"onSubmit\") */\n eventName: string;\n};\n\n/**\n * A strategy for suggesting renamed callback names.\n * Each strategy handles a specific pattern of problematic naming.\n */\nexport type NameSuggestionStrategy = {\n /** Human-readable name for debugging and logging */\n name: string;\n /** Returns true if this strategy should handle the given context */\n matches: (ctx: NameSuggestionContext) => boolean;\n /** Returns the suggested name for the callback */\n suggest: (ctx: NameSuggestionContext) => string;\n};\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Capitalizes the first letter of a string.\n *\n * @example capitalize(\"primary\") // \"Primary\"\n */\nconst capitalize = (str: string): string => {\n if (str.length === 0) return str;\n return str.charAt(0).toUpperCase() + str.slice(1);\n};\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * Strategy: Handle `*Action` suffix event names.\n *\n * When the event prop ends with \"Action\" (e.g., \"primaryAction\", \"secondaryAction\"),\n * the prop itself is poorly named for a click handler. We extract the prefix and\n * construct a proper onClick* name.\n *\n * @example\n * - eventName: \"primaryAction\", callbackName: \"onPrimary\" → \"onClickPrimary\"\n * - eventName: \"secondaryAction\", callbackName: \"action\" → \"onClickSecondary\"\n * - eventName: \"primaryAction\", callbackName: \"foo\" → \"onClickPrimary\"\n */\nconst actionSuffixEventStrategy: NameSuggestionStrategy = {\n name: \"actionSuffixEventStrategy\",\n\n matches: (ctx: NameSuggestionContext): boolean => {\n // Matches when the event name ends with \"Action\" (case-sensitive)\n return ctx.eventName.endsWith(\"Action\");\n },\n\n suggest: (ctx: NameSuggestionContext): string => {\n // Extract the prefix before \"Action\" (e.g., \"primary\" from \"primaryAction\")\n const prefix = ctx.eventName.slice(0, -\"Action\".length);\n\n // If the callback starts with \"on\", extract its semantic part\n // e.g., \"onPrimary\" → \"Primary\", \"onConfirm\" → \"Confirm\"\n if (ctx.callbackName.startsWith(\"on\") && ctx.callbackName.length > 2) {\n const callbackSuffix = ctx.callbackName.slice(2);\n\n // If the callback suffix matches the event prefix (case-insensitive),\n // use the callback's casing for consistency\n // e.g., eventName: \"primaryAction\", callbackName: \"onPrimary\" → \"onClickPrimary\"\n if (callbackSuffix.toLowerCase() === prefix.toLowerCase()) {\n return `onClick${callbackSuffix}`;\n }\n\n // Otherwise, combine both for clarity\n // e.g., eventName: \"primaryAction\", callbackName: \"onConfirm\" → \"onClickPrimaryConfirm\"\n return `onClick${capitalize(prefix)}${callbackSuffix}`;\n }\n\n // For non-\"on\" prefixed callbacks, just use the event prefix\n // e.g., eventName: \"primaryAction\", callbackName: \"foo\" → \"onClickPrimary\"\n return `onClick${capitalize(prefix)}`;\n },\n};\n\n/**\n * Strategy: Handle `*Handler` suffix event names.\n *\n * When the event prop ends with \"Handler\" (e.g., \"clickHandler\", \"submitHandler\"),\n * the prop itself is poorly named. We extract the prefix and construct a proper onClick* name.\n *\n * @example\n * - eventName: \"clickHandler\", callbackName: \"onClose\" → \"onClickClose\"\n * - eventName: \"submitHandler\", callbackName: \"foo\" → \"onClickSubmit\"\n */\nconst handlerSuffixEventStrategy: NameSuggestionStrategy = {\n name: \"handlerSuffixEventStrategy\",\n\n matches: (ctx: NameSuggestionContext): boolean => {\n // Matches when the event name ends with \"Handler\" (case-sensitive)\n return ctx.eventName.endsWith(\"Handler\");\n },\n\n suggest: (ctx: NameSuggestionContext): string => {\n // Extract the prefix before \"Handler\" (e.g., \"click\" from \"clickHandler\")\n const prefix = ctx.eventName.slice(0, -\"Handler\".length);\n\n // If the callback starts with \"on\", extract its semantic part\n if (ctx.callbackName.startsWith(\"on\") && ctx.callbackName.length > 2) {\n const callbackSuffix = ctx.callbackName.slice(2);\n return `onClick${capitalize(prefix)}${callbackSuffix}`;\n }\n\n // For non-\"on\" prefixed callbacks\n return `onClick${capitalize(prefix)}`;\n },\n};\n\n/**\n * Strategy: Handle `on*` callback → `onClick` event.\n *\n * When a callback starts with \"on\" (but not \"onClick\") and is passed to an onClick handler,\n * we replace the \"on\" prefix with \"onClick\".\n *\n * @example\n * - eventName: \"onClick\", callbackName: \"onClose\" → \"onClickClose\"\n * - eventName: \"onClick\", callbackName: \"onCancel\" → \"onClickCancel\"\n */\nconst onPrefixCallbackToClickEventStrategy: NameSuggestionStrategy = {\n name: \"onPrefixCallbackToClickEventStrategy\",\n\n matches: (ctx: NameSuggestionContext): boolean => {\n return ctx.eventName === \"onClick\" && ctx.callbackName.startsWith(\"on\") && !ctx.callbackName.startsWith(\"onClick\");\n },\n\n suggest: (ctx: NameSuggestionContext): string => {\n // Replace \"on\" with \"onClick\"\n // e.g., \"onClose\" → \"onClickClose\"\n const suffix = ctx.callbackName.slice(2);\n return `onClick${suffix}`;\n },\n};\n\n/**\n * Strategy: Handle `on*` callback → generic `on*` event (not onClick).\n *\n * When a callback starts with \"on\" and is passed to a different on* event handler\n * (like onMouseDown, onSubmit, etc.), we replace the \"on\" prefix with the event name.\n *\n * @example\n * - eventName: \"onMouseDown\", callbackName: \"onClose\" → \"onMouseDownClose\"\n * - eventName: \"onSubmit\", callbackName: \"onConfirm\" → \"onSubmitConfirm\"\n */\nconst onPrefixCallbackToGenericEventStrategy: NameSuggestionStrategy = {\n name: \"onPrefixCallbackToGenericEventStrategy\",\n\n matches: (ctx: NameSuggestionContext): boolean => {\n return (\n ctx.eventName.startsWith(\"on\") &&\n ctx.eventName !== \"onClick\" &&\n ctx.callbackName.startsWith(\"on\") &&\n !ctx.callbackName.startsWith(ctx.eventName)\n );\n },\n\n suggest: (ctx: NameSuggestionContext): string => {\n const callbackSuffix = ctx.callbackName.slice(2);\n\n // Avoid duplication: if event name already ends with the suffix, just use the event name\n // e.g., eventName: \"onClickClose\", callbackName: \"onClose\" → \"onClickClose\"\n if (ctx.eventName.toLowerCase().endsWith(callbackSuffix.toLowerCase())) {\n return ctx.eventName;\n }\n\n return `${ctx.eventName}${callbackSuffix}`;\n },\n};\n\n/**\n * Strategy: Default fallback.\n *\n * For any callback that doesn't match other patterns, capitalize and prepend\n * the event name.\n *\n * @example\n * - eventName: \"onClick\", callbackName: \"myCallback\" → \"onClickMyCallback\"\n * - eventName: \"onClick\", callbackName: \"close\" → \"onClickClose\"\n */\nconst defaultCapitalizeStrategy: NameSuggestionStrategy = {\n name: \"defaultCapitalizeStrategy\",\n\n matches: (): boolean => {\n // Always matches as a fallback\n return true;\n },\n\n suggest: (ctx: NameSuggestionContext): string => {\n const capitalizedName = capitalize(ctx.callbackName);\n return `${ctx.eventName}${capitalizedName}`;\n },\n};\n\n// =============================================================================\n// Strategy Registry\n// =============================================================================\n\n/**\n * Ordered list of strategies. First matching strategy wins.\n *\n * Order matters:\n * 1. Specific patterns (Action suffix, Handler suffix) first\n * 2. General patterns (on* prefix) next\n * 3. Default fallback last\n */\nconst strategies: ReadonlyArray<NameSuggestionStrategy> = [\n actionSuffixEventStrategy,\n handlerSuffixEventStrategy,\n onPrefixCallbackToClickEventStrategy,\n onPrefixCallbackToGenericEventStrategy,\n defaultCapitalizeStrategy,\n];\n\n// =============================================================================\n// Public API\n// =============================================================================\n\n/**\n * Generates a suggested name for a callback based on the event it's passed to.\n *\n * Uses a strategy pattern to handle different naming conventions appropriately.\n * The first matching strategy determines the suggested name.\n *\n * @param ctx - The naming context with callback and event names\n * @returns The suggested name for the callback\n * @example\n * getSuggestedName({ callbackName: \"onClose\", eventName: \"onClick\" })\n * // Returns: \"onClickClose\"\n *\n * getSuggestedName({ callbackName: \"onPrimary\", eventName: \"primaryAction\" })\n * // Returns: \"onClickPrimary\"\n */\nexport const getSuggestedName = (ctx: NameSuggestionContext): string => {\n for (const strategy of strategies) {\n if (strategy.matches(ctx)) {\n return strategy.suggest(ctx);\n }\n }\n\n // Should never reach here since defaultCapitalizeStrategy always matches,\n // but TypeScript doesn't know that\n return defaultCapitalizeStrategy.suggest(ctx);\n};\n\n/**\n * Exported for testing purposes - allows testing individual strategies.\n */\nexport const testableStrategies = {\n actionSuffixEventStrategy,\n handlerSuffixEventStrategy,\n onPrefixCallbackToClickEventStrategy,\n onPrefixCallbackToGenericEventStrategy,\n defaultCapitalizeStrategy,\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-event-specific-callback-naming.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;;AAEH,oDAAiE;AAEjE,4DAAmG;AACnG,wDAAiG;AAEjG,MAAM,UAAU,GAAG,mBAAW,CAAC,WAAW,CACxC,IAAI,CAAC,EAAE,CAAC,6FAA6F,IAAI,IAAI,IAAI,KAAK,CACvH,CAAC;AA8BW,QAAA,iCAAiC,GAAG,UAAU,CAAsB;IAC/E,IAAI,EAAE,uCAAuC;IAC7C,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,cAAc,EAAE,IAAI;QACpB,IAAI,EAAE;YACJ,WAAW,EACT,8EAA8E;gBAC9E,uGAAuG;SAC1G;QACD,MAAM,EAAE;YACN;gBACE,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,YAAY,EAAE;wBACZ,IAAI,EAAE,OAAO;wBACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;wBACzB,WAAW,EAAE,yCAAyC;qBACvD;oBACD,MAAM,EAAE;wBACN,IAAI,EAAE,OAAO;wBACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE;wBAC9C,WAAW,EACT,qEAAqE;4BACrE,yEAAyE;qBAC5E;iBACF;gBACD,oBAAoB,EAAE,KAAK;aAC5B;SACF;QACD,QAAQ,EAAE;YACR,GAAG,gCAAiB;YACpB,GAAG,8BAAiB;SACrB;KACF;IACD,cAAc,EAAE;QACd;YACE,YAAY,EAAE,EAAE;YAChB,MAAM,EAAE,CAAC,SAAS,CAAC;SACpB;KACF;IACD,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC;QACvB,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;QAE7C,OAAO;YACL,YAAY,CAAC,IAA2B;gBACtC,IAAA,6BAAc,EAAC,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;gBACpD,IAAA,2BAAc,EAAC,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;YACtD,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["/**\n * ESLint rule: prefer-event-specific-callback-naming\n *\n * Enforces that callbacks passed to event handler attributes use matching\n * event-specific naming (e.g., onClick* for onClick, onSubmit* for onSubmit).\n *\n * This prevents confusing names like `onClose` (sounds like \"when close happens\")\n * when the callback is actually triggered by a click event.\n *\n * Uses two detection strategies:\n * 1. Name-based: Checks configured event attributes for mismatched callback names\n * 2. Type-based: Detects any prop typed as MouseEventHandler with problematic naming\n * (only active when \"onClick\" is in the events list)\n *\n * Configuration via the `events` option controls which event handler attributes\n * are checked by both strategies.\n */\n\nimport { ESLintUtils, TSESTree } from \"@typescript-eslint/utils\";\n\nimport { checkNameBased, NameBasedMessageIds, nameBasedMessages } from \"./strategies/string-based\";\nimport { checkTypeBased, TypeBasedMessageIds, typeBasedMessages } from \"./strategies/type-based\";\n\nconst createRule = ESLintUtils.RuleCreator(\n name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}/${name}.ts`\n);\n\ntype Options = [\n {\n /**\n * Callback names to exempt from this rule.\n *\n * @example [\"onToggle\", \"onTrigger\"]\n */\n allowedNames?: ReadonlyArray<string>;\n\n /**\n * Event handler attributes to check for matching callback naming.\n *\n * For each event in this list, callbacks passed to that attribute must use\n * matching naming (e.g., `onClick*` for `onClick`, `onSubmit*` for `onSubmit`).\n *\n * - `on*` callbacks that don't match the event prefix are always flagged\n * - Other callbacks (`handle*`, `close`, etc.) are only flagged when they come from props\n * - When \"onClick\" is included, type-based MouseEventHandler detection is also enabled\n *\n * @default [\"onClick\"]\n * @example [\"onClick\", \"onSubmit\", \"onDoubleClick\"]\n */\n events?: ReadonlyArray<string>;\n },\n];\n\ntype MessageIds = NameBasedMessageIds | TypeBasedMessageIds;\n\nexport const preferEventSpecificCallbackNaming = createRule<Options, MessageIds>({\n name: \"prefer-event-specific-callback-naming\",\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description:\n \"Enforce using event-specific naming for callbacks passed to event handlers. \" +\n \"Names like onClose are confusing because they sound like lifecycle events rather than click handlers.\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowedNames: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Callback names to exempt from this rule\",\n },\n events: {\n type: \"array\",\n items: { type: \"string\", pattern: \"^on[A-Z]\" },\n description:\n \"Event handler attributes to check (e.g., ['onClick', 'onSubmit']). \" +\n \"Callbacks passed to these handlers must use matching on[Event]* naming.\",\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n ...nameBasedMessages,\n ...typeBasedMessages,\n },\n },\n defaultOptions: [\n {\n allowedNames: [],\n events: [\"onClick\"],\n },\n ],\n create(context, [options]) {\n const allowedNames = options.allowedNames ?? [];\n const events = options.events ?? [\"onClick\"];\n\n return {\n JSXAttribute(node: TSESTree.JSXAttribute) {\n checkNameBased(node, context, allowedNames, events);\n checkTypeBased(node, context, allowedNames, events);\n },\n };\n },\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"string-based.js","sourceRoot":"","sources":["../../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Name-based detection strategy for prefer-event-specific-callback-naming rule.\n *\n * Checks JSX attributes whose names appear in the configured `events` list\n * and flags callbacks that don't use matching event prefix naming.\n *\n * Two levels of strictness:\n * - Callbacks starting with `on*` that don't match the event prefix are always flagged\n * (e.g., onClick={onClose} - regardless of source)\n * - Other callbacks (handle*, close, etc.) are only flagged when they come from props\n * (not internal functions or hook results)\n */\n\nimport { AST_NODE_TYPES, TSESLint, TSESTree } from \"@typescript-eslint/utils\";\n\nimport { isIdentifierFromParameter, isIdentifierFromRenderPropCallback } from \"../../../utils/ast-utils\";\nimport { extractCallbackIdentifier, findLocalDefinitionInfo, getSuggestedNameForEvent } from \"../utils\";\n\nexport type NameBasedMessageIds =\n | \"nameBasedEventNaming\"\n | \"nameBasedEventNamingWrapped\"\n | \"suggestRenameAllNameBased\"\n | \"suggestRenameLocalNameBased\";\n\nexport const nameBasedMessages: Record<NameBasedMessageIds, string> = {\n nameBasedEventNaming:\n \"Callback '{{current}}' passed to {{event}} should use '{{expectedPrefix}}*' naming (e.g., '{{suggested}}'). \" +\n \"Reported because {{event}} is an explicitly monitored event handler name (see rule 'events' option). Rename it at its definition.\",\n nameBasedEventNamingWrapped:\n \"Callback '{{current}}' called in {{event}} should use '{{expectedPrefix}}*' naming (e.g., '{{suggested}}'). \" +\n \"Reported because {{event}} is an explicitly monitored event handler name (see rule 'events' option). Rename it at its definition.\",\n suggestRenameAllNameBased: \"Rename '{{current}}' to '{{suggested}}' (definition and all usages in this file)\",\n suggestRenameLocalNameBased: \"Rename to '{{suggested}}' (this reference only - definition is in another file)\",\n};\n\n/**\n * Extracts the expected naming prefix from a suggested name.\n * This is used to show the correct expected pattern in error messages.\n *\n * @example\n * getExpectedPrefix(\"onClickPrimary\") // \"onClick\"\n * getExpectedPrefix(\"onMouseDownClose\") // \"onMouseDown\"\n */\nconst getExpectedPrefix = (suggestedName: string): string => {\n const match = suggestedName.match(/^(on[A-Z][a-z]*)/);\n return match?.[1] ?? suggestedName;\n};\n\ntype SuggestionDescriptor = {\n messageId: NameBasedMessageIds;\n data: { current: string; suggested: string };\n fix: (fixer: TSESLint.RuleFixer) => TSESLint.RuleFix | ReadonlyArray<TSESLint.RuleFix>;\n};\n\n/**\n * Unified name-based check for event handler callback naming.\n *\n * Checks whether a callback passed to a configured event attribute uses the correct\n * naming convention. The check applies two levels of strictness:\n *\n * 1. `on*` callbacks that don't match the event prefix are always flagged\n * (e.g., `onClick={onClose}` - should be `onClickClose`)\n * 2. Other callbacks (`handle*`, `close`, etc.) are only flagged when they come from\n * component props (not internal functions, hook results, or render prop callbacks)\n *\n * @param node - The JSX attribute node to check\n * @param context - The ESLint rule context\n * @param allowedNames - Callback names to exempt from this rule\n * @param events - Event handler attribute names to check (e.g., [\"onClick\", \"onSubmit\"])\n */\nexport const checkNameBased = (\n node: TSESTree.JSXAttribute,\n context: TSESLint.RuleContext<NameBasedMessageIds, ReadonlyArray<unknown>>,\n allowedNames: ReadonlyArray<string>,\n events: ReadonlyArray<string>\n): void => {\n // Must be a JSX identifier attribute\n if (node.name.type !== AST_NODE_TYPES.JSXIdentifier) {\n return;\n }\n\n const eventName = node.name.name;\n\n // Only check attributes in the events list\n if (!events.includes(eventName)) {\n return;\n }\n\n // Must have a value that's an expression container\n if (!node.value || node.value.type !== AST_NODE_TYPES.JSXExpressionContainer) {\n return;\n }\n\n const { expression } = node.value;\n if (expression.type === AST_NODE_TYPES.JSXEmptyExpression) {\n return;\n }\n\n // Extract callback identifier (without checking specific naming patterns)\n const result = extractCallbackIdentifier(expression);\n\n if (!result) {\n return;\n }\n\n // Check if this name is in the allowed list\n if (allowedNames.includes(result.callbackName)) {\n return;\n }\n\n // Check if the name already matches the event pattern (e.g., onClick* for onClick)\n if (result.callbackName.startsWith(eventName)) {\n return;\n }\n\n // Determine if this is an on* callback that doesn't match the event prefix\n const isOnPrefixMismatch = result.callbackName.startsWith(\"on\") && !result.callbackName.startsWith(eventName);\n\n if (!isOnPrefixMismatch) {\n // For non-on* callbacks (handle*, close, etc.), only flag if they come from props\n // but NOT from render prop callbacks (e.g., {close => <MenuItem onClick={close} />})\n if (!isIdentifierFromParameter(result.identifierNode, context)) {\n return;\n }\n if (isIdentifierFromRenderPropCallback(result.identifierNode, context)) {\n return;\n }\n }\n\n const suggestedName = getSuggestedNameForEvent(result.callbackName, eventName);\n\n // Select the appropriate messageId based on whether the callback is wrapped\n const messageId: NameBasedMessageIds = result.isWrapped ? \"nameBasedEventNamingWrapped\" : \"nameBasedEventNaming\";\n\n // Build suggestions based on whether the definition is in the same file\n const suggestions: Array<SuggestionDescriptor> = [];\n const localInfo = findLocalDefinitionInfo(result.identifierNode, context);\n\n if (localInfo) {\n // Definition is in this file - offer to rename all occurrences\n suggestions.push({\n messageId: \"suggestRenameAllNameBased\",\n data: {\n current: result.callbackName,\n suggested: suggestedName,\n },\n fix: fixer => {\n return localInfo.allNodesToRename.map(nodeToRename => fixer.replaceText(nodeToRename, suggestedName));\n },\n });\n } else {\n // Definition is external - only offer to rename this reference\n suggestions.push({\n messageId: \"suggestRenameLocalNameBased\",\n data: {\n current: result.callbackName,\n suggested: suggestedName,\n },\n fix: fixer => {\n return fixer.replaceText(result.identifierNode, suggestedName);\n },\n });\n }\n\n context.report({\n node: result.node,\n messageId,\n data: {\n current: result.callbackName,\n suggested: suggestedName,\n event: eventName,\n expectedPrefix: getExpectedPrefix(suggestedName),\n },\n suggest: suggestions,\n });\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"type-based.js","sourceRoot":"","sources":["../../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Type-based detection strategy for prefer-event-specific-callback-naming rule.\n *\n * Uses TypeScript's type checker to detect when a callback with problematic naming\n * is passed to a prop typed as a mouse event handler (MouseEventHandler, MouseEvent, etc.).\n *\n * This catches cases where custom components use non-standard prop names for click handlers:\n * - <MobileButton onTap={onClose} /> where onTap is typed as MouseEventHandler\n * - <PressableArea onPress={onCancel} /> where onPress accepts MouseEvent\n */\n\nimport { AST_NODE_TYPES, ESLintUtils, TSESLint, TSESTree } from \"@typescript-eslint/utils\";\nimport * as ts from \"typescript\";\n\nimport { extractCallbackIdentifier, findLocalDefinitionInfo, getSuggestedNameForEvent } from \"../utils\";\n\nexport type TypeBasedMessageIds =\n | \"preferOnClickNamingTyped\"\n | \"preferOnClickNamingTypedWrapped\"\n | \"suggestRenameAllTyped\"\n | \"suggestRenameLocalTyped\";\n\nexport const typeBasedMessages: Record<TypeBasedMessageIds, string> = {\n preferOnClickNamingTyped:\n \"Callback '{{current}}' passed to {{event}} should use '{{expectedPrefix}}*' naming (e.g., '{{suggested}}'). \" +\n \"Reported because {{event}} is typed as a MouseEventHandler. Rename it at its definition.\",\n preferOnClickNamingTypedWrapped:\n \"Callback '{{current}}' called in {{event}} should use '{{expectedPrefix}}*' naming (e.g., '{{suggested}}'). \" +\n \"Reported because {{event}} is typed as a MouseEventHandler. Rename it at its definition.\",\n suggestRenameAllTyped: \"Rename '{{current}}' to '{{suggested}}' (definition and all usages in this file)\",\n suggestRenameLocalTyped: \"Rename to '{{suggested}}' (this reference only - definition is in another file)\",\n};\n\n/**\n * Extracts the expected naming prefix from a suggested name.\n * This is used to show the correct expected pattern in error messages.\n *\n * @example\n * getExpectedPrefix(\"onClickPrimary\") // \"onClick\"\n * getExpectedPrefix(\"onMouseDownClose\") // \"onMouseDown\"\n */\nconst getExpectedPrefix = (suggestedName: string): string => {\n // Find where the capitalized suffix starts (after the event prefix)\n // e.g., \"onClickPrimary\" -> find \"Primary\" -> prefix is \"onClick\"\n const match = suggestedName.match(/^(on[A-Z][a-z]*)/);\n return match?.[1] ?? suggestedName;\n};\n\ntype SuggestionDescriptor = {\n messageId: TypeBasedMessageIds;\n data: { current: string; suggested: string };\n fix: (fixer: TSESLint.RuleFixer) => TSESLint.RuleFix | ReadonlyArray<TSESLint.RuleFix>;\n};\n\n/**\n * Checks if a type represents a mouse event handler.\n * Looks for:\n * - MouseEventHandler<T>\n * - React.MouseEventHandler<T>\n * - Function types with MouseEvent as first parameter\n */\nconst isMouseEventHandlerType = (type: ts.Type, checker: ts.TypeChecker): boolean => {\n const typeString = checker.typeToString(type);\n\n // Quick check for common patterns in the type string\n if (\n typeString.includes(\"MouseEventHandler\") ||\n typeString.includes(\"MouseEvent<\") ||\n typeString.includes(\"MouseEvent)\")\n ) {\n return true;\n }\n\n // Check call signatures for MouseEvent parameter\n const signatures = type.getCallSignatures();\n for (const signature of signatures) {\n const params = signature.getParameters();\n if (params.length > 0) {\n const firstParam = params[0];\n if (firstParam) {\n const paramType = checker.getTypeOfSymbol(firstParam);\n const paramTypeString = checker.typeToString(paramType);\n\n if (paramTypeString.includes(\"MouseEvent\")) {\n return true;\n }\n }\n }\n }\n\n return false;\n};\n\n/**\n * Checks a JSX attribute for type-based mouse event handler naming issues.\n *\n * Only runs when \"onClick\" is in the events list (since this check only detects\n * MouseEventHandler types, which are click events).\n *\n * Skips props already in the events list (those are handled by the name-based strategy).\n *\n * @param node - The JSX attribute node to check\n * @param context - The ESLint rule context\n * @param allowedNames - Callback names to exempt from this rule\n * @param events - Configured event names (used to skip already-covered props)\n */\nexport const checkTypeBased = (\n node: TSESTree.JSXAttribute,\n context: TSESLint.RuleContext<TypeBasedMessageIds, ReadonlyArray<unknown>>,\n allowedNames: ReadonlyArray<string>,\n events: ReadonlyArray<string>\n): void => {\n // Only run if \"onClick\" is in the events list, since this check only detects\n // MouseEventHandler types (which are click events)\n if (!events.includes(\"onClick\")) {\n return;\n }\n\n // Extract prop name - must be a JSXIdentifier\n if (node.name.type !== AST_NODE_TYPES.JSXIdentifier) {\n return;\n }\n\n const propName = node.name.name;\n\n // Skip props already in the events list - those are handled by the name-based strategy\n if (events.includes(propName)) {\n return;\n }\n\n // Must have a value that's an expression container\n if (!node.value || node.value.type !== AST_NODE_TYPES.JSXExpressionContainer) {\n return;\n }\n\n const { expression } = node.value;\n if (expression.type === AST_NODE_TYPES.JSXEmptyExpression) {\n return;\n }\n\n // Extract callback identifier (without checking hardcoded onClick pattern)\n const result = extractCallbackIdentifier(expression);\n\n if (!result) {\n return;\n }\n\n // Check if this name is in the allowed list\n if (allowedNames.includes(result.callbackName)) {\n return;\n }\n\n // Skip if callback name already matches or starts with the prop name\n // e.g., onMouseEnter={onMouseEnter} or onMouseEnter={onMouseEnterSomething}\n if (result.callbackName === propName || result.callbackName.startsWith(propName)) {\n return;\n }\n\n // Skip if callback doesn't start with \"on\" - we only care about on* callbacks\n // that might be confused with lifecycle/event callbacks\n if (!result.callbackName.startsWith(\"on\")) {\n return;\n }\n\n // Now check if the prop is typed as a mouse event handler\n try {\n const services = ESLintUtils.getParserServices(context);\n const checker = services.program.getTypeChecker();\n\n // Get the TypeScript node for the expression (already validated above)\n const tsExpressionNode = services.esTreeNodeToTSNodeMap.get(expression);\n\n // Get the contextual type (what type is expected at this position)\n // The ESTree to TS node map returns a generic node, but getContextualType\n // expects an Expression. This assertion is necessary for TypeScript API interop.\n // eslint-disable-next-line @trackunit/no-typescript-assertion\n const contextualType = checker.getContextualType(tsExpressionNode as ts.Expression);\n\n if (!contextualType) {\n return;\n }\n\n if (!isMouseEventHandlerType(contextualType, checker)) {\n return;\n }\n\n // Calculate suggested name using the actual prop name (e.g., onMouseDown -> onMouseDownClose)\n const suggestedName = getSuggestedNameForEvent(result.callbackName, propName);\n\n // Select the appropriate messageId based on whether the callback is wrapped\n const messageId: TypeBasedMessageIds = result.isWrapped\n ? \"preferOnClickNamingTypedWrapped\"\n : \"preferOnClickNamingTyped\";\n\n // Build suggestions based on whether the definition is in the same file\n const suggestions: Array<SuggestionDescriptor> = [];\n const localInfo = findLocalDefinitionInfo(result.identifierNode, context);\n\n if (localInfo) {\n // Definition is in this file - offer to rename all occurrences\n suggestions.push({\n messageId: \"suggestRenameAllTyped\",\n data: {\n current: result.callbackName,\n suggested: suggestedName,\n },\n fix: fixer => {\n return localInfo.allNodesToRename.map(nodeToRename => fixer.replaceText(nodeToRename, suggestedName));\n },\n });\n } else {\n // Definition is external - only offer to rename this reference\n suggestions.push({\n messageId: \"suggestRenameLocalTyped\",\n data: {\n current: result.callbackName,\n suggested: suggestedName,\n },\n fix: fixer => {\n return fixer.replaceText(result.identifierNode, suggestedName);\n },\n });\n }\n\n context.report({\n node: result.node,\n messageId,\n data: {\n current: result.callbackName,\n suggested: suggestedName,\n event: propName,\n expectedPrefix: getExpectedPrefix(suggestedName),\n },\n suggest: suggestions,\n });\n } catch {\n // Type checking failed - silently skip this check\n // This can happen if TypeScript services are not available\n return;\n }\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/prefer-event-specific-callback-naming/utils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared utilities for the prefer-event-specific-callback-naming rule.\n * These functions handle the naming convention logic and callback extraction\n * used by both string-based and type-based detection strategies.\n */\n\nimport { AST_NODE_TYPES, TSESLint, TSESTree } from \"@typescript-eslint/utils\";\n\nimport { getSuggestedName as getStrategySuggestedName } from \"./name-suggestion-strategies\";\n\n/**\n * Result of extracting callback information from an expression.\n * Does not include messageId - each strategy adds its own.\n */\nexport type CallbackExtractionResult = {\n callbackName: string;\n suggestedName: string;\n node: TSESTree.Node;\n /** The identifier node for the callback - used for fix ranges */\n identifierNode: TSESTree.Identifier;\n /** Whether the callback is wrapped in an arrow function */\n isWrapped: boolean;\n} | null;\n\n/**\n * Information about a locally defined variable, including its definition\n * and all references within the same file.\n */\nexport type LocalDefinitionInfo = {\n /** The identifier node where the variable is defined */\n definitionNode: TSESTree.Identifier;\n /** All nodes that need to be renamed (variable references + type property definitions) */\n allNodesToRename: ReadonlyArray<TSESTree.Identifier>;\n};\n\n/**\n * Recursively finds all TSPropertySignature nodes in the AST with the given name.\n * This finds interface/type property definitions that need to be renamed along with the variable.\n */\nconst findTypePropertyDefinitions = (ast: TSESTree.Program, propertyName: string): Array<TSESTree.Identifier> => {\n const results: Array<TSESTree.Identifier> = [];\n\n const visit = (node: TSESTree.Node): void => {\n // Find interface/type property signatures with matching name\n if (\n node.type === AST_NODE_TYPES.TSPropertySignature &&\n node.key.type === AST_NODE_TYPES.Identifier &&\n node.key.name === propertyName\n ) {\n results.push(node.key);\n }\n\n // Recursively visit all child nodes\n // Use Object.entries to iterate without needing type assertions\n for (const [key, value] of Object.entries(node)) {\n if (key === \"parent\") continue; // Skip parent to avoid cycles\n\n if (value !== null && typeof value === \"object\") {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item !== null && typeof item === \"object\" && \"type\" in item) {\n visit(item);\n }\n }\n } else if (\"type\" in value) {\n visit(value);\n }\n }\n }\n };\n\n visit(ast);\n return results;\n};\n\n/**\n * Finds the local definition and all references of a variable in the current file.\n * Also finds TypeScript interface/type property definitions with the same name\n * (for React component props that need to be renamed in both the type and usage).\n * Returns null if the variable is not defined in the current file (e.g., imported).\n *\n * @param identifier - The identifier node to look up\n * @param context - The ESLint rule context\n * @returns LocalDefinitionInfo if defined locally, null if defined externally\n */\nexport const findLocalDefinitionInfo = (\n identifier: TSESTree.Identifier,\n context: TSESLint.RuleContext<string, ReadonlyArray<unknown>>\n): LocalDefinitionInfo | null => {\n const scope = context.sourceCode.getScope(identifier);\n const variable = scope.references.find(ref => ref.identifier === identifier)?.resolved;\n\n if (!variable) {\n return null;\n }\n\n // Check if the variable has definition nodes in this file\n const definitions = variable.defs;\n if (definitions.length === 0) {\n // No definitions found - likely a global or imported variable\n return null;\n }\n\n // Get the first definition (typically there's only one)\n const definition = definitions[0];\n if (!definition) {\n return null;\n }\n\n // The definition's name node is where the variable is defined\n // It could be an Identifier, ArrayPattern, or ObjectPattern\n // We only handle simple Identifier definitions\n const definitionName = definition.name;\n if (definitionName.type !== AST_NODE_TYPES.Identifier) {\n return null;\n }\n\n // Collect all variable references (identifiers that refer to this variable)\n const variableReferences = variable.references\n .map(ref => ref.identifier)\n .filter((node): node is TSESTree.Identifier => node.type === AST_NODE_TYPES.Identifier);\n\n // Also find TypeScript type property definitions with the same name\n // This handles the case where a callback is destructured from props:\n // interface Props { onCancel: () => void } <-- need to rename this too\n // const { onCancel } = props; <-- the variable reference\n const ast = context.sourceCode.ast;\n const typePropertyDefinitions = findTypePropertyDefinitions(ast, identifier.name);\n\n // Combine definition, variable references, and type property definitions\n // Use a Set based on node range to deduplicate (needed because shorthand properties\n // may share node ranges between definition and reference)\n const seenRanges = new Set<string>();\n const allNodesToRename: Array<TSESTree.Identifier> = [];\n\n for (const node of [definitionName, ...variableReferences, ...typePropertyDefinitions]) {\n const rangeKey = `${node.range[0]}-${node.range[1]}`;\n if (!seenRanges.has(rangeKey)) {\n seenRanges.add(rangeKey);\n allNodesToRename.push(node);\n }\n }\n\n return {\n definitionNode: definitionName,\n allNodesToRename,\n };\n};\n\n/**\n * Checks if a callback name is problematic when used with click handlers.\n * A name is problematic if it starts with \"on\" but not \"onClick\".\n *\n * @example\n * isProblematicName(\"onClose\") // true - should be onClickClose\n * isProblematicName(\"onClickClose\") // false - already correct\n * isProblematicName(\"handleClose\") // false - different convention\n * isProblematicName(\"close\") // false - doesn't start with \"on\"\n */\nconst isProblematicName = (name: string): boolean => {\n return name.startsWith(\"on\") && !name.startsWith(\"onClick\");\n};\n\n/**\n * Converts a problematic callback name to the suggested onClick* format.\n *\n * @example\n * getSuggestedName(\"onClose\") // \"onClickClose\"\n * getSuggestedName(\"onCancel\") // \"onClickCancel\"\n */\nconst getSuggestedName = (name: string): string => {\n // Remove \"on\" prefix and add \"onClick\" prefix\n return `onClick${name.slice(2)}`;\n};\n\n/**\n * Extracts callback info from a direct identifier pattern: onClick={onClose}\n */\nconst extractFromDirectIdentifier = (expression: TSESTree.Expression): CallbackExtractionResult => {\n if (expression.type !== AST_NODE_TYPES.Identifier) {\n return null;\n }\n\n const callbackName = expression.name;\n if (!isProblematicName(callbackName)) {\n return null;\n }\n\n return {\n callbackName,\n suggestedName: getSuggestedName(callbackName),\n node: expression,\n identifierNode: expression,\n isWrapped: false,\n };\n};\n\n/**\n * Extracts callback info from a single-call arrow function: onClick={() => onClose()}\n */\nconst extractFromSingleCallArrow = (expression: TSESTree.Expression): CallbackExtractionResult => {\n if (expression.type !== AST_NODE_TYPES.ArrowFunctionExpression) {\n return null;\n }\n\n const { body } = expression;\n\n // Only check if body is a single call expression (not a block statement)\n if (body.type !== AST_NODE_TYPES.CallExpression) {\n return null;\n }\n\n // Only check if the callee is a simple identifier\n if (body.callee.type !== AST_NODE_TYPES.Identifier) {\n return null;\n }\n\n const callbackName = body.callee.name;\n if (!isProblematicName(callbackName)) {\n return null;\n }\n\n return {\n callbackName,\n suggestedName: getSuggestedName(callbackName),\n node: body.callee,\n identifierNode: body.callee,\n isWrapped: true,\n };\n};\n\n/**\n * Extracts callback information from an expression.\n * Handles both direct identifiers (onClick={onClose}) and\n * single-call arrow functions (onClick={() => onClose()}).\n *\n * @returns Callback extraction result without messageId, or null if no problematic callback found\n */\nconst extractCallbackInfo = (expression: TSESTree.Expression): CallbackExtractionResult => {\n return extractFromDirectIdentifier(expression) ?? extractFromSingleCallArrow(expression);\n};\n\n/**\n * Result of extracting a callback identifier from an expression.\n * Unlike CallbackExtractionResult, this doesn't include suggested names\n * since those depend on the specific event handler being used.\n */\nexport type CallbackIdentifierResult = {\n callbackName: string;\n node: TSESTree.Node;\n /** The identifier node for the callback - used for fix ranges */\n identifierNode: TSESTree.Identifier;\n /** Whether the callback is wrapped in an arrow function */\n isWrapped: boolean;\n} | null;\n\n/**\n * Extracts the callback identifier from an expression without checking naming patterns.\n * Used by strict prop events check which has its own naming criteria.\n *\n * @returns Callback identifier info, or null if not a simple identifier/single-call arrow\n */\nexport const extractCallbackIdentifier = (expression: TSESTree.Expression): CallbackIdentifierResult => {\n // Direct identifier: onClick={onClose}\n if (expression.type === AST_NODE_TYPES.Identifier) {\n return {\n callbackName: expression.name,\n node: expression,\n identifierNode: expression,\n isWrapped: false,\n };\n }\n\n // Single-call arrow function: onClick={() => onClose()}\n if (expression.type === AST_NODE_TYPES.ArrowFunctionExpression) {\n const { body } = expression;\n\n // Only check if body is a single call expression (not a block statement)\n if (body.type !== AST_NODE_TYPES.CallExpression) {\n return null;\n }\n\n // Only check if the callee is a simple identifier\n if (body.callee.type !== AST_NODE_TYPES.Identifier) {\n return null;\n }\n\n return {\n callbackName: body.callee.name,\n node: body.callee,\n identifierNode: body.callee,\n isWrapped: true,\n };\n }\n\n return null;\n};\n\n/**\n * Generates the suggested name for a callback based on the event handler it's passed to.\n *\n * Uses a strategy-based approach to handle different naming patterns appropriately.\n * See `name-suggestion-strategies.ts` for the individual strategies.\n *\n * @param currentName - The current callback name (e.g., \"secondaryAction\", \"close\")\n * @param eventName - The event handler name (e.g., \"onClick\", \"onSubmit\", \"primaryAction\")\n * @returns The suggested name (e.g., \"onClickSecondaryAction\", \"onSubmitClose\", \"onClickPrimary\")\n * @example\n * getSuggestedNameForEvent(\"close\", \"onClick\") // \"onClickClose\"\n * getSuggestedNameForEvent(\"onCancel\", \"onClick\") // \"onClickCancel\"\n * getSuggestedNameForEvent(\"onPrimary\", \"primaryAction\") // \"onClickPrimary\"\n * getSuggestedNameForEvent(\"onClose\", \"onClickClose\") // \"onClickClose\"\n */\nexport const getSuggestedNameForEvent = (currentName: string, eventName: string): string => {\n return getStrategySuggestedName({ callbackName: currentName, eventName });\n};\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-field-components.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/prefer-field-components/prefer-field-components.ts"],"names":[],"mappings":"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","sourcesContent":["import { AST_NODE_TYPES, ESLintUtils, TSESTree } from \"@typescript-eslint/utils\";\nimport { findImportDeclaration, getImportSpecifiers } from \"../../utils/import-utils\";\n\nconst createRule = ESLintUtils.RuleCreator(\n name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}/README.md`\n);\n\ntype MessageIds = \"preferFieldComponent\";\n\nconst FORM_COMPONENTS_PACKAGE = \"@trackunit/react-form-components\";\nconst REACT_COMPONENTS_PACKAGE = \"@trackunit/react-components\";\n\n/**\n * Map of base component names to their recommended Field component replacements\n * used in the error message (human-readable, may contain alternatives).\n */\nconst BASE_TO_FIELD_MESSAGE_MAP: Record<string, string> = {\n BaseSelect: \"SelectField (or MultiSelectField for multi-select)\",\n CreatableSelect: \"CreatableSelectField\",\n BaseInput: \"TextField (or the appropriate *Field component for your input type)\",\n TextBaseInput: \"TextField\",\n NumberBaseInput: \"NumberField\",\n DateBaseInput: \"DateField\",\n PhoneBaseInput: \"PhoneField\",\n TextAreaBaseInput: \"TextAreaField\",\n PasswordBaseInput: \"PasswordField\",\n EmailBaseInput: \"EmailField\",\n UrlBaseInput: \"UrlField\",\n};\n\n/**\n * Map used for autofix — always resolves to a single concrete component name.\n *\n * NOTE: BaseSelect and CreatableSelect are intentionally excluded here.\n * SelectField and CreatableSelectField have different value/onChange APIs\n * compared to their base counterparts:\n * - BaseSelect uses `value={optionObject}` and `onChange={(option) => ...}`\n * - SelectField uses `value={rawValue}` and `onChange={(event) => ...}`\n * Auto-fixing would silently break the component's behaviour.\n *\n * BaseSelect with isMulti → MultiSelectField IS compatible and is handled\n * separately in getAutofixFieldName().\n */\nconst BASE_TO_FIELD_AUTOFIX_MAP: Record<string, string> = {\n BaseInput: \"TextField\",\n TextBaseInput: \"TextField\",\n NumberBaseInput: \"NumberField\",\n DateBaseInput: \"DateField\",\n PhoneBaseInput: \"PhoneField\",\n TextAreaBaseInput: \"TextAreaField\",\n PasswordBaseInput: \"PasswordField\",\n EmailBaseInput: \"EmailField\",\n UrlBaseInput: \"UrlField\",\n};\n\nconst BASE_COMPONENT_NAMES = new Set(Object.keys(BASE_TO_FIELD_MESSAGE_MAP));\n\n/**\n * Component names that act as a manual label above a base input/select.\n */\nconst LABEL_COMPONENT_NAMES = new Set([\"Label\", \"Text\"]);\n\n/**\n * Get the JSX element name from a JSXOpeningElement.\n * Returns null for member expressions or namespaced names.\n */\nfunction getJSXElementName(openingElement: TSESTree.JSXOpeningElement): string | null {\n if (openingElement.name.type === AST_NODE_TYPES.JSXIdentifier) {\n return openingElement.name.name;\n }\n return null;\n}\n\n/**\n * Check if a JSX child is a JSXText node that only contains whitespace.\n */\nfunction isWhitespaceText(child: TSESTree.JSXChild): boolean {\n return child.type === AST_NODE_TYPES.JSXText && child.value.trim() === \"\";\n}\n\n/**\n * Check if a JSX child is a label-like element (Label or Text).\n */\nfunction isLabelLikeElement(child: TSESTree.JSXChild): child is TSESTree.JSXElement {\n if (child.type !== AST_NODE_TYPES.JSXElement) {\n return false;\n }\n const name = getJSXElementName(child.openingElement);\n return name !== null && LABEL_COMPONENT_NAMES.has(name);\n}\n\n/**\n * Find the previous non-whitespace sibling in a list of JSX children.\n */\nfunction getPreviousNonWhitespaceSibling(\n children: Array<TSESTree.JSXChild>,\n currentIndex: number\n): TSESTree.JSXChild | null {\n for (let i = currentIndex - 1; i >= 0; i--) {\n const sibling = children[i];\n if (sibling !== undefined && !isWhitespaceText(sibling)) {\n return sibling;\n }\n }\n return null;\n}\n\n/**\n * Check if a JSXOpeningElement has a specific boolean or truthy prop.\n */\nfunction hasJSXProp(openingElement: TSESTree.JSXOpeningElement, propName: string): boolean {\n return openingElement.attributes.some(\n attr =>\n attr.type === AST_NODE_TYPES.JSXAttribute &&\n attr.name.type === AST_NODE_TYPES.JSXIdentifier &&\n attr.name.name === propName\n );\n}\n\ntype LabelContent = { type: \"string\"; value: string } | { type: \"expression\"; text: string };\n\n/**\n * Extract the meaningful label content from a Label/Text element's children.\n *\n * Returns null if the content is too complex to autofix (e.g. multiple meaningful\n * children, or JSX element children).\n */\nfunction extractLabelContent(\n labelElement: TSESTree.JSXElement,\n sourceCode: Readonly<{ getText(node: TSESTree.Node): string }>\n): LabelContent | null {\n // Filter out whitespace-only text children\n const meaningfulChildren = labelElement.children.filter(node => {\n if (node.type === AST_NODE_TYPES.JSXText) {\n return node.value.trim() !== \"\";\n }\n return true;\n });\n\n if (meaningfulChildren.length !== 1) {\n return null; // Complex content — skip autofix\n }\n\n const child = meaningfulChildren[0];\n if (child === undefined) {\n return null;\n }\n\n if (child.type === AST_NODE_TYPES.JSXText) {\n const trimmed = child.value.trim();\n if (trimmed.length === 0) {\n return null;\n }\n return { type: \"string\", value: trimmed };\n }\n\n if (child.type === AST_NODE_TYPES.JSXExpressionContainer) {\n if (child.expression.type === AST_NODE_TYPES.JSXEmptyExpression) {\n return null;\n }\n return { type: \"expression\", text: sourceCode.getText(child.expression) };\n }\n\n return null;\n}\n\n/**\n * Determine the field component name for autofix.\n *\n * Returns null (no autofix) for BaseSelect without isMulti and CreatableSelect,\n * because SelectField/CreatableSelectField have incompatible value/onChange APIs.\n *\n * BaseSelect with isMulti → MultiSelectField is safe because MultiSelectField\n * preserves the same option-object-based value/onChange API as BaseSelect.\n */\nfunction getAutofixFieldName(baseComponentName: string, openingElement: TSESTree.JSXOpeningElement): string | null {\n if (baseComponentName === \"BaseSelect\" && hasJSXProp(openingElement, \"isMulti\")) {\n return \"MultiSelectField\";\n }\n return BASE_TO_FIELD_AUTOFIX_MAP[baseComponentName] ?? null;\n}\n\n/**\n * Build the label prop string for the autofix.\n */\nfunction buildLabelPropValue(content: LabelContent): string {\n switch (content.type) {\n case \"string\":\n return `\"${content.value}\"`;\n case \"expression\":\n return `{${content.text}}`;\n default: {\n const _exhaustiveCheck: never = content;\n return _exhaustiveCheck;\n }\n }\n}\n\n/**\n * Count how many times a component is used as a JSX opening tag in the source text.\n * Uses a regex that matches `<ComponentName` followed by whitespace, `>`, or `/>`.\n */\nfunction countJSXElementUsages(sourceText: string, componentName: string): number {\n const pattern = new RegExp(`<${componentName}(?=[\\\\s/>])`, \"g\");\n const matches = sourceText.match(pattern);\n return matches ? matches.length : 0;\n}\n\n/**\n * Build a new import statement string with the given specifiers, preserving semicolons.\n */\nfunction buildImportStatement(\n sourceCode: Readonly<{ getText(node: TSESTree.Node): string }>,\n importNode: TSESTree.ImportDeclaration,\n specifiers: Iterable<string>\n): string {\n const sortedSpecifiers = Array.from(specifiers).sort().join(\", \");\n const originalText = sourceCode.getText(importNode);\n const semicolon = originalText.trimEnd().endsWith(\";\") ? \";\" : \"\";\n return `import { ${sortedSpecifiers} } from \"${importNode.source.value}\"${semicolon}`;\n}\n\nexport const preferFieldComponents = createRule<[], MessageIds>({\n name: \"prefer-field-components\",\n meta: {\n type: \"suggestion\",\n fixable: \"code\",\n docs: {\n description:\n \"Prefer using Field components (e.g. SelectField, TextField) instead of manually combining Label/Text with base input components (e.g. BaseSelect, BaseInput).\",\n },\n schema: [],\n messages: {\n preferFieldComponent:\n \"Avoid using {{labelComponent}} with {{baseComponent}}. Use {{fieldComponent}} instead, which includes a built-in label via the `label` prop.\",\n },\n },\n defaultOptions: [],\n create(context) {\n const sourceCode = context.sourceCode;\n\n return {\n JSXElement(node: TSESTree.JSXElement) {\n const children = node.children;\n\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n\n // Only look at JSXElement children\n if (child === undefined || child.type !== AST_NODE_TYPES.JSXElement) {\n continue;\n }\n\n const childName = getJSXElementName(child.openingElement);\n\n // Check if this child is a base component\n if (childName === null || !BASE_COMPONENT_NAMES.has(childName)) {\n continue;\n }\n\n // Found a base component — check if the previous non-whitespace sibling is a Label/Text\n const previousSibling = getPreviousNonWhitespaceSibling(children, i);\n\n if (previousSibling === null || !isLabelLikeElement(previousSibling)) {\n continue;\n }\n\n const labelName = getJSXElementName(previousSibling.openingElement);\n const fieldComponentMessage = BASE_TO_FIELD_MESSAGE_MAP[childName] ?? \"the appropriate *Field component\";\n const fieldComponentAutofix = getAutofixFieldName(childName, child.openingElement);\n const labelContent = extractLabelContent(previousSibling, sourceCode);\n\n context.report({\n node: child,\n messageId: \"preferFieldComponent\",\n data: {\n labelComponent: labelName ?? \"Label/Text\",\n baseComponent: childName,\n fieldComponent: fieldComponentMessage,\n },\n fix:\n fieldComponentAutofix !== null && labelContent !== null\n ? fixer => {\n const fixes: Array<ReturnType<typeof fixer.replaceText>> = [];\n const labelPropValue = buildLabelPropValue(labelContent);\n const fullSourceText = sourceCode.getText();\n\n // 1. Replace everything from Label start through the base component's tag name\n // in a single operation: <Label>...</Label>\\n...<BaseSelect → <SelectField label={...}\n fixes.push(\n fixer.replaceTextRange(\n [previousSibling.range[0], child.openingElement.name.range[1]],\n `<${fieldComponentAutofix} label=${labelPropValue}`\n )\n );\n\n // 2. Rename the closing tag if present (for non-self-closing elements)\n if (child.closingElement !== null) {\n fixes.push(fixer.replaceText(child.closingElement.name, fieldComponentAutofix));\n }\n\n // 3. Update imports: add the field component, remove base/label if no longer used.\n // All modifications to the same import declaration are combined into a single fix.\n const baseUsageCount = countJSXElementUsages(fullSourceText, childName);\n const labelUsageCount = labelName !== null ? countJSXElementUsages(fullSourceText, labelName) : 0;\n\n // Handle @trackunit/react-form-components import\n const formImport = findImportDeclaration(sourceCode, FORM_COMPONENTS_PACKAGE);\n if (formImport) {\n const specifiers = getImportSpecifiers(formImport);\n\n // Add field component\n specifiers.add(fieldComponentAutofix);\n\n // Remove base component if this is the only JSX usage\n if (baseUsageCount <= 1) {\n specifiers.delete(childName);\n }\n\n // Remove Label if it's from this package and this is the only JSX usage\n if (labelName === \"Label\" && labelUsageCount <= 1) {\n specifiers.delete(\"Label\");\n }\n\n fixes.push(\n fixer.replaceText(formImport, buildImportStatement(sourceCode, formImport, specifiers))\n );\n }\n\n // Handle @trackunit/react-components import (for Text removal)\n if (labelName === \"Text\" && labelUsageCount <= 1) {\n const reactImport = findImportDeclaration(sourceCode, REACT_COMPONENTS_PACKAGE);\n if (reactImport) {\n const specifiers = getImportSpecifiers(reactImport);\n if (specifiers.has(\"Text\")) {\n specifiers.delete(\"Text\");\n if (specifiers.size === 0) {\n fixes.push(fixer.remove(reactImport));\n } else {\n fixes.push(\n fixer.replaceText(reactImport, buildImportStatement(sourceCode, reactImport, specifiers))\n );\n }\n }\n }\n }\n\n return fixes;\n }\n : null,\n });\n }\n },\n };\n },\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-mouse-event-handler-in-react-props.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESLint rule: prefer-mouse-event-handler-in-react-props\n *\n * Enforces using MouseEventHandler<T> instead of () => void or (e: MouseEvent) => void\n * for onClick* props in React components. This ensures proper React typing and consistency.\n *\n * Only flags properties in actual React component props, not in hook options or other interfaces.\n *\n * Uses ESLint suggestions (not autofix) to let users choose the appropriate element type.\n */\n\nimport { AST_NODE_TYPES, ESLintUtils, TSESLint, TSESTree } from \"@typescript-eslint/utils\";\nimport * as ts from \"typescript\";\nimport { isReactComponent } from \"../../utils/ast-utils\";\n\ntype SuggestionResult = {\n messageId: \"suggestMouseEventHandler\";\n data: { elementType: string };\n fix: (fixer: TSESLint.RuleFixer) => TSESLint.RuleFix;\n};\n\nconst createRule = ESLintUtils.RuleCreator(\n name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}/${name}.ts`\n);\n\ntype Options = [\n {\n /**\n * Prop names to exempt from this rule.\n *\n * @example [\"onClick\"]\n */\n allowedNames?: ReadonlyArray<string>;\n },\n];\n\ntype MessageIds = \"preferMouseEventHandler\" | \"suggestMouseEventHandler\";\n\n/**\n * Checks if a property name starts with \"onClick\" (case-sensitive).\n */\nconst isOnClickProp = (name: string): boolean => {\n return name.startsWith(\"onClick\");\n};\n\n/**\n * Extracts the element type from a MouseEvent generic type annotation.\n * Returns null if not a MouseEvent type or no generic provided.\n *\n * Examples:\n * - MouseEvent<HTMLButtonElement> -> \"HTMLButtonElement\"\n * - React.MouseEvent<HTMLDivElement> -> \"HTMLDivElement\"\n * - MouseEvent -> null (no generic)\n */\nconst extractElementTypeFromMouseEvent = (typeAnnotation: TSESTree.TypeNode): string | null => {\n if (typeAnnotation.type !== AST_NODE_TYPES.TSTypeReference) {\n return null;\n }\n\n const { typeName, typeArguments } = typeAnnotation;\n\n let isMouseEvent = false;\n\n if (typeName.type === AST_NODE_TYPES.Identifier) {\n isMouseEvent = typeName.name === \"MouseEvent\";\n } else if (typeName.type === AST_NODE_TYPES.TSQualifiedName) {\n isMouseEvent =\n typeName.left.type === AST_NODE_TYPES.Identifier &&\n typeName.left.name === \"React\" &&\n typeName.right.name === \"MouseEvent\";\n }\n\n if (!isMouseEvent) {\n return null;\n }\n\n if (!typeArguments || typeArguments.params.length === 0) {\n return null;\n }\n\n const firstTypeArg = typeArguments.params[0];\n if (firstTypeArg?.type === AST_NODE_TYPES.TSTypeReference) {\n if (firstTypeArg.typeName.type === AST_NODE_TYPES.Identifier) {\n return firstTypeArg.typeName.name;\n }\n }\n\n return null;\n};\n\n/**\n * Checks if a type annotation is a MouseEvent type (with or without generics).\n */\nconst isMouseEventType = (typeAnnotation: TSESTree.TypeNode): boolean => {\n if (typeAnnotation.type !== AST_NODE_TYPES.TSTypeReference) {\n return false;\n }\n\n const { typeName } = typeAnnotation;\n\n if (typeName.type === AST_NODE_TYPES.Identifier) {\n return typeName.name === \"MouseEvent\";\n }\n\n if (typeName.type === AST_NODE_TYPES.TSQualifiedName) {\n return (\n typeName.left.type === AST_NODE_TYPES.Identifier &&\n typeName.left.name === \"React\" &&\n typeName.right.name === \"MouseEvent\"\n );\n }\n\n return false;\n};\n\n/**\n * Checks if a function type returns void.\n */\nconst hasVoidReturn = (funcType: TSESTree.TSFunctionType): boolean => {\n const { returnType } = funcType;\n\n if (!returnType) {\n return true;\n }\n\n const typeAnnotation = returnType.typeAnnotation;\n\n if (typeAnnotation.type === AST_NODE_TYPES.TSVoidKeyword) {\n return true;\n }\n\n return false;\n};\n\ntype AnalysisResult =\n | { shouldFlag: false }\n | {\n shouldFlag: true;\n inferredElementType: string | null;\n typeAnnotationNode: TSESTree.TSFunctionType;\n };\n\n/**\n * Analyzes a TSFunctionType to determine if it should be flagged.\n */\nconst analyzeFunctionType = (funcType: TSESTree.TSFunctionType): AnalysisResult => {\n if (!hasVoidReturn(funcType)) {\n return { shouldFlag: false };\n }\n\n const { params } = funcType;\n\n // Case A: No parameters - () => void\n if (params.length === 0) {\n return {\n shouldFlag: true,\n inferredElementType: null,\n typeAnnotationNode: funcType,\n };\n }\n\n // Case B: Exactly one parameter that is MouseEvent\n if (params.length === 1) {\n const param = params[0];\n\n let paramTypeAnnotation: TSESTree.TypeNode | undefined;\n\n if (param?.type === AST_NODE_TYPES.Identifier && param.typeAnnotation) {\n paramTypeAnnotation = param.typeAnnotation.typeAnnotation;\n }\n\n if (paramTypeAnnotation && isMouseEventType(paramTypeAnnotation)) {\n const elementType = extractElementTypeFromMouseEvent(paramTypeAnnotation);\n return {\n shouldFlag: true,\n inferredElementType: elementType,\n typeAnnotationNode: funcType,\n };\n }\n }\n\n return { shouldFlag: false };\n};\n\n/**\n * Creates suggestion fixes for a given element type.\n */\nconst createSuggestion = (elementType: string, typeAnnotationNode: TSESTree.TSFunctionType): SuggestionResult => {\n return {\n messageId: \"suggestMouseEventHandler\",\n data: { elementType },\n fix: fixer => {\n return fixer.replaceText(typeAnnotationNode, `MouseEventHandler<${elementType}>`);\n },\n };\n};\n\n/**\n * Known valid HTML element types for MouseEventHandler.\n */\nconst VALID_HTML_ELEMENT_TYPES = new Set([\n \"Element\",\n \"HTMLElement\",\n \"HTMLAnchorElement\",\n \"HTMLButtonElement\",\n \"HTMLDivElement\",\n \"HTMLFormElement\",\n \"HTMLImageElement\",\n \"HTMLInputElement\",\n \"HTMLLabelElement\",\n \"HTMLLIElement\",\n \"HTMLParagraphElement\",\n \"HTMLSelectElement\",\n \"HTMLSpanElement\",\n \"HTMLTableElement\",\n \"HTMLTableRowElement\",\n \"HTMLTableCellElement\",\n \"HTMLTextAreaElement\",\n \"HTMLUListElement\",\n \"SVGElement\",\n \"SVGSVGElement\",\n]);\n\n/**\n * Extracts element type from a MouseEventHandler type string.\n */\nconst extractElementTypeFromTypeString = (typeString: string): string | null => {\n const match = typeString.match(/MouseEventHandler<([^>]+)>/);\n if (match?.[1]) {\n const elementType = match[1].trim();\n if (VALID_HTML_ELEMENT_TYPES.has(elementType)) {\n return elementType;\n }\n }\n return null;\n};\n\n/**\n * Tries to infer the element type by finding where the prop is used in JSX.\n */\nconst inferElementTypeFromUsage = (\n propName: string,\n context: TSESLint.RuleContext<MessageIds, ReadonlyArray<unknown>>\n): string | null => {\n try {\n const services = ESLintUtils.getParserServices(context);\n const checker = services.program.getTypeChecker();\n const sourceFile = services.program.getSourceFile(context.filename);\n\n if (!sourceFile) {\n return null;\n }\n\n let inferredType: string | null = null;\n\n const visit = (node: ts.Node): void => {\n if (ts.isJsxAttribute(node)) {\n const attrName = node.name.getText(sourceFile);\n\n if (attrName.startsWith(\"on\")) {\n const initializer = node.initializer;\n\n if (initializer && ts.isJsxExpression(initializer) && initializer.expression) {\n const expr = initializer.expression;\n let referencedName: string | null = null;\n\n if (ts.isIdentifier(expr) && expr.text === propName) {\n referencedName = propName;\n }\n\n if (ts.isArrowFunction(expr) && ts.isCallExpression(expr.body)) {\n const callee = expr.body.expression;\n if (ts.isIdentifier(callee) && callee.text === propName) {\n referencedName = propName;\n }\n }\n\n if (referencedName) {\n const contextualType = checker.getContextualType(expr);\n if (contextualType) {\n const typeString = checker.typeToString(contextualType);\n const elementType = extractElementTypeFromTypeString(typeString);\n if (elementType) {\n inferredType = elementType;\n }\n }\n }\n }\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return inferredType;\n } catch {\n return null;\n }\n};\n\n/**\n * Gets onClick* properties from a component's props type annotation.\n */\nconst getOnClickPropsFromType = (\n context: TSESLint.RuleContext<MessageIds, ReadonlyArray<unknown>>,\n typeAnnotation: TSESTree.TypeNode | undefined\n): Array<{ name: string; typeNode: TSESTree.TSFunctionType; propertyNode: TSESTree.Node }> => {\n if (!typeAnnotation) {\n return [];\n }\n\n const result: Array<{ name: string; typeNode: TSESTree.TSFunctionType; propertyNode: TSESTree.Node }> = [];\n\n // Handle inline type literals: ({ onClick }: { onClick: () => void }) => ...\n if (typeAnnotation.type === AST_NODE_TYPES.TSTypeLiteral) {\n for (const member of typeAnnotation.members) {\n if (\n member.type === AST_NODE_TYPES.TSPropertySignature &&\n member.key.type === AST_NODE_TYPES.Identifier &&\n isOnClickProp(member.key.name) &&\n member.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES.TSFunctionType\n ) {\n result.push({\n name: member.key.name,\n typeNode: member.typeAnnotation.typeAnnotation,\n propertyNode: member,\n });\n }\n }\n return result;\n }\n\n // Handle type references: ({ onClick }: ButtonProps) => ...\n if (typeAnnotation.type === AST_NODE_TYPES.TSTypeReference) {\n try {\n const services = ESLintUtils.getParserServices(context);\n const checker = services.program.getTypeChecker();\n const tsNode = services.esTreeNodeToTSNodeMap.get(typeAnnotation);\n const type = checker.getTypeAtLocation(tsNode);\n\n for (const prop of type.getProperties()) {\n if (isOnClickProp(prop.name)) {\n const propType = checker.getTypeOfSymbolAtLocation(prop, tsNode);\n const callSignatures = propType.getCallSignatures();\n\n if (callSignatures.length > 0) {\n // Find the corresponding ESTree node for this property\n const declarations = prop.getDeclarations();\n const decl = declarations?.[0];\n if (decl) {\n const esTreeNode = services.tsNodeToESTreeNodeMap.get(decl);\n if (\n esTreeNode.type === AST_NODE_TYPES.TSPropertySignature &&\n esTreeNode.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES.TSFunctionType\n ) {\n result.push({\n name: prop.name,\n typeNode: esTreeNode.typeAnnotation.typeAnnotation,\n propertyNode: esTreeNode,\n });\n }\n }\n }\n }\n }\n } catch {\n // Type checking failed - silently skip\n }\n }\n\n return result;\n};\n\nexport const preferMouseEventHandlerInReactProps = createRule<Options, MessageIds>({\n name: \"prefer-mouse-event-handler-in-react-props\",\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Enforce using MouseEventHandler<T> instead of () => void or (e: MouseEvent) => void for onClick* props in React components. \" +\n \"This ensures proper React typing and consistency across the codebase.\",\n },\n hasSuggestions: true,\n schema: [\n {\n type: \"object\",\n properties: {\n allowedNames: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Prop names to exempt from this rule\",\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n preferMouseEventHandler: \"Prop '{{propName}}' should use MouseEventHandler<T> instead of {{currentType}}.\",\n suggestMouseEventHandler: \"Use MouseEventHandler<{{elementType}}>\",\n },\n },\n defaultOptions: [\n {\n allowedNames: [],\n },\n ],\n create(context, [options]) {\n const allowedNames = options.allowedNames ?? [];\n\n // Only run on .tsx files\n const filename = context.filename;\n if (!filename.endsWith(\".tsx\")) {\n return {};\n }\n\n // Track reported property nodes to avoid duplicate reports when multiple components use the same interface\n const reportedNodes = new Set<TSESTree.Node>();\n\n const checkComponentProps = (\n _node: TSESTree.FunctionDeclaration | TSESTree.ArrowFunctionExpression,\n propsParam: TSESTree.Parameter | undefined\n ) => {\n if (!propsParam) {\n return;\n }\n\n let typeAnnotation: TSESTree.TypeNode | undefined;\n\n // Handle both regular parameters (props: Props) and destructured parameters ({prop1, prop2}: Props)\n if (propsParam.type === AST_NODE_TYPES.Identifier || propsParam.type === AST_NODE_TYPES.ObjectPattern) {\n typeAnnotation = propsParam.typeAnnotation?.typeAnnotation;\n } else {\n return;\n }\n\n const onClickProps = getOnClickPropsFromType(context, typeAnnotation);\n\n for (const { name: propName, typeNode, propertyNode } of onClickProps) {\n // Skip if already reported (prevents duplicates when multiple components use the same interface)\n if (reportedNodes.has(propertyNode)) {\n continue;\n }\n\n // Check if it's in the allowed list\n if (allowedNames.includes(propName)) {\n continue;\n }\n\n // Analyze the function type\n const result = analyzeFunctionType(typeNode);\n\n if (!result.shouldFlag) {\n continue;\n }\n\n // Get the current type as a string for the error message\n const sourceCode = context.sourceCode;\n const currentType = sourceCode.getText(typeNode);\n\n // Build suggestions\n const suggestions: Array<SuggestionResult> = [];\n\n let inferredElementType = result.inferredElementType;\n\n if (!inferredElementType) {\n inferredElementType = inferElementTypeFromUsage(propName, context);\n }\n\n if (inferredElementType) {\n suggestions.push(createSuggestion(inferredElementType, result.typeAnnotationNode));\n } else {\n suggestions.push(createSuggestion(\"HTMLButtonElement\", result.typeAnnotationNode));\n suggestions.push(createSuggestion(\"HTMLElement\", result.typeAnnotationNode));\n suggestions.push(createSuggestion(\"HTMLDivElement\", result.typeAnnotationNode));\n }\n\n // Mark as reported to prevent duplicates\n reportedNodes.add(propertyNode);\n\n context.report({\n node: propertyNode,\n messageId: \"preferMouseEventHandler\",\n data: {\n propName,\n currentType,\n },\n suggest: suggestions,\n });\n }\n };\n\n return {\n FunctionDeclaration(node: TSESTree.FunctionDeclaration) {\n if (isReactComponent(node)) {\n checkComponentProps(node, node.params[0]);\n }\n },\n\n ArrowFunctionExpression(node: TSESTree.ArrowFunctionExpression) {\n if (isReactComponent(node)) {\n checkComponentProps(node, node.params[0]);\n }\n },\n };\n },\n});\n"]}
|