@trackunit/eslint-plugin-trackunit 0.0.2

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 (147) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +117 -0
  3. package/package.json +31 -0
  4. package/src/index.d.ts +8 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/config/fragments/ignores.d.ts +2 -0
  8. package/src/lib/config/fragments/ignores.js +18 -0
  9. package/src/lib/config/fragments/ignores.js.map +1 -0
  10. package/src/lib/config/fragments/import-rules.d.ts +3 -0
  11. package/src/lib/config/fragments/import-rules.js +58 -0
  12. package/src/lib/config/fragments/import-rules.js.map +1 -0
  13. package/src/lib/config/fragments/jest-overrides.d.ts +2 -0
  14. package/src/lib/config/fragments/jest-overrides.js +30 -0
  15. package/src/lib/config/fragments/jest-overrides.js.map +1 -0
  16. package/src/lib/config/fragments/jsdoc-rules.d.ts +3 -0
  17. package/src/lib/config/fragments/jsdoc-rules.js +71 -0
  18. package/src/lib/config/fragments/jsdoc-rules.js.map +1 -0
  19. package/src/lib/config/fragments/module-boundaries.d.ts +2 -0
  20. package/src/lib/config/fragments/module-boundaries.js +92 -0
  21. package/src/lib/config/fragments/module-boundaries.js.map +1 -0
  22. package/src/lib/config/fragments/react-rules.d.ts +5 -0
  23. package/src/lib/config/fragments/react-rules.js +137 -0
  24. package/src/lib/config/fragments/react-rules.js.map +1 -0
  25. package/src/lib/config/fragments/restricted-imports.d.ts +2 -0
  26. package/src/lib/config/fragments/restricted-imports.js +58 -0
  27. package/src/lib/config/fragments/restricted-imports.js.map +1 -0
  28. package/src/lib/config/fragments/testing-library.d.ts +2 -0
  29. package/src/lib/config/fragments/testing-library.js +7 -0
  30. package/src/lib/config/fragments/testing-library.js.map +1 -0
  31. package/src/lib/config/fragments/typescript-rules.d.ts +2 -0
  32. package/src/lib/config/fragments/typescript-rules.js +97 -0
  33. package/src/lib/config/fragments/typescript-rules.js.map +1 -0
  34. package/src/lib/config/index.d.ts +863 -0
  35. package/src/lib/config/index.js +10 -0
  36. package/src/lib/config/index.js.map +1 -0
  37. package/src/lib/config/plugins.d.ts +90 -0
  38. package/src/lib/config/plugins.js +44 -0
  39. package/src/lib/config/plugins.js.map +1 -0
  40. package/src/lib/config/presets/base.d.ts +265 -0
  41. package/src/lib/config/presets/base.js +145 -0
  42. package/src/lib/config/presets/base.js.map +1 -0
  43. package/src/lib/config/presets/e2e.d.ts +10 -0
  44. package/src/lib/config/presets/e2e.js +19 -0
  45. package/src/lib/config/presets/e2e.js.map +1 -0
  46. package/src/lib/config/presets/public-api.d.ts +147 -0
  47. package/src/lib/config/presets/public-api.js +62 -0
  48. package/src/lib/config/presets/public-api.js.map +1 -0
  49. package/src/lib/config/presets/react.d.ts +598 -0
  50. package/src/lib/config/presets/react.js +97 -0
  51. package/src/lib/config/presets/react.js.map +1 -0
  52. package/src/lib/config/presets/server.d.ts +36 -0
  53. package/src/lib/config/presets/server.js +37 -0
  54. package/src/lib/config/presets/server.js.map +1 -0
  55. package/src/lib/config/utils.d.ts +6 -0
  56. package/src/lib/config/utils.js +28 -0
  57. package/src/lib/config/utils.js.map +1 -0
  58. package/src/lib/config-helpers/create-skip-when.d.ts +35 -0
  59. package/src/lib/config-helpers/create-skip-when.js +54 -0
  60. package/src/lib/config-helpers/create-skip-when.js.map +1 -0
  61. package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.d.ts +16 -0
  62. package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.js +83 -0
  63. package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.js.map +1 -0
  64. package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.d.ts +4 -0
  65. package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.js +297 -0
  66. package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.js.map +1 -0
  67. package/src/lib/rules/no-internal-barrel-files/examples.d.ts +80 -0
  68. package/src/lib/rules/no-internal-barrel-files/examples.js +84 -0
  69. package/src/lib/rules/no-internal-barrel-files/examples.js.map +1 -0
  70. package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.d.ts +29 -0
  71. package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.js +178 -0
  72. package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.js.map +1 -0
  73. package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.d.ts +5 -0
  74. package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.js +67 -0
  75. package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.js.map +1 -0
  76. package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.d.ts +2 -0
  77. package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.js +34 -0
  78. package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.js.map +1 -0
  79. package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.d.ts +16 -0
  80. package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.js +55 -0
  81. package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.js.map +1 -0
  82. package/src/lib/rules/no-typescript-assertion/examples.d.ts +1 -0
  83. package/src/lib/rules/no-typescript-assertion/examples.js +45 -0
  84. package/src/lib/rules/no-typescript-assertion/examples.js.map +1 -0
  85. package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.d.ts +20 -0
  86. package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.js +83 -0
  87. package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.js.map +1 -0
  88. package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.d.ts +73 -0
  89. package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.js +333 -0
  90. package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.js.map +1 -0
  91. package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.d.ts +56 -0
  92. package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.js +225 -0
  93. package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.js.map +1 -0
  94. package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.d.ts +49 -0
  95. package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.js +75 -0
  96. package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.js.map +1 -0
  97. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.d.ts +32 -0
  98. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.js +143 -0
  99. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.js.map +1 -0
  100. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.d.ts +27 -0
  101. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.js +196 -0
  102. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.js.map +1 -0
  103. package/src/lib/rules/prefer-event-specific-callback-naming/utils.d.ts +76 -0
  104. package/src/lib/rules/prefer-event-specific-callback-naming/utils.js +245 -0
  105. package/src/lib/rules/prefer-event-specific-callback-naming/utils.js.map +1 -0
  106. package/src/lib/rules/prefer-field-components/prefer-field-components.d.ts +4 -0
  107. package/src/lib/rules/prefer-field-components/prefer-field-components.js +289 -0
  108. package/src/lib/rules/prefer-field-components/prefer-field-components.js.map +1 -0
  109. package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.d.ts +26 -0
  110. package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.js +402 -0
  111. package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.js.map +1 -0
  112. package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.d.ts +13 -0
  113. package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.js +271 -0
  114. package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.js.map +1 -0
  115. package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.d.ts +15 -0
  116. package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.js +245 -0
  117. package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.js.map +1 -0
  118. package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.d.ts +17 -0
  119. package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.js +133 -0
  120. package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.js.map +1 -0
  121. package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.d.ts +12 -0
  122. package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.js +128 -0
  123. package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.js.map +1 -0
  124. package/src/lib/rules-map.d.ts +66 -0
  125. package/src/lib/rules-map.js +34 -0
  126. package/src/lib/rules-map.js.map +1 -0
  127. package/src/lib/utils/ast-utils.d.ts +85 -0
  128. package/src/lib/utils/ast-utils.js +530 -0
  129. package/src/lib/utils/ast-utils.js.map +1 -0
  130. package/src/lib/utils/classname-utils.d.ts +150 -0
  131. package/src/lib/utils/classname-utils.js +492 -0
  132. package/src/lib/utils/classname-utils.js.map +1 -0
  133. package/src/lib/utils/file-utils.d.ts +14 -0
  134. package/src/lib/utils/file-utils.js +106 -0
  135. package/src/lib/utils/file-utils.js.map +1 -0
  136. package/src/lib/utils/import-utils.d.ts +85 -0
  137. package/src/lib/utils/import-utils.js +193 -0
  138. package/src/lib/utils/import-utils.js.map +1 -0
  139. package/src/lib/utils/nx-utils.d.ts +59 -0
  140. package/src/lib/utils/nx-utils.js +103 -0
  141. package/src/lib/utils/nx-utils.js.map +1 -0
  142. package/src/lib/utils/package-utils.d.ts +38 -0
  143. package/src/lib/utils/package-utils.js +74 -0
  144. package/src/lib/utils/package-utils.js.map +1 -0
  145. package/src/lib/utils/typescript-utils.d.ts +29 -0
  146. package/src/lib/utils/typescript-utils.js +213 -0
  147. package/src/lib/utils/typescript-utils.js.map +1 -0
@@ -0,0 +1,150 @@
1
+ import { TSESTree } from "@typescript-eslint/utils";
2
+ /**
3
+ * Utility functions for finding and analyzing Tailwind CSS classnames in ESLint rules.
4
+ *
5
+ * This module provides a centralized way to detect classname usage patterns across:
6
+ * - JSX `className` and `class` attributes
7
+ * - Variables named with `...Class` or `...ClassName` suffixes
8
+ * - Tailwind utility functions: `cvaMerge`, `twMerge`, `cva`, `tw`, `twx`, `tws`, `cn`, `clsx`, `classNames`, `twJoin`
9
+ *
10
+ * ## Core Concepts
11
+ *
12
+ * ### ClassnameLocation
13
+ * Represents a single location where a classname string value is found.
14
+ * Contains the string value, the AST node for reporting/fixing, and metadata.
15
+ *
16
+ * ### ClassnameContext
17
+ * Describes where a classname was found (JSX attribute, function call, variable, etc.)
18
+ *
19
+ * ## Usage in ESLint Rules
20
+ *
21
+ * ```typescript
22
+ * import { findClassnameStrings, TAILWIND_FUNCTIONS, isClassnameVariable } from "./classname-utils";
23
+ *
24
+ * // In a CallExpression handler:
25
+ * CallExpression(node) {
26
+ * const locations = findClassnameStrings(node);
27
+ * for (const location of locations) {
28
+ * // Check location.value for banned patterns
29
+ * // Report on location.reportNode
30
+ * // Fix using location.fixNode
31
+ * }
32
+ * }
33
+ * ```
34
+ */
35
+ /**
36
+ * Context describing where a classname was found
37
+ */
38
+ export type ClassnameContext = {
39
+ type: "jsx-attribute";
40
+ attributeName: string;
41
+ } | {
42
+ type: "function-call";
43
+ functionName: string;
44
+ argumentIndex: number;
45
+ } | {
46
+ type: "cva-variant";
47
+ functionName: string;
48
+ variantPath: Array<string>;
49
+ } | {
50
+ type: "variable";
51
+ variableName: string;
52
+ } | {
53
+ type: "array-element";
54
+ parentContext: ClassnameContext;
55
+ elementIndex: number;
56
+ };
57
+ /**
58
+ * Represents a location where a classname string value is found
59
+ */
60
+ export type ClassnameLocation = {
61
+ /** The extracted string value (for template literals, only static parts) */
62
+ value: string;
63
+ /** The AST node to use when reporting errors */
64
+ reportNode: TSESTree.Node;
65
+ /** The AST node to use when creating fixes (string literal or template literal) */
66
+ fixNode: TSESTree.Literal | TSESTree.TemplateLiteral;
67
+ /** Context describing where this classname was found */
68
+ context: ClassnameContext;
69
+ };
70
+ /**
71
+ * Function names that accept Tailwind class strings as arguments.
72
+ * These are commonly used for class merging and conditional class application.
73
+ *
74
+ * Corresponds to the `tailwindFunctions` config in .prettierrc for Prettier's
75
+ * Tailwind CSS plugin.
76
+ */
77
+ export declare const TAILWIND_FUNCTIONS: readonly ["cva", "cvaMerge", "tw", "twx", "tws", "twMerge", "twJoin", "cn", "clsx", "classNames"];
78
+ export type TailwindFunction = (typeof TAILWIND_FUNCTIONS)[number];
79
+ /**
80
+ * JSX attribute names that contain classname values
81
+ */
82
+ export declare const CLASSNAME_ATTRIBUTES: readonly ["className", "class"];
83
+ /**
84
+ * Check if a function name is a known Tailwind utility function
85
+ */
86
+ export declare const isTailwindFunction: (name: string) => name is TailwindFunction;
87
+ /**
88
+ * Check if a variable name indicates it contains classnames
89
+ */
90
+ export declare const isClassnameVariable: (name: string) => boolean;
91
+ /**
92
+ * Check if a JSX attribute name is a classname attribute
93
+ */
94
+ export declare const isClassnameAttribute: (name: string) => boolean;
95
+ /**
96
+ * Get the function name from a CallExpression callee
97
+ */
98
+ export declare const getCalleeName: (callee: TSESTree.CallExpression["callee"]) => string | null;
99
+ /**
100
+ * Find all classname string locations in a CallExpression node.
101
+ *
102
+ * Use this in your ESLint rule's CallExpression handler to process
103
+ * Tailwind utility function calls like twMerge, cvaMerge, etc.
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * CallExpression(node) {
108
+ * const locations = findClassnameStringsInCall(node);
109
+ * for (const location of locations) {
110
+ * if (hasBannedPattern(location.value)) {
111
+ * context.report({ node: location.reportNode, ... });
112
+ * }
113
+ * }
114
+ * }
115
+ * ```
116
+ */
117
+ export declare const findClassnameStringsInCall: (node: TSESTree.CallExpression) => Array<ClassnameLocation>;
118
+ /**
119
+ * Find all classname string locations in a JSXAttribute node.
120
+ *
121
+ * Use this in your ESLint rule's JSXAttribute handler to process
122
+ * className and class attributes.
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * JSXAttribute(node) {
127
+ * const locations = findClassnameStringsInAttribute(node);
128
+ * for (const location of locations) {
129
+ * if (hasBannedPattern(location.value)) {
130
+ * context.report({ node: location.reportNode, ... });
131
+ * }
132
+ * }
133
+ * }
134
+ * ```
135
+ */
136
+ export declare const findClassnameStringsInAttribute: (node: TSESTree.JSXAttribute) => Array<ClassnameLocation>;
137
+ /**
138
+ * Split a classname string into individual class names.
139
+ * Handles whitespace-separated classes.
140
+ */
141
+ export declare const splitClasses: (value: string) => Array<string>;
142
+ /**
143
+ * Join class names into a single string.
144
+ */
145
+ export declare const joinClasses: (classes: Array<string>) => string;
146
+ /**
147
+ * Create a JSON-formatted array string from an array of class names.
148
+ * Useful for auto-fix operations.
149
+ */
150
+ export declare const formatAsArray: (classes: Array<string>) => string;
@@ -0,0 +1,492 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatAsArray = exports.joinClasses = exports.splitClasses = exports.findClassnameStringsInAttribute = exports.findClassnameStringsInCall = exports.getCalleeName = exports.isClassnameAttribute = exports.isClassnameVariable = exports.isTailwindFunction = exports.CLASSNAME_ATTRIBUTES = exports.TAILWIND_FUNCTIONS = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ // =============================================================================
6
+ // Constants
7
+ // =============================================================================
8
+ /**
9
+ * Function names that accept Tailwind class strings as arguments.
10
+ * These are commonly used for class merging and conditional class application.
11
+ *
12
+ * Corresponds to the `tailwindFunctions` config in .prettierrc for Prettier's
13
+ * Tailwind CSS plugin.
14
+ */
15
+ exports.TAILWIND_FUNCTIONS = [
16
+ "cva",
17
+ "cvaMerge",
18
+ "tw",
19
+ "twx",
20
+ "tws",
21
+ "twMerge",
22
+ "twJoin",
23
+ "cn",
24
+ "clsx",
25
+ "classNames",
26
+ ];
27
+ /**
28
+ * Variable name patterns that indicate a classname value.
29
+ * Matches variables like: containerClass, buttonClassName, rootClasses
30
+ */
31
+ const CLASSNAME_VARIABLE_PATTERNS = [/Class$/, /ClassName$/, /Classes$/];
32
+ /**
33
+ * JSX attribute names that contain classname values
34
+ */
35
+ exports.CLASSNAME_ATTRIBUTES = ["className", "class"];
36
+ // =============================================================================
37
+ // Detection Helpers
38
+ // =============================================================================
39
+ /**
40
+ * Check if a function name is a known Tailwind utility function
41
+ */
42
+ const isTailwindFunction = (name) => {
43
+ return exports.TAILWIND_FUNCTIONS.some(fn => fn === name);
44
+ };
45
+ exports.isTailwindFunction = isTailwindFunction;
46
+ /**
47
+ * Check if a variable name indicates it contains classnames
48
+ */
49
+ const isClassnameVariable = (name) => {
50
+ return CLASSNAME_VARIABLE_PATTERNS.some(pattern => pattern.test(name));
51
+ };
52
+ exports.isClassnameVariable = isClassnameVariable;
53
+ /**
54
+ * Check if a JSX attribute name is a classname attribute
55
+ */
56
+ const isClassnameAttribute = (name) => {
57
+ return exports.CLASSNAME_ATTRIBUTES.some(attr => attr === name);
58
+ };
59
+ exports.isClassnameAttribute = isClassnameAttribute;
60
+ /**
61
+ * Get the function name from a CallExpression callee
62
+ */
63
+ const getCalleeName = (callee) => {
64
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
65
+ return callee.name;
66
+ }
67
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression && callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
68
+ return callee.property.name;
69
+ }
70
+ return null;
71
+ };
72
+ exports.getCalleeName = getCalleeName;
73
+ // =============================================================================
74
+ // String Extraction
75
+ // =============================================================================
76
+ /**
77
+ * Extract a string value from a Literal node
78
+ */
79
+ const extractFromLiteral = (node) => {
80
+ if (typeof node.value === "string") {
81
+ return { value: node.value, node };
82
+ }
83
+ return null;
84
+ };
85
+ /**
86
+ * Extract the static parts of a TemplateLiteral as a single string.
87
+ * Template expressions are replaced with a single space to preserve word boundaries.
88
+ */
89
+ const extractFromTemplateLiteral = (node) => {
90
+ // Join static parts with spaces (representing where expressions would be)
91
+ const value = node.quasis.map(quasi => quasi.value.raw).join(" ");
92
+ return { value, node };
93
+ };
94
+ /**
95
+ * Extract string value from a node that might contain a classname string.
96
+ * Handles Literal and TemplateLiteral nodes.
97
+ */
98
+ const extractStringValue = (node) => {
99
+ switch (node.type) {
100
+ case utils_1.AST_NODE_TYPES.Literal:
101
+ return extractFromLiteral(node);
102
+ case utils_1.AST_NODE_TYPES.TemplateLiteral:
103
+ return extractFromTemplateLiteral(node);
104
+ default:
105
+ return null;
106
+ }
107
+ };
108
+ // =============================================================================
109
+ // Array Traversal
110
+ // =============================================================================
111
+ /**
112
+ * Extract classname locations from an ArrayExpression.
113
+ * Handles arrays like: ["flex", "items-center", "bg-primary-500"]
114
+ */
115
+ const extractFromArray = (node, parentContext) => {
116
+ const locations = [];
117
+ node.elements.forEach((element, index) => {
118
+ if (!element || element.type === utils_1.AST_NODE_TYPES.SpreadElement) {
119
+ return;
120
+ }
121
+ const extracted = extractStringValue(element);
122
+ if (extracted) {
123
+ locations.push({
124
+ value: extracted.value,
125
+ reportNode: element,
126
+ fixNode: extracted.node,
127
+ context: { type: "array-element", parentContext, elementIndex: index },
128
+ });
129
+ }
130
+ });
131
+ return locations;
132
+ };
133
+ // =============================================================================
134
+ // CVA/CVAMerge Object Traversal
135
+ // =============================================================================
136
+ /**
137
+ * Recursively extract classname locations from a CVA options object.
138
+ *
139
+ * CVA structure:
140
+ * ```
141
+ * cva(baseClasses, {
142
+ * variants: {
143
+ * variantName: {
144
+ * variantValue: "classnames here",
145
+ * }
146
+ * },
147
+ * compoundVariants: [
148
+ * { variantName: "value", className: "classnames" }
149
+ * ],
150
+ * defaultVariants: { ... }
151
+ * })
152
+ * ```
153
+ */
154
+ const extractFromCvaObject = (node, functionName, currentPath = []) => {
155
+ const locations = [];
156
+ for (const property of node.properties) {
157
+ if (property.type !== utils_1.AST_NODE_TYPES.Property) {
158
+ continue;
159
+ }
160
+ const keyName = getPropertyKeyName(property.key);
161
+ if (!keyName) {
162
+ continue;
163
+ }
164
+ const propertyPath = [...currentPath, keyName];
165
+ // Skip defaultVariants as it doesn't contain classnames
166
+ if (keyName === "defaultVariants") {
167
+ continue;
168
+ }
169
+ // Recurse into nested objects (variants, compoundVariants entries, etc.)
170
+ if (property.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
171
+ locations.push(...extractFromCvaObject(property.value, functionName, propertyPath));
172
+ continue;
173
+ }
174
+ // Handle arrays (compoundVariants array, or array of class strings)
175
+ if (property.value.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
176
+ // Check if this is an array of objects (like compoundVariants)
177
+ const hasObjectElements = property.value.elements.some(el => el?.type === utils_1.AST_NODE_TYPES.ObjectExpression);
178
+ if (hasObjectElements) {
179
+ // Recurse into each object in the array
180
+ property.value.elements.forEach((element, index) => {
181
+ if (element?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
182
+ locations.push(...extractFromCvaObject(element, functionName, [...propertyPath, `[${index}]`]));
183
+ }
184
+ });
185
+ }
186
+ else {
187
+ // This is an array of class strings
188
+ const arrayContext = {
189
+ type: "cva-variant",
190
+ functionName,
191
+ variantPath: propertyPath,
192
+ };
193
+ locations.push(...extractFromArray(property.value, arrayContext));
194
+ }
195
+ continue;
196
+ }
197
+ // Extract string values at leaf nodes
198
+ const extracted = extractStringValue(property.value);
199
+ if (extracted) {
200
+ locations.push({
201
+ value: extracted.value,
202
+ reportNode: property.value,
203
+ fixNode: extracted.node,
204
+ context: {
205
+ type: "cva-variant",
206
+ functionName,
207
+ variantPath: propertyPath,
208
+ },
209
+ });
210
+ }
211
+ }
212
+ return locations;
213
+ };
214
+ /**
215
+ * Get the name of an object property key
216
+ */
217
+ const getPropertyKeyName = (key) => {
218
+ if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
219
+ return key.name;
220
+ }
221
+ if (key.type === utils_1.AST_NODE_TYPES.Literal && typeof key.value === "string") {
222
+ return key.value;
223
+ }
224
+ return null;
225
+ };
226
+ // =============================================================================
227
+ // Function Call Extraction
228
+ // =============================================================================
229
+ /**
230
+ * Extract classname locations from a Tailwind function call.
231
+ *
232
+ * Handles various argument patterns:
233
+ * - String literals: twMerge("flex items-center")
234
+ * - Template literals: twMerge(`flex ${condition && "hidden"}`)
235
+ * - Arrays: cvaMerge(["flex", "items-center"], options)
236
+ * - CVA options objects: cva(base, { variants: { ... } })
237
+ */
238
+ const extractFromFunctionCall = (node) => {
239
+ const calleeName = (0, exports.getCalleeName)(node.callee);
240
+ if (!calleeName || !(0, exports.isTailwindFunction)(calleeName)) {
241
+ return [];
242
+ }
243
+ const locations = [];
244
+ const isCvaStyle = calleeName === "cva" || calleeName === "cvaMerge";
245
+ node.arguments.forEach((arg, argIndex) => {
246
+ const baseContext = {
247
+ type: "function-call",
248
+ functionName: calleeName,
249
+ argumentIndex: argIndex,
250
+ };
251
+ // Handle string literals
252
+ const extracted = extractStringValue(arg);
253
+ if (extracted) {
254
+ locations.push({
255
+ value: extracted.value,
256
+ reportNode: arg,
257
+ fixNode: extracted.node,
258
+ context: baseContext,
259
+ });
260
+ return;
261
+ }
262
+ // Handle array arguments
263
+ if (arg.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
264
+ locations.push(...extractFromArray(arg, baseContext));
265
+ return;
266
+ }
267
+ // Handle CVA options object (typically second argument)
268
+ if (isCvaStyle && arg.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
269
+ locations.push(...extractFromCvaObject(arg, calleeName));
270
+ }
271
+ });
272
+ return locations;
273
+ };
274
+ // =============================================================================
275
+ // JSX Attribute Extraction
276
+ // =============================================================================
277
+ /**
278
+ * Extract classname locations from a JSX attribute (className or class).
279
+ *
280
+ * Handles various value patterns:
281
+ * - String literals: className="flex items-center"
282
+ * - Expression with string: className={"flex items-center"}
283
+ * - Template literals: className={`flex ${condition && "hidden"}`}
284
+ */
285
+ const extractFromJsxAttribute = (node) => {
286
+ if (node.name.type !== utils_1.AST_NODE_TYPES.JSXIdentifier) {
287
+ return [];
288
+ }
289
+ const attrName = node.name.name;
290
+ if (!(0, exports.isClassnameAttribute)(attrName)) {
291
+ return [];
292
+ }
293
+ const attrValue = node.value;
294
+ if (!attrValue) {
295
+ return [];
296
+ }
297
+ const context = {
298
+ type: "jsx-attribute",
299
+ attributeName: attrName,
300
+ };
301
+ // Direct string literal: className="flex items-center"
302
+ if (attrValue.type === utils_1.AST_NODE_TYPES.Literal) {
303
+ const extracted = extractFromLiteral(attrValue);
304
+ if (extracted) {
305
+ return [
306
+ {
307
+ value: extracted.value,
308
+ reportNode: node,
309
+ fixNode: extracted.node,
310
+ context,
311
+ },
312
+ ];
313
+ }
314
+ }
315
+ // JSX Expression: className={...}
316
+ if (attrValue.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
317
+ const expression = attrValue.expression;
318
+ // Skip empty expressions
319
+ if (expression.type === utils_1.AST_NODE_TYPES.JSXEmptyExpression) {
320
+ return [];
321
+ }
322
+ // String literal in expression: className={"flex items-center"}
323
+ const extracted = extractStringValue(expression);
324
+ if (extracted) {
325
+ return [
326
+ {
327
+ value: extracted.value,
328
+ reportNode: node,
329
+ fixNode: extracted.node,
330
+ context,
331
+ },
332
+ ];
333
+ }
334
+ // Note: We don't recurse into function calls here as they're handled
335
+ // separately by the CallExpression handler in the rule
336
+ }
337
+ return [];
338
+ };
339
+ // =============================================================================
340
+ // Variable Declaration Extraction
341
+ // =============================================================================
342
+ /**
343
+ * Extract classname locations from a variable declaration with a classname-like name.
344
+ *
345
+ * Example: const buttonClass = "flex items-center";
346
+ */
347
+ const extractFromVariableDeclarator = (node) => {
348
+ if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
349
+ return [];
350
+ }
351
+ const varName = node.id.name;
352
+ if (!(0, exports.isClassnameVariable)(varName)) {
353
+ return [];
354
+ }
355
+ if (!node.init) {
356
+ return [];
357
+ }
358
+ const context = {
359
+ type: "variable",
360
+ variableName: varName,
361
+ };
362
+ // Handle string literal
363
+ const extracted = extractStringValue(node.init);
364
+ if (extracted) {
365
+ return [
366
+ {
367
+ value: extracted.value,
368
+ reportNode: node.init,
369
+ fixNode: extracted.node,
370
+ context,
371
+ },
372
+ ];
373
+ }
374
+ // Handle array of strings
375
+ if (node.init.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
376
+ return extractFromArray(node.init, context);
377
+ }
378
+ // Note: Function calls in variable initializers are handled separately
379
+ // by the CallExpression handler
380
+ return [];
381
+ };
382
+ // =============================================================================
383
+ // Unified Entry Points
384
+ // =============================================================================
385
+ /**
386
+ * Find all classname string locations in a CallExpression node.
387
+ *
388
+ * Use this in your ESLint rule's CallExpression handler to process
389
+ * Tailwind utility function calls like twMerge, cvaMerge, etc.
390
+ *
391
+ * @example
392
+ * ```typescript
393
+ * CallExpression(node) {
394
+ * const locations = findClassnameStringsInCall(node);
395
+ * for (const location of locations) {
396
+ * if (hasBannedPattern(location.value)) {
397
+ * context.report({ node: location.reportNode, ... });
398
+ * }
399
+ * }
400
+ * }
401
+ * ```
402
+ */
403
+ const findClassnameStringsInCall = (node) => {
404
+ return extractFromFunctionCall(node);
405
+ };
406
+ exports.findClassnameStringsInCall = findClassnameStringsInCall;
407
+ /**
408
+ * Find all classname string locations in a JSXAttribute node.
409
+ *
410
+ * Use this in your ESLint rule's JSXAttribute handler to process
411
+ * className and class attributes.
412
+ *
413
+ * @example
414
+ * ```typescript
415
+ * JSXAttribute(node) {
416
+ * const locations = findClassnameStringsInAttribute(node);
417
+ * for (const location of locations) {
418
+ * if (hasBannedPattern(location.value)) {
419
+ * context.report({ node: location.reportNode, ... });
420
+ * }
421
+ * }
422
+ * }
423
+ * ```
424
+ */
425
+ const findClassnameStringsInAttribute = (node) => {
426
+ return extractFromJsxAttribute(node);
427
+ };
428
+ exports.findClassnameStringsInAttribute = findClassnameStringsInAttribute;
429
+ /**
430
+ * Find all classname string locations in a VariableDeclarator node.
431
+ *
432
+ * Use this in your ESLint rule's VariableDeclarator handler to process
433
+ * variables with classname-like names.
434
+ *
435
+ * @example
436
+ * ```typescript
437
+ * VariableDeclarator(node) {
438
+ * const locations = findClassnameStringsInVariable(node);
439
+ * for (const location of locations) {
440
+ * if (hasBannedPattern(location.value)) {
441
+ * context.report({ node: location.reportNode, ... });
442
+ * }
443
+ * }
444
+ * }
445
+ * ```
446
+ */
447
+ const findClassnameStringsInVariable = (node) => {
448
+ return extractFromVariableDeclarator(node);
449
+ };
450
+ // =============================================================================
451
+ // Utility Helpers for Fixing
452
+ // =============================================================================
453
+ /**
454
+ * Split a classname string into individual class names.
455
+ * Handles whitespace-separated classes.
456
+ */
457
+ const splitClasses = (value) => {
458
+ return value.split(/\s+/).filter(Boolean);
459
+ };
460
+ exports.splitClasses = splitClasses;
461
+ /**
462
+ * Join class names into a single string.
463
+ */
464
+ const joinClasses = (classes) => {
465
+ return classes.join(" ");
466
+ };
467
+ exports.joinClasses = joinClasses;
468
+ /**
469
+ * Create a JSON-formatted array string from an array of class names.
470
+ * Useful for auto-fix operations.
471
+ */
472
+ const formatAsArray = (classes) => {
473
+ return JSON.stringify(classes);
474
+ };
475
+ exports.formatAsArray = formatAsArray;
476
+ /**
477
+ * Get the quote character used in a string literal.
478
+ */
479
+ const getQuoteChar = (node) => {
480
+ if (typeof node.value === "string" && node.raw) {
481
+ return node.raw.charAt(0);
482
+ }
483
+ return '"';
484
+ };
485
+ /**
486
+ * Create a quoted string with the same quote style as the original.
487
+ */
488
+ const quoteString = (value, originalNode) => {
489
+ const quote = getQuoteChar(originalNode);
490
+ return `${quote}${value}${quote}`;
491
+ };
492
+ //# sourceMappingURL=classname-utils.js.map