@trackunit/eslint-plugin-trackunit 0.6.170 → 0.6.171-alpha-e1e6a0f33ce.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/migrations/entry.js.map +1 -0
  2. package/package.json +2 -2
  3. package/src/index.js.map +1 -0
  4. package/src/lib/config/fragments/ignores.js.map +1 -0
  5. package/src/lib/config/fragments/import-rules.js.map +1 -0
  6. package/src/lib/config/fragments/jest-overrides.js.map +1 -0
  7. package/src/lib/config/fragments/jsdoc-rules.js.map +1 -0
  8. package/src/lib/config/fragments/module-boundaries.js.map +1 -0
  9. package/src/lib/config/fragments/no-direct-storage-access.js.map +1 -0
  10. package/src/lib/config/fragments/react-rules.js.map +1 -0
  11. package/src/lib/config/fragments/restricted-imports.js.map +1 -0
  12. package/src/lib/config/fragments/story-relaxations.js.map +1 -0
  13. package/src/lib/config/fragments/testing-library.js.map +1 -0
  14. package/src/lib/config/fragments/typescript-rules.js.map +1 -0
  15. package/src/lib/config/index.js.map +1 -0
  16. package/src/lib/config/plugins.js.map +1 -0
  17. package/src/lib/config/presets/base.js.map +1 -0
  18. package/src/lib/config/presets/e2e.js.map +1 -0
  19. package/src/lib/config/presets/react.js.map +1 -0
  20. package/src/lib/config/presets/server.js.map +1 -0
  21. package/src/lib/config/utils.js.map +1 -0
  22. package/src/lib/config-helpers/create-skip-when.js.map +1 -0
  23. package/src/lib/rules/component-name-matches-filename/component-name-matches-filename.js.map +1 -0
  24. package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.js.map +1 -0
  25. package/src/lib/rules/design-guideline-icon-size-match/design-guideline-icon-size-match.js.map +1 -0
  26. package/src/lib/rules/no-deprecated-graphql-fields/no-deprecated-graphql-fields.js.map +1 -0
  27. package/src/lib/rules/no-deprecated-graphql-fields/schema-cache.js.map +1 -0
  28. package/src/lib/rules/no-dynamic-translation-key/no-dynamic-translation-key.js.map +1 -0
  29. package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.js.map +1 -0
  30. package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.js.map +1 -0
  31. package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.js.map +1 -0
  32. package/src/lib/rules/no-local-meta-component/no-local-meta-component.js.map +1 -0
  33. package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.js.map +1 -0
  34. package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.js.map +1 -0
  35. package/src/lib/rules/no-unused-translation-key/expand-dynamic-key.js.map +1 -0
  36. package/src/lib/rules/no-unused-translation-key/no-unused-translation-key.js.map +1 -0
  37. package/src/lib/rules/no-unused-translation-key/used-strings.js.map +1 -0
  38. package/src/lib/rules/one-component-per-file/one-component-per-file.js.map +1 -0
  39. package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.js.map +1 -0
  40. package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.js.map +1 -0
  41. package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.js.map +1 -0
  42. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.js.map +1 -0
  43. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.js.map +1 -0
  44. package/src/lib/rules/prefer-event-specific-callback-naming/utils.js.map +1 -0
  45. package/src/lib/rules/prefer-field-components/prefer-field-components.js.map +1 -0
  46. package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.js.map +1 -0
  47. package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.js.map +1 -0
  48. package/src/lib/rules/require-component-prop-contracts/require-component-prop-contracts.js.map +1 -0
  49. package/src/lib/rules/require-csf-factory/require-csf-factory.js.map +1 -0
  50. package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.js.map +1 -0
  51. package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.js.map +1 -0
  52. package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.js.map +1 -0
  53. package/src/lib/rules/require-storybook-folder-landing-page/require-storybook-folder-landing-page.js.map +1 -0
  54. package/src/lib/rules-map.js.map +1 -0
  55. package/src/lib/utils/ast-utils.js.map +1 -0
  56. package/src/lib/utils/component-utils.js.map +1 -0
  57. package/src/lib/utils/file-utils.js.map +1 -0
  58. package/src/lib/utils/import-utils.js.map +1 -0
  59. package/src/lib/utils/nx-utils.js.map +1 -0
  60. package/src/lib/utils/package-utils.js.map +1 -0
  61. package/src/lib/utils/program-utils.js.map +1 -0
  62. package/src/lib/utils/translation-key-types.js.map +1 -0
  63. package/src/lib/utils/typescript-utils.js.map +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-local-meta-component.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/no-local-meta-component/no-local-meta-component.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Error when a Storybook meta's `component` is declared inside a `*.stories.*` file.\n *\n * Architecture: per-story-file rule, syntax-only AST + ESLint scope analysis (no TypeScript type\n * information). The meta object is read from any of:\n * - `export default { component, ... }` (including a `satisfies` / `as` wrapper),\n * - `const meta = { component, ... } satisfies Meta<...>; export default meta;` (or `as`, or a\n * plain type annotation: `const meta: Meta<...> = { component, ... };`),\n * - any `<x>.meta({ component, ... })` member call (covers `preview.meta({...})` and\n * `preview.type<...>().meta({...})`).\n *\n * The `component` value is unwrapped (`satisfies`, `as`, instantiation expressions such as\n * `MultiSelectField<Option>`) and resolved through scope. It is reported when it resolves to:\n * - a function/arrow/class expression written inline or declared in this file,\n * - a call (`memo(Local)`, `forwardRef(Local)`, any call) whose wrapped component resolves locally.\n * The wrapped component is the first traceable argument, skipping literal options and inline\n * functions after the first position — `memo(Widget, areEqual)` wraps `Widget`,\n * `memo(Local, isEqual)` wraps `Local`,\n * - member access on, destructuring from, or a `const` alias of, something that resolves locally\n * (for an object literal, only the accessed or destructured property is checked, following\n * spreads into their source; a computed member or a nested/unresolvable destructuring pattern\n * is untraced rather than checked against every sibling),\n * - an import from another `*.stories` module.\n * Imports from non-story modules (and aliases / member access / instantiations of them) pass.\n *\n * Metas without `component`, non-story files, and locally declared components that are not the\n * meta component (e.g. example-only demos used in `render`) are never checked.\n *\n * This is a **detection-only** rule: no autofix and no suggestions — the fix is either importing\n * the real component or moving the demo into a sibling `*.demo.tsx` file, both of which need\n * developer intent and cross-file edits.\n *\n * See README.md for incorrect/correct examples.\n */\n\nimport { AST_NODE_TYPES, ASTUtils, ESLintUtils, TSESLint, TSESTree } from \"@typescript-eslint/utils\";\n\nconst { DefinitionType } = TSESLint.Scope;\n\nconst createRule = ESLintUtils.RuleCreator(\n name =>\n `https://github.com/trackunit/manager/blob/master/libs/eslint/plugin-trackunit/src/lib/rules/${name}/${name}.ts`\n);\n\ntype MessageIds = \"localMetaComponent\";\ntype Options = [];\ntype Origin = \"local\" | \"external\" | null;\n\n/** Canonical permalink to this rule's README, embedded in the message. */\nexport const NO_LOCAL_META_COMPONENT_DOCS_URL =\n \"https://github.com/Trackunit/manager/blob/master/libs/eslint/plugin-trackunit/src/lib/rules/no-local-meta-component/README.md\";\n\nconst STORY_FILE_REGEX = /\\.stories\\.(js|jsx|ts|tsx)$/;\n// Import sources are usually extensionless (`./Other.stories`), so the extension is optional.\nconst STORY_IMPORT_SOURCE_REGEX = /\\.stories(\\.(js|jsx|ts|tsx))?$/;\n\n/** Unwrap `satisfies` / `as` wrappers and instantiation expressions (`MultiSelectField<Option>`). */\nconst unwrapExpression = (node: TSESTree.Node): TSESTree.Node => {\n let current = node;\n while (\n current.type === AST_NODE_TYPES.TSSatisfiesExpression ||\n current.type === AST_NODE_TYPES.TSAsExpression ||\n current.type === AST_NODE_TYPES.TSInstantiationExpression\n ) {\n current = current.expression;\n }\n return current;\n};\n\nconst getComponentValue = (object: TSESTree.ObjectExpression): TSESTree.Node | null => {\n for (const property of object.properties) {\n if (\n property.type === AST_NODE_TYPES.Property &&\n !property.computed &&\n property.key.type === AST_NODE_TYPES.Identifier &&\n property.key.name === \"component\"\n ) {\n return property.value;\n }\n }\n return null;\n};\n\nconst isInlineComponent = (node: TSESTree.Node): boolean =>\n node.type === AST_NODE_TYPES.ArrowFunctionExpression ||\n node.type === AST_NODE_TYPES.FunctionExpression ||\n node.type === AST_NODE_TYPES.ClassExpression;\n\nconst create: TSESLint.RuleCreateFunction<MessageIds, Options> = context => {\n if (!STORY_FILE_REGEX.test(context.filename)) {\n return {};\n }\n\n /** The key `name` is destructured from, and its default (`= …`), if any. */\n type DestructuredProperty = { key: string; default: TSESTree.Expression | null };\n\n /**\n * `name`'s key and default in `const { key: name = default } = ...`. `null` when `name` isn't a\n * direct property of `pattern` — a nested pattern (`{ a: { b: name } }`) or a computed key\n * (`{ [k]: name }`) — since which property that is can't be read syntactically.\n */\n const getDestructuredKey = (\n pattern: TSESTree.ObjectPattern,\n name: TSESTree.Identifier\n ): DestructuredProperty | null => {\n for (const property of pattern.properties) {\n if (\n property.type !== AST_NODE_TYPES.Property ||\n property.computed ||\n property.key.type !== AST_NODE_TYPES.Identifier\n ) {\n continue;\n }\n if (property.value.type === AST_NODE_TYPES.AssignmentPattern) {\n if (property.value.left === name) {\n return { key: property.key.name, default: property.value.right };\n }\n continue;\n }\n if (property.value === name) {\n return { key: property.key.name, default: null };\n }\n }\n return null;\n };\n\n /**\n * The object literal `node` evaluates to, following `const` aliases (`alias = widgets`), member\n * access (`ns.widgets`) and destructuring (`const { inner } = ...`); `null` when it can't be traced.\n *\n * `path` holds the nodes currently being resolved, so only a real cycle (`const A = B; const B = A;`)\n * is cut; a node reached again by another route is resolved normally.\n */\n const resolveObjectLiteral = (node: TSESTree.Node, path: Set<TSESTree.Node>): TSESTree.ObjectExpression | null => {\n const expression = unwrapExpression(node);\n if (expression.type === AST_NODE_TYPES.ObjectExpression) {\n return expression;\n }\n if (path.has(expression)) {\n return null;\n }\n path.add(expression);\n try {\n switch (expression.type) {\n case AST_NODE_TYPES.MemberExpression: {\n if (expression.computed || expression.property.type !== AST_NODE_TYPES.Identifier) {\n return null;\n }\n const value = findPropertyValue(expression.object, expression.property.name, path);\n return value ? resolveObjectLiteral(value, path) : null;\n }\n case AST_NODE_TYPES.Identifier: {\n const definition = ASTUtils.findVariable(context.sourceCode.getScope(expression), expression)?.defs[0];\n if (definition?.type !== DefinitionType.Variable || definition.node.init === null) {\n return null;\n }\n if (definition.node.id.type !== AST_NODE_TYPES.ObjectPattern) {\n return resolveObjectLiteral(definition.node.init, path);\n }\n const destructured = getDestructuredKey(definition.node.id, definition.name);\n if (destructured === null) {\n return null;\n }\n const value = findPropertyValue(definition.node.init, destructured.key, path);\n if (value === undefined) {\n return null;\n }\n const resolved = value ?? destructured.default;\n return resolved ? resolveObjectLiteral(resolved, path) : null;\n }\n default:\n return null;\n }\n } finally {\n path.delete(expression);\n }\n };\n\n /**\n * The node that provides `object[key]` when `object` traces to an object literal: the property\n * value, or the source of an untraceable spread that may provide it. `null` when the literal has\n * no such key; `undefined` when `object` isn't a traceable object literal.\n *\n * Properties are read last to first, so a later key overrides an earlier spread; a spread whose\n * source is a literal without the key falls through to earlier properties. A literal already on\n * `path` is a spread cycle (`const a = { ...b }; const b = { ...a };`) and provides nothing.\n */\n const findPropertyValue = (\n object: TSESTree.Node,\n key: string,\n path: Set<TSESTree.Node>\n ): TSESTree.Node | null | undefined => {\n const literal = resolveObjectLiteral(object, path);\n if (literal === null) {\n return undefined;\n }\n if (path.has(literal)) {\n return null;\n }\n path.add(literal);\n try {\n for (const property of [...literal.properties].reverse()) {\n if (property.type === AST_NODE_TYPES.SpreadElement) {\n const spreadValue = findPropertyValue(property.argument, key, path);\n if (spreadValue === undefined) {\n return property.argument;\n }\n if (spreadValue !== null) {\n return spreadValue;\n }\n continue;\n }\n if (!property.computed && property.key.type === AST_NODE_TYPES.Identifier && property.key.name === key) {\n return property.value;\n }\n }\n return null;\n } finally {\n path.delete(literal);\n }\n };\n\n /**\n * Classifies `object[key]`; checks only the accessed property when `object` traces to an object\n * literal, falling back to `defaultNode` (a destructuring default) when the literal lacks `key`.\n *\n * `key === null` means the accessed property can't be read syntactically — a computed member\n * (`bag[k]`) or an unresolvable destructuring pattern. When `object` traces to an object literal,\n * this is untraced (`null`) rather than classifying every sibling, which would flag e.g. an\n * imported `widgets.Widget` merely because `widgets` also has a local `Demo`. When `object` isn't\n * a traceable literal (e.g. a namespace import, `Ns.Widget`), `object` itself is classified.\n */\n const classifyProperty = (\n object: TSESTree.Node,\n key: string | null,\n defaultNode: TSESTree.Node | null,\n visited: Set<TSESTree.Node>\n ): Origin => {\n if (key === null) {\n return resolveObjectLiteral(object, new Set()) === null ? classify(object, visited) : null;\n }\n const value = findPropertyValue(object, key, new Set());\n if (value === undefined) {\n return classify(object, visited);\n }\n if (value === null) {\n return defaultNode ? classify(defaultNode, visited) : null;\n }\n return classify(value, visited);\n };\n\n /**\n * Arguments that can't be the wrapped component: literal options and, after the first position,\n * inline callbacks (`memo`'s comparator).\n */\n const isNonComponentArgument = (argument: TSESTree.Node, index: number): boolean => {\n const expression = unwrapExpression(argument);\n switch (expression.type) {\n case AST_NODE_TYPES.ObjectExpression:\n case AST_NODE_TYPES.ArrayExpression:\n case AST_NODE_TYPES.Literal:\n case AST_NODE_TYPES.TemplateLiteral:\n return true;\n default:\n return index > 0 && isInlineComponent(expression);\n }\n };\n\n const classifyDefinition = (def: TSESLint.Scope.Definition, visited: Set<TSESTree.Node>): Origin => {\n switch (def.type) {\n case DefinitionType.FunctionName:\n case DefinitionType.ClassName:\n return \"local\";\n case DefinitionType.Variable:\n if (def.node.init === null) {\n return null;\n }\n if (def.node.id.type === AST_NODE_TYPES.ObjectPattern) {\n const destructured = getDestructuredKey(def.node.id, def.name);\n return classifyProperty(def.node.init, destructured?.key ?? null, destructured?.default ?? null, visited);\n }\n return classify(def.node.init, visited);\n case DefinitionType.ImportBinding:\n if (def.parent.type !== AST_NODE_TYPES.ImportDeclaration) {\n return null;\n }\n return STORY_IMPORT_SOURCE_REGEX.test(def.parent.source.value) ? \"local\" : \"external\";\n default:\n return null;\n }\n };\n\n /**\n * `\"local\"` when `node` resolves to something declared in a story file, `\"external\"` when it\n * resolves to an import from a non-story module, `null` when it can't be traced.\n */\n const classify = (node: TSESTree.Node, visited: Set<TSESTree.Node>): Origin => {\n const expression = unwrapExpression(node);\n // Guards alias cycles such as `const A = B; const B = A;`.\n if (visited.has(expression)) {\n return null;\n }\n visited.add(expression);\n\n if (isInlineComponent(expression)) {\n return \"local\";\n }\n switch (expression.type) {\n case AST_NODE_TYPES.CallExpression: {\n // The wrapped component is the first traceable argument that could be one; later arguments\n // are its callbacks and options, so they can't outvote it (`memo(Local, isEqual)`,\n // `memo(Widget, areEqual)`, `withTheme({ strict: true }, Local)`).\n for (const [index, argument] of expression.arguments.entries()) {\n if (isNonComponentArgument(argument, index)) {\n continue;\n }\n const origin = classify(argument, visited);\n if (origin !== null) {\n return origin;\n }\n }\n return null;\n }\n case AST_NODE_TYPES.MemberExpression:\n return classifyProperty(\n expression.object,\n !expression.computed && expression.property.type === AST_NODE_TYPES.Identifier\n ? expression.property.name\n : null,\n null,\n visited\n );\n case AST_NODE_TYPES.ObjectExpression:\n return expression.properties.some(\n property => property.type === AST_NODE_TYPES.Property && classify(property.value, visited) === \"local\"\n )\n ? \"local\"\n : null;\n case AST_NODE_TYPES.Identifier: {\n const variable = ASTUtils.findVariable(context.sourceCode.getScope(expression), expression);\n const origins = variable?.defs.map(def => classifyDefinition(def, visited)) ?? [];\n return origins.includes(\"local\") ? \"local\" : (origins.find(origin => origin !== null) ?? null);\n }\n default:\n return null;\n }\n };\n\n const checkMetaObject = (object: TSESTree.ObjectExpression): void => {\n const component = getComponentValue(object);\n if (component && classify(component, new Set()) === \"local\") {\n context.report({ node: component, messageId: \"localMetaComponent\" });\n }\n };\n\n return {\n ExportDefaultDeclaration(node: TSESTree.ExportDefaultDeclaration) {\n const declaration = unwrapExpression(node.declaration);\n\n if (declaration.type === AST_NODE_TYPES.ObjectExpression) {\n checkMetaObject(declaration);\n return;\n }\n\n if (declaration.type === AST_NODE_TYPES.Identifier) {\n const variable = ASTUtils.findVariable(context.sourceCode.getScope(node), declaration);\n const definition = variable?.defs[0];\n if (definition?.node.type === AST_NODE_TYPES.VariableDeclarator && definition.node.init) {\n const initializer = unwrapExpression(definition.node.init);\n if (initializer.type === AST_NODE_TYPES.ObjectExpression) {\n checkMetaObject(initializer);\n }\n }\n }\n },\n\n CallExpression(node: TSESTree.CallExpression) {\n const { callee } = node;\n if (\n callee.type !== AST_NODE_TYPES.MemberExpression ||\n callee.property.type !== AST_NODE_TYPES.Identifier ||\n callee.property.name !== \"meta\"\n ) {\n return;\n }\n const [firstArgument] = node.arguments;\n if (firstArgument?.type === AST_NODE_TYPES.ObjectExpression) {\n checkMetaObject(firstArgument);\n }\n },\n };\n};\n\nexport const noLocalMetaComponent = createRule<Options, MessageIds>({\n name: \"no-local-meta-component\",\n meta: {\n type: \"problem\",\n docs: {\n description: \"Disallow a Storybook meta `component` that is declared inside a story file\",\n },\n schema: [],\n messages: {\n localMetaComponent: `[no-local-meta-component] The meta \\`component\\` is declared in a story file. Storybook's docgen enricher skips \\`*.stories.*\\` files, so Controls loses the component's JSDoc prop descriptions, and \"Show code\" names an identifier that only exists inside the story. Either point \\`component\\` at the real component (specialise a generic with an instantiation expression, e.g. \\`component: MultiSelectField<Option>\\`), or move the documented demo into a sibling \\`*.demo.tsx\\` file and import it from there. See ${NO_LOCAL_META_COMPONENT_DOCS_URL}.`,\n },\n },\n defaultOptions: [],\n create,\n});\n"]}
@@ -0,0 +1 @@
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,wEAAsG;AACtG,oDAAiF;AAEjF,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,qLAAqL;SACxL;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,0CAAoB,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;YAED,kBAAkB,CAAC,IAAiC;gBAClD,MAAM,SAAS,GAAG,IAAA,oDAA8B,EAAC,IAAI,CAAC,CAAC;gBACvD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;oBACjC,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,sBAAc,CAAC,eAAe,EAAE,CAAC;wBAC7D,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,UAAU,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC,CAAC;oBACxF,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { findClassnameStringsInVariable, isClassnameAttribute } from \"@trackunit/css-classname-utils\";\nimport { AST_NODE_TYPES, ESLintUtils, TSESTree } from \"@typescript-eslint/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 cx() for simple inline merging or make a cva() 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 VariableDeclarator(node: TSESTree.VariableDeclarator) {\n const locations = findClassnameStringsInVariable(node);\n for (const location of locations) {\n if (location.fixNode.type === AST_NODE_TYPES.TemplateLiteral) {\n context.report({ node: location.reportNode, messageId: \"templateStringInClassName\" });\n }\n }\n },\n };\n },\n});\n"]}
@@ -0,0 +1 @@
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":";;;AAAA;;;;;;;;;;;;;;GAcG;AACH,oDAAiF;AAEjF,MAAM,UAAU,GAAG,mBAAW,CAAC,WAAW,CACxC,IAAI,CAAC,EAAE,CAAC,6FAA6F,IAAI,IAAI,IAAI,KAAK,CACvH,CAAC;AAKF,SAAS,sBAAsB,CAAC,cAAiC;IAC/D,IAAI,cAAc,CAAC,IAAI,KAAK,sBAAc,CAAC,gBAAgB,EAAE,CAAC;QAC5D,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,cAAc,CAAC,IAAI,KAAK,sBAAc,CAAC,eAAe,EAAE,CAAC;QAC3D,MAAM,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC;QACpC,IAAI,QAAQ,CAAC,IAAI,KAAK,sBAAc,CAAC,UAAU,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC7E,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAEY,QAAA,qBAAqB,GAAG,UAAU,CAAsB;IACnE,IAAI,EAAE,yBAAyB;IAC/B,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EAAE,qEAAqE;SACnF;QACD,QAAQ,EAAE;YACR,qBAAqB,EAAE,wEAAwE;YAC/F,WAAW,EAAE,2EAA2E;YACxF,gBAAgB,EAAE,iFAAiF;SACpG;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,eAAe,CAAC,IAA8B;gBAC5C,IAAI,sBAAsB,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;oBAChD,OAAO;gBACT,CAAC;gBACD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,uBAAuB,EAAE,CAAC,CAAC;YAC/D,CAAC;YACD,cAAc,CAAC,IAA6B;gBAC1C,IAAI,sBAAsB,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;oBAChD,OAAO;gBACT,CAAC;gBACD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,mBAAmB,CAAC,IAAkC;gBACpD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC1D,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["/**\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 { AST_NODE_TYPES, ESLintUtils, TSESTree } from \"@typescript-eslint/utils\";\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 MessageIds = \"angleBracketAssertion\" | \"asAssertion\" | \"nonNullAssertion\";\ntype Options = [];\n\nfunction isAllowedTypeAssertion(typeAnnotation: TSESTree.TypeNode): boolean {\n if (typeAnnotation.type === AST_NODE_TYPES.TSUnknownKeyword) {\n return true;\n }\n if (typeAnnotation.type === AST_NODE_TYPES.TSTypeReference) {\n const { typeName } = typeAnnotation;\n if (typeName.type === AST_NODE_TYPES.Identifier && typeName.name === \"const\") {\n return true;\n }\n }\n return false;\n}\n\nexport const noTypescriptAssertion = createRule<Options, MessageIds>({\n name: \"no-typescript-assertion\",\n meta: {\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 defaultOptions: [],\n create(context) {\n return {\n TSTypeAssertion(node: TSESTree.TSTypeAssertion) {\n if (isAllowedTypeAssertion(node.typeAnnotation)) {\n return;\n }\n context.report({ node, messageId: \"angleBracketAssertion\" });\n },\n TSAsExpression(node: TSESTree.TSAsExpression) {\n if (isAllowedTypeAssertion(node.typeAnnotation)) {\n return;\n }\n context.report({ node, messageId: \"asAssertion\" });\n },\n TSNonNullExpression(node: TSESTree.TSNonNullExpression) {\n context.report({ node, messageId: \"nonNullAssertion\" });\n },\n };\n },\n});\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expand-dynamic-key.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/no-unused-translation-key/expand-dynamic-key.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Type-aware expansion of dynamic (template-literal) translation keys.\n *\n * `used-strings.ts` detects dynamic keys via regex over raw source text, producing a\n * `{ prefix, suffix }` wildcard pattern that conservatively treats every key matching that\n * prefix/suffix as \"used\". This module replaces that conservative pattern with the exact set of\n * concrete keys whenever every interpolation in a direct `t(...)`/`i18nKey={...}` template\n * literal is a finite string-literal union — `` t(`cat.${category}`) `` with\n * `category: \"A\" | \"B\"` becomes `[\"cat.A\", \"cat.B\"]` instead of `{ prefix: \"cat.\", suffix: \"\" }`.\n *\n * This walks a real `ts.Program` (see `program-utils.ts`) directly, independent of ESLint's own\n * per-file visitor, because it needs to examine every file in a library — not just the one file\n * ESLint happens to be linting when it asks \"is this key used?\".\n *\n * Indirect/variable-built keys (`const key = \\`a.${x}\\`; t(key)`) are out of scope here and stay on\n * the existing regex path in `used-strings.ts` — this only matches template literals written\n * directly inside the call/attribute, mirroring the restriction `no-dynamic-translation-key`\n * itself enforces.\n */\nimport * as path from \"path\";\nimport * as ts from \"typescript\";\nimport { classifyInterpolationType } from \"../../utils/translation-key-types\";\n\n/**\n * The result of expanding one direct `t(...)`/`i18nKey={...}` template-literal call:\n * - `concrete` — every interpolation was a finite string-literal union, so every possible key is\n * enumerated exactly.\n * - `pattern` — at least one interpolation widened to `string`/`any`/`unknown`/an enum, so we fall\n * back to today's conservative prefix/suffix wildcard.\n */\nexport type ExpandedDynamicKey =\n | { kind: \"concrete\"; keys: Array<string> }\n | { kind: \"pattern\"; prefix: string; suffix: string };\n\nconst EXCLUDED_PATH_SEGMENTS = [\"/node_modules/\", \"/dist/\", \"/generated/\", \"/coverage/\", \"/.git/\"];\n\n// Type guard for TypeScript API. Mirrors `translation-key-types.ts`'s `isFiniteStringLiteralType`:\n// excludes `EnumLiteral` members, since string enum members carry both the `StringLiteral` and\n// `EnumLiteral` flags and enums are out of scope for concrete expansion (see Phase 1).\ntype StringLiteralType = ts.Type & { value: string };\n\nfunction isFiniteStringLiteralType(type: ts.Type): type is StringLiteralType {\n const flags = type.getFlags();\n return (flags & ts.TypeFlags.StringLiteral) !== 0 && (flags & ts.TypeFlags.EnumLiteral) === 0;\n}\n\n/**\n * Extracts the exact string values of a finite string-literal type or union thereof.\n *\n * A union may also carry an `undefined` member (see `classifyInterpolationType`, which still\n * classifies such a union as `literalUnion` as long as every *other* member is a string literal).\n * A real call site with that interpolation `undefined` produces the runtime string `\"undefined\"` --\n * JS template literals stringify `undefined` to the text \"undefined\" -- so that literal string is\n * included as one of the concrete values, alongside the real string-literal members.\n */\nconst getLiteralStringValues = (type: ts.Type): Array<string> => {\n if (type.isUnion()) {\n const literalValues = type.types.filter(isFiniteStringLiteralType).map(member => member.value);\n const hasUndefinedMember = type.types.some(member => (member.getFlags() & ts.TypeFlags.Undefined) !== 0);\n return hasUndefinedMember ? [...literalValues, \"undefined\"] : literalValues;\n }\n return isFiniteStringLiteralType(type) ? [type.value] : [];\n};\n\n/** A call to the translation function: either a bare `t(...)` or a member call like `i18n.t(...)`. */\nconst isTranslationCalleeNode = (callee: ts.Expression): boolean => {\n if (ts.isIdentifier(callee)) {\n return callee.text === \"t\";\n }\n return ts.isPropertyAccessExpression(callee) && callee.name.text === \"t\";\n};\n\n/** Today's conservative prefix/suffix wildcard: the static text before the first, and after the last, interpolation. */\nconst buildPatternFallback = (template: ts.TemplateExpression): ExpandedDynamicKey => {\n const spans = template.templateSpans;\n const lastSpan = spans[spans.length - 1];\n return {\n kind: \"pattern\",\n prefix: template.head.text,\n suffix: lastSpan ? lastSpan.literal.text : \"\",\n };\n};\n\n/**\n * Expands one direct template-literal call: concrete cross-product if every interpolation is a\n * finite string-literal union, otherwise the prefix/suffix fallback. Fails safe to the fallback\n * if type resolution throws for this call site, so one bad call can't crash the whole scan.\n */\nconst expandTemplate = (template: ts.TemplateExpression, checker: ts.TypeChecker): ExpandedDynamicKey => {\n try {\n const interpolations: Array<{ values: Array<string>; literalText: string }> = [];\n\n for (const span of template.templateSpans) {\n const type = checker.getTypeAtLocation(span.expression);\n if (classifyInterpolationType(type) !== \"literalUnion\") {\n return buildPatternFallback(template);\n }\n const values = getLiteralStringValues(type);\n if (values.length === 0) {\n return buildPatternFallback(template);\n }\n interpolations.push({ values, literalText: span.literal.text });\n }\n\n let keys: Array<string> = [template.head.text];\n for (const { values, literalText } of interpolations) {\n const next: Array<string> = [];\n for (const key of keys) {\n for (const value of values) {\n next.push(key + value + literalText);\n }\n }\n keys = next;\n }\n\n return { kind: \"concrete\", keys };\n } catch {\n return buildPatternFallback(template);\n }\n};\n\n/**\n * Unwraps `as`/`satisfies`/parenthesized wrappers to find a `TemplateExpression` underneath.\n *\n * `` t(`cat.${x}` as const) `` parses as an `AsExpression` whose `.expression` is the actual\n * `TemplateExpression` — the wrapper is invisible to a direct `ts.isTemplateExpression(node)` check,\n * even though the checker still reports a type for the inner expression regardless of the wrapper\n * (see `expandTemplate`'s use of `checker.getTypeAtLocation(span.expression)`, which is unaffected by\n * unwrapping here since spans live inside the returned node either way).\n */\nconst unwrapToTemplateExpression = (node: ts.Node): ts.TemplateExpression | null => {\n let current = node;\n while (ts.isAsExpression(current) || ts.isSatisfiesExpression(current) || ts.isParenthesizedExpression(current)) {\n current = current.expression;\n }\n return ts.isTemplateExpression(current) ? current : null;\n};\n\n/**\n * Whether a JSX attribute is `i18nKey={...}` with a template-literal expression inside the braces.\n */\nconst getI18nKeyTemplate = (node: ts.JsxAttribute): ts.TemplateExpression | null => {\n if (!ts.isIdentifier(node.name) || node.name.text !== \"i18nKey\") {\n return null;\n }\n const { initializer } = node;\n if (initializer && ts.isJsxExpression(initializer) && initializer.expression) {\n return unwrapToTemplateExpression(initializer.expression);\n }\n return null;\n};\n\n/** Finds every direct `t(`...${...}...`)`/`i18nKey={`...${...}...`}` template literal in a source file. */\nconst collectDirectTemplates = (sourceFile: ts.SourceFile): Array<ts.TemplateExpression> => {\n const templates: Array<ts.TemplateExpression> = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node) && isTranslationCalleeNode(node.expression)) {\n const [firstArg] = node.arguments;\n const template = firstArg && unwrapToTemplateExpression(firstArg);\n if (template) {\n templates.push(template);\n }\n } else if (ts.isJsxAttribute(node)) {\n const template = getI18nKeyTemplate(node);\n if (template) {\n templates.push(template);\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return templates;\n};\n\n/**\n * Whether a source file belongs to the library's own real source — not a declaration file, a\n * vendored/build artifact, or a file pulled into the `Program` only because the library imports it\n * (`ts.createProgram` includes every file transitively reachable from `rootNames`, which in this\n * monorepo's path-mapped setup means every other library the library under scan depends on).\n * `projectDirPrefix` restricts the scan back to files actually inside the library being linted, so a\n * dependency's own `t(...)` calls aren't misattributed as this library's usage (and aren't re-walked\n * on every dependent library's lint pass).\n */\nconst isScannableSourceFile = (sourceFile: ts.SourceFile, projectDirPrefix: string): boolean => {\n if (sourceFile.isDeclarationFile) {\n return false;\n }\n const fileName = sourceFile.fileName.replace(/\\\\/g, \"/\");\n if (!/\\.tsx?$/.test(fileName)) {\n return false;\n }\n if (!fileName.startsWith(projectDirPrefix)) {\n return false;\n }\n return !EXCLUDED_PATH_SEGMENTS.some(segment => fileName.includes(segment));\n};\n\n/**\n * Walks every real source file belonging to `projectDir` within `program`, expanding each direct\n * `t(...)`/`i18nKey={...}` template-literal call into either its exact concrete keys or a\n * conservative prefix/suffix pattern (see `ExpandedDynamicKey`).\n *\n * `program` may contain many more files than just this library's own — see `isScannableSourceFile` —\n * so `projectDir` scopes the walk to the library actually being linted.\n *\n * Indirect/variable-built keys (`const key = \\`a.${x}\\`; t(key)`) are not matched — only template\n * literals written directly inside the call/attribute are.\n */\nexport const expandDynamicKeysInProgram = (\n program: ts.Program,\n checker: ts.TypeChecker,\n projectDir: string\n): Array<ExpandedDynamicKey> => {\n const resolvedProjectDir = path.resolve(projectDir).replace(/\\\\/g, \"/\");\n const projectDirPrefix = resolvedProjectDir.endsWith(\"/\") ? resolvedProjectDir : `${resolvedProjectDir}/`;\n\n const results: Array<ExpandedDynamicKey> = [];\n\n for (const sourceFile of program.getSourceFiles()) {\n if (!isScannableSourceFile(sourceFile, projectDirPrefix)) {\n continue;\n }\n\n for (const template of collectDirectTemplates(sourceFile)) {\n results.push(expandTemplate(template, checker));\n }\n }\n\n return results;\n};\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unused-translation-key.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/no-unused-translation-key/no-unused-translation-key.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESLint rule that reports translation keys defined in a library's\n * `locales/en/translation.json` that are never referenced anywhere in that\n * library's source.\n *\n * Each library owns its own namespaced translations (see docs/tech/I18N.md), so\n * a key is considered used when it appears quoted anywhere in the owning\n * library's `.ts`/`.tsx` files (\"conservative-anywhere\" matching). Keys reached\n * through a dynamic template (`` t(`prefix.${x}`) ``) are kept via the static\n * text bookending the interpolation, and i18next plural forms are kept whenever\n * their base key is referenced. A dynamic template keeps every key under its\n * prefix, so keys behind one cannot be reported until the call site uses a\n * static key — what the companion `no-dynamic-translation-key` rule asks for.\n *\n * Matching is a heuristic over source text: this rule has no autofix, so a key\n * it reports is still a candidate for review before applying the removal\n * suggestion.\n *\n * Runs only on `**\\/locales/en/translation.json` — the English file is the\n * source of truth; other languages are managed by Crowdin.\n */\nimport { ESLintUtils, TSESTree } from \"@typescript-eslint/utils\";\nimport * as path from \"path\";\nimport { findNearestFile } from \"../../utils/file-utils\";\nimport { getUsedTranslationStrings, isTranslationKeyUsed, type UsedStrings } from \"./used-strings\";\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 MessageIds = \"unusedKey\" | \"removeUnusedKey\";\ntype Options = [];\n\nconst TRANSLATION_FILE_SUFFIX = \"/locales/en/translation.json\";\n\n/**\n * The slice of `jsonc-eslint-parser`'s AST this rule visits. Declared here\n * because the parser ships ESM-only declarations (`lib/index.d.mts`) whose named\n * type exports are invisible to this CommonJS-compiled package — importing\n * anything from it fails with \"has no exported member\". The shapes are covered\n * end-to-end by the specs, which run the real parser.\n */\ninterface JsonNode {\n readonly range: readonly [number, number];\n}\n\ninterface JsonIdentifierKey {\n readonly type: \"JSONIdentifier\";\n readonly name: string;\n readonly loc: TSESTree.SourceLocation;\n}\n\ninterface JsonLiteralKey {\n readonly type: \"JSONLiteral\";\n readonly value: string | number | boolean | null;\n readonly loc: TSESTree.SourceLocation;\n}\n\ninterface JsonObjectExpression extends JsonNode {\n readonly properties: ReadonlyArray<JsonProperty>;\n readonly parent: { readonly type: string };\n}\n\ninterface JsonProperty extends JsonNode {\n readonly key: JsonIdentifierKey | JsonLiteralKey;\n readonly parent: JsonObjectExpression;\n}\n\nconst getKeyText = (key: JsonProperty[\"key\"]): string => (key.type === \"JSONIdentifier\" ? key.name : String(key.value));\n\n/**\n * The source range to drop so the remaining object stays valid JSON: a property\n * with a following sibling reaches up to that sibling (taking its own separating\n * comma and indentation with it), a trailing property reaches back to the end of\n * the preceding one, and a sole property leaves an empty object behind.\n */\nconst getRemovalRange = (property: JsonProperty): [number, number] => {\n const siblings = property.parent.properties;\n const position = siblings.indexOf(property);\n const nextSibling = siblings[position + 1];\n const previousSibling = siblings[position - 1];\n\n if (nextSibling) {\n return [property.range[0], nextSibling.range[0]];\n }\n if (previousSibling) {\n return [previousSibling.range[1], property.range[1]];\n }\n\n return [property.parent.range[0] + 1, property.parent.range[1] - 1];\n};\n\nexport const noUnusedTranslationKey = createRule<Options, MessageIds>({\n name: \"no-unused-translation-key\",\n meta: {\n type: \"suggestion\",\n hasSuggestions: true,\n docs: {\n description:\n \"Report translation keys in a library's locales/en/translation.json that are never referenced in that library's source.\",\n },\n messages: {\n unusedKey: 'Translation key \"{{key}}\" is not referenced anywhere in this library and can be removed.',\n removeUnusedKey: 'Remove translation key \"{{key}}\".',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const filePath = context.filename.replace(/\\\\/g, \"/\");\n if (!filePath.endsWith(TRANSLATION_FILE_SUFFIX)) {\n return {};\n }\n\n const projectJsonPath = findNearestFile(context.filename, \"project.json\");\n if (!projectJsonPath) {\n return {};\n }\n\n // Scanning the whole library is expensive, so only pay for it once a key is actually inspected.\n let usedStrings: UsedStrings | undefined;\n const getUsed = (): UsedStrings => {\n usedStrings ??= getUsedTranslationStrings(path.dirname(projectJsonPath));\n return usedStrings;\n };\n\n return {\n JSONProperty: (property: JsonProperty) => {\n // Translation files are flat; anything nested belongs to a value, not to a key.\n if (property.parent.parent.type !== \"JSONExpressionStatement\") {\n return;\n }\n\n const key = getKeyText(property.key);\n if (isTranslationKeyUsed(getUsed(), key)) {\n return;\n }\n\n context.report({\n messageId: \"unusedKey\",\n data: { key },\n loc: property.key.loc,\n suggest: [\n {\n messageId: \"removeUnusedKey\",\n data: { key },\n fix: fixer => fixer.removeRange(getRemovalRange(property)),\n },\n ],\n });\n },\n };\n },\n});\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"used-strings.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/no-unused-translation-key/used-strings.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from \"fs\";\nimport * as path from \"path\";\nimport { getLibraryProgram } from \"../../utils/program-utils\";\nimport { expandDynamicKeysInProgram } from \"./expand-dynamic-key\";\n\n/** The static parts surrounding the interpolation(s) in a dynamically built key. */\nexport type DynamicPattern = {\n /** Static text before the first `${...}` (empty for leading interpolation). */\n prefix: string;\n /** Static text after the last `${...}` (empty for trailing interpolation). */\n suffix: string;\n};\n\n/**\n * What a library's source says about the translation keys it uses.\n *\n * - `sourceText` — the library's `.ts`/`.tsx` files concatenated. A key is used when it appears in\n * this text wrapped in quotes. Searching for the key itself puts no constraints on what a key may\n * contain, so keys with spaces (`machine.types.Air Compressor`), empty segments (`a..b`) and\n * punctuation (`p&pMigrated`) all match. Matching is deliberately broad (\"conservative-anywhere\"):\n * a key counts as used wherever it appears, including as a prop passed to a child component\n * (e.g. `titleKey=\"a.b\"`).\n * - `dynamicPatterns` — the static bookends of a dynamically built key, e.g.\n * `` t(`pages.categories.${x}`) `` contributes `{ prefix: \"pages.categories.\", suffix: \"\" }` and\n * `` t(`${root}.title`) `` contributes `{ prefix: \"\", suffix: \".title\" }`. A key is treated as used\n * when it both starts with the prefix and ends with the suffix of some pattern — the smallest set of\n * keys the interpolation could possibly produce. Populated both by the regex scan below (for\n * indirect/variable-built templates, and for direct templates when a type-aware `ts.Program`\n * couldn't be built) and by `expandDynamicKeysInProgram`'s `\"pattern\"` results (for direct\n * templates whose interpolation type widened past a finite literal union).\n * - `concreteDynamicKeys` — exact keys a direct `` t(`...${x}...`) ``/`i18nKey={...}` template can\n * produce, when every interpolation resolves to a finite string-literal union. Computed by\n * `expandDynamicKeysInProgram` walking a real type-checked `ts.Program` (see `program-utils.ts`).\n * Precise, unlike `dynamicPatterns`: a key under the same static prefix that isn't one of these\n * exact keys is correctly reported as unused.\n */\nexport type UsedStrings = {\n sourceText: string;\n dynamicPatterns: Array<DynamicPattern>;\n concreteDynamicKeys: Set<string>;\n};\n\nconst EXCLUDED_DIRS = new Set([\"node_modules\", \"dist\", \"generated\", \"coverage\", \".git\"]);\nconst SCANNED_EXTENSIONS = [\".ts\", \".tsx\"];\n\n/**\n * The plural (and optional ordinal) suffix i18next appends to a base key at runtime: `entityCount`\n * resolves to `entityCount_one` or `entityCount_other` depending on the `count` option. Source code\n * only ever mentions the base key, so a suffixed key is used whenever its base key is.\n */\nconst PLURAL_SUFFIX_REGEX = /_(?:ordinal_)?(?:zero|one|two|few|many|other|plural)$/;\n\n/** A template literal passed directly to `t(`/`.t(` or an `i18nKey={` attribute. */\nconst TRANSLATION_TEMPLATE_REGEX = /(?:\\bt\\(\\s*|i18nKey=\\{\\s*)`([^`]*)`/g;\n/** Any template literal — catches keys built through a variable, e.g. `const k = `a.b.${x}`; t(k)`. */\nconst ANY_TEMPLATE_REGEX = /`([^`]*)`/g;\n\n/**\n * Record the dynamic-key pattern for a template body, if applicable.\n *\n * `trusted` templates (written directly inside `t(...)`/`i18nKey={...}`) always contribute a pattern —\n * including a bare `${x}` (empty prefix and suffix), which acts as a wildcard for libraries whose whole\n * key is dynamic (e.g. `t(`${category}`)`). Untrusted templates (any other backtick string, e.g. one\n * assigned to a variable then passed to `t`) only contribute when they look like a namespaced key — a\n * dot in the prefix or suffix — so unrelated templates like `${count}` or `hello ${name}` don't\n * wildcard-disable detection.\n */\nconst recordTemplatePattern = (body: string, trusted: boolean, patterns: Map<string, DynamicPattern>): void => {\n const interpolationStart = body.indexOf(\"${\");\n if (interpolationStart === -1) {\n // A backtick string with no interpolation is a plain key; sourceText already covers it.\n return;\n }\n const prefix = body.slice(0, interpolationStart);\n const lastInterpolationEnd = body.lastIndexOf(\"}\");\n const suffix = lastInterpolationEnd === -1 ? \"\" : body.slice(lastInterpolationEnd + 1);\n if (!trusted && !prefix.includes(\".\") && !suffix.includes(\".\")) {\n return;\n }\n patterns.set(JSON.stringify([prefix, suffix]), { prefix, suffix });\n};\n\nconst collectSourceFiles = (dir: string, files: Array<string> = []): Array<string> => {\n let entries: Array<fs.Dirent>;\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return files;\n }\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (!EXCLUDED_DIRS.has(entry.name)) {\n collectSourceFiles(fullPath, files);\n }\n } else if (SCANNED_EXTENSIONS.some(ext => entry.name.endsWith(ext))) {\n files.push(fullPath);\n }\n }\n return files;\n};\n\n/**\n * Build the used-strings summary from already-read source file contents.\n *\n * Kept free of filesystem access so the ESLint rule and the\n * `devtools/translations/remove-unused-translation-keys` script share one implementation of what\n * \"used\" means, each doing its own IO.\n *\n * `skipTrustedTemplates` — when a type-aware `ts.Program` was available and already produced precise\n * results for every direct `t(...)`/`i18nKey={...}` template (see `getUsedTranslationStrings`), the\n * regex-based \"trusted\" detection is redundant and would only re-introduce the conservative\n * prefix/suffix wildcard the Program-based scan was meant to replace. When `true`, the trusted regex\n * pass is skipped, and any template body it would have matched is also excluded from the \"untrusted\"\n * pass (which otherwise re-discovers the same body via `ANY_TEMPLATE_REGEX` and — for a namespaced,\n * dotted key — records the same wildcard pattern anyway). Defaults to `false`, preserving today's\n * behavior exactly for libraries with no resolvable tsconfig.\n */\nexport const buildUsedStrings = (\n contents: Array<string>,\n options: { skipTrustedTemplates?: boolean } = {}\n): UsedStrings => {\n const { skipTrustedTemplates = false } = options;\n const patterns = new Map<string, DynamicPattern>();\n\n for (const content of contents) {\n const trustedBodies = new Set<string>();\n\n let translationTemplate: RegExpExecArray | null;\n while ((translationTemplate = TRANSLATION_TEMPLATE_REGEX.exec(content)) !== null) {\n const body = translationTemplate[1] ?? \"\";\n trustedBodies.add(body);\n if (!skipTrustedTemplates) {\n recordTemplatePattern(body, true, patterns);\n }\n }\n\n let anyTemplate: RegExpExecArray | null;\n while ((anyTemplate = ANY_TEMPLATE_REGEX.exec(content)) !== null) {\n const body = anyTemplate[1] ?? \"\";\n if (skipTrustedTemplates && trustedBodies.has(body)) {\n continue;\n }\n recordTemplatePattern(body, false, patterns);\n }\n }\n\n return { sourceText: contents.join(\"\\n\"), dynamicPatterns: [...patterns.values()], concreteDynamicKeys: new Set() };\n};\n\n/**\n * Read a library's `.ts`/`.tsx` source, keeping the raw text plus any dynamic key patterns it builds.\n *\n * Also attempts a type-aware pass: `getLibraryProgram` builds (and caches) a `ts.Program` scoped to\n * the library, and `expandDynamicKeysInProgram` walks it to resolve every direct\n * `t(...)`/`i18nKey={...}` template literal into either its exact concrete keys (when every\n * interpolation is a finite string-literal union) or today's conservative `{prefix, suffix}` pattern\n * (when any interpolation widened). When that succeeds, it fully replaces the regex-based \"trusted\"\n * template detection (see `buildUsedStrings`'s `skipTrustedTemplates`), since it covers the same call\n * sites more precisely. When no tsconfig can be resolved, or anything throws, this step is skipped\n * entirely and behavior falls back to the regex-only detection that ran before this rule was made\n * type-aware.\n */\nexport const getUsedTranslationStrings = (projectDir: string): UsedStrings => {\n const contents: Array<string> = [];\n\n for (const file of collectSourceFiles(projectDir)) {\n try {\n contents.push(fs.readFileSync(file, \"utf8\"));\n } catch {\n continue;\n }\n }\n\n let expandedResults: ReturnType<typeof expandDynamicKeysInProgram> | null = null;\n try {\n const libraryProgram = getLibraryProgram(projectDir);\n if (libraryProgram) {\n expandedResults = expandDynamicKeysInProgram(libraryProgram.program, libraryProgram.checker, projectDir);\n }\n } catch {\n expandedResults = null;\n }\n\n const used = buildUsedStrings(contents, { skipTrustedTemplates: expandedResults !== null });\n\n if (expandedResults) {\n const patternKeys = new Set(used.dynamicPatterns.map(({ prefix, suffix }) => JSON.stringify([prefix, suffix])));\n\n for (const result of expandedResults) {\n if (result.kind === \"concrete\") {\n for (const key of result.keys) {\n used.concreteDynamicKeys.add(key);\n }\n continue;\n }\n\n const patternKey = JSON.stringify([result.prefix, result.suffix]);\n if (!patternKeys.has(patternKey)) {\n patternKeys.add(patternKey);\n used.dynamicPatterns.push({ prefix: result.prefix, suffix: result.suffix });\n }\n }\n }\n\n return used;\n};\n\n/** Whether the key appears in the source as a quoted string, in any of the three quote styles. */\nconst isQuotedInSource = (sourceText: string, key: string): boolean =>\n sourceText.includes(`\"${key}\"`) || sourceText.includes(`'${key}'`) || sourceText.includes(`\\`${key}\\``);\n\n/**\n * Whether a translation key is referenced by the scanned source — quoted verbatim, through a dynamic\n * template's static bookends, as one of the exact keys a type-aware dynamic template can produce, or\n * as the plural form of a referenced base key.\n */\nexport const isTranslationKeyUsed = (used: UsedStrings, key: string): boolean => {\n const isReferenced = (candidate: string): boolean =>\n isQuotedInSource(used.sourceText, candidate) ||\n used.concreteDynamicKeys.has(candidate) ||\n used.dynamicPatterns.some(({ prefix, suffix }) => candidate.startsWith(prefix) && candidate.endsWith(suffix));\n\n return isReferenced(key) || (PLURAL_SUFFIX_REGEX.test(key) && isReferenced(key.replace(PLURAL_SUFFIX_REGEX, \"\")));\n};\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"one-component-per-file.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/one-component-per-file/one-component-per-file.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAEH,oDAAuD;AACvD,iEAAyE;AAEzE,MAAM,UAAU,GAAG,mBAAW,CAAC,WAAW,CACxC,IAAI,CAAC,EAAE,CAAC,6FAA6F,IAAI,IAAI,IAAI,KAAK,CACvH,CAAC;AAMW,QAAA,mBAAmB,GAAG,UAAU,CAAsB;IACjE,IAAI,EAAE,wBAAwB;IAC9B,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EACT,+DAA+D;gBAC/D,4EAA4E;SAC/E;QACD,QAAQ,EAAE;YACR,eAAe,EAAE,sFAAsF;YACvG,iBAAiB,EAAE,8EAA8E;SAClG;QACD,MAAM,EAAE;YACN;gBACE,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,YAAY,EAAE;wBACZ,IAAI,EAAE,SAAS;wBACf,WAAW,EACT,6DAA6D;4BAC7D,gEAAgE;qBACnE;iBACF;gBACD,oBAAoB,EAAE,KAAK;aAC5B;SACF;KACF;IACD,cAAc,EAAE,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;IACxC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,YAAY,GAAG,IAAI,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAA,4CAA0B,GAAE,CAAC;QAE7C,OAAO;YACL,mBAAmB,EAAE,OAAO,CAAC,qBAAqB;YAClD,kBAAkB,EAAE,OAAO,CAAC,oBAAoB;YAChD,sBAAsB,EAAE,OAAO,CAAC,wBAAwB;YACxD,wBAAwB,EAAE,OAAO,CAAC,0BAA0B;YAE5D,cAAc;gBACZ,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;gBAE/F,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC;oBAAE,OAAO;gBAEnC,MAAM,SAAS,GAAe,YAAY,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,mBAAmB,CAAC;gBAErF,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC3C,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;gBACtC,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["/**\n * ESLint rule: one-component-per-file\n *\n * Enforces that each file contains at most one React component.\n *\n * Default mode (`exportedOnly: true`): only one exported component is allowed.\n * Internal/private components are fine.\n *\n * Strict mode (`exportedOnly: false`): only one component total, regardless of\n * whether it is exported.\n *\n * @example\n * // Bad (default) — two exported components\n * export function Foo() { return <div />; }\n * export function Bar() { return <span />; }\n *\n * // Good (default) — one exported, one internal\n * function InternalHelper() { return <span />; }\n * export function Foo() { return <div><InternalHelper /></div>; }\n */\n\nimport { ESLintUtils } from \"@typescript-eslint/utils\";\nimport { createComponentFileTracker } from \"../../utils/component-utils\";\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 MessageIds = \"tooManyExported\" | \"tooManyComponents\";\n\ntype Options = [{ exportedOnly?: boolean }];\n\nexport const oneComponentPerFile = createRule<Options, MessageIds>({\n name: \"one-component-per-file\",\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Enforce that each file contains at most one React component. \" +\n \"Use exportedOnly: false to also disallow multiple non-exported components.\",\n },\n messages: {\n tooManyExported: \"Only one component should be exported per file. Move this component to its own file.\",\n tooManyComponents: \"Only one component is allowed per file. Move this component to its own file.\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n exportedOnly: {\n type: \"boolean\",\n description:\n \"When true (default), only exported components are counted. \" +\n \"When false, all components in the file count toward the limit.\",\n },\n },\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [{ exportedOnly: true }],\n create(context, [{ exportedOnly = true }]) {\n const tracker = createComponentFileTracker();\n\n return {\n FunctionDeclaration: tracker.onFunctionDeclaration,\n VariableDeclarator: tracker.onVariableDeclarator,\n ExportNamedDeclaration: tracker.onExportNamedDeclaration,\n ExportDefaultDeclaration: tracker.onExportDefaultDeclaration,\n\n \"Program:exit\"() {\n const components = exportedOnly ? tracker.getExportedComponents() : tracker.getAllComponents();\n\n if (components.length <= 1) return;\n\n const messageId: MessageIds = exportedOnly ? \"tooManyExported\" : \"tooManyComponents\";\n\n for (const { node } of components.slice(1)) {\n context.report({ node, messageId });\n }\n },\n };\n },\n});\n"]}
@@ -0,0 +1 @@
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":"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","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"]}
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1 @@
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":";AAAA;;;;;;;;;GASG;;;AAEH,oDAA2F;AAG3F,oCAAwG;AAQ3F,QAAA,iBAAiB,GAAwC;IACpE,wBAAwB,EACtB,8GAA8G;QAC9G,0FAA0F;IAC5F,+BAA+B,EAC7B,8GAA8G;QAC9G,0FAA0F;IAC5F,qBAAqB,EAAE,kFAAkF;IACzG,uBAAuB,EAAE,iFAAiF;CAC3G,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,iBAAiB,GAAG,CAAC,aAAqB,EAAU,EAAE;IAC1D,oEAAoE;IACpE,kEAAkE;IAClE,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtD,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC;AACrC,CAAC,CAAC;AAQF;;;;;;GAMG;AACH,MAAM,uBAAuB,GAAG,CAAC,IAAa,EAAE,OAAuB,EAAW,EAAE;IAClF,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAE9C,qDAAqD;IACrD,IACE,UAAU,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QACxC,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC;QAClC,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC,EAClC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iDAAiD;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC5C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,SAAS,CAAC,aAAa,EAAE,CAAC;QACzC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YAC7B,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,SAAS,GAAG,OAAO,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;gBACtD,MAAM,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;gBAExD,IAAI,eAAe,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;oBAC3C,OAAO,IAAI,CAAC;gBACd,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACI,MAAM,cAAc,GAAG,CAC5B,IAA2B,EAC3B,OAA0E,EAC1E,YAAmC,EACnC,MAA6B,EACvB,EAAE;IACR,6EAA6E;IAC7E,mDAAmD;IACnD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAChC,OAAO;IACT,CAAC;IAED,8CAA8C;IAC9C,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,sBAAc,CAAC,aAAa,EAAE,CAAC;QACpD,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IAEhC,uFAAuF;IACvF,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,mDAAmD;IACnD,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,sBAAc,CAAC,sBAAsB,EAAE,CAAC;QAC7E,OAAO;IACT,CAAC;IAED,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;IAClC,IAAI,UAAU,CAAC,IAAI,KAAK,sBAAc,CAAC,kBAAkB,EAAE,CAAC;QAC1D,OAAO;IACT,CAAC;IAED,2EAA2E;IAC3E,MAAM,MAAM,GAAG,IAAA,iCAAyB,EAAC,UAAU,CAAC,CAAC;IAErD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;IACT,CAAC;IAED,4CAA4C;IAC5C,IAAI,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;QAC/C,OAAO;IACT,CAAC;IAED,qEAAqE;IACrE,4EAA4E;IAC5E,IAAI,MAAM,CAAC,YAAY,KAAK,QAAQ,IAAI,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjF,OAAO;IACT,CAAC;IAED,8EAA8E;IAC9E,wDAAwD;IACxD,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1C,OAAO;IACT,CAAC;IAED,0DAA0D;IAC1D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,mBAAW,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;QAElD,uEAAuE;QACvE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,qBAAqB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAExE,mEAAmE;QACnE,0EAA0E;QAC1E,iFAAiF;QACjF,8DAA8D;QAC9D,MAAM,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC,gBAAiC,CAAC,CAAC;QAEpF,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,uBAAuB,CAAC,cAAc,EAAE,OAAO,CAAC,EAAE,CAAC;YACtD,OAAO;QACT,CAAC;QAED,8FAA8F;QAC9F,MAAM,aAAa,GAAG,IAAA,gCAAwB,EAAC,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QAE9E,4EAA4E;QAC5E,MAAM,SAAS,GAAwB,MAAM,CAAC,SAAS;YACrD,CAAC,CAAC,iCAAiC;YACnC,CAAC,CAAC,0BAA0B,CAAC;QAE/B,wEAAwE;QACxE,MAAM,WAAW,GAAgC,EAAE,CAAC;QACpD,MAAM,SAAS,GAAG,IAAA,+BAAuB,EAAC,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QAE1E,IAAI,SAAS,EAAE,CAAC;YACd,+DAA+D;YAC/D,WAAW,CAAC,IAAI,CAAC;gBACf,SAAS,EAAE,uBAAuB;gBAClC,IAAI,EAAE;oBACJ,OAAO,EAAE,MAAM,CAAC,YAAY;oBAC5B,SAAS,EAAE,aAAa;iBACzB;gBACD,GAAG,EAAE,KAAK,CAAC,EAAE;oBACX,OAAO,SAAS,CAAC,gBAAgB,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,CAAC;gBACxG,CAAC;aACF,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,+DAA+D;YAC/D,WAAW,CAAC,IAAI,CAAC;gBACf,SAAS,EAAE,yBAAyB;gBACpC,IAAI,EAAE;oBACJ,OAAO,EAAE,MAAM,CAAC,YAAY;oBAC5B,SAAS,EAAE,aAAa;iBACzB;gBACD,GAAG,EAAE,KAAK,CAAC,EAAE;oBACX,OAAO,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;gBACjE,CAAC;aACF,CAAC,CAAC;QACL,CAAC;QAED,OAAO,CAAC,MAAM,CAAC;YACb,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,SAAS;YACT,IAAI,EAAE;gBACJ,OAAO,EAAE,MAAM,CAAC,YAAY;gBAC5B,SAAS,EAAE,aAAa;gBACxB,KAAK,EAAE,QAAQ;gBACf,cAAc,EAAE,iBAAiB,CAAC,aAAa,CAAC;aACjD;YACD,OAAO,EAAE,WAAW;SACrB,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,kDAAkD;QAClD,2DAA2D;QAC3D,OAAO;IACT,CAAC;AACH,CAAC,CAAC;AAtIW,QAAA,cAAc,kBAsIzB","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"]}
@@ -0,0 +1 @@
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 * 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 * Result of extracting a callback identifier from an expression.\n * Does not include suggested names 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"]}
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1 @@
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"]}