Skip to content

Commit

Permalink
add validations
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
RobinMalfait committed Dec 22, 2024
1 parent ce1e1ca commit 4eba187
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 1 deletion.
57 changes: 56 additions & 1 deletion packages/tailwindcss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { substituteFunctions } from './css-functions'
import * as CSS from './css-parser'
import { buildDesignSystem, type DesignSystem } from './design-system'
import { Theme, ThemeOptions } from './theme'
import { inferDataType } from './utils/infer-data-type'
import { inferDataType, isPositiveInteger, isValidSpacingMultiplier } from './utils/infer-data-type'
import { segment } from './utils/segment'
import * as ValueParser from './value-parser'
import { compoundsForSelectors } from './variants'
Expand Down Expand Up @@ -217,6 +217,9 @@ async function parseCss(
// in order to make the utility valid.
let resolvedValueFn = false

// The resolved value type, e.g.: `integer`
let resolvedValueType = null as string | null

// Whether `modifier(…)` was used
let usedModifierFn = false

Expand Down Expand Up @@ -277,6 +280,27 @@ async function parseCss(

let type = inferDataType(value, [arg.value as any])
if (type !== null) {
// Ratio must be a valid fraction, e.g.: <integer>/<integer>
if (type === 'ratio') {
let [lhs, rhs] = segment(value, '/')
if (!isPositiveInteger(lhs) || !isPositiveInteger(rhs)) continue
}

// Non-integer numbers should be a valid multiplier,
// e.g.: `1.5`
else if (type === 'number' && !isValidSpacingMultiplier(value)) {
continue
}

// Percentages must be an integer, e.g.: `50%`
else if (
type === 'percentage' &&
!isPositiveInteger(value.slice(0, -1))
) {
continue
}

resolvedValueType = type
resolvedValueFn = true
replaceWith(ValueParser.parse(value))
return ValueParser.ValueWalkAction.Skip
Expand Down Expand Up @@ -379,6 +403,20 @@ async function parseCss(
let value = candidate.modifier.value
let type = inferDataType(value, [arg.value as any])
if (type !== null) {
// Non-integer numbers should be a valid multiplier,
// e.g.: `1.5`
if (type === 'number' && !isValidSpacingMultiplier(value)) {
continue
}

// Percentages must be an integer, e.g.: `50%`
else if (
type === 'percentage' &&
!isPositiveInteger(value.slice(0, -1))
) {
continue
}

resolvedModifierFn = true
replaceWith(ValueParser.parse(value))
return ValueParser.ValueWalkAction.Skip
Expand Down Expand Up @@ -424,6 +462,23 @@ async function parseCss(
}
})

// Ensure that the modifier was resolved if present on the
// candidate. We also have to make sure that the value is _not_
// using a fraction.
//
// E.g.:
//
// - `w-1/2`, can be a value of `1` and modifier of `2`
// - `w-1/2`, can be a fraction of `1/2` and no modifier
if (
candidate.value.kind === 'named' &&
resolvedValueType !== 'ratio' &&
!usedModifierFn &&
candidate.modifier !== null
) {
return null
}

if (usedValueFn && !resolvedValueFn) return null
if (usedModifierFn && !resolvedModifierFn) return null

Expand Down
41 changes: 41 additions & 0 deletions packages/tailwindcss/src/utilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17514,6 +17514,47 @@ describe('custom utilities', () => {
expect(await compileCss(input, ['tab-foo'])).toEqual('')
})

test('resolving bare values with constraints for integer, percentage, and ratio', async () => {
let input = css`
@utility example-* {
--value-as-number: value(number);
--value-as-percentage: value(percentage);
--value-as-ratio: value(ratio);
}
@tailwind utilities;
`

expect(await compileCss(input, ['example-1', 'example-0.5', 'example-20%', 'example-2/3']))
.toMatchInlineSnapshot(`
".example-0\\.5 {
--value-as-number: .5;
}
.example-1 {
--value-as-number: 1;
}
.example-2\\/3 {
--value-as-number: 2;
--value-as-ratio: 2 / 3;
}
.example-20\\% {
--value-as-percentage: 20%;
}"
`)
expect(
await compileCss(input, [
'example-1.23',
'example-12.34%',
'example-1.2/3',
'example-1/2.3',
'example-1.2/3.4',
]),
).toEqual('')
})

test('resolving unsupported bare values', async () => {
let input = css`
@utility tab-* {
Expand Down

0 comments on commit 4eba187

Please sign in to comment.