@@ -379,31 +379,81 @@ pub fn swc_parse_css(source: &str) -> (Result<Stylesheet, Error>, Vec<Error>) {
379379 ( parse_string_input ( input, None , config, & mut errors) , errors)
380380}
381381
382+ /// A byte that may appear in a CSS identifier (used to ensure a matched
383+ /// function name is not a suffix of a longer identifier).
384+ fn is_ident_byte ( byte : u8 ) -> bool {
385+ byte. is_ascii_alphanumeric ( ) || byte == b'-' || byte == b'_'
386+ }
387+
382388fn contains_css_function_call ( value : & str , function_name : & str ) -> bool {
389+ find_css_function_call ( value, function_name, |_, _| true )
390+ }
391+
392+ fn find_css_function_call (
393+ value : & str ,
394+ function_name : & str ,
395+ mut matches_after_open_paren : impl FnMut ( & [ u8 ] , usize ) -> bool ,
396+ ) -> bool {
383397 let value_bytes = value. as_bytes ( ) ;
384398 let function_bytes = function_name. as_bytes ( ) ;
385399
386400 if function_bytes. is_empty ( ) || value_bytes. len ( ) <= function_bytes. len ( ) {
387401 return false ;
388402 }
389403
390- for i in 0 ..=( value_bytes. len ( ) - function_bytes. len ( ) - 1 ) {
391- if value_bytes[ i..i + function_bytes. len ( ) ] . eq_ignore_ascii_case ( function_bytes)
392- && value_bytes[ i + function_bytes. len ( ) ] == b'('
393- {
394- return true ;
404+ let mut quote: Option < u8 > = None ;
405+ let mut is_comment = false ;
406+ let mut index = 0 ;
407+
408+ while index < value_bytes. len ( ) {
409+ let byte = value_bytes[ index] ;
410+
411+ if quote. is_none ( ) {
412+ if is_comment {
413+ if byte == b'*' && value_bytes. get ( index + 1 ) == Some ( & b'/' ) {
414+ is_comment = false ;
415+ index += 2 ;
416+ continue ;
417+ }
418+
419+ index += 1 ;
420+ continue ;
421+ }
422+
423+ if byte == b'/' && value_bytes. get ( index + 1 ) == Some ( & b'*' ) {
424+ is_comment = true ;
425+ index += 2 ;
426+ continue ;
427+ }
395428 }
429+
430+ match quote {
431+ Some ( current_quote) if byte == current_quote && !is_escaped ( value_bytes, index) => {
432+ quote = None ;
433+ } ,
434+ Some ( _) => { } ,
435+ None if ( byte == b'\'' || byte == b'"' ) && !is_escaped ( value_bytes, index) => {
436+ quote = Some ( byte) ;
437+ } ,
438+ None
439+ if index + function_bytes. len ( ) < value_bytes. len ( )
440+ && value_bytes[ index..index + function_bytes. len ( ) ]
441+ . eq_ignore_ascii_case ( function_bytes)
442+ && value_bytes[ index + function_bytes. len ( ) ] == b'('
443+ && ( index == 0 || !is_ident_byte ( value_bytes[ index - 1 ] ) )
444+ && matches_after_open_paren ( value_bytes, index + function_bytes. len ( ) + 1 ) =>
445+ {
446+ return true ;
447+ } ,
448+ _ => { } ,
449+ }
450+
451+ index += 1 ;
396452 }
397453
398454 false
399455}
400456
401- /// A byte that may appear in a CSS identifier (used to ensure a matched
402- /// function name is not a suffix of a longer identifier).
403- fn is_ident_byte ( byte : u8 ) -> bool {
404- byte. is_ascii_alphanumeric ( ) || byte == b'-' || byte == b'_'
405- }
406-
407457/// Detects CSS relative color syntax, e.g. `rgb(from red r g b)`.
408458///
409459/// A relative color function is any color function (see
@@ -420,46 +470,19 @@ fn contains_relative_color_function(value: &str) -> bool {
420470/// `(` and, after any whitespace, the `from` keyword (the relative color
421471/// marker).
422472fn has_relative_color_call ( value : & str , function_name : & str ) -> bool {
423- let value_bytes = value. as_bytes ( ) ;
424- let name_bytes = function_name. as_bytes ( ) ;
425-
426- if value_bytes. len ( ) <= name_bytes. len ( ) {
427- return false ;
428- }
429-
430473 const FROM : & [ u8 ] = b"from" ;
431474
432- for i in 0 ..=( value_bytes. len ( ) - name_bytes. len ( ) - 1 ) {
433- if !value_bytes[ i..i + name_bytes. len ( ) ] . eq_ignore_ascii_case ( name_bytes) {
434- continue ;
435- }
436-
437- // The function name must be immediately followed by `(`.
438- if value_bytes[ i + name_bytes. len ( ) ] != b'(' {
439- continue ;
440- }
441-
442- // Avoid matching a suffix of a longer identifier (e.g. `srgb(` for `rgb`).
443- if i > 0 && is_ident_byte ( value_bytes[ i - 1 ] ) {
444- continue ;
445- }
446-
475+ find_css_function_call ( value, function_name, |value_bytes, mut cursor| {
447476 // Skip whitespace after `(`, then look for the `from` keyword followed by a
448477 // whitespace boundary.
449- let mut cursor = i + name_bytes. len ( ) + 1 ;
450478 while cursor < value_bytes. len ( ) && value_bytes[ cursor] . is_ascii_whitespace ( ) {
451479 cursor += 1 ;
452480 }
453481
454- if cursor + FROM . len ( ) < value_bytes. len ( )
482+ cursor + FROM . len ( ) < value_bytes. len ( )
455483 && value_bytes[ cursor..cursor + FROM . len ( ) ] . eq_ignore_ascii_case ( FROM )
456484 && value_bytes[ cursor + FROM . len ( ) ] . is_ascii_whitespace ( )
457- {
458- return true ;
459- }
460- }
461-
462- false
485+ } )
463486}
464487
465488fn is_escaped ( value : & [ u8 ] , index : usize ) -> bool {
@@ -638,7 +661,9 @@ pub fn normalize_css_property_value(
638661 detect_unclosed_strings ( css_property_value) ;
639662
640663 if should_normalize_spacing_only {
641- return MANY_SPACES . replace_all ( css_property_value, " " ) . to_string ( ) ;
664+ return MANY_SPACES
665+ . replace_all ( css_property_value, " " )
666+ . replace ( "( " , "(" ) ;
642667 }
643668
644669 let ( parsed_css, errors) = swc_parse_css ( css_rule. as_str ( ) ) ;
0 commit comments