Skip to content

Commit b37a44a

Browse files
committed
Improve type checking for formal syntax
1 parent 4fddd2d commit b37a44a

5 files changed

+84
-12
lines changed

src/corePlugins.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1481,7 +1481,7 @@ export let corePlugins = {
14811481
},
14821482

14831483
backgroundSize: createUtilityPlugin('backgroundSize', [['bg', ['background-size']]], {
1484-
type: ['lookup', 'length', 'percentage'],
1484+
type: ['lookup', 'length', 'percentage', 'size'],
14851485
}),
14861486

14871487
backgroundAttachment: ({ 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 @@ 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+
const 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+
const 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

+36
Original file line numberDiff line numberDiff line change
@@ -416,3 +416,39 @@ it('should correctly validate each part when checking for `percentage` data type
416416
`)
417417
})
418418
})
419+
420+
it('should correctly validate background size', () => {
421+
let config = {
422+
content: [{ raw: html`<div class="bg-[auto_auto,cover,_contain,10px,10px_10%]"></div>` }],
423+
corePlugins: { preflight: false },
424+
plugins: [],
425+
}
426+
427+
let input = css`
428+
@tailwind utilities;
429+
`
430+
431+
return run(input, config).then((result) => {
432+
expect(result.css).toMatchFormattedCss(css`
433+
.bg-\[auto_auto\2c cover\2c _contain\2c 10px\2c 10px_10\%\] {
434+
background-size: auto auto, cover, contain, 10px, 10px 10%;
435+
}
436+
`)
437+
})
438+
})
439+
440+
it('should correctly validate combination of percentage and length', () => {
441+
let config = {
442+
content: [{ raw: html`<div class="bg-[50px_10%] bg-[50%_10%] bg-[50px_10px]"></div>` }],
443+
corePlugins: { preflight: false },
444+
plugins: [],
445+
}
446+
447+
let input = css`
448+
@tailwind utilities;
449+
`
450+
451+
return run(input, config).then((result) => {
452+
expect(result.css.trim()).toHaveLength(0)
453+
})
454+
})

0 commit comments

Comments
 (0)