Skip to content

Commit 08a07f6

Browse files
Support square bracket notation in paths (#6519)
1 parent fb545bc commit 08a07f6

5 files changed

+137
-8
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3434
- Don't output unparsable values ([#6469](https://github.com/tailwindlabs/tailwindcss/pull/6469))
3535
- Fix text decoration utilities from overriding the new text decoration color/style/thickness utilities when used with a modifier ([#6378](https://github.com/tailwindlabs/tailwindcss/pull/6378))
3636
- Move defaults to their own always-on layer ([#6500](https://github.com/tailwindlabs/tailwindcss/pull/6500))
37-
- Support negative values in safelist patterns ([6480](https://github.com/tailwindlabs/tailwindcss/pull/6480))
37+
- Support negative values in safelist patterns ([#6480](https://github.com/tailwindlabs/tailwindcss/pull/6480))
38+
- Support square bracket notation in paths ([#6519](https://github.com/tailwindlabs/tailwindcss/pull/6519))
3839

3940
## [3.0.2] - 2021-12-13
4041

src/lib/evaluateTailwindFunctions.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function validatePath(config, path, defaultValue) {
4242
? pathToString(path)
4343
: path.replace(/^['"]+/g, '').replace(/['"]+$/g, '')
4444
const pathSegments = Array.isArray(path) ? path : toPath(pathString)
45-
const value = dlv(config.theme, pathString, defaultValue)
45+
const value = dlv(config.theme, pathSegments, defaultValue)
4646

4747
if (value === undefined) {
4848
let error = `'${pathString}' does not exist in your theme config.`

src/util/toPath.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,26 @@
1+
/**
2+
* Parse a path string into an array of path segments.
3+
*
4+
* Square bracket notation `a[b]` may be used to "escape" dots that would otherwise be interpreted as path separators.
5+
*
6+
* Example:
7+
* a -> ['a]
8+
* a.b.c -> ['a', 'b', 'c']
9+
* a[b].c -> ['a', 'b', 'c']
10+
* a[b.c].e.f -> ['a', 'b.c', 'e', 'f']
11+
* a[b][c][d] -> ['a', 'b', 'c', 'd']
12+
*
13+
* @param {string|string[]} path
14+
**/
115
export function toPath(path) {
216
if (Array.isArray(path)) return path
3-
return path.split(/[\.\]\[]+/g)
17+
18+
let openBrackets = path.split('[').length - 1
19+
let closedBrackets = path.split(']').length - 1
20+
21+
if (openBrackets !== closedBrackets) {
22+
throw new Error(`Path is invalid. Has unbalanced brackets: ${path}`)
23+
}
24+
25+
return path.split(/\.(?![^\[]*\])|[\[\]]/g).filter(Boolean)
426
}

tests/evaluateTailwindFunctions.test.js

+105
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,111 @@ test('it looks up values in the theme using dot notation', () => {
3131
})
3232
})
3333

34+
test('it looks up values in the theme using bracket notation', () => {
35+
let input = css`
36+
.banana {
37+
color: theme('colors[yellow]');
38+
}
39+
`
40+
41+
let output = css`
42+
.banana {
43+
color: #f7cc50;
44+
}
45+
`
46+
47+
return run(input, {
48+
theme: {
49+
colors: {
50+
yellow: '#f7cc50',
51+
},
52+
},
53+
}).then((result) => {
54+
expect(result.css).toEqual(output)
55+
expect(result.warnings().length).toBe(0)
56+
})
57+
})
58+
59+
test('it looks up values in the theme using consecutive bracket notation', () => {
60+
let input = css`
61+
.banana {
62+
color: theme('colors[yellow][100]');
63+
}
64+
`
65+
66+
let output = css`
67+
.banana {
68+
color: #f7cc50;
69+
}
70+
`
71+
72+
return run(input, {
73+
theme: {
74+
colors: {
75+
yellow: {
76+
100: '#f7cc50',
77+
},
78+
},
79+
},
80+
}).then((result) => {
81+
expect(result.css).toEqual(output)
82+
expect(result.warnings().length).toBe(0)
83+
})
84+
})
85+
86+
test('it looks up values in the theme using bracket notation that have dots in them', () => {
87+
let input = css`
88+
.banana {
89+
padding-top: theme('spacing[1.5]');
90+
}
91+
`
92+
93+
let output = css`
94+
.banana {
95+
padding-top: 0.375rem;
96+
}
97+
`
98+
99+
return run(input, {
100+
theme: {
101+
spacing: {
102+
'1.5': '0.375rem',
103+
},
104+
},
105+
}).then((result) => {
106+
expect(result.css).toEqual(output)
107+
expect(result.warnings().length).toBe(0)
108+
})
109+
})
110+
111+
test('theme with mismatched brackets throws an error ', async () => {
112+
let config = {
113+
theme: {
114+
spacing: {
115+
'1.5': '0.375rem',
116+
},
117+
},
118+
}
119+
120+
let input = (path) => css`
121+
.banana {
122+
padding-top: theme('${path}');
123+
}
124+
`
125+
126+
await expect(run(input('spacing[1.5]]'), config)).rejects.toThrowError(
127+
`Path is invalid. Has unbalanced brackets: spacing[1.5]]`
128+
)
129+
130+
await expect(run(input('spacing[[1.5]'), config)).rejects.toThrowError(
131+
`Path is invalid. Has unbalanced brackets: spacing[[1.5]`
132+
)
133+
134+
await expect(run(input('spacing[a['), config)).rejects.toThrowError(
135+
`Path is invalid. Has unbalanced brackets: spacing[a[`
136+
)
137+
})
138+
34139
test('color can be a function', () => {
35140
let input = css`
36141
.backgroundColor {

tests/to-path.test.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ it('should keep an array as an array', () => {
77
})
88

99
it.each`
10-
input | output
11-
${'a.b.c'} | ${['a', 'b', 'c']}
12-
${'a[0].b.c'} | ${['a', '0', 'b', 'c']}
13-
${'.a'} | ${['', 'a']}
14-
${'[].a'} | ${['', 'a']}
10+
input | output
11+
${'a.b.c'} | ${['a', 'b', 'c']}
12+
${'a[0].b.c'} | ${['a', '0', 'b', 'c']}
13+
${'.a'} | ${['a']}
14+
${'[].a'} | ${['a']}
15+
${'a[1.5][b][c]'} | ${['a', '1.5', 'b', 'c']}
1516
`('should convert "$input" to "$output"', ({ input, output }) => {
1617
expect(toPath(input)).toEqual(output)
1718
})

0 commit comments

Comments
 (0)