Skip to content

Commit 979d8c9

Browse files
authored
fix: false positive in no-nesting rule (#99)
1 parent 9dad52a commit 979d8c9

File tree

4 files changed

+145
-12
lines changed

4 files changed

+145
-12
lines changed

.changeset/new-students-leave.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 positive in @pandacss/no-invalid-nesting

plugin/src/rules/no-invalid-nesting.ts

+48-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { isIdentifier, isLiteral, isObjectExpression, isTemplateLiteral } from '../utils/nodes'
22
import { type Rule, createRule } from '../utils'
3-
import { isInJSXProp, isInPandaFunction, isStyledProperty } from '../utils/helpers'
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'
46

57
export const RULE_NAME = 'no-invalid-nesting'
68

@@ -21,22 +23,57 @@ const rule: Rule = createRule({
2123
return {
2224
Property(node) {
2325
if (!isObjectExpression(node.value) || isIdentifier(node.key)) return
24-
if (!isInPandaFunction(node, context) && !isInJSXProp(node, context)) return
26+
const caller = isInPandaFunction(node, context)
27+
if (!caller && !isInJSXProp(node, context)) return
2528
if (isStyledProperty(node, context)) return
2629

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

31-
if (invalidLiteral || invalidTemplateLiteral) {
32-
context.report({
33-
node: node.key,
34-
messageId: 'nesting',
35-
})
36-
}
33+
context.report({
34+
node: node.key,
35+
messageId: 'nesting',
36+
})
3737
},
3838
}
3939
},
4040
})
4141

4242
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/utils/helpers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const _getImports = (context: RuleContext<any, any>) => {
7777
return imports
7878
}
7979

80-
const getImports = (context: RuleContext<any, any>) => {
80+
export const getImports = (context: RuleContext<any, any>) => {
8181
const imports = _getImports(context)
8282
return imports.filter((imp) => syncAction('matchImports', getSyncOpts(context), imp))
8383
}

plugin/tests/_parsing.test.ts

+91
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import rule2, { RULE_NAME as RULE_NAME2 } from '../src/rules/no-dynamic-styling'
33
import rule3, { RULE_NAME as RULE_NAME3 } from '../src/rules/no-escape-hatch'
44
import rule4, { RULE_NAME as RULE_NAME4 } from '../src/rules/no-unsafe-token-fn-usage'
55
import rule5, { RULE_NAME as RULE_NAME5 } from '../src/rules/no-invalid-token-paths'
6+
import rule6, { RULE_NAME as RULE_NAME6 } from '../src/rules/no-invalid-nesting'
67
import { eslintTester } from '../test-utils'
78
import { getArbitraryValue } from '@pandacss/shared'
89

@@ -195,3 +196,93 @@ eslintTester.run(RULE_NAME5, rule5 as any, {
195196
errors,
196197
})),
197198
})
199+
200+
//? Testing nesting in sva and cva
201+
202+
const imports6 = `import { cva, sva } from './panda/css';`
203+
204+
const valids6 = [
205+
`const heading = cva({
206+
base: {
207+
color: 'red.400',
208+
},
209+
variants: {
210+
size: {
211+
'large-bold': {
212+
color: 'red.800',
213+
},
214+
},
215+
},
216+
})`,
217+
`const heading2 = sva({
218+
slots: ['sm-ca'],
219+
base: {
220+
'sm-ca': {
221+
color: 'red.600',
222+
},
223+
},
224+
variants: {
225+
size: {
226+
'va-riant': {
227+
'sm-ca': {
228+
color: 'red.600',
229+
},
230+
},
231+
},
232+
},
233+
})`,
234+
]
235+
236+
const invalids6 = [
237+
{
238+
code: `const heading = cva({
239+
base: {
240+
'> div': {
241+
color: 'red.500',
242+
},
243+
},
244+
variants: {
245+
size: {
246+
'large-bold': {
247+
'> div': {
248+
color: 'red.500',
249+
},
250+
},
251+
},
252+
},
253+
})`,
254+
},
255+
{
256+
code: `const heading2 = sva({
257+
slots: ['sm-ca'],
258+
base: {
259+
'sm-ca': {
260+
'> div': {
261+
color: 'red.600',
262+
},
263+
},
264+
},
265+
variants: {
266+
size: {
267+
'va-riant': {
268+
'sm-ca': {
269+
'> div': {
270+
color: 'red.600',
271+
},
272+
},
273+
},
274+
},
275+
},
276+
})`,
277+
},
278+
]
279+
280+
eslintTester.run(RULE_NAME6, rule6 as any, {
281+
valid: valids6.map((code) => ({
282+
code: imports6 + code,
283+
})),
284+
invalid: invalids6.map(({ code }) => ({
285+
code: imports6 + code,
286+
errors: 2,
287+
})),
288+
})

0 commit comments

Comments
 (0)