Skip to content

Commit 13e75f4

Browse files
committed
add validations
- If you are using a bare value or modifier that is a number, then we make sure that it is a valid multiplier of `0.25` - If you are using a bare value or modifier that is a percentage, then we make sure that it is a valid positive integer. - If you are using a fraction, then we make sure that both the numerator and denominator are positive integers. - If the bare value resolves to a non-ratio value, and if a modifier is used, then we need to make sure that the modifier resolves as well. E.g.: `example-1/2.3` this won't resolve to a `ratio` because the denominator is invalid. This will resolve to an `integer` or `number` for the value of `1`, but then we need to make sure that `2.3` is a valid modifier.
1 parent 92e21a4 commit 13e75f4

File tree

2 files changed

+97
-1
lines changed

2 files changed

+97
-1
lines changed

packages/tailwindcss/src/index.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { substituteFunctions } from './css-functions'
2626
import * as CSS from './css-parser'
2727
import { buildDesignSystem, type DesignSystem } from './design-system'
2828
import { Theme, ThemeOptions } from './theme'
29-
import { inferDataType } from './utils/infer-data-type'
29+
import { inferDataType, isPositiveInteger, isValidSpacingMultiplier } from './utils/infer-data-type'
3030
import { segment } from './utils/segment'
3131
import * as ValueParser from './value-parser'
3232
import { compoundsForSelectors } from './variants'
@@ -217,6 +217,9 @@ async function parseCss(
217217
// in order to make the utility valid.
218218
let resolvedValueFn = false
219219

220+
// The resolved value type, e.g.: `integer`
221+
let resolvedValueType = null as string | null
222+
220223
// Whether `modifier(…)` was used
221224
let usedModifierFn = false
222225

@@ -277,6 +280,27 @@ async function parseCss(
277280

278281
let type = inferDataType(value, [arg.value as any])
279282
if (type !== null) {
283+
// Ratio must be a valid fraction, e.g.: <integer>/<integer>
284+
if (type === 'ratio') {
285+
let [lhs, rhs] = segment(value, '/')
286+
if (!isPositiveInteger(lhs) || !isPositiveInteger(rhs)) continue
287+
}
288+
289+
// Non-integer numbers should be a valid multiplier,
290+
// e.g.: `1.5`
291+
else if (type === 'number' && !isValidSpacingMultiplier(value)) {
292+
continue
293+
}
294+
295+
// Percentages must be an integer, e.g.: `50%`
296+
else if (
297+
type === 'percentage' &&
298+
!isPositiveInteger(value.slice(0, -1))
299+
) {
300+
continue
301+
}
302+
303+
resolvedValueType = type
280304
resolvedValueFn = true
281305
replaceWith(ValueParser.parse(value))
282306
return ValueParser.ValueWalkAction.Skip
@@ -379,6 +403,20 @@ async function parseCss(
379403
let value = candidate.modifier.value
380404
let type = inferDataType(value, [arg.value as any])
381405
if (type !== null) {
406+
// Non-integer numbers should be a valid multiplier,
407+
// e.g.: `1.5`
408+
if (type === 'number' && !isValidSpacingMultiplier(value)) {
409+
continue
410+
}
411+
412+
// Percentages must be an integer, e.g.: `50%`
413+
else if (
414+
type === 'percentage' &&
415+
!isPositiveInteger(value.slice(0, -1))
416+
) {
417+
continue
418+
}
419+
382420
resolvedModifierFn = true
383421
replaceWith(ValueParser.parse(value))
384422
return ValueParser.ValueWalkAction.Skip
@@ -424,6 +462,23 @@ async function parseCss(
424462
}
425463
})
426464

465+
// Ensure that the modifier was resolved if present on the
466+
// candidate. We also have to make sure that the value is _not_
467+
// using a fraction.
468+
//
469+
// E.g.:
470+
//
471+
// - `w-1/2`, can be a value of `1` and modifier of `2`
472+
// - `w-1/2`, can be a fraction of `1/2` and no modifier
473+
if (
474+
candidate.value.kind === 'named' &&
475+
resolvedValueType !== 'ratio' &&
476+
!usedModifierFn &&
477+
candidate.modifier !== null
478+
) {
479+
return null
480+
}
481+
427482
if (usedValueFn && !resolvedValueFn) return null
428483
if (usedModifierFn && !resolvedModifierFn) return null
429484

packages/tailwindcss/src/utilities.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17514,6 +17514,47 @@ describe('custom utilities', () => {
1751417514
expect(await compileCss(input, ['tab-foo'])).toEqual('')
1751517515
})
1751617516

17517+
test('resolving bare values with constraints for integer, percentage, and ratio', async () => {
17518+
let input = css`
17519+
@utility example-* {
17520+
--value-as-number: value(number);
17521+
--value-as-percentage: value(percentage);
17522+
--value-as-ratio: value(ratio);
17523+
}
17524+
17525+
@tailwind utilities;
17526+
`
17527+
17528+
expect(await compileCss(input, ['example-1', 'example-0.5', 'example-20%', 'example-2/3']))
17529+
.toMatchInlineSnapshot(`
17530+
".example-0\\.5 {
17531+
--value-as-number: .5;
17532+
}
17533+
17534+
.example-1 {
17535+
--value-as-number: 1;
17536+
}
17537+
17538+
.example-2\\/3 {
17539+
--value-as-number: 2;
17540+
--value-as-ratio: 2 / 3;
17541+
}
17542+
17543+
.example-20\\% {
17544+
--value-as-percentage: 20%;
17545+
}"
17546+
`)
17547+
expect(
17548+
await compileCss(input, [
17549+
'example-1.23',
17550+
'example-12.34%',
17551+
'example-1.2/3',
17552+
'example-1/2.3',
17553+
'example-1.2/3.4',
17554+
]),
17555+
).toEqual('')
17556+
})
17557+
1751717558
test('resolving unsupported bare values', async () => {
1751817559
let input = css`
1751917560
@utility tab-* {

0 commit comments

Comments
 (0)