@@ -656,37 +656,72 @@ function isIgnoredProperty(node: TSESTree.Node, ignoreProperties: string[]): boo
656656}
657657
658658/**
659- * Checks if a string is a direct property value in an object assigned to a styling constant.
659+ * Checks if a string is inside a variable assignment to a styling constant/variable .
660660 *
661- * Only matches the exact structure :
661+ * Matches structures like :
662662 * const STATUS_COLORS = { active: "bg-green-100..." }
663+ * const colorClasses = { primary: "text-blue-500" }
664+ * const indicatorClassName = cn("shrink-0", { "w-1": condition })
663665 *
664- * Does NOT match strings inside functions, IIFEs, or nested structures :
665- * const STATUS_COLORS = { active: (() => "value")() } // ❌ not matched
666- * const STATUS_COLORS = { active: fn("value") } // ❌ not matched
666+ * Does NOT match nested function calls or nested objects :
667+ * const STATUS_COLORS = { active: fn("Hello") } // fn() is nested in property
668+ * const STATUS_COLORS = { active: { x: "Hello" } } // nested object
667669 */
668670function isInsideStylingConstant ( node : TSESTree . Node ) : boolean {
669- // Must be: Literal → Property (as value) → ObjectExpression → VariableDeclarator
670- const property = node . parent
671- if ( property ?. type !== AST_NODE_TYPES . Property || property . value !== node ) {
672- return false
673- }
671+ let current : TSESTree . Node | undefined = node . parent ?? undefined
672+ let lastCallExpression : TSESTree . Node | undefined = undefined
673+ let objectDepth = 0
674674
675- const objectExpr = property . parent
676- if ( objectExpr . type !== AST_NODE_TYPES . ObjectExpression ) {
677- return false
678- }
675+ while ( current !== undefined ) {
676+ // Track the most recent CallExpression we've passed through
677+ if ( current . type === AST_NODE_TYPES . CallExpression ) {
678+ lastCallExpression = current
679+ }
679680
680- const declarator = objectExpr . parent
681- if (
682- declarator . type !== AST_NODE_TYPES . VariableDeclarator ||
683- declarator . id . type !== AST_NODE_TYPES . Identifier ||
684- declarator . init !== objectExpr
685- ) {
686- return false
681+ // Track object nesting depth
682+ if ( current . type === AST_NODE_TYPES . ObjectExpression ) {
683+ objectDepth ++
684+ }
685+
686+ // Found a variable declarator - check if it has a styling name
687+ if (
688+ current . type === AST_NODE_TYPES . VariableDeclarator &&
689+ current . id . type === AST_NODE_TYPES . Identifier &&
690+ isStylingConstant ( current . id . name )
691+ ) {
692+ // Check if the init is what we expect
693+ const init = current . init
694+
695+ // Case 1: Direct object - const x = { key: "value" }
696+ if ( init ?. type === AST_NODE_TYPES . ObjectExpression ) {
697+ // Only allow if:
698+ // - We didn't pass through a CallExpression (fn() inside property)
699+ // - We didn't pass through nested objects (depth must be 1)
700+ return lastCallExpression === undefined && objectDepth === 1
701+ }
702+
703+ // Case 2: Direct function call - const x = cn("value", {...})
704+ if ( init ?. type === AST_NODE_TYPES . CallExpression ) {
705+ // Only allow if the CallExpression we passed through IS the init
706+ return lastCallExpression === init
707+ }
708+
709+ return false
710+ }
711+
712+ // Stop at function boundaries (don't cross into function bodies)
713+ if (
714+ current . type === AST_NODE_TYPES . FunctionDeclaration ||
715+ current . type === AST_NODE_TYPES . FunctionExpression ||
716+ current . type === AST_NODE_TYPES . ArrowFunctionExpression
717+ ) {
718+ return false
719+ }
720+
721+ current = current . parent ?? undefined
687722 }
688723
689- return isStylingConstant ( declarator . id . name )
724+ return false
690725}
691726
692727// ============================================================================
0 commit comments