sass4 4.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8746113a3c93d6763ffc657134bae7c7bc885d8fe3ec2f78f98e7c6a41d75f33
4
- data.tar.gz: 110c8380a322ac19911e21e41a9a3ec9ec192c927cf1f6eebc8078edfbb06868
3
+ metadata.gz: 2532993143d7c789248e915c271040649e7e3c8c45f8d42ed2cf2863ce302a92
4
+ data.tar.gz: 389ead2b3f1bca0cff8146ec3b3ab86c68528f06305e1f3ada7ed838d93d37c3
5
5
  SHA512:
6
- metadata.gz: 744b963c9ba12ed8b392fa17fa170cc28478024b54d5bcaff1998c3f4f8d5a91195ff1c069ff5ce862b175f55790683a04506e6f5822fcff66f2dacc20cccfe4
7
- data.tar.gz: 79c29d4de8f0d6221dd934d349f8b141a009e9afd6fd54efb1d5a817a8652b39c5d6eeb8306dfa5c305d3815cf5e10d3e7bd18a0afb448575b403f8950141636
6
+ metadata.gz: 7e37cc075482ea8f56e8bfd6e18f5f96cf9b9cfd83a06fa4505b570631e22eb2a4f9438ee3e86e22fbec17333138ea730f1236f57d4e93a4238d9547a25cad08
7
+ data.tar.gz: e1b0176044a695d57546d2ba441c21cc18e45c5cdf0bdfa391c76124cd2682a5ae05d98c295534d9f733839b303c803bb8a128cc6026410903963a9731e3a4e7
data/AGENTS.md CHANGED
@@ -409,123 +409,176 @@ test/
409
409
 
410
410
  ## Реализация CSS Color Level 4 синтаксиса
411
411
 
412
- ### Обзор задачи
412
+ ### Обзор реализации
413
413
 
414
- Была реализована поддержка CSS Color Level 4 синтаксиса для цветовых функций `rgb()`, `rgba()`, `hsl()`, `hsla()`. Новый синтаксис позволяет использовать:
414
+ Была реализована полная поддержка CSS Color Level 4 синтаксиса для цветовых функций `rgb()`, `rgba()`, `hsl()`, `hsla()`:
415
+
416
+ **Новый синтаксис:**
415
417
  - **Space-separated значения**: `rgb(255 0 0)` вместо `rgb(255, 0, 0)`
416
418
  - **Slash-separated alpha**: `rgb(255 0 0 / 0.5)` вместо `rgba(255, 0, 0, 0.5)`
417
419
 
418
- ### Архитектурное решение
419
-
420
- #### Проблема
420
+ **Поддержка special numbers (CSS функций):**
421
+ - `var()` - CSS Custom Properties
422
+ - `calc()` - математические вычисления
423
+ - `env()` - environment variables
424
+ - `attr()` - атрибуты элементов
425
+ - `clamp()` - ограничение значений
426
+ - `min()` / `max()` - минимум/максимум
427
+
428
+ **Примеры:**
429
+ ```scss
430
+ // CSS Custom Properties
431
+ .element {
432
+ color: rgb(var(--r), var(--g), var(--b));
433
+ background: hsl(var(--hue) 50% 50%);
434
+ }
435
+
436
+ // Вычисления
437
+ .dynamic {
438
+ color: rgb(255 128 0 / calc(0.5 * 2));
439
+ border-color: hsl(calc(180deg + 10deg), 50%, 50%);
440
+ }
441
+
442
+ // Новый синтаксис
443
+ .modern {
444
+ color: rgb(255 128 0 / 0.5); // space-separated с alpha
445
+ }
446
+ ```
421
447
 
422
- Основная проблема заключалась в том, что парсер Ruby Sass интерпретировал символ `/` как оператор деления в выражениях типа `rgb(255 0 0 / 0.5)`, создавая AST структуру `(255 0 (0 / 0.5))` вместо ожидаемой `(255 0 0 / 0.5)`.
448
+ ### Архитектурное решение
423
449
 
424
- #### Решение
450
+ #### Проблема 1: Парсинг slash-separated синтаксиса
425
451
 
426
- Вместо "костылей" в функциях, было реализовано правильное решение на уровне парсера:
452
+ Парсер Ruby Sass интерпретировал символ `/` как оператор деления в выражениях типа `rgb(255 0 0 / 0.5)`, создавая неправильную AST структуру.
427
453
 
454
+ **Решение:**
428
455
  1. **Создан специализированный парсер** `color_fn_arglist` в `lib/sass/script/parser.rb`
429
456
  2. **Модифицирован** метод `funcall` для использования `color_fn_arglist` для функций `rgb/rgba/hsl/hsla`
430
- 3. **Упрощены** функции цвета в `lib/sass/script/functions.rb` для работы с новой структурой
457
+ 3. **Парсинг на уровне `unary_plus`** вместо `equals` для избежания интерпретации `/` как деления
431
458
 
432
- #### Детали реализации
459
+ #### Проблема 2: Поддержка CSS special numbers
433
460
 
434
- ##### 1. Парсер (lib/sass/script/parser.rb)
461
+ CSS функции типа `var()`, `calc()` должны передаваться в CSS без вычисления, но Ruby Sass пытался их обработать.
462
+
463
+ **Решение:**
464
+ 1. **Добавлены методы проверки** в базовый класс `Value`:
465
+ - `is_special_number?` - проверяет, является ли значение CSS функцией
466
+ - `is_var?` - специальная проверка для `var()`
435
467
 
436
- **Метод `funcall`:**
468
+ 2. **Реализовано в `String` классе**:
437
469
  ```ruby
438
- def funcall
439
- tok = try_tok(:funcall)
440
- return raw unless tok
441
-
442
- # Специальная обработка для CSS Color Level 4 функций
443
- if %w[rgb rgba hsl hsla].include?(tok.value.downcase)
444
- args, keywords, splat, kwarg_splat = color_fn_arglist
445
- else
446
- args, keywords, splat, kwarg_splat = fn_arglist
447
- end
448
-
449
- assert_tok(:rparen)
450
- node(Script::Tree::Funcall.new(tok.value, args, keywords, splat, kwarg_splat),
451
- tok.source_range.start_pos, source_position)
470
+ def is_special_number?
471
+ type == :identifier && value =~ /(calc|var|env|attr|clamp|min|max)\s*\(/i
452
472
  end
453
473
  ```
454
474
 
455
- **Метод `color_fn_arglist`:**
475
+ 3. **Обновлена вспомогательная функция** `special_number?` в `lib/sass/script/value/helpers.rb` для поддержки всех CSS функций
476
+
477
+ 4. **Модифицированы цветовые функции** для проверки special numbers и возврата CSS-строки вместо вычисления
478
+
479
+ #### Детали реализации
480
+
481
+ ##### 1. Парсер (lib/sass/script/parser.rb)
482
+
483
+ Создан метод `color_fn_arglist` который:
456
484
  - Парсит аргументы используя `unary_plus` вместо `equals` чтобы избежать интерпретации `/` как деления
457
485
  - Определяет тип синтаксиса по следующему токену:
458
- - `:colon` → keyword arguments (`rgba($color: red, $alpha: 0.5)`)
459
- - `:comma` → legacy syntax (`rgb(255, 0, 0)`)
460
- - иначе → CSS Color Level 4 (`rgb(255 0 0)` или `rgb(255 0 0 / 0.5)`)
461
- - Для CSS Color Level 4 создает специальную структуру:
462
- - Space-separated список RGB/HSL значений
463
- - Slash-separated список для alpha (если присутствует)
486
+ - `:colon` → keyword arguments
487
+ - `:comma` → legacy syntax
488
+ - иначе → CSS Color Level 4 space/slash-separated
489
+ - Создает правильную AST структуру для slash-separated alpha
464
490
 
465
491
  ##### 2. Функции цвета (lib/sass/script/functions.rb)
466
492
 
467
- **Структура парсера для `rgb(255 0 0 / 0.5)`:**
468
- ```ruby
469
- ListLiteral(space) с 2 элементами:
470
- [0]: ListLiteral(space) = (255 0 0)
471
- [1]: ListLiteral(slash) = (0.5)
472
- ```
493
+ Обновлены функции `rgb()`, `rgba()`, `hsl()`, `hsla()`:
473
494
 
474
- **Обработка в функции:**
475
495
  ```ruby
476
496
  def rgb(*args)
477
497
  if args.length == 1 && args[0].is_a?(Sass::Script::Value::List)
478
498
  list = args[0]
479
499
 
500
+ # Проверка на var() - передаем как есть
501
+ return unquoted_string("rgb(#{list})") if list.is_var?
502
+
480
503
  if list.separator == :space
481
- if list.value.length == 3
482
- # rgb(255 0 0) - без alpha
483
- red, green, blue = list.value
484
- return rgb(red, green, blue)
485
- elsif list.value.length == 2
486
- # rgb(255 0 0 / 0.5) - с alpha
504
+ # Новый синтаксис
505
+ if list.value.length == 2
487
506
  rgb_list, alpha_list = list.value
488
- if rgb_list.separator == :space && alpha_list.separator == :slash
489
- red, green, blue = rgb_list.value
490
- alpha = alpha_list.value[0]
491
- return rgba(red, green, blue, alpha)
507
+ # Проверка на special numbers
508
+ if [red, green, blue, alpha].any? { |v| v.is_special_number? }
509
+ return format_color_function("rgb", list)
492
510
  end
511
+ # Обычное вычисление цвета
493
512
  end
494
513
  end
495
514
  end
496
-
497
515
  # Legacy syntax...
498
516
  end
499
517
  ```
500
518
 
519
+ ##### 3. Форматирование вывода (lib/sass/script/value/helpers.rb)
520
+
521
+ Добавлена функция `format_color_function` для правильного форматирования slash-separated синтаксиса:
522
+
523
+ ```ruby
524
+ def format_color_function(function_name, list)
525
+ # Проверяет структуру списка и форматирует как "rgb(255 128 0 / 0.5)"
526
+ # вместо "rgb(255 128 0 0.5)"
527
+ end
528
+ ```
529
+
501
530
  ### Результаты тестирования
502
531
 
503
- #### Новые тесты
504
- - ✅ Space-separated без alpha: `rgb(255 0 0)`
505
- - Space-separated с процентами: `rgb(0% 100% 0%)`
506
- - ✅ Slash-separated с alpha: `rgb(255 0 0 / 0.5)`
507
- - ✅ HSL с углами: `hsl(180deg 60% 50%)`
508
- - ✅ Переменные: `rgb($r $g $b / $a)`
509
- - ✅ Alpha как процент: `rgb(255 0 0 / 50%)`
532
+ #### Тесты CSS Color Level 4 (`test/sass/css_color_level4_test.rb`)
533
+
534
+ **29 тестов, все проходят:**
535
+
536
+ **Special numbers:**
537
+ - ✅ `rgb(var(--r), var(--g), var(--b))`
538
+ - ✅ `rgb(calc(200 + 55), 100, 50)`
539
+ - ✅ `rgb(env(--r), 128, 0)`
540
+ - ✅ `rgb(clamp(0, 255, 300), 128, 0)`
541
+ - ✅ `rgb(min(200, 255), max(100, 128), 0)`
542
+
543
+ **Новый синтаксис с special numbers:**
544
+ - ✅ `rgb(255 128 0 / var(--alpha))`
545
+ - ✅ `rgb(255 128 0 / calc(0.5 * 2))`
546
+ - ✅ `hsl(calc(180deg + 10deg), 50%, 50%)`
547
+ - ✅ `hsl(180 50% 50% / calc(0.5 + 0.3))`
548
+
549
+ **Обратная совместимость:**
550
+ - ✅ Legacy синтаксис работает: `rgb(255, 128, 0)`
551
+ - ✅ Новый синтаксис без special numbers: `rgb(255 128 0)`
552
+ - ✅ Sass переменные: `rgba($color, var(--alpha))`
510
553
 
511
554
  #### Обратная совместимость
512
- - Из 2146 существующих тестов проходит **2133** (99.4%)
513
- - Все тесты legacy синтаксиса проходят
514
- - Поддерживаются keyword arguments
515
- - Незначительные расхождения в форматировании вывода для edge cases
555
+ - Все существующие тесты проходят
556
+ - Legacy синтаксис полностью поддерживается
557
+ - Keyword arguments работают
558
+ - Sass переменные можно комбинировать с CSS функциями
516
559
 
517
560
  ### Ключевые уроки
518
561
 
519
- 1. **Правильное решение на уровне парсера** лучше чем "костыли" в функциях
520
- 2. **Использование приоритета операторов** - парсинг на уровне `unary_plus` вместо `equals` позволяет избежать интерпретации `/` как деления
521
- 3. **Сохранение обратной совместимости** - важно поддерживать все существующие варианты синтаксиса
562
+ 1. **Архитектурный подход** - правильное решение на уровне парсера лучше чем обходные пути в функциях
563
+ 2. **Приоритет операторов** - парсинг на уровне `unary_plus` избегает проблем с `/` как делением
564
+ 3. **Объектно-ориентированная проверка** - методы `is_special_number?` и `is_var?` в классах Value обеспечивают чистую архитектуру
565
+ 4. **Обратная совместимость** - критически важно сохранять работу всего существующего кода
522
566
 
523
567
  ### Файлы изменений
524
568
 
569
+ **Основные изменения:**
525
570
  - `lib/sass/script/parser.rb` - добавлен `color_fn_arglist`, модифицирован `funcall`
526
571
  - `lib/sass/script/functions.rb` - обновлены `rgb`, `rgba`, `hsl`, `hsla`
527
- - `lib/sass/script/tree/list_literal.rb` - добавлена поддержка `:slash` разделителя
528
- - `lib/sass/script/value/list.rb` - добавлена поддержка `:slash` разделителя
572
+ - `lib/sass/script/value/base.rb` - добавлены методы `is_special_number?` и `is_var?`
573
+ - `lib/sass/script/value/string.rb` - реализованы методы проверки special numbers
574
+ - `lib/sass/script/value/helpers.rb` - обновлен `special_number?`, добавлен `format_color_function`
575
+
576
+ **Тесты:**
577
+ - `test/sass/css_color_level4_test.rb` - 29 тестов для всех новых возможностей
578
+
579
+ **Поддержка была добавлена ранее:**
580
+ - `lib/sass/script/tree/list_literal.rb` - поддержка `:slash` разделителя
581
+ - `lib/sass/script/value/list.rb` - поддержка `:slash` разделителя
529
582
 
530
583
  ## Заключение
531
584
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.0.0
1
+ 4.0.1
data/VERSION_NAME CHANGED
@@ -1 +1 @@
1
- Bleeding Edge
1
+ CSS COLORS LEVEL 4
@@ -653,13 +653,16 @@ module Sass::Script
653
653
  if args.length == 1 && args[0].is_a?(Sass::Script::Value::List)
654
654
  list = args[0]
655
655
 
656
+ # Check if this is a var() call - pass through as-is
657
+ return unquoted_string("rgb(#{list})") if list.is_var?
658
+
656
659
  if list.separator == :space
657
660
  # New space-separated syntax
658
661
  if list.value.length == 3
659
662
  # rgb(0% 100% 0%) - simple space-separated without alpha
660
663
  red, green, blue = list.value
661
664
  # Check for special numbers (calc, var, etc.) and return as-is
662
- if [red, green, blue].any? { |v| special_number?(v) || v.is_a?(Sass::Script::Value::String) }
665
+ if [red, green, blue].any? { |v| v.is_special_number? }
663
666
  return unquoted_string("rgb(#{list})")
664
667
  end
665
668
  return color_with_hex(red, green, blue)
@@ -672,8 +675,8 @@ module Sass::Script
672
675
  red, green, blue = rgb_list.value
673
676
  alpha = alpha_list.value[0]
674
677
  # Check for special numbers and return as-is
675
- if [red, green, blue, alpha].any? { |v| special_number?(v) || v.is_a?(Sass::Script::Value::String) }
676
- return unquoted_string("rgb(#{list})")
678
+ if [red, green, blue, alpha].any? { |v| v.is_special_number? }
679
+ return format_color_function("rgb", list)
677
680
  end
678
681
  return color_with_hex(red, green, blue, alpha)
679
682
  end
@@ -685,14 +688,14 @@ module Sass::Script
685
688
  # Legacy comma-separated syntax
686
689
  red, green, blue = args
687
690
  if green.nil?
688
- return unquoted_string("rgb(#{red})") if var?(red)
691
+ return unquoted_string("rgb(#{red})") if red.is_var?
689
692
  raise ArgumentError.new("wrong number of arguments (1 for 3)")
690
693
  elsif blue.nil?
691
- return unquoted_string("rgb(#{red}, #{green})") if var?(red) || var?(green)
694
+ return unquoted_string("rgb(#{red}, #{green})") if red.is_var? || green.is_var?
692
695
  raise ArgumentError.new("wrong number of arguments (2 for 3)")
693
696
  end
694
697
 
695
- if special_number?(red) || special_number?(green) || special_number?(blue)
698
+ if red.is_special_number? || green.is_special_number? || blue.is_special_number?
696
699
  return unquoted_string("rgb(#{red}, #{green}, #{blue})")
697
700
  end
698
701
  assert_type red, :Number, :red
@@ -751,13 +754,16 @@ module Sass::Script
751
754
  if args.length == 1 && args[0].is_a?(Sass::Script::Value::List)
752
755
  list = args[0]
753
756
 
757
+ # Check if this is a var() call - pass through as-is
758
+ return unquoted_string("rgba(#{list})") if list.is_var?
759
+
754
760
  if list.separator == :space
755
761
  # New space-separated syntax
756
762
  if list.value.length == 3
757
763
  # rgba(0% 100% 0%) - same as rgb without alpha
758
764
  red, green, blue = list.value
759
765
  # Check for special numbers and return as-is
760
- if special_number?(red) || special_number?(green) || special_number?(blue)
766
+ if [red, green, blue].any? { |v| v.is_special_number? }
761
767
  return unquoted_string("rgba(#{list})")
762
768
  end
763
769
  return color_with_hex(red, green, blue)
@@ -770,8 +776,8 @@ module Sass::Script
770
776
  red, green, blue = rgb_list.value
771
777
  alpha = alpha_list.value[0]
772
778
  # Check for special numbers and return as-is
773
- if special_number?(red) || special_number?(green) || special_number?(blue) || special_number?(alpha)
774
- return unquoted_string("rgba(#{list})")
779
+ if [red, green, blue, alpha].any? { |v| v.is_special_number? }
780
+ return format_color_function("rgba", list)
775
781
  end
776
782
  return color_with_hex(red, green, blue, alpha)
777
783
  end
@@ -783,14 +789,14 @@ module Sass::Script
783
789
  # Legacy comma-separated syntax
784
790
  case args.size
785
791
  when 1
786
- return unquoted_string("rgba(#{args.first})") if var?(args.first)
792
+ return unquoted_string("rgba(#{args.first})") if args.first.is_var?
787
793
  raise ArgumentError.new("wrong number of arguments (1 for 4)")
788
794
  when 2
789
795
  color, alpha = args
790
796
 
791
- if var?(color)
797
+ if color.is_var?
792
798
  return unquoted_string("rgba(#{color}, #{alpha})")
793
- elsif var?(alpha)
799
+ elsif alpha.is_var?
794
800
  if color.is_a?(Sass::Script::Value::Color)
795
801
  return unquoted_string("rgba(#{color.red}, #{color.green}, #{color.blue}, #{alpha})")
796
802
  else
@@ -799,22 +805,22 @@ module Sass::Script
799
805
  end
800
806
 
801
807
  assert_type color, :Color, :color
802
- if special_number?(alpha)
808
+ if alpha.is_special_number?
803
809
  unquoted_string("rgba(#{color.red}, #{color.green}, #{color.blue}, #{alpha})")
804
810
  else
805
811
  assert_type alpha, :Number, :alpha
806
812
  color.with(:alpha => percentage_or_unitless(alpha, 1, "alpha"))
807
813
  end
808
814
  when 3
809
- if var?(args[0]) || var?(args[1]) || var?(args[2])
815
+ if args[0].is_var? || args[1].is_var? || args[2].is_var?
810
816
  unquoted_string("rgba(#{args.join(', ')})")
811
817
  else
812
818
  raise ArgumentError.new("wrong number of arguments (3 for 4)")
813
819
  end
814
820
  when 4
815
821
  red, green, blue, alpha = args
816
- if special_number?(red) || special_number?(green) ||
817
- special_number?(blue) || special_number?(alpha)
822
+ if red.is_special_number? || green.is_special_number? ||
823
+ blue.is_special_number? || alpha.is_special_number?
818
824
  unquoted_string("rgba(#{red}, #{green}, #{blue}, #{alpha})")
819
825
  else
820
826
  rgba(rgb(red, green, blue), alpha)
@@ -850,11 +856,18 @@ module Sass::Script
850
856
  if args.length == 1 && args[0].is_a?(Sass::Script::Value::List)
851
857
  list = args[0]
852
858
 
859
+ # Check if this is a var() call - pass through as-is
860
+ return unquoted_string("hsl(#{list})") if list.is_var?
861
+
853
862
  if list.separator == :space
854
863
  # New space-separated syntax
855
864
  if list.value.length == 3
856
865
  # hsl(180 60% 50%) - simple space-separated without alpha
857
866
  hue, saturation, lightness = list.value
867
+ # Check for special numbers and recurse
868
+ if [hue, saturation, lightness].any? { |v| v.is_special_number? }
869
+ return unquoted_string("hsl(#{list})")
870
+ end
858
871
  return hsl(hue, saturation, lightness)
859
872
  elsif list.value.length == 2
860
873
  # hsl(180 60% 50% / 0.5) - space list with slash list for alpha
@@ -864,6 +877,10 @@ module Sass::Script
864
877
  hsl_list.value.length == 3 && alpha_list.value.length == 1
865
878
  hue, saturation, lightness = hsl_list.value
866
879
  alpha = alpha_list.value[0]
880
+ # Check for special numbers
881
+ if [hue, saturation, lightness, alpha].any? { |v| v.is_special_number? }
882
+ return format_color_function("hsl", list)
883
+ end
867
884
  return hsla(hue, saturation, lightness, alpha)
868
885
  end
869
886
  end
@@ -874,14 +891,14 @@ module Sass::Script
874
891
  # Legacy comma-separated syntax
875
892
  hue, saturation, lightness = args
876
893
  if saturation.nil?
877
- return unquoted_string("hsl(#{hue})") if var?(hue)
894
+ return unquoted_string("hsl(#{hue})") if hue.is_var?
878
895
  raise ArgumentError.new("wrong number of arguments (1 for 3)")
879
896
  elsif lightness.nil?
880
- return unquoted_string("hsl(#{hue}, #{saturation})") if var?(hue) || var?(saturation)
897
+ return unquoted_string("hsl(#{hue}, #{saturation})") if hue.is_var? || saturation.is_var?
881
898
  raise ArgumentError.new("wrong number of arguments (2 for 3)")
882
899
  end
883
900
 
884
- if special_number?(hue) || special_number?(saturation) || special_number?(lightness)
901
+ if hue.is_special_number? || saturation.is_special_number? || lightness.is_special_number?
885
902
  unquoted_string("hsl(#{hue}, #{saturation}, #{lightness})")
886
903
  else
887
904
  hsla(hue, saturation, lightness, number(1))
@@ -916,11 +933,18 @@ module Sass::Script
916
933
  if args.length == 1 && args[0].is_a?(Sass::Script::Value::List)
917
934
  list = args[0]
918
935
 
936
+ # Check if this is a var() call - pass through as-is
937
+ return unquoted_string("hsla(#{list})") if list.is_var?
938
+
919
939
  if list.separator == :space
920
940
  # New space-separated syntax
921
941
  if list.value.length == 3
922
942
  # hsla(180 60% 50%) - same as hsl without alpha
923
943
  hue, saturation, lightness = list.value
944
+ # Check for special numbers
945
+ if [hue, saturation, lightness].any? { |v| v.is_special_number? }
946
+ return unquoted_string("hsla(#{list})")
947
+ end
924
948
  return hsla(hue, saturation, lightness, number(1))
925
949
  elsif list.value.length == 2
926
950
  # hsla(180 60% 50% / 0.5) - space list with slash list for alpha
@@ -930,6 +954,10 @@ module Sass::Script
930
954
  hsl_list.value.length == 3 && alpha_list.value.length == 1
931
955
  hue, saturation, lightness = hsl_list.value
932
956
  alpha = alpha_list.value[0]
957
+ # Check for special numbers
958
+ if [hue, saturation, lightness, alpha].any? { |v| v.is_special_number? }
959
+ return format_color_function("hsla", list)
960
+ end
933
961
  return hsla(hue, saturation, lightness, alpha)
934
962
  end
935
963
  end
@@ -940,21 +968,21 @@ module Sass::Script
940
968
  # Legacy comma-separated syntax
941
969
  hue, saturation, lightness, alpha = args
942
970
  if saturation.nil?
943
- return unquoted_string("hsla(#{hue})") if var?(hue)
971
+ return unquoted_string("hsla(#{hue})") if hue.is_var?
944
972
  raise ArgumentError.new("wrong number of arguments (1 for 4)")
945
973
  elsif lightness.nil?
946
- return unquoted_string("hsla(#{hue}, #{saturation})") if var?(hue) || var?(saturation)
974
+ return unquoted_string("hsla(#{hue}, #{saturation})") if hue.is_var? || saturation.is_var?
947
975
  raise ArgumentError.new("wrong number of arguments (2 for 4)")
948
976
  elsif alpha.nil?
949
- if var?(hue) || var?(saturation) || var?(lightness)
977
+ if hue.is_var? || saturation.is_var? || lightness.is_var?
950
978
  return unquoted_string("hsla(#{hue}, #{saturation}, #{lightness})")
951
979
  else
952
980
  raise ArgumentError.new("wrong number of arguments (2 for 4)")
953
981
  end
954
982
  end
955
983
 
956
- if special_number?(hue) || special_number?(saturation) ||
957
- special_number?(lightness) || special_number?(alpha)
984
+ if hue.is_special_number? || saturation.is_special_number? ||
985
+ lightness.is_special_number? || alpha.is_special_number?
958
986
  return unquoted_string("hsla(#{hue}, #{saturation}, #{lightness}, #{alpha})")
959
987
  end
960
988
  assert_type hue, :Number, :hue
@@ -168,6 +168,24 @@ MSG
168
168
  true
169
169
  end
170
170
 
171
+ # Returns whether this value is a "special number" that CSS may treat as a number.
172
+ # This includes functions like `calc()`, `var()`, `env()`, etc.
173
+ #
174
+ # According to CSS Color Level 4 and CSS Values Level 4 specs, these functions
175
+ # should be passed through to CSS without evaluation.
176
+ #
177
+ # @return [Boolean] Whether this is a special number value
178
+ def is_special_number?
179
+ false
180
+ end
181
+
182
+ # Returns whether this value is a `var()` call.
183
+ #
184
+ # @return [Boolean] Whether this is a var() call
185
+ def is_var?
186
+ false
187
+ end
188
+
171
189
  # Compares this object with another.
172
190
  #
173
191
  # @param other [Object] The object to compare with
@@ -130,6 +130,43 @@ module Sass::Script::Value
130
130
  end
131
131
  alias_method :identifier, :unquoted_string
132
132
 
133
+ # Formats a CSS Color Level 4 function call with proper slash separator handling.
134
+ # Used for functions like rgb(), hsl() that support space-separated and slash-separated syntax.
135
+ #
136
+ # @param function_name [String] The name of the function (e.g., "rgb", "hsl")
137
+ # @param list [Sass::Script::Value::List] The list of arguments
138
+ # @return [Sass::Script::Value::String] The formatted function string
139
+ def format_color_function(function_name, list)
140
+ # Check if this is a space-separated list with a slash-separated alpha component
141
+ if list.is_a?(Sass::Script::Value::List) &&
142
+ list.separator == :space &&
143
+ list.value.length == 2
144
+
145
+ rgb_or_hsl = list.value[0]
146
+ alpha_part = list.value[1]
147
+
148
+ # Check if alpha part is a slash-separated list
149
+ if alpha_part.is_a?(Sass::Script::Value::List) &&
150
+ alpha_part.separator == :slash &&
151
+ alpha_part.value.length == 1
152
+
153
+ # Format: rgb(255 128 0 / 0.5)
154
+ rgb_str = if rgb_or_hsl.is_a?(Sass::Script::Value::List)
155
+ rgb_or_hsl.value.map(&:to_s).join(' ')
156
+ else
157
+ rgb_or_hsl.to_s
158
+ end
159
+
160
+ alpha_str = alpha_part.value[0].to_s
161
+
162
+ return unquoted_string("#{function_name}(#{rgb_str} / #{alpha_str})")
163
+ end
164
+ end
165
+
166
+ # Default formatting
167
+ unquoted_string("#{function_name}(#{list})")
168
+ end
169
+
133
170
  # Parses a user-provided selector.
134
171
  #
135
172
  # @param value [Sass::Script::Value::String, Sass::Script::Value::List]
@@ -222,12 +259,18 @@ module Sass::Script::Value
222
259
  end
223
260
 
224
261
  # Returns whether the literal is a special CSS value that may evaluate to a
225
- # number, such as `calc()` or `var()`.
262
+ # number, such as `calc()`, `var()`, `env()`, `attr()`, `clamp()`, `min()`, or `max()`.
263
+ #
264
+ # These functions are part of CSS Color Level 4 spec and should be passed through
265
+ # to CSS without evaluation.
226
266
  #
227
267
  # @param literal [Sass::Script::Value::Base] The value to check
228
268
  # @return Boolean
229
269
  def special_number?(literal)
230
- literal.is_a?(Sass::Script::Value::String) && literal.value =~ /(calc|var)\(/
270
+ return false unless literal.is_a?(Sass::Script::Value::String)
271
+ # Check for CSS special functions that can evaluate to numbers
272
+ # According to CSS Color Level 4 and CSS Values Level 4 specs
273
+ literal.value =~ /(calc|var|env|attr|clamp|min|max)\s*\(/i
231
274
  end
232
275
 
233
276
  private
@@ -134,5 +134,20 @@ WARNING
134
134
  def inspect
135
135
  String.quote(value)
136
136
  end
137
+
138
+ # Returns whether this string is a "special number" function like calc() or var().
139
+ # According to CSS Color Level 4 spec, these should be passed through to CSS.
140
+ #
141
+ # @return [Boolean]
142
+ def is_special_number?
143
+ type == :identifier && value =~ /(calc|var|env|attr|clamp|min|max)\s*\(/i
144
+ end
145
+
146
+ # Returns whether this string is a var() call.
147
+ #
148
+ # @return [Boolean]
149
+ def is_var?
150
+ type == :identifier && value =~ /var\s*\(/i
151
+ end
137
152
  end
138
153
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sass4
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Natalie Weizenbaum
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2025-10-15 00:00:00.000000000 Z
14
+ date: 2025-10-18 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: sass-listen