Skip to content

Commit d02fcf6

Browse files
Copilotanubra266
andauthored
fix: use replaceAll for duplicate token references in composite values (#3499)
* Initial plan * fix: use replaceAll for duplicate token references in composite values When the same token reference (e.g. {sizes.0.5}) appears more than once in a composite value, String.replace() only replaces the first occurrence. Changed to String.replaceAll() in both Token.expandReferences() and expandReferences() utility to resolve all occurrences correctly. Fixes #2996 Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com> Agent-Logs-Url: https://github.com/chakra-ui/panda/sessions/45fce8e1-9a22-443c-8b3f-9565bd0190ae * Add changeset for duplicate token references fix Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com> Agent-Logs-Url: https://github.com/chakra-ui/panda/sessions/6617edb1-1a07-446f-a728-a7e7fb23f177 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>
1 parent 5125b73 commit d02fcf6

File tree

5 files changed

+58
-2
lines changed

5 files changed

+58
-2
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@pandacss/token-dictionary': patch
3+
---
4+
5+
Fix duplicate token references with special characters resolving incorrectly in composite values
6+
7+
When the same token reference containing special characters (e.g. `{sizes.0.5}`) appeared more than once in a composite value, only the first occurrence was resolved correctly. The second occurrence produced a malformed CSS variable name.
8+
9+
This was caused by `String.replace()` only replacing the first match. Changed to `String.replaceAll()` in `Token.expandReferences()` and `expandReferences()` utility.
10+
11+
**Before (broken):**
12+
`--shadows-control-accent: 0 var(--sizes-0\.5) var(--sizes-0-5) rgba(92, 225, 113, 0.25)`
13+
14+
**After (fixed):**
15+
`--shadows-control-accent: 0 var(--sizes-0\.5) var(--sizes-0\.5) rgba(92, 225, 113, 0.25)`

packages/token-dictionary/__tests__/expand-references.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,3 +258,19 @@ test('expand references in value - token fn with deeply nested ref fallback', ()
258258
`"var(--colors-red-300, var(--colors-blue-500, var(--colors-primary, var(--colors-blue-700, var(--colors-red-500)))))"`,
259259
)
260260
})
261+
262+
test('expand references in value - duplicate curly ref with special characters', () => {
263+
const dictionary = new TokenDictionary({
264+
tokens: {
265+
sizes: {
266+
0.5: { value: '0.125rem' },
267+
},
268+
},
269+
})
270+
271+
dictionary.init()
272+
273+
expect(
274+
dictionary.expandReferenceInValue('0 {sizes.0.5} {sizes.0.5} rgba(92, 225, 113, 0.25)'),
275+
).toMatchInlineSnapshot(`"0 var(--sizes-0\\.5) var(--sizes-0\\.5) rgba(92, 225, 113, 0.25)"`)
276+
})

packages/token-dictionary/__tests__/semantic-token.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,31 @@
11
import { expect, test } from 'vitest'
22
import { TokenDictionary } from '../src/dictionary'
33

4+
test('semantic tokens / duplicate token references with special characters', () => {
5+
const dictionary = new TokenDictionary({
6+
tokens: {
7+
sizes: {
8+
0.5: { value: '0.125rem' },
9+
},
10+
},
11+
semanticTokens: {
12+
shadows: {
13+
controlAccent: {
14+
value: '0 {sizes.0.5} {sizes.0.5} rgba(92, 225, 113, 0.25)',
15+
},
16+
},
17+
},
18+
})
19+
20+
dictionary.registerTokens()
21+
dictionary.build()
22+
23+
const shadowToken = dictionary.allTokens.find((t) => t.name === 'shadows.controlAccent')
24+
expect(shadowToken).toBeDefined()
25+
// Both occurrences of {sizes.0.5} should be resolved to the same value
26+
expect(shadowToken!.value).toMatchInlineSnapshot(`"0 0.125rem 0.125rem rgba(92, 225, 113, 0.25)"`)
27+
})
28+
429
test('semantic tokens / deeply nested', () => {
530
const dictionary = new TokenDictionary({
631
semanticTokens: {

packages/token-dictionary/src/token.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export class Token {
130130
return valueStr
131131
}
132132
const value = referenceToken.expandReferences()
133-
return valueStr.replace(`{${key}}`, value)
133+
return valueStr.replaceAll(`{${key}}`, value)
134134
}, this.value)
135135

136136
delete this.extensions.references

packages/token-dictionary/src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function expandReferences(value: string, fn: (key: string) => string) {
5151
}
5252
const expandedValue = resolved ?? esc(key)
5353

54-
return valueStr.replace(`{${key}}`, expandedValue)
54+
return valueStr.replaceAll(`{${key}}`, expandedValue)
5555
}, value)
5656

5757
if (!expanded.includes(`token(`)) return expanded

0 commit comments

Comments
 (0)