Skip to content

Commit

Permalink
fix: false positives in recipe variants (#134)
Browse files Browse the repository at this point in the history
  • Loading branch information
anubra266 authored Sep 9, 2024
1 parent 0d9f6f4 commit 4914e3d
Show file tree
Hide file tree
Showing 19 changed files with 88 additions and 57 deletions.
5 changes: 5 additions & 0 deletions .changeset/wild-snakes-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@pandacss/eslint-plugin': patch
---

Fix false positives in recipe variants
2 changes: 2 additions & 0 deletions plugin/src/rules/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fileNotIncluded, { RULE_NAME as FileNotIncluded } from './file-not-included'
import noConfigunctionInSource, { RULE_NAME as NoConfigunctionInSource } from './no-config-function-in-source'
import noDebug, { RULE_NAME as NoDebug } from './no-debug'
import noDeprecatedTokens, { RULE_NAME as NoDeprecatedTokens } from './no-deprecated-tokens'
import noDynamicStyling, { RULE_NAME as NoDynamicStyling } from './no-dynamic-styling'
import noEscapeHatch, { RULE_NAME as NoEscapeHatch } from './no-escape-hatch'
import noHardCodedColor, { RULE_NAME as NoHardCodedColor } from './no-hardcoded-color'
Expand All @@ -21,6 +22,7 @@ export const rules = {
[FileNotIncluded]: fileNotIncluded,
[NoConfigunctionInSource]: noConfigunctionInSource,
[NoDebug]: noDebug,
[NoDeprecatedTokens]: noDeprecatedTokens,
[NoDynamicStyling]: noDynamicStyling,
[NoEscapeHatch]: noEscapeHatch,
[NoHardCodedColor]: noHardCodedColor,
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/rules/no-debug.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
import { type Rule, createRule } from '../utils'
import { isPandaAttribute, isPandaProp } from '../utils/helpers'
import { isPandaAttribute, isPandaProp, isRecipeVariant } from '../utils/helpers'

export const RULE_NAME = 'no-debug'

Expand Down Expand Up @@ -41,6 +41,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key) || node.key.name !== 'debug') return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

context.report({
node: node.key,
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/rules/no-escape-hatch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isPandaAttribute, isPandaProp } from '../utils/helpers'
import { isPandaAttribute, isPandaProp, isRecipeVariant } from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { getArbitraryValue } from '@pandacss/shared'
import { isIdentifier, isJSXExpressionContainer, isLiteral, isTemplateLiteral, type Node } from '../utils/nodes'
Expand Down Expand Up @@ -78,6 +78,7 @@ const rule: Rule = createRule({
if (!isIdentifier(node.key)) return
if (!isLiteral(node.value) && !isTemplateLiteral(node.value)) return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

handleLiteral(node.value)
handleTemplateLiteral(node.value)
Expand Down
10 changes: 9 additions & 1 deletion plugin/src/rules/no-hardcoded-color.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { extractTokens, isColorAttribute, isColorToken, isPandaAttribute, isPandaProp } from '../utils/helpers'
import {
extractTokens,
isColorAttribute,
isColorToken,
isPandaAttribute,
isPandaProp,
isRecipeVariant,
} from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { isIdentifier, isJSXExpressionContainer, isJSXIdentifier, isLiteral } from '../utils/nodes'

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

if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return
if (!isColorAttribute(node.key.name, context)) return
if (isTokenFn(node.value.value?.toString())) return
if (testColorToken(node.value.value?.toString())) return
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/rules/no-important.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isPandaAttribute, isPandaProp } from '../utils/helpers'
import { isPandaAttribute, isPandaProp, isRecipeVariant } from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { isIdentifier, isJSXExpressionContainer, isLiteral, isTemplateLiteral, type Node } from '../utils/nodes'
import { getArbitraryValue } from '@pandacss/shared'
Expand Down Expand Up @@ -100,6 +100,7 @@ const rule: Rule = createRule({
if (!isIdentifier(node.key)) return
if (!isLiteral(node.value) && !isTemplateLiteral(node.value)) return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

handleLiteral(node.value)
handleTemplateLiteral(node.value)
Expand Down
49 changes: 7 additions & 42 deletions plugin/src/rules/no-invalid-nesting.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { isIdentifier, isLiteral, isObjectExpression, isTemplateLiteral } from '../utils/nodes'
import { type Rule, createRule } from '../utils'
import { getImports, isInJSXProp, isInPandaFunction, isStyledProperty } from '../utils/helpers'
import type { TSESTree } from '@typescript-eslint/utils'
import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'
import { isInJSXProp, isInPandaFunction, isRecipeVariant, isStyledProperty } from '../utils/helpers'

export const RULE_NAME = 'no-invalid-nesting'

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

const invalidNesting = isInvalidNesting(node, context, caller)
if (!invalidNesting) return
const invalidLiteral =
isLiteral(node.key) && typeof node.key.value === 'string' && !node.key.value.includes('&')
const invalidTemplateLiteral = isTemplateLiteral(node.key) && !node.key.quasis[0].value.raw.includes('&')

if (!(invalidLiteral || invalidTemplateLiteral)) return

context.report({
node: node.key,
Expand All @@ -40,40 +42,3 @@ const rule: Rule = createRule({
})

export default rule

function isInvalidNesting(node: TSESTree.Property, context: RuleContext<any, any>, caller: string | undefined) {
// Check if the caller is either 'cva' or 'sva'
const recipe = getImports(context).find((imp) => ['cva', 'sva'].includes(imp.name) && imp.alias === caller)
if (!recipe) return checkNode(node)

//* Nesting is different here because of slots and variants. We don't want to warn about those.
let currentNode: any = node
let length = 0
let styleObjectParent = null

// Traverse up the AST
while (currentNode) {
if (currentNode.key && ['base', 'variants'].includes(currentNode.key.name)) {
styleObjectParent = currentNode.key.name
}
currentNode = currentNode.parent
if (!styleObjectParent) length++
}

// Determine the required length based on caller and styleObjectParent
const requiredLength = caller === 'cva' ? 2 : 4
const extraLength = styleObjectParent === 'base' ? 0 : 4

if (length >= requiredLength + extraLength) {
return checkNode(node)
}

return false
}

function checkNode(node: TSESTree.Property) {
const invalidLiteral = isLiteral(node.key) && typeof node.key.value === 'string' && !node.key.value.includes('&')
const invalidTemplateLiteral = isTemplateLiteral(node.key) && !node.key.quasis[0].value.raw.includes('&')

return invalidLiteral || invalidTemplateLiteral
}
10 changes: 9 additions & 1 deletion plugin/src/rules/no-invalid-token-paths.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { getInvalidTokens, getTaggedTemplateCaller, isPandaAttribute, isPandaIsh, isPandaProp } from '../utils/helpers'
import {
getInvalidTokens,
getTaggedTemplateCaller,
isPandaAttribute,
isPandaIsh,
isPandaProp,
isRecipeVariant,
} from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { AST_NODE_TYPES } from '@typescript-eslint/utils'
import { isNodeOfTypes } from '@typescript-eslint/utils/ast-utils'
Expand Down Expand Up @@ -65,6 +72,7 @@ const rule: Rule = createRule({
if (!isIdentifier(node.key)) return
if (!isNodeOfTypes([AST_NODE_TYPES.Literal, AST_NODE_TYPES.TemplateLiteral])(node.value)) return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

handleLiteral(node.value)
handleTemplateLiteral(node.value)
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/rules/no-margin-properties.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isPandaAttribute, isPandaProp, resolveLonghand } from '../utils/helpers'
import { isRecipeVariant, isPandaAttribute, isPandaProp, resolveLonghand } from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
import type { TSESTree } from '@typescript-eslint/utils'
Expand Down Expand Up @@ -43,6 +43,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

sendReport(node.key)
},
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/rules/no-physical-properties.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isPandaAttribute, isPandaProp, resolveLonghand } from '../utils/helpers'
import { isRecipeVariant, isPandaAttribute, isPandaProp, resolveLonghand } from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
import { physicalProperties } from '../utils/physical-properties'
Expand Down Expand Up @@ -64,6 +64,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

sendReport(node.key)
},
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/rules/no-property-renaming.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TSESTree } from '@typescript-eslint/utils'
import { type Rule, createRule } from '../utils'
import { isPandaAttribute, isPandaProp } from '../utils/helpers'
import { isPandaAttribute, isPandaProp, isRecipeVariant } from '../utils/helpers'
import { isIdentifier, isJSXExpressionContainer, isMemberExpression } from '../utils/nodes'

export const RULE_NAME = 'no-property-renaming'
Expand Down Expand Up @@ -57,6 +57,7 @@ const rule: Rule = createRule({
if (!isIdentifier(node.key)) return
if (!isIdentifier(node.value) && !isMemberExpression(node.value)) return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

const attr = node.key.name.toString()
const value = node.value
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/rules/no-unsafe-token-fn-usage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extractTokens, getTokenImport, isPandaAttribute, isPandaProp } from '../utils/helpers'
import { extractTokens, getTokenImport, isPandaAttribute, isPandaProp, isRecipeVariant } from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { TSESTree } from '@typescript-eslint/utils'
import {
Expand Down Expand Up @@ -110,6 +110,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isCallExpression(node.value) && !isLiteral(node.value) && !isTemplateLiteral(node.value)) return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

handleRuntimeFm(node.value)
handleLiteral(node.value)
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/rules/prefer-atomic-properties.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isPandaAttribute, isPandaProp, isValidProperty, resolveLonghand } from '../utils/helpers'
import { isPandaAttribute, isPandaProp, isRecipeVariant, isValidProperty, resolveLonghand } from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { compositeProperties } from '../utils/composite-properties'
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
Expand Down Expand Up @@ -54,6 +54,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

sendReport(node.key)
},
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/rules/prefer-composite-properties.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isPandaAttribute, isPandaProp, isValidProperty, resolveLonghand } from '../utils/helpers'
import { isPandaAttribute, isPandaProp, isRecipeVariant, isValidProperty, resolveLonghand } from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { compositeProperties } from '../utils/composite-properties'
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
Expand Down Expand Up @@ -52,6 +52,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

sendReport(node.key)
},
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/rules/prefer-longhand-properties.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isPandaAttribute, isPandaProp, resolveLonghand } from '../utils/helpers'
import { isPandaAttribute, isPandaProp, isRecipeVariant, resolveLonghand } from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
import type { TSESTree } from '@typescript-eslint/utils'
Expand Down Expand Up @@ -58,6 +58,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

sendReport(node.key)
},
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/rules/prefer-shorthand-properties.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isPandaAttribute, isPandaProp, resolveLonghand, resolveShorthands } from '../utils/helpers'
import { isPandaAttribute, isPandaProp, isRecipeVariant, resolveLonghand, resolveShorthands } from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
import type { TSESTree } from '@typescript-eslint/utils'
Expand Down Expand Up @@ -63,6 +63,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

sendReport(node.key)
},
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/rules/prefer-unified-property-style.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isPandaAttribute, isPandaProp, isValidProperty, resolveLonghand } from '../utils/helpers'
import { isPandaAttribute, isPandaProp, isRecipeVariant, isValidProperty, resolveLonghand } from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { compositeProperties } from '../utils/composite-properties'
import { isIdentifier, isJSXIdentifier, isJSXOpeningElement, isObjectExpression } from '../utils/nodes'
Expand Down Expand Up @@ -66,6 +66,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return
if (isRecipeVariant(node, context)) return

const cmp = resolveCompositeProperty(node.key.name)
if (!cmp) return
Expand Down
31 changes: 31 additions & 0 deletions plugin/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,34 @@ export const getTaggedTemplateCaller = (node: TSESTree.TaggedTemplateExpression)
return node.tag.callee.name
}
}

export function isRecipeVariant(node: TSESTree.Property, context: RuleContext<any, any>) {
const caller = isInPandaFunction(node, context)
if (!caller) return

// Check if the caller is either 'cva' or 'sva'
const recipe = getImports(context).find((imp) => ['cva', 'sva'].includes(imp.name) && imp.alias === caller)
if (!recipe) return

//* Nesting is different here because of slots and variants. We don't want to warn about those.
let currentNode: any = node
let length = 0
let styleObjectParent: string | null = null

// Traverse up the AST
while (currentNode) {
const keyName = currentNode?.key?.name
if (keyName && ['base', 'variants'].includes(keyName)) {
styleObjectParent = keyName
}
currentNode = currentNode.parent
if (!styleObjectParent) length++
}

// Determine the required length based on caller and styleObjectParent
const isCvaCaller = caller === 'cva'
const requiredLength = isCvaCaller ? 2 : 4
const extraLength = styleObjectParent === 'base' ? 0 : 4

if (length < requiredLength + extraLength) return true
}
2 changes: 1 addition & 1 deletion sandbox/v9/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export default tseslint.config({
},
rules: {
...panda.configs.recommended.rules,
'@pandacss/no-debug': 'off',
'@pandacss/no-margin-properties': 'warn',
'@pandacss/no-physical-properties': 'error',
'@pandacss/no-hardcoded-color': ['error', { noOpacity: true }],
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
Expand Down

0 comments on commit 4914e3d

Please sign in to comment.