Skip to content

Commit 727de66

Browse files
RobinMalfaitlzt1008thecrypticaceliangzhengtai
authored
Improve type checking for formal syntax (#9448)
* Improve type checking for formal syntax * Add test * Change order of test class name * fix failing tests * prefer `position` over `size` for backwards compatibility reasons Previously `bg-[10px_10%]` generated `background-position: 10px 10%` before we introduced the fallback plugins. Therefore we should prefer `position` over `size` as the default for backwards compatibility. * update changelog * ensure correct order Thanks Prettier! * update changelog Co-authored-by: lzt1008 <[email protected]> Co-authored-by: Jordan Pittman <[email protected]> Co-authored-by: liangzhengtai <[email protected]>
1 parent 94d6e72 commit 727de66

6 files changed

+126
-15
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4141
- Revert change that only listened for stdin close on TTYs ([#9331](https://github.com/tailwindlabs/tailwindcss/pull/9331))
4242
- Ignore unset values (like `null` or `undefined`) when resolving the classList for intellisense ([#9385](https://github.com/tailwindlabs/tailwindcss/pull/9385))
4343
- Implement fallback plugins when arbitrary values result in css from multiple plugins ([#9376](https://github.com/tailwindlabs/tailwindcss/pull/9376))
44+
- Improve type checking for formal syntax ([#9349](https://github.com/tailwindlabs/tailwindcss/pull/9349), [#9448](https://github.com/tailwindlabs/tailwindcss/pull/9448))
4445

4546
## [3.1.8] - 2022-08-05
4647

src/corePlugins.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1482,7 +1482,7 @@ export let corePlugins = {
14821482
},
14831483

14841484
backgroundSize: createUtilityPlugin('backgroundSize', [['bg', ['background-size']]], {
1485-
type: ['lookup', ['length', { preferOnConflict: true }], 'percentage'],
1485+
type: ['lookup', 'length', 'percentage', 'size'],
14861486
}),
14871487

14881488
backgroundAttachment: ({ addUtilities }) => {
@@ -1503,7 +1503,7 @@ export let corePlugins = {
15031503
},
15041504

15051505
backgroundPosition: createUtilityPlugin('backgroundPosition', [['bg', ['background-position']]], {
1506-
type: ['lookup', 'position'],
1506+
type: ['lookup', ['position', { preferOnConflict: true }]],
15071507
}),
15081508

15091509
backgroundRepeat: ({ addUtilities }) => {

src/util/dataTypes.js

+11-11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ let cssFunctions = ['min', 'max', 'clamp', 'calc']
66

77
// Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types
88

9+
function isCSSFunction(value) {
10+
return cssFunctions.some((fn) => new RegExp(`^${fn}\\(.*\\)`).test(value))
11+
}
12+
913
// This is not a data type, but rather a function that can normalize the
1014
// correct values.
1115
export function normalize(value, isRoot = true) {
@@ -55,13 +59,11 @@ export function url(value) {
5559
}
5660

5761
export function number(value) {
58-
return !isNaN(Number(value)) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?`).test(value))
62+
return !isNaN(Number(value)) || isCSSFunction(value)
5963
}
6064

6165
export function percentage(value) {
62-
return splitAtTopLevelOnly(value, '_').every((part) => {
63-
return /%$/g.test(part) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?%`).test(part))
64-
})
66+
return (value.endsWith('%') && number(value.slice(0, -1))) || isCSSFunction(value)
6567
}
6668

6769
let lengthUnits = [
@@ -84,13 +86,11 @@ let lengthUnits = [
8486
]
8587
let lengthUnitsPattern = `(?:${lengthUnits.join('|')})`
8688
export function length(value) {
87-
return splitAtTopLevelOnly(value, '_').every((part) => {
88-
return (
89-
part === '0' ||
90-
new RegExp(`${lengthUnitsPattern}$`).test(part) ||
91-
cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?${lengthUnitsPattern}`).test(part))
92-
)
93-
})
89+
return (
90+
value === '0' ||
91+
new RegExp(`^[+-]?[0-9]*\.?[0-9]+(?:[eE][+-]?[0-9]+)?${lengthUnitsPattern}$`).test(value) ||
92+
isCSSFunction(value)
93+
)
9494
}
9595

9696
let lineWidths = new Set(['thin', 'medium', 'thick'])

src/util/pluginUtils.js

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
shadow,
1919
} from './dataTypes'
2020
import negateValue from './negateValue'
21+
import { backgroundSize } from './validateFormalSyntax'
2122

2223
export function updateAllClasses(selectors, updateClass) {
2324
let parser = selectorParser((selectors) => {
@@ -162,6 +163,7 @@ export let typeMap = {
162163
'absolute-size': guess(absoluteSize),
163164
'relative-size': guess(relativeSize),
164165
shadow: guess(shadow),
166+
size: guess(backgroundSize),
165167
}
166168

167169
let supportedTypes = Object.keys(typeMap)

src/util/validateFormalSyntax.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { length, percentage } from './dataTypes'
2+
import { splitAtTopLevelOnly } from './splitAtTopLevelOnly'
3+
4+
/**
5+
*
6+
* https://developer.mozilla.org/en-US/docs/Web/CSS/background-size#formal_syntax
7+
*
8+
* background-size =
9+
* <bg-size>#
10+
*
11+
* <bg-size> =
12+
* [ <length-percentage [0,∞]> | auto ]{1,2} |
13+
* cover |
14+
* contain
15+
*
16+
* <length-percentage> =
17+
* <length> |
18+
* <percentage>
19+
*
20+
* @param {string} value
21+
*/
22+
export function backgroundSize(value) {
23+
let keywordValues = ['cover', 'contain']
24+
// the <length-percentage> type will probably be a css function
25+
// so we have to use `splitAtTopLevelOnly`
26+
return splitAtTopLevelOnly(value, ',').every((part) => {
27+
let sizes = splitAtTopLevelOnly(part, '_').filter(Boolean)
28+
if (sizes.length === 1 && keywordValues.includes(sizes[0])) return true
29+
30+
if (sizes.length !== 1 && sizes.length !== 2) return false
31+
32+
return sizes.every((size) => length(size) || percentage(size) || size === 'auto')
33+
})
34+
}

tests/arbitrary-values.test.js

+76-2
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ it('should pick the fallback plugin when arbitrary values collide', () => {
285285
}
286286
287287
.bg-\[200px_100px\] {
288-
background-size: 200px 100px;
288+
background-position: 200px 100px;
289289
}
290290
`)
291291
})
@@ -311,7 +311,7 @@ it('should warn and not generate if arbitrary values are ambiguous (without fall
311311
plugins: [
312312
function ({ matchUtilities }) {
313313
matchUtilities({ foo: (value) => ({ value }) }, { type: ['position'] })
314-
matchUtilities({ foo: (value) => ({ value }) }, { type: ['length'] })
314+
matchUtilities({ foo: (value) => ({ value }) }, { type: ['size'] })
315315
},
316316
],
317317
}
@@ -463,3 +463,77 @@ it('should correctly validate each part when checking for `percentage` data type
463463
`)
464464
})
465465
})
466+
467+
it('should correctly validate background size', () => {
468+
let config = {
469+
content: [{ raw: html`<div class="bg-[auto_auto,cover,_contain,10px,10px_10%]"></div>` }],
470+
corePlugins: { preflight: false },
471+
plugins: [],
472+
}
473+
474+
let input = css`
475+
@tailwind utilities;
476+
`
477+
478+
return run(input, config).then((result) => {
479+
expect(result.css).toMatchFormattedCss(css`
480+
.bg-\[auto_auto\2c cover\2c _contain\2c 10px\2c 10px_10\%\] {
481+
background-size: auto auto, cover, contain, 10px, 10px 10%;
482+
}
483+
`)
484+
})
485+
})
486+
487+
it('should correctly validate combination of percentage and length', () => {
488+
let config = {
489+
content: [{ raw: html`<div class="bg-[50px_10%] bg-[50%_10%] bg-[50px_10px]"></div>` }],
490+
corePlugins: { preflight: false },
491+
plugins: [],
492+
}
493+
494+
let input = css`
495+
@tailwind utilities;
496+
`
497+
498+
return run(input, config).then((result) => {
499+
expect(result.css).toMatchFormattedCss(css`
500+
.bg-\[50px_10\%\] {
501+
background-position: 50px 10%;
502+
}
503+
.bg-\[50\%_10\%\] {
504+
background-position: 50% 10%;
505+
}
506+
.bg-\[50px_10px\] {
507+
background-position: 50px 10px;
508+
}
509+
`)
510+
})
511+
})
512+
513+
it('can explicitly specify type for percentage and length', () => {
514+
let config = {
515+
content: [
516+
{ raw: html`<div class="bg-[size:50px_10%] bg-[50px_10px] bg-[position:50%_10%]"></div>` },
517+
],
518+
corePlugins: { preflight: false },
519+
plugins: [],
520+
}
521+
522+
let input = css`
523+
@tailwind utilities;
524+
`
525+
526+
return run(input, config).then((result) => {
527+
expect(result.css).toMatchFormattedCss(css`
528+
.bg-\[size\:50px_10\%\] {
529+
background-size: 50px 10%;
530+
}
531+
.bg-\[50px_10px\] {
532+
background-position: 50px 10px;
533+
}
534+
.bg-\[position\:50\%_10\%\] {
535+
background-position: 50% 10%;
536+
}
537+
`)
538+
})
539+
})

0 commit comments

Comments
 (0)