Skip to content

Commit 4914e3d

Browse files
authored
fix: false positives in recipe variants (#134)
1 parent 0d9f6f4 commit 4914e3d

19 files changed

+88
-57
lines changed

.changeset/wild-snakes-give.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@pandacss/eslint-plugin': patch
3+
---
4+
5+
Fix false positives in recipe variants

plugin/src/rules/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fileNotIncluded, { RULE_NAME as FileNotIncluded } from './file-not-included'
22
import noConfigunctionInSource, { RULE_NAME as NoConfigunctionInSource } from './no-config-function-in-source'
33
import noDebug, { RULE_NAME as NoDebug } from './no-debug'
4+
import noDeprecatedTokens, { RULE_NAME as NoDeprecatedTokens } from './no-deprecated-tokens'
45
import noDynamicStyling, { RULE_NAME as NoDynamicStyling } from './no-dynamic-styling'
56
import noEscapeHatch, { RULE_NAME as NoEscapeHatch } from './no-escape-hatch'
67
import noHardCodedColor, { RULE_NAME as NoHardCodedColor } from './no-hardcoded-color'
@@ -21,6 +22,7 @@ export const rules = {
2122
[FileNotIncluded]: fileNotIncluded,
2223
[NoConfigunctionInSource]: noConfigunctionInSource,
2324
[NoDebug]: noDebug,
25+
[NoDeprecatedTokens]: noDeprecatedTokens,
2426
[NoDynamicStyling]: noDynamicStyling,
2527
[NoEscapeHatch]: noEscapeHatch,
2628
[NoHardCodedColor]: noHardCodedColor,

plugin/src/rules/no-debug.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
22
import { type Rule, createRule } from '../utils'
3-
import { isPandaAttribute, isPandaProp } from '../utils/helpers'
3+
import { isPandaAttribute, isPandaProp, isRecipeVariant } from '../utils/helpers'
44

55
export const RULE_NAME = 'no-debug'
66

@@ -41,6 +41,7 @@ const rule: Rule = createRule({
4141
Property(node) {
4242
if (!isIdentifier(node.key) || node.key.name !== 'debug') return
4343
if (!isPandaAttribute(node, context)) return
44+
if (isRecipeVariant(node, context)) return
4445

4546
context.report({
4647
node: node.key,

plugin/src/rules/no-escape-hatch.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isPandaAttribute, isPandaProp } from '../utils/helpers'
1+
import { isPandaAttribute, isPandaProp, isRecipeVariant } from '../utils/helpers'
22
import { type Rule, createRule } from '../utils'
33
import { getArbitraryValue } from '@pandacss/shared'
44
import { isIdentifier, isJSXExpressionContainer, isLiteral, isTemplateLiteral, type Node } from '../utils/nodes'
@@ -78,6 +78,7 @@ const rule: Rule = createRule({
7878
if (!isIdentifier(node.key)) return
7979
if (!isLiteral(node.value) && !isTemplateLiteral(node.value)) return
8080
if (!isPandaAttribute(node, context)) return
81+
if (isRecipeVariant(node, context)) return
8182

8283
handleLiteral(node.value)
8384
handleTemplateLiteral(node.value)

plugin/src/rules/no-hardcoded-color.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { extractTokens, isColorAttribute, isColorToken, isPandaAttribute, isPandaProp } from '../utils/helpers'
1+
import {
2+
extractTokens,
3+
isColorAttribute,
4+
isColorToken,
5+
isPandaAttribute,
6+
isPandaProp,
7+
isRecipeVariant,
8+
} from '../utils/helpers'
29
import { type Rule, createRule } from '../utils'
310
import { isIdentifier, isJSXExpressionContainer, isJSXIdentifier, isLiteral } from '../utils/nodes'
411

@@ -90,6 +97,7 @@ const rule: Rule = createRule({
9097
if (!isLiteral(node.value)) return
9198

9299
if (!isPandaAttribute(node, context)) return
100+
if (isRecipeVariant(node, context)) return
93101
if (!isColorAttribute(node.key.name, context)) return
94102
if (isTokenFn(node.value.value?.toString())) return
95103
if (testColorToken(node.value.value?.toString())) return

plugin/src/rules/no-important.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isPandaAttribute, isPandaProp } from '../utils/helpers'
1+
import { isPandaAttribute, isPandaProp, isRecipeVariant } from '../utils/helpers'
22
import { type Rule, createRule } from '../utils'
33
import { isIdentifier, isJSXExpressionContainer, isLiteral, isTemplateLiteral, type Node } from '../utils/nodes'
44
import { getArbitraryValue } from '@pandacss/shared'
@@ -100,6 +100,7 @@ const rule: Rule = createRule({
100100
if (!isIdentifier(node.key)) return
101101
if (!isLiteral(node.value) && !isTemplateLiteral(node.value)) return
102102
if (!isPandaAttribute(node, context)) return
103+
if (isRecipeVariant(node, context)) return
103104

104105
handleLiteral(node.value)
105106
handleTemplateLiteral(node.value)
+7-42
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { isIdentifier, isLiteral, isObjectExpression, isTemplateLiteral } from '../utils/nodes'
22
import { type Rule, createRule } from '../utils'
3-
import { getImports, isInJSXProp, isInPandaFunction, isStyledProperty } from '../utils/helpers'
4-
import type { TSESTree } from '@typescript-eslint/utils'
5-
import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'
3+
import { isInJSXProp, isInPandaFunction, isRecipeVariant, isStyledProperty } from '../utils/helpers'
64

75
export const RULE_NAME = 'no-invalid-nesting'
86

@@ -25,10 +23,14 @@ const rule: Rule = createRule({
2523
if (!isObjectExpression(node.value) || isIdentifier(node.key)) return
2624
const caller = isInPandaFunction(node, context)
2725
if (!caller && !isInJSXProp(node, context)) return
26+
if (isRecipeVariant(node, context)) return
2827
if (isStyledProperty(node, context)) return
2928

30-
const invalidNesting = isInvalidNesting(node, context, caller)
31-
if (!invalidNesting) return
29+
const invalidLiteral =
30+
isLiteral(node.key) && typeof node.key.value === 'string' && !node.key.value.includes('&')
31+
const invalidTemplateLiteral = isTemplateLiteral(node.key) && !node.key.quasis[0].value.raw.includes('&')
32+
33+
if (!(invalidLiteral || invalidTemplateLiteral)) return
3234

3335
context.report({
3436
node: node.key,
@@ -40,40 +42,3 @@ const rule: Rule = createRule({
4042
})
4143

4244
export default rule
43-
44-
function isInvalidNesting(node: TSESTree.Property, context: RuleContext<any, any>, caller: string | undefined) {
45-
// Check if the caller is either 'cva' or 'sva'
46-
const recipe = getImports(context).find((imp) => ['cva', 'sva'].includes(imp.name) && imp.alias === caller)
47-
if (!recipe) return checkNode(node)
48-
49-
//* Nesting is different here because of slots and variants. We don't want to warn about those.
50-
let currentNode: any = node
51-
let length = 0
52-
let styleObjectParent = null
53-
54-
// Traverse up the AST
55-
while (currentNode) {
56-
if (currentNode.key && ['base', 'variants'].includes(currentNode.key.name)) {
57-
styleObjectParent = currentNode.key.name
58-
}
59-
currentNode = currentNode.parent
60-
if (!styleObjectParent) length++
61-
}
62-
63-
// Determine the required length based on caller and styleObjectParent
64-
const requiredLength = caller === 'cva' ? 2 : 4
65-
const extraLength = styleObjectParent === 'base' ? 0 : 4
66-
67-
if (length >= requiredLength + extraLength) {
68-
return checkNode(node)
69-
}
70-
71-
return false
72-
}
73-
74-
function checkNode(node: TSESTree.Property) {
75-
const invalidLiteral = isLiteral(node.key) && typeof node.key.value === 'string' && !node.key.value.includes('&')
76-
const invalidTemplateLiteral = isTemplateLiteral(node.key) && !node.key.quasis[0].value.raw.includes('&')
77-
78-
return invalidLiteral || invalidTemplateLiteral
79-
}

plugin/src/rules/no-invalid-token-paths.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { getInvalidTokens, getTaggedTemplateCaller, isPandaAttribute, isPandaIsh, isPandaProp } from '../utils/helpers'
1+
import {
2+
getInvalidTokens,
3+
getTaggedTemplateCaller,
4+
isPandaAttribute,
5+
isPandaIsh,
6+
isPandaProp,
7+
isRecipeVariant,
8+
} from '../utils/helpers'
29
import { type Rule, createRule } from '../utils'
310
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
411
import { isNodeOfTypes } from '@typescript-eslint/utils/ast-utils'
@@ -65,6 +72,7 @@ const rule: Rule = createRule({
6572
if (!isIdentifier(node.key)) return
6673
if (!isNodeOfTypes([AST_NODE_TYPES.Literal, AST_NODE_TYPES.TemplateLiteral])(node.value)) return
6774
if (!isPandaAttribute(node, context)) return
75+
if (isRecipeVariant(node, context)) return
6876

6977
handleLiteral(node.value)
7078
handleTemplateLiteral(node.value)

plugin/src/rules/no-margin-properties.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isPandaAttribute, isPandaProp, resolveLonghand } from '../utils/helpers'
1+
import { isRecipeVariant, isPandaAttribute, isPandaProp, resolveLonghand } from '../utils/helpers'
22
import { type Rule, createRule } from '../utils'
33
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
44
import type { TSESTree } from '@typescript-eslint/utils'
@@ -43,6 +43,7 @@ const rule: Rule = createRule({
4343
Property(node) {
4444
if (!isIdentifier(node.key)) return
4545
if (!isPandaAttribute(node, context)) return
46+
if (isRecipeVariant(node, context)) return
4647

4748
sendReport(node.key)
4849
},

plugin/src/rules/no-physical-properties.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isPandaAttribute, isPandaProp, resolveLonghand } from '../utils/helpers'
1+
import { isRecipeVariant, isPandaAttribute, isPandaProp, resolveLonghand } from '../utils/helpers'
22
import { type Rule, createRule } from '../utils'
33
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
44
import { physicalProperties } from '../utils/physical-properties'
@@ -64,6 +64,7 @@ const rule: Rule = createRule({
6464
Property(node) {
6565
if (!isIdentifier(node.key)) return
6666
if (!isPandaAttribute(node, context)) return
67+
if (isRecipeVariant(node, context)) return
6768

6869
sendReport(node.key)
6970
},

plugin/src/rules/no-property-renaming.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { TSESTree } from '@typescript-eslint/utils'
22
import { type Rule, createRule } from '../utils'
3-
import { isPandaAttribute, isPandaProp } from '../utils/helpers'
3+
import { isPandaAttribute, isPandaProp, isRecipeVariant } from '../utils/helpers'
44
import { isIdentifier, isJSXExpressionContainer, isMemberExpression } from '../utils/nodes'
55

66
export const RULE_NAME = 'no-property-renaming'
@@ -57,6 +57,7 @@ const rule: Rule = createRule({
5757
if (!isIdentifier(node.key)) return
5858
if (!isIdentifier(node.value) && !isMemberExpression(node.value)) return
5959
if (!isPandaAttribute(node, context)) return
60+
if (isRecipeVariant(node, context)) return
6061

6162
const attr = node.key.name.toString()
6263
const value = node.value

plugin/src/rules/no-unsafe-token-fn-usage.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { extractTokens, getTokenImport, isPandaAttribute, isPandaProp } from '../utils/helpers'
1+
import { extractTokens, getTokenImport, isPandaAttribute, isPandaProp, isRecipeVariant } from '../utils/helpers'
22
import { type Rule, createRule } from '../utils'
33
import { TSESTree } from '@typescript-eslint/utils'
44
import {
@@ -110,6 +110,7 @@ const rule: Rule = createRule({
110110
Property(node) {
111111
if (!isCallExpression(node.value) && !isLiteral(node.value) && !isTemplateLiteral(node.value)) return
112112
if (!isPandaAttribute(node, context)) return
113+
if (isRecipeVariant(node, context)) return
113114

114115
handleRuntimeFm(node.value)
115116
handleLiteral(node.value)

plugin/src/rules/prefer-atomic-properties.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isPandaAttribute, isPandaProp, isValidProperty, resolveLonghand } from '../utils/helpers'
1+
import { isPandaAttribute, isPandaProp, isRecipeVariant, isValidProperty, resolveLonghand } from '../utils/helpers'
22
import { type Rule, createRule } from '../utils'
33
import { compositeProperties } from '../utils/composite-properties'
44
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
@@ -54,6 +54,7 @@ const rule: Rule = createRule({
5454
Property(node) {
5555
if (!isIdentifier(node.key)) return
5656
if (!isPandaAttribute(node, context)) return
57+
if (isRecipeVariant(node, context)) return
5758

5859
sendReport(node.key)
5960
},

plugin/src/rules/prefer-composite-properties.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isPandaAttribute, isPandaProp, isValidProperty, resolveLonghand } from '../utils/helpers'
1+
import { isPandaAttribute, isPandaProp, isRecipeVariant, isValidProperty, resolveLonghand } from '../utils/helpers'
22
import { type Rule, createRule } from '../utils'
33
import { compositeProperties } from '../utils/composite-properties'
44
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
@@ -52,6 +52,7 @@ const rule: Rule = createRule({
5252
Property(node) {
5353
if (!isIdentifier(node.key)) return
5454
if (!isPandaAttribute(node, context)) return
55+
if (isRecipeVariant(node, context)) return
5556

5657
sendReport(node.key)
5758
},

plugin/src/rules/prefer-longhand-properties.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isPandaAttribute, isPandaProp, resolveLonghand } from '../utils/helpers'
1+
import { isPandaAttribute, isPandaProp, isRecipeVariant, resolveLonghand } from '../utils/helpers'
22
import { type Rule, createRule } from '../utils'
33
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
44
import type { TSESTree } from '@typescript-eslint/utils'
@@ -58,6 +58,7 @@ const rule: Rule = createRule({
5858
Property(node) {
5959
if (!isIdentifier(node.key)) return
6060
if (!isPandaAttribute(node, context)) return
61+
if (isRecipeVariant(node, context)) return
6162

6263
sendReport(node.key)
6364
},

plugin/src/rules/prefer-shorthand-properties.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isPandaAttribute, isPandaProp, resolveLonghand, resolveShorthands } from '../utils/helpers'
1+
import { isPandaAttribute, isPandaProp, isRecipeVariant, resolveLonghand, resolveShorthands } from '../utils/helpers'
22
import { type Rule, createRule } from '../utils'
33
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
44
import type { TSESTree } from '@typescript-eslint/utils'
@@ -63,6 +63,7 @@ const rule: Rule = createRule({
6363
Property(node) {
6464
if (!isIdentifier(node.key)) return
6565
if (!isPandaAttribute(node, context)) return
66+
if (isRecipeVariant(node, context)) return
6667

6768
sendReport(node.key)
6869
},

plugin/src/rules/prefer-unified-property-style.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isPandaAttribute, isPandaProp, isValidProperty, resolveLonghand } from '../utils/helpers'
1+
import { isPandaAttribute, isPandaProp, isRecipeVariant, isValidProperty, resolveLonghand } from '../utils/helpers'
22
import { type Rule, createRule } from '../utils'
33
import { compositeProperties } from '../utils/composite-properties'
44
import { isIdentifier, isJSXIdentifier, isJSXOpeningElement, isObjectExpression } from '../utils/nodes'
@@ -66,6 +66,7 @@ const rule: Rule = createRule({
6666
Property(node) {
6767
if (!isIdentifier(node.key)) return
6868
if (!isPandaAttribute(node, context)) return
69+
if (isRecipeVariant(node, context)) return
6970

7071
const cmp = resolveCompositeProperty(node.key.name)
7172
if (!cmp) return

plugin/src/utils/helpers.ts

+31
Original file line numberDiff line numberDiff line change
@@ -283,3 +283,34 @@ export const getTaggedTemplateCaller = (node: TSESTree.TaggedTemplateExpression)
283283
return node.tag.callee.name
284284
}
285285
}
286+
287+
export function isRecipeVariant(node: TSESTree.Property, context: RuleContext<any, any>) {
288+
const caller = isInPandaFunction(node, context)
289+
if (!caller) return
290+
291+
// Check if the caller is either 'cva' or 'sva'
292+
const recipe = getImports(context).find((imp) => ['cva', 'sva'].includes(imp.name) && imp.alias === caller)
293+
if (!recipe) return
294+
295+
//* Nesting is different here because of slots and variants. We don't want to warn about those.
296+
let currentNode: any = node
297+
let length = 0
298+
let styleObjectParent: string | null = null
299+
300+
// Traverse up the AST
301+
while (currentNode) {
302+
const keyName = currentNode?.key?.name
303+
if (keyName && ['base', 'variants'].includes(keyName)) {
304+
styleObjectParent = keyName
305+
}
306+
currentNode = currentNode.parent
307+
if (!styleObjectParent) length++
308+
}
309+
310+
// Determine the required length based on caller and styleObjectParent
311+
const isCvaCaller = caller === 'cva'
312+
const requiredLength = isCvaCaller ? 2 : 4
313+
const extraLength = styleObjectParent === 'base' ? 0 : 4
314+
315+
if (length < requiredLength + extraLength) return true
316+
}

sandbox/v9/eslint.config.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ export default tseslint.config({
3434
},
3535
rules: {
3636
...panda.configs.recommended.rules,
37-
'@pandacss/no-debug': 'off',
3837
'@pandacss/no-margin-properties': 'warn',
38+
'@pandacss/no-physical-properties': 'error',
3939
'@pandacss/no-hardcoded-color': ['error', { noOpacity: true }],
4040
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
4141
},

0 commit comments

Comments
 (0)