Skip to content

Commit

Permalink
Support square bracket notation in paths (#6519)
Browse files Browse the repository at this point in the history
  • Loading branch information
thecrypticace committed Dec 15, 2021
1 parent fb545bc commit 08a07f6
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 8 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Expand Up @@ -34,7 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Don't output unparsable values ([#6469](https://github.com/tailwindlabs/tailwindcss/pull/6469))
- 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))
- Move defaults to their own always-on layer ([#6500](https://github.com/tailwindlabs/tailwindcss/pull/6500))
- Support negative values in safelist patterns ([6480](https://github.com/tailwindlabs/tailwindcss/pull/6480))
- Support negative values in safelist patterns ([#6480](https://github.com/tailwindlabs/tailwindcss/pull/6480))
- Support square bracket notation in paths ([#6519](https://github.com/tailwindlabs/tailwindcss/pull/6519))

## [3.0.2] - 2021-12-13

Expand Down
2 changes: 1 addition & 1 deletion src/lib/evaluateTailwindFunctions.js
Expand Up @@ -42,7 +42,7 @@ function validatePath(config, path, defaultValue) {
? pathToString(path)
: path.replace(/^['"]+/g, '').replace(/['"]+$/g, '')
const pathSegments = Array.isArray(path) ? path : toPath(pathString)
const value = dlv(config.theme, pathString, defaultValue)
const value = dlv(config.theme, pathSegments, defaultValue)

if (value === undefined) {
let error = `'${pathString}' does not exist in your theme config.`
Expand Down
24 changes: 23 additions & 1 deletion src/util/toPath.js
@@ -1,4 +1,26 @@
/**
* Parse a path string into an array of path segments.
*
* Square bracket notation `a[b]` may be used to "escape" dots that would otherwise be interpreted as path separators.
*
* Example:
* a -> ['a]
* a.b.c -> ['a', 'b', 'c']
* a[b].c -> ['a', 'b', 'c']
* a[b.c].e.f -> ['a', 'b.c', 'e', 'f']
* a[b][c][d] -> ['a', 'b', 'c', 'd']
*
* @param {string|string[]} path
**/
export function toPath(path) {
if (Array.isArray(path)) return path
return path.split(/[\.\]\[]+/g)

let openBrackets = path.split('[').length - 1
let closedBrackets = path.split(']').length - 1

if (openBrackets !== closedBrackets) {
throw new Error(`Path is invalid. Has unbalanced brackets: ${path}`)
}

return path.split(/\.(?![^\[]*\])|[\[\]]/g).filter(Boolean)
}
105 changes: 105 additions & 0 deletions tests/evaluateTailwindFunctions.test.js
Expand Up @@ -31,6 +31,111 @@ test('it looks up values in the theme using dot notation', () => {
})
})

test('it looks up values in the theme using bracket notation', () => {
let input = css`
.banana {
color: theme('colors[yellow]');
}
`

let output = css`
.banana {
color: #f7cc50;
}
`

return run(input, {
theme: {
colors: {
yellow: '#f7cc50',
},
},
}).then((result) => {
expect(result.css).toEqual(output)
expect(result.warnings().length).toBe(0)
})
})

test('it looks up values in the theme using consecutive bracket notation', () => {
let input = css`
.banana {
color: theme('colors[yellow][100]');
}
`

let output = css`
.banana {
color: #f7cc50;
}
`

return run(input, {
theme: {
colors: {
yellow: {
100: '#f7cc50',
},
},
},
}).then((result) => {
expect(result.css).toEqual(output)
expect(result.warnings().length).toBe(0)
})
})

test('it looks up values in the theme using bracket notation that have dots in them', () => {
let input = css`
.banana {
padding-top: theme('spacing[1.5]');
}
`

let output = css`
.banana {
padding-top: 0.375rem;
}
`

return run(input, {
theme: {
spacing: {
'1.5': '0.375rem',
},
},
}).then((result) => {
expect(result.css).toEqual(output)
expect(result.warnings().length).toBe(0)
})
})

test('theme with mismatched brackets throws an error ', async () => {
let config = {
theme: {
spacing: {
'1.5': '0.375rem',
},
},
}

let input = (path) => css`
.banana {
padding-top: theme('${path}');
}
`

await expect(run(input('spacing[1.5]]'), config)).rejects.toThrowError(
`Path is invalid. Has unbalanced brackets: spacing[1.5]]`
)

await expect(run(input('spacing[[1.5]'), config)).rejects.toThrowError(
`Path is invalid. Has unbalanced brackets: spacing[[1.5]`
)

await expect(run(input('spacing[a['), config)).rejects.toThrowError(
`Path is invalid. Has unbalanced brackets: spacing[a[`
)
})

test('color can be a function', () => {
let input = css`
.backgroundColor {
Expand Down
11 changes: 6 additions & 5 deletions tests/to-path.test.js
Expand Up @@ -7,11 +7,12 @@ it('should keep an array as an array', () => {
})

it.each`
input | output
${'a.b.c'} | ${['a', 'b', 'c']}
${'a[0].b.c'} | ${['a', '0', 'b', 'c']}
${'.a'} | ${['', 'a']}
${'[].a'} | ${['', 'a']}
input | output
${'a.b.c'} | ${['a', 'b', 'c']}
${'a[0].b.c'} | ${['a', '0', 'b', 'c']}
${'.a'} | ${['a']}
${'[].a'} | ${['a']}
${'a[1.5][b][c]'} | ${['a', '1.5', 'b', 'c']}
`('should convert "$input" to "$output"', ({ input, output }) => {
expect(toPath(input)).toEqual(output)
})

0 comments on commit 08a07f6

Please sign in to comment.