@@ -28,9 +28,6 @@ import { segment } from './utils/segment'
2828import * as ValueParser from './value-parser'
2929import { walk , WalkAction } from './walk'
3030
31- const IS_VALID_STATIC_UTILITY_NAME = / ^ - ? [ a - z ] [ a - z A - Z 0 - 9 / % . _ - ] * $ /
32- const IS_VALID_FUNCTIONAL_UTILITY_NAME = / ^ - ? [ a - z ] [ a - z A - Z 0 - 9 / % . _ - ] * - \* $ /
33-
3431const DEFAULT_SPACING_SUGGESTIONS = [
3532 '0' ,
3633 '0.5' ,
@@ -5835,7 +5832,7 @@ export function createCssUtility(node: AtRule) {
58355832 let name = node . params
58365833
58375834 // Functional utilities. E.g.: `tab-size-*`
5838- if ( IS_VALID_FUNCTIONAL_UTILITY_NAME . test ( name ) ) {
5835+ if ( isValidFunctionalUtilityName ( name ) ) {
58395836 // API:
58405837 //
58415838 // - `--value('literal')` resolves a literal named value
@@ -6184,7 +6181,7 @@ export function createCssUtility(node: AtRule) {
61846181 }
61856182 }
61866183
6187- if ( IS_VALID_STATIC_UTILITY_NAME . test ( name ) ) {
6184+ if ( isValidStaticUtilityName ( name ) ) {
61886185 return ( designSystem : DesignSystem ) => {
61896186 designSystem . utilities . static ( name , ( ) => node . nodes . map ( cloneAstNode ) )
61906187 }
@@ -6428,3 +6425,144 @@ function alphaReplacedDropShadowProperties(
64286425 return [ decl ( property , prefix + replacedValue ) ]
64296426 }
64306427}
6428+
6429+ const UTILITY_ROOT = / ^ - ? [ a - z ] [ a - z A - Z 0 - 9 _ - ] * /
6430+
6431+ const PERCENT = 37
6432+ const SLASH = 47
6433+ const DOT = 46
6434+ const LOWER_A = 97
6435+ const LOWER_Z = 122
6436+ const UPPER_A = 65
6437+ const UPPER_Z = 90
6438+ const ZERO = 48
6439+ const NINE = 57
6440+ const UNDERSCORE = 95
6441+ const DASH = 45
6442+
6443+ export function isValidStaticUtilityName ( name : string ) : boolean {
6444+ let match = UTILITY_ROOT . exec ( name )
6445+ if ( match === null ) return false // Invalid root
6446+
6447+ let root = match [ 0 ]
6448+ let value = name . slice ( root . length )
6449+
6450+ // Root should not end in `-` if there is no value
6451+ //
6452+ // `tab-size-`
6453+ // --------- Root
6454+ if ( value . length === 0 && root . endsWith ( '-' ) ) {
6455+ return false
6456+ }
6457+
6458+ // No remaining value is valid
6459+ //
6460+ // `tab-size`
6461+ // -------- Root
6462+ if ( value . length === 0 ) {
6463+ return true
6464+ }
6465+
6466+ // Any valid (static) utility should be valid including:
6467+ // - Bare values with `.`: `p-1.5`
6468+ // - Bare values with `%`: `w-50%`
6469+ // - With an embedded modifier: `text-xs/8`
6470+
6471+ let seenSlash = false
6472+ for ( let i = 0 ; i < value . length ; i ++ ) {
6473+ let charCode = value . charCodeAt ( i )
6474+ switch ( charCode ) {
6475+ case PERCENT : {
6476+ // A percentage is only valid at the end of the value
6477+ if ( i !== value . length - 1 ) return false
6478+
6479+ // A percent is only valid when preceded by a digit. E.g.: `w-%` is invalid
6480+ let previousChar = value [ i - 1 ] || root [ root . length - 1 ] || ''
6481+ let previousCharCode = previousChar . charCodeAt ( 0 )
6482+ if ( previousCharCode < ZERO || previousCharCode > NINE ) return false
6483+ break
6484+ }
6485+
6486+ case SLASH : {
6487+ // A slash must be followed by at least 1 character. E.g.: `foo/` is invalid
6488+ if ( i === value . length - 1 ) return false
6489+
6490+ // A slash can only appear once. E.g.: `foo/bar/baz` is invalid
6491+ if ( seenSlash ) return false
6492+ seenSlash = true
6493+ break
6494+ }
6495+
6496+ case DOT : {
6497+ // Dots are only allowed between digits. E.g.: `p-1.a` is invalid
6498+ let previousChar = value [ i - 1 ] || root [ root . length - 1 ] || ''
6499+ let previousCharCode = previousChar . charCodeAt ( 0 )
6500+ if ( previousCharCode < ZERO || previousCharCode > NINE ) return false
6501+
6502+ let nextChar = value [ i + 1 ] || ''
6503+ let nextCharCode = nextChar . charCodeAt ( 0 )
6504+ if ( nextCharCode < ZERO || nextCharCode > NINE ) return false
6505+ break
6506+ }
6507+
6508+ // Allowed special characters
6509+ case UNDERSCORE :
6510+ case DASH : {
6511+ continue
6512+ }
6513+
6514+ default : {
6515+ if (
6516+ ( charCode >= LOWER_A && charCode <= LOWER_Z ) || // Allow a-z
6517+ ( charCode >= UPPER_A && charCode <= UPPER_Z ) || // Allow A-Z
6518+ ( charCode >= ZERO && charCode <= NINE ) // Allow 0-9
6519+ ) {
6520+ continue
6521+ }
6522+
6523+ // Everything else is invalid
6524+ return false
6525+ }
6526+ }
6527+ }
6528+
6529+ return true
6530+ }
6531+
6532+ export function isValidFunctionalUtilityName ( name : string ) : boolean {
6533+ if ( ! name . endsWith ( '-*' ) ) return false // Missing '-*' suffix
6534+ name = name . slice ( 0 , - 2 )
6535+
6536+ let match = UTILITY_ROOT . exec ( name )
6537+ if ( match === null ) return false // Invalid root
6538+
6539+ let root = match [ 0 ]
6540+ let value = name . slice ( root . length )
6541+
6542+ // Root should not end in `-` if there is no value
6543+ //
6544+ // `tab-size--*`
6545+ // --------- Root
6546+ // -- Suffix
6547+ //
6548+ // Because with default values, this could match `tab-size-` which is invalid.
6549+ if ( value . length === 0 && root . endsWith ( '-' ) ) {
6550+ return false
6551+ }
6552+
6553+ // No remaining value is valid
6554+ //
6555+ // `tab-size-*`
6556+ // -------- Root
6557+ // -- Suffix
6558+ if ( value . length === 0 ) {
6559+ return true
6560+ }
6561+
6562+ // But if there is a value remaining, it's invalid.
6563+ //
6564+ // E.g.: `tab-size-[…]-*`
6565+ //
6566+ // If we allow more characters, we can extend the validation here
6567+ return false
6568+ }
0 commit comments