Responsive Menu - Version 4.1.10

Version Description

(30th Mar 2022) = * Bug: Fixed issue with submenu font-wieght * Bug: Fixed issue with svg icons * Enhancement: Updated scssphp to latest version (1.10.2)

Download this release

Release Info

Developer expresstech
Plugin Icon 128x128 Responsive Menu
Version 4.1.10
Comparing to
See all releases

Code changes from version 4.1.9 to 4.1.10

Files changed (74) hide show
  1. readme.txt +12 -4
  2. responsive-menu.php +2 -2
  3. v4.0.0/assets/scss/main.scss +1 -0
  4. v4.0.0/inc/classes/class-control-manager.php +31 -16
  5. v4.0.0/inc/classes/class-editor.php +14 -9
  6. v4.0.0/inc/classes/class-style-manager.php +6 -0
  7. v4.0.0/inc/classes/class-ui-manager.php +9 -3
  8. v4.0.0/libs/scssphp/composer.json +1 -1
  9. v4.0.0/libs/scssphp/composer.lock +23 -9
  10. v4.0.0/libs/scssphp/vendor/bin/pscss +0 -14
  11. v4.0.0/libs/scssphp/vendor/composer/ClassLoader.php +40 -4
  12. v4.0.0/libs/scssphp/vendor/composer/InstalledVersions.php +337 -0
  13. v4.0.0/libs/scssphp/vendor/composer/autoload_classmap.php +1 -0
  14. v4.0.0/libs/scssphp/vendor/composer/autoload_real.php +7 -2
  15. v4.0.0/libs/scssphp/vendor/composer/autoload_static.php +5 -0
  16. v4.0.0/libs/scssphp/vendor/composer/installed.json +82 -64
  17. v4.0.0/libs/scssphp/vendor/composer/installed.php +32 -0
  18. v4.0.0/libs/scssphp/vendor/composer/platform_check.php +26 -0
  19. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/README.md +34 -9
  20. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/bin/pscss +83 -54
  21. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/composer.json +72 -12
  22. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/phpcs.xml.dist +12 -0
  23. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/scss.inc.php +15 -28
  24. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Base/Range.php +14 -4
  25. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block.php +10 -7
  26. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/AtRootBlock.php +37 -0
  27. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/CallableBlock.php +45 -0
  28. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/ContentBlock.php +38 -0
  29. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/DirectiveBlock.php +37 -0
  30. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/EachBlock.php +37 -0
  31. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/ElseBlock.php +27 -0
  32. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/ElseifBlock.php +32 -0
  33. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/ForBlock.php +47 -0
  34. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/IfBlock.php +37 -0
  35. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/MediaBlock.php +37 -0
  36. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/NestedPropertyBlock.php +37 -0
  37. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/WhileBlock.php +32 -0
  38. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Cache.php +47 -14
  39. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Colors.php +20 -14
  40. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/CompilationResult.php +69 -0
  41. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Compiler.php +4122 -1552
  42. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Compiler/CachedResult.php +77 -0
  43. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Compiler/Environment.php +26 -3
  44. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Exception/CompilerException.php +4 -1
  45. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Exception/ParserException.php +38 -1
  46. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Exception/RangeException.php +4 -1
  47. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Exception/SassException.php +7 -0
  48. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Exception/SassScriptException.php +32 -0
  49. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Exception/ServerException.php +6 -1
  50. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter.php +67 -21
  51. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/Compact.php +7 -0
  52. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/Compressed.php +5 -3
  53. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/Crunched.php +9 -1
  54. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/Debug.php +7 -1
  55. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/Expanded.php +6 -2
  56. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/Nested.php +14 -5
  57. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/OutputBlock.php +12 -9
  58. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Logger/LoggerInterface.php +48 -0
  59. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Logger/QuietLogger.php +27 -0
  60. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Logger/StreamLogger.php +60 -0
  61. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Node.php +6 -3
  62. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Node/Number.php +567 -153
  63. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/OutputStyle.php +9 -0
  64. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Parser.php +1273 -476
  65. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/SourceMap/Base64.php +7 -4
  66. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/SourceMap/Base64VLQ.php +16 -11
  67. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/SourceMap/SourceMapGenerator.php +61 -19
  68. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Type.php +138 -0
  69. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Util.php +119 -5
  70. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Util/Path.php +77 -0
  71. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/ValueConverter.php +95 -0
  72. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Version.php +2 -1
  73. v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Warn.php +84 -0
  74. v4.0.0/templates/rmp-editor.php +27 -23
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: expresstech, responsivemenu, imvarunkmr, surajkumarsingh, infosate
3
  Tags: responsive, mega menu, navigation, mobile, hamburger
4
  Requires at least: 3.6
5
  Tested up to: 5.9
6
- Stable tag: 4.1.9
7
  Requires PHP: 5.6
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
@@ -11,7 +11,8 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
  Highly customisable Responsive Menu plugin with 150+ options. No coding knowledge needed to design it exactly as you want.
12
 
13
  == Description ==
14
- **Description:**
 
15
 
16
  Highly customisable Responsive Menu Plugin for WordPress. With over 150 customisable options you get a combination of 22,500 options! No coding experience or knowledge is needed with an easy to use interface you can get it looking exactly as you want with minimal fuss.
17
 
@@ -73,9 +74,11 @@ For more reasons to go Pro, please visit [this page](https://responsive.menu#why
73
 
74
  [youtube https://www.youtube.com/watch?v=aj6ba0tGKPg]
75
 
76
- 🌐Our Plugins
 
 
77
 
78
- If you like this plugin, consider exploring our other plugins:
79
 
80
  ⏱️ [Quiz and Survey Master](https://wordpress.org/plugins/quiz-master-next/) - Best WordPress Quiz Plugin to create engaging quizzes, surveys, & exams using WordPress and convert your website into a lead generation machine.
81
 
@@ -117,6 +120,11 @@ To view our FAQ, please go to [https://responsive.menu/faq/](https://responsive.
117
 
118
  == Changelog ==
119
 
 
 
 
 
 
120
  = 4.1.9 (8th Mar 2022) =
121
  * Enhancement: Fixed issues with admin bar settings
122
 
3
  Tags: responsive, mega menu, navigation, mobile, hamburger
4
  Requires at least: 3.6
5
  Tested up to: 5.9
6
+ Stable tag: 4.1.10
7
  Requires PHP: 5.6
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
  Highly customisable Responsive Menu plugin with 150+ options. No coding knowledge needed to design it exactly as you want.
12
 
13
  == Description ==
14
+
15
+ Try Demo ➡️ [Get a personal sandbox demo with Response Menu](https://instawp.io/plugins/responsive-menu)
16
 
17
  Highly customisable Responsive Menu Plugin for WordPress. With over 150 customisable options you get a combination of 22,500 options! No coding experience or knowledge is needed with an easy to use interface you can get it looking exactly as you want with minimal fuss.
18
 
74
 
75
  [youtube https://www.youtube.com/watch?v=aj6ba0tGKPg]
76
 
77
+ 🌐Our themes and plugins
78
+
79
+ If you like this plugin, consider exploring our other themes and plugins:
80
 
81
+ 🧱 [Gutena](https://wordpress.org/themes/gutena/) - Block Based Theme for WordPress includes over 50 free block patterns with new patterns added every week.
82
 
83
  ⏱️ [Quiz and Survey Master](https://wordpress.org/plugins/quiz-master-next/) - Best WordPress Quiz Plugin to create engaging quizzes, surveys, & exams using WordPress and convert your website into a lead generation machine.
84
 
120
 
121
  == Changelog ==
122
 
123
+ = 4.1.10 (30th Mar 2022) =
124
+ * Bug: Fixed issue with submenu font-wieght
125
+ * Bug: Fixed issue with svg icons
126
+ * Enhancement: Updated scssphp to latest version (1.10.2)
127
+
128
  = 4.1.9 (8th Mar 2022) =
129
  * Enhancement: Fixed issues with admin bar settings
130
 
responsive-menu.php CHANGED
@@ -4,7 +4,7 @@
4
  Plugin Name: Responsive Menu
5
  Plugin URI: https://expresstech.io
6
  Description: Highly Customisable Responsive Menu Plugin for WordPress
7
- Version: 4.1.9
8
  Author: ExpressTech
9
  Text Domain: responsive-menu
10
  Author URI: https://responsive.menu
@@ -16,7 +16,7 @@ Tags: responsive, menu, responsive menu, mega menu, max mega menu, max menu
16
  * Constant as plugin version.
17
  */
18
  if ( ! defined( 'RMP_PLUGIN_VERSION' ) ) {
19
- define( 'RMP_PLUGIN_VERSION', '4.1.9' );
20
  }
21
 
22
  define( 'RESPONSIVE_MENU_URL', plugin_dir_url( __FILE__ ) );
4
  Plugin Name: Responsive Menu
5
  Plugin URI: https://expresstech.io
6
  Description: Highly Customisable Responsive Menu Plugin for WordPress
7
+ Version: 4.1.10
8
  Author: ExpressTech
9
  Text Domain: responsive-menu
10
  Author URI: https://responsive.menu
16
  * Constant as plugin version.
17
  */
18
  if ( ! defined( 'RMP_PLUGIN_VERSION' ) ) {
19
+ define( 'RMP_PLUGIN_VERSION', '4.1.10' );
20
  }
21
 
22
  define( 'RESPONSIVE_MENU_URL', plugin_dir_url( __FILE__ ) );
v4.0.0/assets/scss/main.scss CHANGED
@@ -448,6 +448,7 @@
448
  font-family: inherit;
449
  }
450
 
 
451
  color: $submenu_item_text_color;
452
  text-align: $submenu_item_text_alignment;
453
  background-color: $submenu_item_background_color;
448
  font-family: inherit;
449
  }
450
 
451
+ font-weight: $submenu_font_weight;
452
  color: $submenu_item_text_color;
453
  text-align: $submenu_item_text_alignment;
454
  background-color: $submenu_item_background_color;
v4.0.0/inc/classes/class-control-manager.php CHANGED
@@ -740,6 +740,12 @@ class Control_Manager {
740
  return;
741
  }
742
 
 
 
 
 
 
 
743
  $group_classes = '';
744
 
745
  if ( ! empty( $param['group_classes'] ) ) {
@@ -802,9 +808,9 @@ class Control_Manager {
802
  <div class="rmp-icon-picker-placeholder">
803
  <span>
804
  <?php
805
- $svg_placeholder = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/image-placeholder.svg' );
806
- if ( is_array( $svg_placeholder ) && ! is_wp_error( $svg_placeholder ) ) {
807
- echo wp_kses( $svg_placeholder['body'], rmp_allow_svg_html_tags() );
808
  }
809
  ?>
810
  </span>
@@ -845,7 +851,11 @@ class Control_Manager {
845
  if ( empty( $param ) ) {
846
  return;
847
  }
848
-
 
 
 
 
849
  $group_classes = '';
850
 
851
  if ( ! empty( $param['group_classes'] ) ) {
@@ -899,9 +909,9 @@ class Control_Manager {
899
  <div class="rmp-image-picker-placeholder">
900
  <span>
901
  <?php
902
- $svg_placeholder = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/image-placeholder.svg' );
903
- if ( is_array( $svg_placeholder ) && ! is_wp_error( $svg_placeholder ) ) {
904
- echo wp_kses( $svg_placeholder['body'], rmp_allow_svg_html_tags() );
905
  }
906
  ?>
907
  </span>
@@ -1107,31 +1117,36 @@ class Control_Manager {
1107
  * @return HTML
1108
  */
1109
  protected function get_device_options() {
 
 
 
 
 
1110
  ?>
1111
  <div class="rmp-device-switcher-holder">
1112
  <a target="_blank" rel="noopener" class="upgrade-tooltip" href="<?php echo esc_url( $this->pro_plugin_url ); ?>" > PRO </a>
1113
  <ul class="select rmp-device-switcher" >
1114
  <li data-device="mobile">
1115
  <?php
1116
- $svg_mobile = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/mobile.svg' );
1117
- if ( is_array( $svg_mobile ) && ! is_wp_error( $svg_mobile ) ) {
1118
- echo wp_kses( $svg_mobile['body'], rmp_allow_svg_html_tags() );
1119
  }
1120
  ?>
1121
  </li>
1122
  <li data-device="tablet">
1123
  <?php
1124
- $svg_tablet = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/tablet.svg' );
1125
- if ( is_array( $svg_tablet ) && ! is_wp_error( $svg_tablet ) ) {
1126
- echo wp_kses( $svg_tablet['body'], rmp_allow_svg_html_tags() );
1127
  }
1128
  ?>
1129
  </li>
1130
  <li data-device="desktop">
1131
  <?php
1132
- $svg_desktop = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/desktop.svg' );
1133
- if ( is_array( $svg_desktop ) && ! is_wp_error( $svg_desktop ) ) {
1134
- echo wp_kses( $svg_desktop['body'], rmp_allow_svg_html_tags() );
1135
  }
1136
  ?>
1137
  </li>
740
  return;
741
  }
742
 
743
+ global $wp_filesystem;
744
+ if ( empty( $wp_filesystem ) ) {
745
+ require_once ABSPATH . 'wp-admin/includes/file.php';
746
+ }
747
+ WP_Filesystem();
748
+
749
  $group_classes = '';
750
 
751
  if ( ! empty( $param['group_classes'] ) ) {
808
  <div class="rmp-icon-picker-placeholder">
809
  <span>
810
  <?php
811
+ $svg_placeholder = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/image-placeholder.svg' );
812
+ if ( $svg_placeholder ) {
813
+ echo wp_kses( $svg_placeholder, rmp_allow_svg_html_tags() );
814
  }
815
  ?>
816
  </span>
851
  if ( empty( $param ) ) {
852
  return;
853
  }
854
+ global $wp_filesystem;
855
+ if ( empty( $wp_filesystem ) ) {
856
+ require_once ABSPATH . 'wp-admin/includes/file.php';
857
+ }
858
+ WP_Filesystem();
859
  $group_classes = '';
860
 
861
  if ( ! empty( $param['group_classes'] ) ) {
909
  <div class="rmp-image-picker-placeholder">
910
  <span>
911
  <?php
912
+ $svg_placeholder = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/image-placeholder.svg' );
913
+ if ( $svg_placeholder ) {
914
+ echo wp_kses( $svg_placeholder, rmp_allow_svg_html_tags() );
915
  }
916
  ?>
917
  </span>
1117
  * @return HTML
1118
  */
1119
  protected function get_device_options() {
1120
+ global $wp_filesystem;
1121
+ if ( empty( $wp_filesystem ) ) {
1122
+ require_once ABSPATH . 'wp-admin/includes/file.php';
1123
+ }
1124
+ WP_Filesystem();
1125
  ?>
1126
  <div class="rmp-device-switcher-holder">
1127
  <a target="_blank" rel="noopener" class="upgrade-tooltip" href="<?php echo esc_url( $this->pro_plugin_url ); ?>" > PRO </a>
1128
  <ul class="select rmp-device-switcher" >
1129
  <li data-device="mobile">
1130
  <?php
1131
+ $svg_mobile = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/mobile.svg' );
1132
+ if ( $svg_mobile ) {
1133
+ echo wp_kses( $svg_mobile, rmp_allow_svg_html_tags() );
1134
  }
1135
  ?>
1136
  </li>
1137
  <li data-device="tablet">
1138
  <?php
1139
+ $svg_tablet = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/tablet.svg' );
1140
+ if ( $svg_tablet ) {
1141
+ echo wp_kses( $svg_tablet, rmp_allow_svg_html_tags() );
1142
  }
1143
  ?>
1144
  </li>
1145
  <li data-device="desktop">
1146
  <?php
1147
+ $svg_desktop = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/desktop.svg' );
1148
+ if ( $svg_desktop ) {
1149
+ echo wp_kses( $svg_desktop, rmp_allow_svg_html_tags() );
1150
  }
1151
  ?>
1152
  </li>
v4.0.0/inc/classes/class-editor.php CHANGED
@@ -105,6 +105,11 @@ class Editor {
105
  * @param HTML.
106
  */
107
  public function footer_section() {
 
 
 
 
 
108
  ?>
109
  <div id="rmp-editor-footer" class="rmp-editor-footer">
110
 
@@ -141,9 +146,9 @@ class Editor {
141
 
142
  <button type="button" id="rmp-preview-mobile" class=" rmp-device-preview rmp-preview-mobile active" aria-pressed="1" data-device="mobile">
143
  <?php
144
- $svg_mobile = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/mobile.svg' );
145
- if ( is_array( $svg_mobile ) && ! is_wp_error( $svg_mobile ) ) {
146
- echo wp_kses( $svg_mobile['body'], rmp_allow_svg_html_tags() );
147
  }
148
  ?>
149
  <span class="screen-reader-text">
@@ -153,9 +158,9 @@ class Editor {
153
 
154
  <button type="button" id="rmp-preview-tablet" class="rmp-preview-tablet rmp-device-preview" aria-pressed="" data-device="tablet">
155
  <?php
156
- $svg_tablet = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/tablet.svg' );
157
- if ( is_array( $svg_tablet ) && ! is_wp_error( $svg_tablet ) ) {
158
- echo wp_kses( $svg_tablet['body'], rmp_allow_svg_html_tags() );
159
  }
160
  ?>
161
  <span class="screen-reader-text">
@@ -165,9 +170,9 @@ class Editor {
165
 
166
  <button type="button" id="rmp-preview-desktop" class="rmp-preview-desktop rmp-device-preview" aria-pressed="" data-device="desktop">
167
  <?php
168
- $svg_desktop = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/desktop.svg' );
169
- if ( is_array( $svg_desktop ) && ! is_wp_error( $svg_desktop ) ) {
170
- echo wp_kses( $svg_desktop['body'], rmp_allow_svg_html_tags() );
171
  }
172
  ?>
173
  <span class="screen-reader-text">
105
  * @param HTML.
106
  */
107
  public function footer_section() {
108
+ global $wp_filesystem;
109
+ if ( empty( $wp_filesystem ) ) {
110
+ require_once ABSPATH . 'wp-admin/includes/file.php';
111
+ }
112
+ WP_Filesystem();
113
  ?>
114
  <div id="rmp-editor-footer" class="rmp-editor-footer">
115
 
146
 
147
  <button type="button" id="rmp-preview-mobile" class=" rmp-device-preview rmp-preview-mobile active" aria-pressed="1" data-device="mobile">
148
  <?php
149
+ $svg_mobile = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/mobile.svg' );
150
+ if ( $svg_mobile ) {
151
+ echo wp_kses( $svg_mobile, rmp_allow_svg_html_tags() );
152
  }
153
  ?>
154
  <span class="screen-reader-text">
158
 
159
  <button type="button" id="rmp-preview-tablet" class="rmp-preview-tablet rmp-device-preview" aria-pressed="" data-device="tablet">
160
  <?php
161
+ $svg_tablet = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/tablet.svg' );
162
+ if ( $svg_tablet ) {
163
+ echo wp_kses( $svg_tablet, rmp_allow_svg_html_tags() );
164
  }
165
  ?>
166
  <span class="screen-reader-text">
170
 
171
  <button type="button" id="rmp-preview-desktop" class="rmp-preview-desktop rmp-device-preview" aria-pressed="" data-device="desktop">
172
  <?php
173
+ $svg_desktop = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/desktop.svg' );
174
+ if ( $svg_desktop ) {
175
+ echo wp_kses( $svg_desktop, rmp_allow_svg_html_tags() );
176
  }
177
  ?>
178
  <span class="screen-reader-text">
v4.0.0/inc/classes/class-style-manager.php CHANGED
@@ -1064,6 +1064,11 @@ class Style_Manager {
1064
  $submenu_item_font_size = $options['submenu_font_size'];
1065
  }
1066
 
 
 
 
 
 
1067
  $submenu_item_font_size_unit = '';
1068
  if ( ! empty( $options['submenu_font_size_unit'] ) ) {
1069
  $submenu_item_font_size_unit = $options['submenu_font_size_unit'];
@@ -1412,6 +1417,7 @@ class Style_Manager {
1412
  'submenu_current_item_border_color_hover' => $submenu_current_item_border_color_hover,
1413
 
1414
  'submenu_item_font_size' => $submenu_item_font_size,
 
1415
  'submenu_item_font_size_unit' => $submenu_item_font_size_unit,
1416
  'submenu_item_font_family' => $submenu_item_font_family,
1417
  'submenu_item_text_alignment' => $submenu_item_text_alignment,
1064
  $submenu_item_font_size = $options['submenu_font_size'];
1065
  }
1066
 
1067
+ $submenu_font_weight = '';
1068
+ if ( ! empty( $options['submenu_font_weight'] ) ) {
1069
+ $submenu_font_weight = $options['submenu_font_weight'];
1070
+ }
1071
+
1072
  $submenu_item_font_size_unit = '';
1073
  if ( ! empty( $options['submenu_font_size_unit'] ) ) {
1074
  $submenu_item_font_size_unit = $options['submenu_font_size_unit'];
1417
  'submenu_current_item_border_color_hover' => $submenu_current_item_border_color_hover,
1418
 
1419
  'submenu_item_font_size' => $submenu_item_font_size,
1420
+ 'submenu_font_weight' => $submenu_font_weight,
1421
  'submenu_item_font_size_unit' => $submenu_item_font_size_unit,
1422
  'submenu_item_font_family' => $submenu_item_font_family,
1423
  'submenu_item_text_alignment' => $submenu_item_text_alignment,
v4.0.0/inc/classes/class-ui-manager.php CHANGED
@@ -162,6 +162,12 @@ class UI_Manager {
162
  return;
163
  }
164
 
 
 
 
 
 
 
165
  $item_class = '';
166
  if ( ! empty( $tab_attr['item_class'] ) ) {
167
  $item_class = $tab_attr['item_class'];
@@ -187,9 +193,9 @@ class UI_Manager {
187
  ?>
188
  <span class="rmp-tab-item-icon">
189
  <?php
190
- $svg_icon = wp_remote_get( $tab_attr['item_header']['item_svg_icon'] );
191
- if ( is_array( $svg_icon ) && ! is_wp_error( $svg_icon ) ) {
192
- echo wp_kses( $svg_icon['body'], rmp_allow_svg_html_tags() );
193
  }
194
  ?>
195
  </span>
162
  return;
163
  }
164
 
165
+ global $wp_filesystem;
166
+ if ( empty( $wp_filesystem ) ) {
167
+ require_once ABSPATH . 'wp-admin/includes/file.php';
168
+ }
169
+ WP_Filesystem();
170
+
171
  $item_class = '';
172
  if ( ! empty( $tab_attr['item_class'] ) ) {
173
  $item_class = $tab_attr['item_class'];
193
  ?>
194
  <span class="rmp-tab-item-icon">
195
  <?php
196
+ $svg_icon = $wp_filesystem->get_contents( $tab_attr['item_header']['item_svg_icon'] );
197
+ if ( $svg_icon ) {
198
+ echo wp_kses( $svg_icon, rmp_allow_svg_html_tags() );
199
  }
200
  ?>
201
  </span>
v4.0.0/libs/scssphp/composer.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
  "require": {
3
- "scssphp/scssphp": "^1.1"
4
  }
5
  }
1
  {
2
  "require": {
3
+ "scssphp/scssphp": "^1.10.2"
4
  }
5
  }
v4.0.0/libs/scssphp/composer.lock CHANGED
@@ -4,20 +4,20 @@
4
  "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
  "This file is @generated automatically"
6
  ],
7
- "content-hash": "639d6b1231e90f706ba0d28d5e0a622f",
8
  "packages": [
9
  {
10
  "name": "scssphp/scssphp",
11
- "version": "1.1.0",
12
  "source": {
13
  "type": "git",
14
  "url": "https://github.com/scssphp/scssphp.git",
15
- "reference": "4363ddce8d750f055c436833dd77d83517946532"
16
  },
17
  "dist": {
18
  "type": "zip",
19
- "url": "https://api.github.com/repos/scssphp/scssphp/zipball/4363ddce8d750f055c436833dd77d83517946532",
20
- "reference": "4363ddce8d750f055c436833dd77d83517946532",
21
  "shasum": ""
22
  },
23
  "require": {
@@ -26,11 +26,20 @@
26
  "php": ">=5.6.0"
27
  },
28
  "require-dev": {
29
- "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.3",
 
 
30
  "squizlabs/php_codesniffer": "~3.5",
31
- "twbs/bootstrap": "~4.3",
 
 
 
32
  "zurb/foundation": "~6.5"
33
  },
 
 
 
 
34
  "bin": [
35
  "bin/pscss"
36
  ],
@@ -65,7 +74,11 @@
65
  "scss",
66
  "stylesheet"
67
  ],
68
- "time": "2020-04-21T15:53:32+00:00"
 
 
 
 
69
  }
70
  ],
71
  "packages-dev": [],
@@ -75,5 +88,6 @@
75
  "prefer-stable": false,
76
  "prefer-lowest": false,
77
  "platform": [],
78
- "platform-dev": []
 
79
  }
4
  "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
  "This file is @generated automatically"
6
  ],
7
+ "content-hash": "1ea5523c517849d7d79d7278b95713b7",
8
  "packages": [
9
  {
10
  "name": "scssphp/scssphp",
11
+ "version": "v1.10.2",
12
  "source": {
13
  "type": "git",
14
  "url": "https://github.com/scssphp/scssphp.git",
15
+ "reference": "387f4f4abf5d99f16be16314c5ab856f81c82f46"
16
  },
17
  "dist": {
18
  "type": "zip",
19
+ "url": "https://api.github.com/repos/scssphp/scssphp/zipball/387f4f4abf5d99f16be16314c5ab856f81c82f46",
20
+ "reference": "387f4f4abf5d99f16be16314c5ab856f81c82f46",
21
  "shasum": ""
22
  },
23
  "require": {
26
  "php": ">=5.6.0"
27
  },
28
  "require-dev": {
29
+ "bamarni/composer-bin-plugin": "^1.4",
30
+ "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.3 || ^9.4",
31
+ "sass/sass-spec": "*",
32
  "squizlabs/php_codesniffer": "~3.5",
33
+ "symfony/phpunit-bridge": "^5.1",
34
+ "thoughtbot/bourbon": "^7.0",
35
+ "twbs/bootstrap": "~5.0",
36
+ "twbs/bootstrap4": "4.6.1",
37
  "zurb/foundation": "~6.5"
38
  },
39
+ "suggest": {
40
+ "ext-iconv": "Can be used as fallback when ext-mbstring is not available",
41
+ "ext-mbstring": "For best performance, mbstring should be installed as it is faster than ext-iconv"
42
+ },
43
  "bin": [
44
  "bin/pscss"
45
  ],
74
  "scss",
75
  "stylesheet"
76
  ],
77
+ "support": {
78
+ "issues": "https://github.com/scssphp/scssphp/issues",
79
+ "source": "https://github.com/scssphp/scssphp/tree/v1.10.2"
80
+ },
81
+ "time": "2022-03-02T21:15:09+00:00"
82
  }
83
  ],
84
  "packages-dev": [],
88
  "prefer-stable": false,
89
  "prefer-lowest": false,
90
  "platform": [],
91
+ "platform-dev": [],
92
+ "plugin-api-version": "2.1.0"
93
  }
v4.0.0/libs/scssphp/vendor/bin/pscss DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env sh
2
-
3
- dir=$(cd "${0%[/\\]*}" > /dev/null; cd "../scssphp/scssphp/bin" && pwd)
4
-
5
- if [ -d /proc/cygdrive ]; then
6
- case $(which php) in
7
- $(readlink -n /proc/cygdrive)/*)
8
- # We are in Cygwin using Windows php, so the path must be translated
9
- dir=$(cygpath -m "$dir");
10
- ;;
11
- esac
12
- fi
13
-
14
- "${dir}/pscss" "$@"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
v4.0.0/libs/scssphp/vendor/composer/ClassLoader.php CHANGED
@@ -37,11 +37,13 @@ namespace Composer\Autoload;
37
  *
38
  * @author Fabien Potencier <fabien@symfony.com>
39
  * @author Jordi Boggiano <j.boggiano@seld.be>
40
- * @see http://www.php-fig.org/psr/psr-0/
41
- * @see http://www.php-fig.org/psr/psr-4/
42
  */
43
  class ClassLoader
44
  {
 
 
45
  // PSR-4
46
  private $prefixLengthsPsr4 = array();
47
  private $prefixDirsPsr4 = array();
@@ -57,10 +59,17 @@ class ClassLoader
57
  private $missingClasses = array();
58
  private $apcuPrefix;
59
 
 
 
 
 
 
 
 
60
  public function getPrefixes()
61
  {
62
  if (!empty($this->prefixesPsr0)) {
63
- return call_user_func_array('array_merge', $this->prefixesPsr0);
64
  }
65
 
66
  return array();
@@ -300,6 +309,17 @@ class ClassLoader
300
  public function register($prepend = false)
301
  {
302
  spl_autoload_register(array($this, 'loadClass'), true, $prepend);
 
 
 
 
 
 
 
 
 
 
 
303
  }
304
 
305
  /**
@@ -308,13 +328,17 @@ class ClassLoader
308
  public function unregister()
309
  {
310
  spl_autoload_unregister(array($this, 'loadClass'));
 
 
 
 
311
  }
312
 
313
  /**
314
  * Loads the given class or interface.
315
  *
316
  * @param string $class The name of the class
317
- * @return bool|null True if loaded, null otherwise
318
  */
319
  public function loadClass($class)
320
  {
@@ -323,6 +347,8 @@ class ClassLoader
323
 
324
  return true;
325
  }
 
 
326
  }
327
 
328
  /**
@@ -367,6 +393,16 @@ class ClassLoader
367
  return $file;
368
  }
369
 
 
 
 
 
 
 
 
 
 
 
370
  private function findFileWithExtension($class, $ext)
371
  {
372
  // PSR-4 lookup
37
  *
38
  * @author Fabien Potencier <fabien@symfony.com>
39
  * @author Jordi Boggiano <j.boggiano@seld.be>
40
+ * @see https://www.php-fig.org/psr/psr-0/
41
+ * @see https://www.php-fig.org/psr/psr-4/
42
  */
43
  class ClassLoader
44
  {
45
+ private $vendorDir;
46
+
47
  // PSR-4
48
  private $prefixLengthsPsr4 = array();
49
  private $prefixDirsPsr4 = array();
59
  private $missingClasses = array();
60
  private $apcuPrefix;
61
 
62
+ private static $registeredLoaders = array();
63
+
64
+ public function __construct($vendorDir = null)
65
+ {
66
+ $this->vendorDir = $vendorDir;
67
+ }
68
+
69
  public function getPrefixes()
70
  {
71
  if (!empty($this->prefixesPsr0)) {
72
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
73
  }
74
 
75
  return array();
309
  public function register($prepend = false)
310
  {
311
  spl_autoload_register(array($this, 'loadClass'), true, $prepend);
312
+
313
+ if (null === $this->vendorDir) {
314
+ return;
315
+ }
316
+
317
+ if ($prepend) {
318
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
319
+ } else {
320
+ unset(self::$registeredLoaders[$this->vendorDir]);
321
+ self::$registeredLoaders[$this->vendorDir] = $this;
322
+ }
323
  }
324
 
325
  /**
328
  public function unregister()
329
  {
330
  spl_autoload_unregister(array($this, 'loadClass'));
331
+
332
+ if (null !== $this->vendorDir) {
333
+ unset(self::$registeredLoaders[$this->vendorDir]);
334
+ }
335
  }
336
 
337
  /**
338
  * Loads the given class or interface.
339
  *
340
  * @param string $class The name of the class
341
+ * @return true|null True if loaded, null otherwise
342
  */
343
  public function loadClass($class)
344
  {
347
 
348
  return true;
349
  }
350
+
351
+ return null;
352
  }
353
 
354
  /**
393
  return $file;
394
  }
395
 
396
+ /**
397
+ * Returns the currently registered loaders indexed by their corresponding vendor directories.
398
+ *
399
+ * @return self[]
400
+ */
401
+ public static function getRegisteredLoaders()
402
+ {
403
+ return self::$registeredLoaders;
404
+ }
405
+
406
  private function findFileWithExtension($class, $ext)
407
  {
408
  // PSR-4 lookup
v4.0.0/libs/scssphp/vendor/composer/InstalledVersions.php ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer;
14
+
15
+ use Composer\Autoload\ClassLoader;
16
+ use Composer\Semver\VersionParser;
17
+
18
+ /**
19
+ * This class is copied in every Composer installed project and available to all
20
+ *
21
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
22
+ *
23
+ * To require it's presence, you can require `composer-runtime-api ^2.0`
24
+ */
25
+ class InstalledVersions
26
+ {
27
+ private static $installed;
28
+ private static $canGetVendors;
29
+ private static $installedByVendor = array();
30
+
31
+ /**
32
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
33
+ *
34
+ * @return string[]
35
+ * @psalm-return list<string>
36
+ */
37
+ public static function getInstalledPackages()
38
+ {
39
+ $packages = array();
40
+ foreach (self::getInstalled() as $installed) {
41
+ $packages[] = array_keys($installed['versions']);
42
+ }
43
+
44
+ if (1 === \count($packages)) {
45
+ return $packages[0];
46
+ }
47
+
48
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
49
+ }
50
+
51
+ /**
52
+ * Returns a list of all package names with a specific type e.g. 'library'
53
+ *
54
+ * @param string $type
55
+ * @return string[]
56
+ * @psalm-return list<string>
57
+ */
58
+ public static function getInstalledPackagesByType($type)
59
+ {
60
+ $packagesByType = array();
61
+
62
+ foreach (self::getInstalled() as $installed) {
63
+ foreach ($installed['versions'] as $name => $package) {
64
+ if (isset($package['type']) && $package['type'] === $type) {
65
+ $packagesByType[] = $name;
66
+ }
67
+ }
68
+ }
69
+
70
+ return $packagesByType;
71
+ }
72
+
73
+ /**
74
+ * Checks whether the given package is installed
75
+ *
76
+ * This also returns true if the package name is provided or replaced by another package
77
+ *
78
+ * @param string $packageName
79
+ * @param bool $includeDevRequirements
80
+ * @return bool
81
+ */
82
+ public static function isInstalled($packageName, $includeDevRequirements = true)
83
+ {
84
+ foreach (self::getInstalled() as $installed) {
85
+ if (isset($installed['versions'][$packageName])) {
86
+ return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
87
+ }
88
+ }
89
+
90
+ return false;
91
+ }
92
+
93
+ /**
94
+ * Checks whether the given package satisfies a version constraint
95
+ *
96
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
97
+ *
98
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
99
+ *
100
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
101
+ * @param string $packageName
102
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
103
+ * @return bool
104
+ */
105
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
106
+ {
107
+ $constraint = $parser->parseConstraints($constraint);
108
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
109
+
110
+ return $provided->matches($constraint);
111
+ }
112
+
113
+ /**
114
+ * Returns a version constraint representing all the range(s) which are installed for a given package
115
+ *
116
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
117
+ * whether a given version of a package is installed, and not just whether it exists
118
+ *
119
+ * @param string $packageName
120
+ * @return string Version constraint usable with composer/semver
121
+ */
122
+ public static function getVersionRanges($packageName)
123
+ {
124
+ foreach (self::getInstalled() as $installed) {
125
+ if (!isset($installed['versions'][$packageName])) {
126
+ continue;
127
+ }
128
+
129
+ $ranges = array();
130
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
131
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
132
+ }
133
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
134
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
135
+ }
136
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
137
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
138
+ }
139
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
140
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
141
+ }
142
+
143
+ return implode(' || ', $ranges);
144
+ }
145
+
146
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
147
+ }
148
+
149
+ /**
150
+ * @param string $packageName
151
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
152
+ */
153
+ public static function getVersion($packageName)
154
+ {
155
+ foreach (self::getInstalled() as $installed) {
156
+ if (!isset($installed['versions'][$packageName])) {
157
+ continue;
158
+ }
159
+
160
+ if (!isset($installed['versions'][$packageName]['version'])) {
161
+ return null;
162
+ }
163
+
164
+ return $installed['versions'][$packageName]['version'];
165
+ }
166
+
167
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
168
+ }
169
+
170
+ /**
171
+ * @param string $packageName
172
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
173
+ */
174
+ public static function getPrettyVersion($packageName)
175
+ {
176
+ foreach (self::getInstalled() as $installed) {
177
+ if (!isset($installed['versions'][$packageName])) {
178
+ continue;
179
+ }
180
+
181
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
182
+ return null;
183
+ }
184
+
185
+ return $installed['versions'][$packageName]['pretty_version'];
186
+ }
187
+
188
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
189
+ }
190
+
191
+ /**
192
+ * @param string $packageName
193
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
194
+ */
195
+ public static function getReference($packageName)
196
+ {
197
+ foreach (self::getInstalled() as $installed) {
198
+ if (!isset($installed['versions'][$packageName])) {
199
+ continue;
200
+ }
201
+
202
+ if (!isset($installed['versions'][$packageName]['reference'])) {
203
+ return null;
204
+ }
205
+
206
+ return $installed['versions'][$packageName]['reference'];
207
+ }
208
+
209
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
210
+ }
211
+
212
+ /**
213
+ * @param string $packageName
214
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
215
+ */
216
+ public static function getInstallPath($packageName)
217
+ {
218
+ foreach (self::getInstalled() as $installed) {
219
+ if (!isset($installed['versions'][$packageName])) {
220
+ continue;
221
+ }
222
+
223
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
224
+ }
225
+
226
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
227
+ }
228
+
229
+ /**
230
+ * @return array
231
+ * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}
232
+ */
233
+ public static function getRootPackage()
234
+ {
235
+ $installed = self::getInstalled();
236
+
237
+ return $installed[0]['root'];
238
+ }
239
+
240
+ /**
241
+ * Returns the raw installed.php data for custom implementations
242
+ *
243
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
244
+ * @return array[]
245
+ * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}
246
+ */
247
+ public static function getRawData()
248
+ {
249
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
250
+
251
+ if (null === self::$installed) {
252
+ // only require the installed.php file if this file is loaded from its dumped location,
253
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
254
+ if (substr(__DIR__, -8, 1) !== 'C') {
255
+ self::$installed = include __DIR__ . '/installed.php';
256
+ } else {
257
+ self::$installed = array();
258
+ }
259
+ }
260
+
261
+ return self::$installed;
262
+ }
263
+
264
+ /**
265
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
266
+ *
267
+ * @return array[]
268
+ * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
269
+ */
270
+ public static function getAllRawData()
271
+ {
272
+ return self::getInstalled();
273
+ }
274
+
275
+ /**
276
+ * Lets you reload the static array from another file
277
+ *
278
+ * This is only useful for complex integrations in which a project needs to use
279
+ * this class but then also needs to execute another project's autoloader in process,
280
+ * and wants to ensure both projects have access to their version of installed.php.
281
+ *
282
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
283
+ * the data it needs from this class, then call reload() with
284
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
285
+ * the project in which it runs can then also use this class safely, without
286
+ * interference between PHPUnit's dependencies and the project's dependencies.
287
+ *
288
+ * @param array[] $data A vendor/composer/installed.php data set
289
+ * @return void
290
+ *
291
+ * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} $data
292
+ */
293
+ public static function reload($data)
294
+ {
295
+ self::$installed = $data;
296
+ self::$installedByVendor = array();
297
+ }
298
+
299
+ /**
300
+ * @return array[]
301
+ * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
302
+ */
303
+ private static function getInstalled()
304
+ {
305
+ if (null === self::$canGetVendors) {
306
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
307
+ }
308
+
309
+ $installed = array();
310
+
311
+ if (self::$canGetVendors) {
312
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
313
+ if (isset(self::$installedByVendor[$vendorDir])) {
314
+ $installed[] = self::$installedByVendor[$vendorDir];
315
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
316
+ $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
317
+ if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
318
+ self::$installed = $installed[count($installed) - 1];
319
+ }
320
+ }
321
+ }
322
+ }
323
+
324
+ if (null === self::$installed) {
325
+ // only require the installed.php file if this file is loaded from its dumped location,
326
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
327
+ if (substr(__DIR__, -8, 1) !== 'C') {
328
+ self::$installed = require __DIR__ . '/installed.php';
329
+ } else {
330
+ self::$installed = array();
331
+ }
332
+ }
333
+ $installed[] = self::$installed;
334
+
335
+ return $installed;
336
+ }
337
+ }
v4.0.0/libs/scssphp/vendor/composer/autoload_classmap.php CHANGED
@@ -6,4 +6,5 @@ $vendorDir = dirname(dirname(__FILE__));
6
  $baseDir = dirname($vendorDir);
7
 
8
  return array(
 
9
  );
6
  $baseDir = dirname($vendorDir);
7
 
8
  return array(
9
+ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
10
  );
v4.0.0/libs/scssphp/vendor/composer/autoload_real.php CHANGED
@@ -13,19 +13,24 @@ class ComposerAutoloaderInit63d42ceb3ded2b5ffe0e0f4c4066ef1c
13
  }
14
  }
15
 
 
 
 
16
  public static function getLoader()
17
  {
18
  if (null !== self::$loader) {
19
  return self::$loader;
20
  }
21
 
 
 
22
  spl_autoload_register(array('ComposerAutoloaderInit63d42ceb3ded2b5ffe0e0f4c4066ef1c', 'loadClassLoader'), true, true);
23
- self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
  spl_autoload_unregister(array('ComposerAutoloaderInit63d42ceb3ded2b5ffe0e0f4c4066ef1c', 'loadClassLoader'));
25
 
26
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27
  if ($useStaticLoader) {
28
- require_once __DIR__ . '/autoload_static.php';
29
 
30
  call_user_func(\Composer\Autoload\ComposerStaticInit63d42ceb3ded2b5ffe0e0f4c4066ef1c::getInitializer($loader));
31
  } else {
13
  }
14
  }
15
 
16
+ /**
17
+ * @return \Composer\Autoload\ClassLoader
18
+ */
19
  public static function getLoader()
20
  {
21
  if (null !== self::$loader) {
22
  return self::$loader;
23
  }
24
 
25
+ require __DIR__ . '/platform_check.php';
26
+
27
  spl_autoload_register(array('ComposerAutoloaderInit63d42ceb3ded2b5ffe0e0f4c4066ef1c', 'loadClassLoader'), true, true);
28
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
29
  spl_autoload_unregister(array('ComposerAutoloaderInit63d42ceb3ded2b5ffe0e0f4c4066ef1c', 'loadClassLoader'));
30
 
31
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
32
  if ($useStaticLoader) {
33
+ require __DIR__ . '/autoload_static.php';
34
 
35
  call_user_func(\Composer\Autoload\ComposerStaticInit63d42ceb3ded2b5ffe0e0f4c4066ef1c::getInitializer($loader));
36
  } else {
v4.0.0/libs/scssphp/vendor/composer/autoload_static.php CHANGED
@@ -20,11 +20,16 @@ class ComposerStaticInit63d42ceb3ded2b5ffe0e0f4c4066ef1c
20
  ),
21
  );
22
 
 
 
 
 
23
  public static function getInitializer(ClassLoader $loader)
24
  {
25
  return \Closure::bind(function () use ($loader) {
26
  $loader->prefixLengthsPsr4 = ComposerStaticInit63d42ceb3ded2b5ffe0e0f4c4066ef1c::$prefixLengthsPsr4;
27
  $loader->prefixDirsPsr4 = ComposerStaticInit63d42ceb3ded2b5ffe0e0f4c4066ef1c::$prefixDirsPsr4;
 
28
 
29
  }, null, ClassLoader::class);
30
  }
20
  ),
21
  );
22
 
23
+ public static $classMap = array (
24
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
25
+ );
26
+
27
  public static function getInitializer(ClassLoader $loader)
28
  {
29
  return \Closure::bind(function () use ($loader) {
30
  $loader->prefixLengthsPsr4 = ComposerStaticInit63d42ceb3ded2b5ffe0e0f4c4066ef1c::$prefixLengthsPsr4;
31
  $loader->prefixDirsPsr4 = ComposerStaticInit63d42ceb3ded2b5ffe0e0f4c4066ef1c::$prefixDirsPsr4;
32
+ $loader->classMap = ComposerStaticInit63d42ceb3ded2b5ffe0e0f4c4066ef1c::$classMap;
33
 
34
  }, null, ClassLoader::class);
35
  }
v4.0.0/libs/scssphp/vendor/composer/installed.json CHANGED
@@ -1,65 +1,83 @@
1
- [
2
- {
3
- "name": "scssphp/scssphp",
4
- "version": "1.1.0",
5
- "version_normalized": "1.1.0.0",
6
- "source": {
7
- "type": "git",
8
- "url": "https://github.com/scssphp/scssphp.git",
9
- "reference": "4363ddce8d750f055c436833dd77d83517946532"
10
- },
11
- "dist": {
12
- "type": "zip",
13
- "url": "https://api.github.com/repos/scssphp/scssphp/zipball/4363ddce8d750f055c436833dd77d83517946532",
14
- "reference": "4363ddce8d750f055c436833dd77d83517946532",
15
- "shasum": ""
16
- },
17
- "require": {
18
- "ext-ctype": "*",
19
- "ext-json": "*",
20
- "php": ">=5.6.0"
21
- },
22
- "require-dev": {
23
- "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.3",
24
- "squizlabs/php_codesniffer": "~3.5",
25
- "twbs/bootstrap": "~4.3",
26
- "zurb/foundation": "~6.5"
27
- },
28
- "time": "2020-04-21T15:53:32+00:00",
29
- "bin": [
30
- "bin/pscss"
31
- ],
32
- "type": "library",
33
- "installation-source": "dist",
34
- "autoload": {
35
- "psr-4": {
36
- "ScssPhp\\ScssPhp\\": "src/"
37
- }
38
- },
39
- "notification-url": "https://packagist.org/downloads/",
40
- "license": [
41
- "MIT"
42
- ],
43
- "authors": [
44
- {
45
- "name": "Anthon Pang",
46
- "email": "apang@softwaredevelopment.ca",
47
- "homepage": "https://github.com/robocoder"
48
  },
49
- {
50
- "name": "Cédric Morin",
51
- "email": "cedric@yterium.com",
52
- "homepage": "https://github.com/Cerdic"
53
- }
54
- ],
55
- "description": "scssphp is a compiler for SCSS written in PHP.",
56
- "homepage": "http://scssphp.github.io/scssphp/",
57
- "keywords": [
58
- "css",
59
- "less",
60
- "sass",
61
- "scss",
62
- "stylesheet"
63
- ]
64
- }
65
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "packages": [
3
+ {
4
+ "name": "scssphp/scssphp",
5
+ "version": "v1.10.2",
6
+ "version_normalized": "1.10.2.0",
7
+ "source": {
8
+ "type": "git",
9
+ "url": "https://github.com/scssphp/scssphp.git",
10
+ "reference": "387f4f4abf5d99f16be16314c5ab856f81c82f46"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  },
12
+ "dist": {
13
+ "type": "zip",
14
+ "url": "https://api.github.com/repos/scssphp/scssphp/zipball/387f4f4abf5d99f16be16314c5ab856f81c82f46",
15
+ "reference": "387f4f4abf5d99f16be16314c5ab856f81c82f46",
16
+ "shasum": ""
17
+ },
18
+ "require": {
19
+ "ext-ctype": "*",
20
+ "ext-json": "*",
21
+ "php": ">=5.6.0"
22
+ },
23
+ "require-dev": {
24
+ "bamarni/composer-bin-plugin": "^1.4",
25
+ "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.3 || ^9.4",
26
+ "sass/sass-spec": "*",
27
+ "squizlabs/php_codesniffer": "~3.5",
28
+ "symfony/phpunit-bridge": "^5.1",
29
+ "thoughtbot/bourbon": "^7.0",
30
+ "twbs/bootstrap": "~5.0",
31
+ "twbs/bootstrap4": "4.6.1",
32
+ "zurb/foundation": "~6.5"
33
+ },
34
+ "suggest": {
35
+ "ext-iconv": "Can be used as fallback when ext-mbstring is not available",
36
+ "ext-mbstring": "For best performance, mbstring should be installed as it is faster than ext-iconv"
37
+ },
38
+ "time": "2022-03-02T21:15:09+00:00",
39
+ "bin": [
40
+ "bin/pscss"
41
+ ],
42
+ "type": "library",
43
+ "installation-source": "dist",
44
+ "autoload": {
45
+ "psr-4": {
46
+ "ScssPhp\\ScssPhp\\": "src/"
47
+ }
48
+ },
49
+ "notification-url": "https://packagist.org/downloads/",
50
+ "license": [
51
+ "MIT"
52
+ ],
53
+ "authors": [
54
+ {
55
+ "name": "Anthon Pang",
56
+ "email": "apang@softwaredevelopment.ca",
57
+ "homepage": "https://github.com/robocoder"
58
+ },
59
+ {
60
+ "name": "Cédric Morin",
61
+ "email": "cedric@yterium.com",
62
+ "homepage": "https://github.com/Cerdic"
63
+ }
64
+ ],
65
+ "description": "scssphp is a compiler for SCSS written in PHP.",
66
+ "homepage": "http://scssphp.github.io/scssphp/",
67
+ "keywords": [
68
+ "css",
69
+ "less",
70
+ "sass",
71
+ "scss",
72
+ "stylesheet"
73
+ ],
74
+ "support": {
75
+ "issues": "https://github.com/scssphp/scssphp/issues",
76
+ "source": "https://github.com/scssphp/scssphp/tree/v1.10.2"
77
+ },
78
+ "install-path": "../scssphp/scssphp"
79
+ }
80
+ ],
81
+ "dev": true,
82
+ "dev-package-names": []
83
+ }
v4.0.0/libs/scssphp/vendor/composer/installed.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php return array(
2
+ 'root' => array(
3
+ 'pretty_version' => 'dev-master',
4
+ 'version' => 'dev-master',
5
+ 'type' => 'library',
6
+ 'install_path' => __DIR__ . '/../../',
7
+ 'aliases' => array(),
8
+ 'reference' => '93878f5bdc00aaa000cdaab9278b8b7b2eb5f483',
9
+ 'name' => '__root__',
10
+ 'dev' => true,
11
+ ),
12
+ 'versions' => array(
13
+ '__root__' => array(
14
+ 'pretty_version' => 'dev-master',
15
+ 'version' => 'dev-master',
16
+ 'type' => 'library',
17
+ 'install_path' => __DIR__ . '/../../',
18
+ 'aliases' => array(),
19
+ 'reference' => '93878f5bdc00aaa000cdaab9278b8b7b2eb5f483',
20
+ 'dev_requirement' => false,
21
+ ),
22
+ 'scssphp/scssphp' => array(
23
+ 'pretty_version' => 'v1.10.2',
24
+ 'version' => '1.10.2.0',
25
+ 'type' => 'library',
26
+ 'install_path' => __DIR__ . '/../scssphp/scssphp',
27
+ 'aliases' => array(),
28
+ 'reference' => '387f4f4abf5d99f16be16314c5ab856f81c82f46',
29
+ 'dev_requirement' => false,
30
+ ),
31
+ ),
32
+ );
v4.0.0/libs/scssphp/vendor/composer/platform_check.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // platform_check.php @generated by Composer
4
+
5
+ $issues = array();
6
+
7
+ if (!(PHP_VERSION_ID >= 50600)) {
8
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.';
9
+ }
10
+
11
+ if ($issues) {
12
+ if (!headers_sent()) {
13
+ header('HTTP/1.1 500 Internal Server Error');
14
+ }
15
+ if (!ini_get('display_errors')) {
16
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
17
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
18
+ } elseif (!headers_sent()) {
19
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
20
+ }
21
+ }
22
+ trigger_error(
23
+ 'Composer detected issues in your platform: ' . implode(' ', $issues),
24
+ E_USER_ERROR
25
+ );
26
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/README.md CHANGED
@@ -1,12 +1,12 @@
1
  # scssphp
2
- ### <http://scssphp.github.io/scssphp>
3
 
4
- [![Build](https://travis-ci.org/scssphp/scssphp.svg?branch=master)](http://travis-ci.org/scssphp/scssphp)
5
  [![License](https://poser.pugx.org/scssphp/scssphp/license)](https://packagist.org/packages/scssphp/scssphp)
6
 
7
  `scssphp` is a compiler for SCSS written in PHP.
8
 
9
- Checkout the homepage, <http://scssphp.github.io/scssphp>, for directions on how to use.
10
 
11
  ## Running Tests
12
 
@@ -23,7 +23,7 @@ There are several tests in the `tests/` directory:
23
  * `FailingTest.php` contains tests reported in Github issues that demonstrate compatibility bugs.
24
  * `InputTest.php` compiles every `.scss` file in the `tests/inputs` directory
25
  then compares to the respective `.css` file in the `tests/outputs` directory.
26
- * `ScssTest.php` extracts (ruby) `scss` tests from the `tests/scss_test.rb` file.
27
 
28
  When changing any of the tests in `tests/inputs`, the tests will most likely
29
  fail because the output has changed. Once you verify that the output is correct
@@ -31,16 +31,41 @@ you can run the following command to rebuild all the tests:
31
 
32
  BUILD=1 vendor/bin/phpunit tests
33
 
34
- This will compile all the tests, and save results into `tests/outputs`.
 
35
 
36
- To enable the `scss` compatibility tests:
37
 
38
- TEST_SCSS_COMPAT=1 vendor/bin/phpunit tests
39
 
40
  ## Coding Standard
41
 
42
- `scssphp` source conforms to [PSR2](http://www.php-fig.org/psr/psr-2/).
43
 
44
  Run the following command from the root directory to check the code for "sniffs".
45
 
46
- vendor/bin/phpcs --standard=PSR2 bin src tests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # scssphp
2
+ ### <https://scssphp.github.io/scssphp>
3
 
4
+ ![Build](https://github.com/scssphp/scssphp/workflows/CI/badge.svg)
5
  [![License](https://poser.pugx.org/scssphp/scssphp/license)](https://packagist.org/packages/scssphp/scssphp)
6
 
7
  `scssphp` is a compiler for SCSS written in PHP.
8
 
9
+ Checkout the homepage, <https://scssphp.github.io/scssphp>, for directions on how to use.
10
 
11
  ## Running Tests
12
 
23
  * `FailingTest.php` contains tests reported in Github issues that demonstrate compatibility bugs.
24
  * `InputTest.php` compiles every `.scss` file in the `tests/inputs` directory
25
  then compares to the respective `.css` file in the `tests/outputs` directory.
26
+ * `SassSpecTest.php` extracts tests from the `sass/sass-spec` repository.
27
 
28
  When changing any of the tests in `tests/inputs`, the tests will most likely
29
  fail because the output has changed. Once you verify that the output is correct
31
 
32
  BUILD=1 vendor/bin/phpunit tests
33
 
34
+ This will compile all the tests, and save results into `tests/outputs`. It also
35
+ updates the list of excluded specs from sass-spec.
36
 
37
+ To enable the full `sass-spec` compatibility tests:
38
 
39
+ TEST_SASS_SPEC=1 vendor/bin/phpunit tests
40
 
41
  ## Coding Standard
42
 
43
+ `scssphp` source conforms to [PSR12](https://www.php-fig.org/psr/psr-12/).
44
 
45
  Run the following command from the root directory to check the code for "sniffs".
46
 
47
+ vendor/bin/phpcs --standard=PSR12 --extensions=php bin src tests *.php
48
+
49
+ ## Static Analysis
50
+
51
+ `scssphp` uses [phpstan](https://phpstan.org/) for static analysis.
52
+
53
+ Run the following command from the root directory to analyse the codebase:
54
+
55
+ make phpstan
56
+
57
+ As most of the codebase is composed of legacy code which cannot be type-checked
58
+ fully, the setup contains a baseline file with all errors we want to ignore. In
59
+ particular, we ignore all errors related to not specifying the types inside arrays
60
+ when these arrays correspond to the representation of Sass values and Sass AST nodes
61
+ in the parser and compiler.
62
+ When contributing, the proper process to deal with static analysis is the following:
63
+
64
+ 1. Make your change in the codebase
65
+ 2. Run `make phpstan`
66
+ 3. Fix errors reported by phpstan when possible
67
+ 4. Repeat step 2 and 3 until nothing gets fixed anymore at step 3
68
+ 5. Run `make phpstan-baseline` to regenerate the phpstan baseline
69
+
70
+ Additions to the baseline will be reviewed to avoid ignoring errors that should have
71
+ been fixed.
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/bin/pscss CHANGED
@@ -1,5 +1,6 @@
1
  #!/usr/bin/env php
2
  <?php
 
3
  /**
4
  * SCSSPHP
5
  *
@@ -19,26 +20,26 @@ if (version_compare(PHP_VERSION, '5.6') < 0) {
19
  include __DIR__ . '/../scss.inc.php';
20
 
21
  use ScssPhp\ScssPhp\Compiler;
 
 
22
  use ScssPhp\ScssPhp\Parser;
23
  use ScssPhp\ScssPhp\Version;
24
 
25
  $style = null;
26
- $loadPaths = null;
27
- $precision = null;
28
  $dumpTree = false;
29
  $inputFile = null;
30
  $changeDir = false;
31
- $debugInfo = false;
32
- $lineNumbers = false;
33
- $ignoreErrors = false;
34
  $encoding = false;
35
  $sourceMap = false;
 
 
36
 
37
  /**
38
  * Parse argument
39
  *
40
- * @param integer $i
41
- * @param array $options
42
  *
43
  * @return string|null
44
  */
@@ -61,25 +62,29 @@ function parseArgument(&$i, $options) {
61
  }
62
  }
63
 
 
 
64
  for ($i = 1; $i < $argc; $i++) {
65
  if ($argv[$i] === '-?' || $argv[$i] === '-h' || $argv[$i] === '--help') {
66
  $exe = $argv[0];
67
 
68
  $HELP = <<<EOT
69
- Usage: $exe [options] [input-file]
70
 
71
  Options include:
72
 
73
  --help Show this message [-h, -?]
74
- --continue-on-error Continue compilation (as best as possible) when error encountered
75
- --debug-info Annotate selectors with CSS referring to the source file and line number [-g]
76
- --dump-tree Dump formatted parse tree [-T]
77
  --iso8859-1 Use iso8859-1 encoding instead of default utf-8
78
- --line-numbers Annotate selectors with comments referring to the source file and line number [--line-comments]
79
  --load-path=PATH Set import path [-I]
80
- --precision=N Set decimal number precision (default 10) [-p]
81
  --sourcemap Create source map file
82
- --style=FORMAT Set the output format (compact, compressed, crunched, expanded, or nested) [-s, -t]
 
 
83
  --version Print the version [-v]
84
 
85
  EOT;
@@ -90,13 +95,15 @@ EOT;
90
  exit(Version::VERSION . "\n");
91
  }
92
 
 
93
  if ($argv[$i] === '--continue-on-error') {
94
- $ignoreErrors = true;
95
  continue;
96
  }
97
 
 
98
  if ($argv[$i] === '-g' || $argv[$i] === '--debug-info') {
99
- $debugInfo = true;
100
  continue;
101
  }
102
 
@@ -105,8 +112,9 @@ EOT;
105
  continue;
106
  }
107
 
 
108
  if ($argv[$i] === '--line-numbers' || $argv[$i] === '--line-comments') {
109
- $lineNumbers = true;
110
  continue;
111
  }
112
 
@@ -115,6 +123,16 @@ EOT;
115
  continue;
116
  }
117
 
 
 
 
 
 
 
 
 
 
 
118
  if ($argv[$i] === '-T' || $argv[$i] === '--dump-tree') {
119
  $dumpTree = true;
120
  continue;
@@ -130,34 +148,25 @@ EOT;
130
  $value = parseArgument($i, array('-I', '--load-path'));
131
 
132
  if (isset($value)) {
133
- $loadPaths = $value;
134
  continue;
135
  }
136
 
 
137
  $value = parseArgument($i, array('-p', '--precision'));
138
 
139
  if (isset($value)) {
140
- $precision = $value;
141
  continue;
142
  }
143
 
144
- if (file_exists($argv[$i])) {
145
- $inputFile = $argv[$i];
146
- continue;
147
- }
148
  }
149
 
150
 
151
- if ($inputFile) {
 
152
  $data = file_get_contents($inputFile);
153
-
154
- $newWorkingDir = dirname(realpath($inputFile));
155
- $oldWorkingDir = getcwd();
156
-
157
- if ($oldWorkingDir !== $newWorkingDir) {
158
- $changeDir = chdir($newWorkingDir);
159
- $inputFile = basename($inputFile);
160
- }
161
  } else {
162
  $data = '';
163
 
@@ -171,45 +180,65 @@ if ($dumpTree) {
171
 
172
  print_r(json_decode(json_encode($parser->parse($data)), true));
173
 
 
 
174
  exit();
175
  }
176
 
177
  $scss = new Compiler();
178
 
179
- if ($debugInfo) {
180
- $scss->setLineNumberStyle(Compiler::DEBUG_INFO);
181
- }
182
-
183
- if ($lineNumbers) {
184
- $scss->setLineNumberStyle(Compiler::LINE_COMMENTS);
185
- }
186
-
187
- if ($ignoreErrors) {
188
- $scss->setIgnoreErrors($ignoreErrors);
189
- }
190
-
191
  if ($loadPaths) {
192
- $scss->setImportPaths(explode(PATH_SEPARATOR, $loadPaths));
193
- }
194
-
195
- if ($precision) {
196
- $scss->setNumberPrecision($precision);
197
  }
198
 
199
  if ($style) {
200
- $scss->setFormatter('ScssPhp\\ScssPhp\\Formatter\\' . ucfirst($style));
 
 
 
 
 
201
  }
202
 
 
 
 
203
  if ($sourceMap) {
204
- $scss->setSourceMap(Compiler::SOURCE_MAP_INLINE);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  }
206
 
207
  if ($encoding) {
208
  $scss->setEncoding($encoding);
209
  }
210
 
211
- echo $scss->compile($data, $inputFile);
 
 
 
 
 
212
 
213
- if ($changeDir) {
214
- chdir($oldWorkingDir);
 
 
 
 
 
 
215
  }
1
  #!/usr/bin/env php
2
  <?php
3
+
4
  /**
5
  * SCSSPHP
6
  *
20
  include __DIR__ . '/../scss.inc.php';
21
 
22
  use ScssPhp\ScssPhp\Compiler;
23
+ use ScssPhp\ScssPhp\Exception\SassException;
24
+ use ScssPhp\ScssPhp\OutputStyle;
25
  use ScssPhp\ScssPhp\Parser;
26
  use ScssPhp\ScssPhp\Version;
27
 
28
  $style = null;
29
+ $loadPaths = [];
 
30
  $dumpTree = false;
31
  $inputFile = null;
32
  $changeDir = false;
 
 
 
33
  $encoding = false;
34
  $sourceMap = false;
35
+ $embedSources = false;
36
+ $embedSourceMap = false;
37
 
38
  /**
39
  * Parse argument
40
  *
41
+ * @param int $i
42
+ * @param string[] $options
43
  *
44
  * @return string|null
45
  */
62
  }
63
  }
64
 
65
+ $arguments = [];
66
+
67
  for ($i = 1; $i < $argc; $i++) {
68
  if ($argv[$i] === '-?' || $argv[$i] === '-h' || $argv[$i] === '--help') {
69
  $exe = $argv[0];
70
 
71
  $HELP = <<<EOT
72
+ Usage: $exe [options] [input-file] [output-file]
73
 
74
  Options include:
75
 
76
  --help Show this message [-h, -?]
77
+ --continue-on-error [deprecated] Ignored
78
+ --debug-info [deprecated] Ignored [-g]
79
+ --dump-tree [deprecated] Dump formatted parse tree [-T]
80
  --iso8859-1 Use iso8859-1 encoding instead of default utf-8
81
+ --line-numbers [deprecated] Ignored [--line-comments]
82
  --load-path=PATH Set import path [-I]
83
+ --precision=N [deprecated] Ignored. (default 10) [-p]
84
  --sourcemap Create source map file
85
+ --embed-sources Embed source file contents in source maps
86
+ --embed-source-map Embed the source map contents in CSS (default if writing to stdout)
87
+ --style=FORMAT Set the output style (compressed or expanded) [-s, -t]
88
  --version Print the version [-v]
89
 
90
  EOT;
95
  exit(Version::VERSION . "\n");
96
  }
97
 
98
+ // Keep parsing --continue-on-error to avoid BC breaks for scripts using it
99
  if ($argv[$i] === '--continue-on-error') {
100
+ // TODO report it as a warning ?
101
  continue;
102
  }
103
 
104
+ // Keep parsing it to avoid BC breaks for scripts using it
105
  if ($argv[$i] === '-g' || $argv[$i] === '--debug-info') {
106
+ // TODO report it as a warning ?
107
  continue;
108
  }
109
 
112
  continue;
113
  }
114
 
115
+ // Keep parsing it to avoid BC breaks for scripts using it
116
  if ($argv[$i] === '--line-numbers' || $argv[$i] === '--line-comments') {
117
+ // TODO report it as a warning ?
118
  continue;
119
  }
120
 
123
  continue;
124
  }
125
 
126
+ if ($argv[$i] === '--embed-sources') {
127
+ $embedSources = true;
128
+ continue;
129
+ }
130
+
131
+ if ($argv[$i] === '--embed-source-map') {
132
+ $embedSourceMap = true;
133
+ continue;
134
+ }
135
+
136
  if ($argv[$i] === '-T' || $argv[$i] === '--dump-tree') {
137
  $dumpTree = true;
138
  continue;
148
  $value = parseArgument($i, array('-I', '--load-path'));
149
 
150
  if (isset($value)) {
151
+ $loadPaths[] = $value;
152
  continue;
153
  }
154
 
155
+ // Keep parsing --precision to avoid BC breaks for scripts using it
156
  $value = parseArgument($i, array('-p', '--precision'));
157
 
158
  if (isset($value)) {
159
+ // TODO report it as a warning ?
160
  continue;
161
  }
162
 
163
+ $arguments[] = $argv[$i];
 
 
 
164
  }
165
 
166
 
167
+ if (isset($arguments[0]) && file_exists($arguments[0])) {
168
+ $inputFile = $arguments[0];
169
  $data = file_get_contents($inputFile);
 
 
 
 
 
 
 
 
170
  } else {
171
  $data = '';
172
 
180
 
181
  print_r(json_decode(json_encode($parser->parse($data)), true));
182
 
183
+ fwrite(STDERR, 'Warning: the --dump-tree option is deprecated. Use proper debugging tools instead.');
184
+
185
  exit();
186
  }
187
 
188
  $scss = new Compiler();
189
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  if ($loadPaths) {
191
+ $scss->setImportPaths($loadPaths);
 
 
 
 
192
  }
193
 
194
  if ($style) {
195
+ if ($style === OutputStyle::COMPRESSED || $style === OutputStyle::EXPANDED) {
196
+ $scss->setOutputStyle($style);
197
+ } else {
198
+ fwrite(STDERR, "WARNING: the $style style is deprecated.\n");
199
+ $scss->setFormatter('ScssPhp\\ScssPhp\\Formatter\\' . ucfirst($style));
200
+ }
201
  }
202
 
203
+ $outputFile = isset($arguments[1]) ? $arguments[1] : null;
204
+ $sourceMapFile = null;
205
+
206
  if ($sourceMap) {
207
+ $sourceMapOptions = array(
208
+ 'outputSourceFiles' => $embedSources,
209
+ );
210
+ if ($embedSourceMap || $outputFile === null) {
211
+ $scss->setSourceMap(Compiler::SOURCE_MAP_INLINE);
212
+ } else {
213
+ $sourceMapFile = $outputFile . '.map';
214
+ $sourceMapOptions['sourceMapWriteTo'] = $sourceMapFile;
215
+ $sourceMapOptions['sourceMapURL'] = basename($sourceMapFile);
216
+ $sourceMapOptions['sourceMapBasepath'] = getcwd();
217
+ $sourceMapOptions['sourceMapFilename'] = basename($outputFile);
218
+
219
+ $scss->setSourceMap(Compiler::SOURCE_MAP_FILE);
220
+ }
221
+
222
+ $scss->setSourceMapOptions($sourceMapOptions);
223
  }
224
 
225
  if ($encoding) {
226
  $scss->setEncoding($encoding);
227
  }
228
 
229
+ try {
230
+ $result = $scss->compileString($data, $inputFile);
231
+ } catch (SassException $e) {
232
+ fwrite(STDERR, 'Error: '.$e->getMessage()."\n");
233
+ exit(1);
234
+ }
235
 
236
+ if ($outputFile) {
237
+ file_put_contents($outputFile, $result->getCss());
238
+
239
+ if ($sourceMapFile !== null && $result->getSourceMap() !== null) {
240
+ file_put_contents($sourceMapFile, $result->getSourceMap());
241
+ }
242
+ } else {
243
+ echo $result->getCss();
244
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/composer.json CHANGED
@@ -30,22 +30,82 @@
30
  "ext-json": "*",
31
  "ext-ctype": "*"
32
  },
 
 
 
 
33
  "require-dev": {
 
 
 
34
  "squizlabs/php_codesniffer": "~3.5",
35
- "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.3",
36
- "twbs/bootstrap": "~4.3",
 
 
37
  "zurb/foundation": "~6.5"
38
  },
39
- "minimum-stability": "dev",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  "bin": ["bin/pscss"],
41
- "archive": {
42
- "exclude": [
43
- "/Makefile",
44
- "/.gitattributes",
45
- "/.gitignore",
46
- "/.travis.yml",
47
- "/phpunit.xml.dist",
48
- "/tests"
49
- ]
50
  }
51
  }
30
  "ext-json": "*",
31
  "ext-ctype": "*"
32
  },
33
+ "suggest": {
34
+ "ext-mbstring": "For best performance, mbstring should be installed as it is faster than ext-iconv",
35
+ "ext-iconv": "Can be used as fallback when ext-mbstring is not available"
36
+ },
37
  "require-dev": {
38
+ "bamarni/composer-bin-plugin": "^1.4",
39
+ "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.3 || ^9.4",
40
+ "sass/sass-spec": "*",
41
  "squizlabs/php_codesniffer": "~3.5",
42
+ "symfony/phpunit-bridge": "^5.1",
43
+ "thoughtbot/bourbon": "^7.0",
44
+ "twbs/bootstrap": "~5.0",
45
+ "twbs/bootstrap4": "4.6.1",
46
  "zurb/foundation": "~6.5"
47
  },
48
+ "repositories": [
49
+ {
50
+ "type": "package",
51
+ "package": {
52
+ "name": "sass/sass-spec",
53
+ "version": "2022.02.24",
54
+ "source": {
55
+ "type": "git",
56
+ "url": "https://github.com/sass/sass-spec.git",
57
+ "reference": "f41b9bfb9a3013392f2136c79f7f3356f15fb8ba"
58
+ },
59
+ "dist": {
60
+ "type": "zip",
61
+ "url": "https://api.github.com/repos/sass/sass-spec/zipball/f41b9bfb9a3013392f2136c79f7f3356f15fb8ba",
62
+ "reference": "f41b9bfb9a3013392f2136c79f7f3356f15fb8ba",
63
+ "shasum": ""
64
+ }
65
+ }
66
+ },
67
+ {
68
+ "type": "package",
69
+ "package": {
70
+ "name": "thoughtbot/bourbon",
71
+ "version": "v7.0.0",
72
+ "source": {
73
+ "type": "git",
74
+ "url": "https://github.com/thoughtbot/bourbon.git",
75
+ "reference": "fbe338ee6807e7f7aa996d82c8a16f248bb149b3"
76
+ },
77
+ "dist": {
78
+ "type": "zip",
79
+ "url": "https://api.github.com/repos/thoughtbot/bourbon/zipball/fbe338ee6807e7f7aa996d82c8a16f248bb149b3",
80
+ "reference": "fbe338ee6807e7f7aa996d82c8a16f248bb149b3",
81
+ "shasum": ""
82
+ }
83
+ }
84
+ },
85
+ {
86
+ "type": "package",
87
+ "package": {
88
+ "name": "twbs/bootstrap4",
89
+ "version": "v4.6.1",
90
+ "source": {
91
+ "type": "git",
92
+ "url": "https://github.com/twbs/bootstrap.git",
93
+ "reference": "043a03c95a2ad6738f85b65e53b9dbdfb03b8d10"
94
+ },
95
+ "dist": {
96
+ "type": "zip",
97
+ "url": "https://api.github.com/repos/twbs/bootstrap/zipball/043a03c95a2ad6738f85b65e53b9dbdfb03b8d10",
98
+ "reference": "043a03c95a2ad6738f85b65e53b9dbdfb03b8d10",
99
+ "shasum": ""
100
+ }
101
+ }
102
+ }
103
+ ],
104
  "bin": ["bin/pscss"],
105
+ "config": {
106
+ "sort-packages": true,
107
+ "allow-plugins": {
108
+ "bamarni/composer-bin-plugin": true
109
+ }
 
 
 
 
110
  }
111
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/phpcs.xml.dist ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <ruleset name="PSR12 (adapted for PHP 5.6+)">
3
+ <rule ref="PSR12">
4
+ <!-- Ignore this PHP 7.1+ sniff as long as we support PHP 5.6+ -->
5
+ <exclude name="PSR12.Properties.ConstantVisibility.NotFound"/>
6
+
7
+ <!-- This sniff doesn't ignore comment blocks -->
8
+ <!--
9
+ <exclude name="Generic.Files.LineLength"/>
10
+ -->
11
+ </rule>
12
+ </ruleset>
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/scss.inc.php CHANGED
@@ -1,34 +1,21 @@
1
  <?php
 
2
  if (version_compare(PHP_VERSION, '5.6') < 0) {
3
  throw new \Exception('scssphp requires PHP 5.6 or above');
4
  }
5
 
6
- if (! class_exists('ScssPhp\ScssPhp\Version', false)) {
7
- include_once __DIR__ . '/src/Base/Range.php';
8
- include_once __DIR__ . '/src/Block.php';
9
- include_once __DIR__ . '/src/Cache.php';
10
- include_once __DIR__ . '/src/Colors.php';
11
- include_once __DIR__ . '/src/Compiler.php';
12
- include_once __DIR__ . '/src/Compiler/Environment.php';
13
- include_once __DIR__ . '/src/Exception/CompilerException.php';
14
- include_once __DIR__ . '/src/Exception/ParserException.php';
15
- include_once __DIR__ . '/src/Exception/RangeException.php';
16
- include_once __DIR__ . '/src/Exception/ServerException.php';
17
- include_once __DIR__ . '/src/Formatter.php';
18
- include_once __DIR__ . '/src/Formatter/Compact.php';
19
- include_once __DIR__ . '/src/Formatter/Compressed.php';
20
- include_once __DIR__ . '/src/Formatter/Crunched.php';
21
- include_once __DIR__ . '/src/Formatter/Debug.php';
22
- include_once __DIR__ . '/src/Formatter/Expanded.php';
23
- include_once __DIR__ . '/src/Formatter/Nested.php';
24
- include_once __DIR__ . '/src/Formatter/OutputBlock.php';
25
- include_once __DIR__ . '/src/Node.php';
26
- include_once __DIR__ . '/src/Node/Number.php';
27
- include_once __DIR__ . '/src/Parser.php';
28
- include_once __DIR__ . '/src/SourceMap/Base64.php';
29
- include_once __DIR__ . '/src/SourceMap/Base64VLQ.php';
30
- include_once __DIR__ . '/src/SourceMap/SourceMapGenerator.php';
31
- include_once __DIR__ . '/src/Type.php';
32
- include_once __DIR__ . '/src/Util.php';
33
- include_once __DIR__ . '/src/Version.php';
34
  }
1
  <?php
2
+
3
  if (version_compare(PHP_VERSION, '5.6') < 0) {
4
  throw new \Exception('scssphp requires PHP 5.6 or above');
5
  }
6
 
7
+ if (! class_exists('ScssPhp\ScssPhp\Version')) {
8
+ spl_autoload_register(function ($class) {
9
+ if (0 !== strpos($class, 'ScssPhp\ScssPhp\\')) {
10
+ // Not a ScssPhp class
11
+ return;
12
+ }
13
+
14
+ $subClass = substr($class, strlen('ScssPhp\ScssPhp\\'));
15
+ $path = __DIR__ . '/src/' . str_replace('\\', '/', $subClass) . '.php';
16
+
17
+ if (file_exists($path)) {
18
+ require $path;
19
+ }
20
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Base/Range.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,17 +16,26 @@ namespace ScssPhp\ScssPhp\Base;
15
  * Range
16
  *
17
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
18
  */
19
  class Range
20
  {
 
 
 
21
  public $first;
 
 
 
 
22
  public $last;
23
 
24
  /**
25
  * Initialize range
26
  *
27
- * @param integer|float $first
28
- * @param integer|float $last
29
  */
30
  public function __construct($first, $last)
31
  {
@@ -36,9 +46,9 @@ class Range
36
  /**
37
  * Test for inclusion in range
38
  *
39
- * @param integer|float $value
40
  *
41
- * @return boolean
42
  */
43
  public function includes($value)
44
  {
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
16
  * Range
17
  *
18
  * @author Anthon Pang <anthon.pang@gmail.com>
19
+ *
20
+ * @internal
21
  */
22
  class Range
23
  {
24
+ /**
25
+ * @var float|int
26
+ */
27
  public $first;
28
+
29
+ /**
30
+ * @var float|int
31
+ */
32
  public $last;
33
 
34
  /**
35
  * Initialize range
36
  *
37
+ * @param int|float $first
38
+ * @param int|float $last
39
  */
40
  public function __construct($first, $last)
41
  {
46
  /**
47
  * Test for inclusion in range
48
  *
49
+ * @param int|float $value
50
  *
51
+ * @return bool
52
  */
53
  public function includes($value)
54
  {
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,16 +16,18 @@ namespace ScssPhp\ScssPhp;
15
  * Block
16
  *
17
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
18
  */
19
  class Block
20
  {
21
  /**
22
- * @var string
23
  */
24
  public $type;
25
 
26
  /**
27
- * @var \ScssPhp\ScssPhp\Block
28
  */
29
  public $parent;
30
 
@@ -34,22 +37,22 @@ class Block
34
  public $sourceName;
35
 
36
  /**
37
- * @var integer
38
  */
39
  public $sourceIndex;
40
 
41
  /**
42
- * @var integer
43
  */
44
  public $sourceLine;
45
 
46
  /**
47
- * @var integer
48
  */
49
  public $sourceColumn;
50
 
51
  /**
52
- * @var array
53
  */
54
  public $selectors;
55
 
@@ -64,7 +67,7 @@ class Block
64
  public $children;
65
 
66
  /**
67
- * @var \ScssPhp\ScssPhp\Block
68
  */
69
  public $selfParent;
70
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
16
  * Block
17
  *
18
  * @author Anthon Pang <anthon.pang@gmail.com>
19
+ *
20
+ * @internal
21
  */
22
  class Block
23
  {
24
  /**
25
+ * @var string|null
26
  */
27
  public $type;
28
 
29
  /**
30
+ * @var Block|null
31
  */
32
  public $parent;
33
 
37
  public $sourceName;
38
 
39
  /**
40
+ * @var int
41
  */
42
  public $sourceIndex;
43
 
44
  /**
45
+ * @var int
46
  */
47
  public $sourceLine;
48
 
49
  /**
50
+ * @var int
51
  */
52
  public $sourceColumn;
53
 
54
  /**
55
+ * @var array|null
56
  */
57
  public $selectors;
58
 
67
  public $children;
68
 
69
  /**
70
+ * @var Block|null
71
  */
72
  public $selfParent;
73
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/AtRootBlock.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Block;
14
+
15
+ use ScssPhp\ScssPhp\Block;
16
+ use ScssPhp\ScssPhp\Type;
17
+
18
+ /**
19
+ * @internal
20
+ */
21
+ class AtRootBlock extends Block
22
+ {
23
+ /**
24
+ * @var array|null
25
+ */
26
+ public $selector;
27
+
28
+ /**
29
+ * @var array|null
30
+ */
31
+ public $with;
32
+
33
+ public function __construct()
34
+ {
35
+ $this->type = Type::T_AT_ROOT;
36
+ }
37
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/CallableBlock.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Block;
14
+
15
+ use ScssPhp\ScssPhp\Block;
16
+ use ScssPhp\ScssPhp\Compiler\Environment;
17
+
18
+ /**
19
+ * @internal
20
+ */
21
+ class CallableBlock extends Block
22
+ {
23
+ /**
24
+ * @var string
25
+ */
26
+ public $name;
27
+
28
+ /**
29
+ * @var array|null
30
+ */
31
+ public $args;
32
+
33
+ /**
34
+ * @var Environment|null
35
+ */
36
+ public $parentEnv;
37
+
38
+ /**
39
+ * @param string $type
40
+ */
41
+ public function __construct($type)
42
+ {
43
+ $this->type = $type;
44
+ }
45
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/ContentBlock.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Block;
14
+
15
+ use ScssPhp\ScssPhp\Block;
16
+ use ScssPhp\ScssPhp\Compiler\Environment;
17
+ use ScssPhp\ScssPhp\Type;
18
+
19
+ /**
20
+ * @internal
21
+ */
22
+ class ContentBlock extends Block
23
+ {
24
+ /**
25
+ * @var array|null
26
+ */
27
+ public $child;
28
+
29
+ /**
30
+ * @var Environment|null
31
+ */
32
+ public $scope;
33
+
34
+ public function __construct()
35
+ {
36
+ $this->type = Type::T_INCLUDE;
37
+ }
38
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/DirectiveBlock.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Block;
14
+
15
+ use ScssPhp\ScssPhp\Block;
16
+ use ScssPhp\ScssPhp\Type;
17
+
18
+ /**
19
+ * @internal
20
+ */
21
+ class DirectiveBlock extends Block
22
+ {
23
+ /**
24
+ * @var string|array
25
+ */
26
+ public $name;
27
+
28
+ /**
29
+ * @var string|array|null
30
+ */
31
+ public $value;
32
+
33
+ public function __construct()
34
+ {
35
+ $this->type = Type::T_DIRECTIVE;
36
+ }
37
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/EachBlock.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Block;
14
+
15
+ use ScssPhp\ScssPhp\Block;
16
+ use ScssPhp\ScssPhp\Type;
17
+
18
+ /**
19
+ * @internal
20
+ */
21
+ class EachBlock extends Block
22
+ {
23
+ /**
24
+ * @var string[]
25
+ */
26
+ public $vars = [];
27
+
28
+ /**
29
+ * @var array
30
+ */
31
+ public $list;
32
+
33
+ public function __construct()
34
+ {
35
+ $this->type = Type::T_EACH;
36
+ }
37
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/ElseBlock.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Block;
14
+
15
+ use ScssPhp\ScssPhp\Block;
16
+ use ScssPhp\ScssPhp\Type;
17
+
18
+ /**
19
+ * @internal
20
+ */
21
+ class ElseBlock extends Block
22
+ {
23
+ public function __construct()
24
+ {
25
+ $this->type = Type::T_ELSE;
26
+ }
27
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/ElseifBlock.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Block;
14
+
15
+ use ScssPhp\ScssPhp\Block;
16
+ use ScssPhp\ScssPhp\Type;
17
+
18
+ /**
19
+ * @internal
20
+ */
21
+ class ElseifBlock extends Block
22
+ {
23
+ /**
24
+ * @var array
25
+ */
26
+ public $cond;
27
+
28
+ public function __construct()
29
+ {
30
+ $this->type = Type::T_ELSEIF;
31
+ }
32
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/ForBlock.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Block;
14
+
15
+ use ScssPhp\ScssPhp\Block;
16
+ use ScssPhp\ScssPhp\Type;
17
+
18
+ /**
19
+ * @internal
20
+ */
21
+ class ForBlock extends Block
22
+ {
23
+ /**
24
+ * @var string
25
+ */
26
+ public $var;
27
+
28
+ /**
29
+ * @var array
30
+ */
31
+ public $start;
32
+
33
+ /**
34
+ * @var array
35
+ */
36
+ public $end;
37
+
38
+ /**
39
+ * @var bool
40
+ */
41
+ public $until;
42
+
43
+ public function __construct()
44
+ {
45
+ $this->type = Type::T_FOR;
46
+ }
47
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/IfBlock.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Block;
14
+
15
+ use ScssPhp\ScssPhp\Block;
16
+ use ScssPhp\ScssPhp\Type;
17
+
18
+ /**
19
+ * @internal
20
+ */
21
+ class IfBlock extends Block
22
+ {
23
+ /**
24
+ * @var array
25
+ */
26
+ public $cond;
27
+
28
+ /**
29
+ * @var array<ElseifBlock|ElseBlock>
30
+ */
31
+ public $cases = [];
32
+
33
+ public function __construct()
34
+ {
35
+ $this->type = Type::T_IF;
36
+ }
37
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/MediaBlock.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Block;
14
+
15
+ use ScssPhp\ScssPhp\Block;
16
+ use ScssPhp\ScssPhp\Type;
17
+
18
+ /**
19
+ * @internal
20
+ */
21
+ class MediaBlock extends Block
22
+ {
23
+ /**
24
+ * @var string|array|null
25
+ */
26
+ public $value;
27
+
28
+ /**
29
+ * @var array|null
30
+ */
31
+ public $queryList;
32
+
33
+ public function __construct()
34
+ {
35
+ $this->type = Type::T_MEDIA;
36
+ }
37
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/NestedPropertyBlock.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Block;
14
+
15
+ use ScssPhp\ScssPhp\Block;
16
+ use ScssPhp\ScssPhp\Type;
17
+
18
+ /**
19
+ * @internal
20
+ */
21
+ class NestedPropertyBlock extends Block
22
+ {
23
+ /**
24
+ * @var bool
25
+ */
26
+ public $hasValue;
27
+
28
+ /**
29
+ * @var array
30
+ */
31
+ public $prefix;
32
+
33
+ public function __construct()
34
+ {
35
+ $this->type = Type::T_NESTED_PROPERTY;
36
+ }
37
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Block/WhileBlock.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Block;
14
+
15
+ use ScssPhp\ScssPhp\Block;
16
+ use ScssPhp\ScssPhp\Type;
17
+
18
+ /**
19
+ * @internal
20
+ */
21
+ class WhileBlock extends Block
22
+ {
23
+ /**
24
+ * @var array
25
+ */
26
+ public $cond;
27
+
28
+ public function __construct()
29
+ {
30
+ $this->type = Type::T_WHILE;
31
+ }
32
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Cache.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -12,6 +13,7 @@
12
  namespace ScssPhp\ScssPhp;
13
 
14
  use Exception;
 
15
 
16
  /**
17
  * The scss cache manager.
@@ -28,30 +30,54 @@ use Exception;
28
  * SCSS cache
29
  *
30
  * @author Cedric Morin <cedric@yterium.com>
 
 
31
  */
32
  class Cache
33
  {
34
  const CACHE_VERSION = 1;
35
 
36
- // directory used for storing data
 
 
 
 
37
  public static $cacheDir = false;
38
 
39
- // prefix for the storing data
 
 
 
 
40
  public static $prefix = 'scssphp_';
41
 
42
- // force a refresh : 'once' for refreshing the first hit on a cache only, true to never use the cache in this hit
 
 
 
 
43
  public static $forceRefresh = false;
44
 
45
- // specifies the number of seconds after which data cached will be seen as 'garbage' and potentially cleaned up
 
 
 
 
46
  public static $gcLifetime = 604800;
47
 
48
- // array of already refreshed cache if $forceRefresh==='once'
 
 
 
 
49
  protected static $refreshed = [];
50
 
51
  /**
52
  * Constructor
53
  *
54
  * @param array $options
 
 
55
  */
56
  public function __construct($options)
57
  {
@@ -83,10 +109,10 @@ class Cache
83
  * Get the cached result of $operation on $what,
84
  * which is known as dependant from the content of $options
85
  *
86
- * @param string $operation parse, compile...
87
- * @param mixed $what content key (e.g., filename to be treated)
88
- * @param array $options any option that affect the operation result on the content
89
- * @param integer $lastModified last modified timestamp
90
  *
91
  * @return mixed
92
  *
@@ -96,12 +122,14 @@ class Cache
96
  {
97
  $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options);
98
 
99
- if (((self::$forceRefresh === false) || (self::$forceRefresh === 'once' &&
 
100
  isset(self::$refreshed[$fileCache]))) && file_exists($fileCache)
101
  ) {
102
  $cacheTime = filemtime($fileCache);
103
 
104
- if ((\is_null($lastModified) || $cacheTime > $lastModified) &&
 
105
  $cacheTime + self::$gcLifetime > time()
106
  ) {
107
  $c = file_get_contents($fileCache);
@@ -124,6 +152,8 @@ class Cache
124
  * @param mixed $what
125
  * @param mixed $value
126
  * @param array $options
 
 
127
  */
128
  public function setCache($operation, $what, $value, $options = [])
129
  {
@@ -153,6 +183,7 @@ class Cache
153
  {
154
  $t = [
155
  'version' => self::CACHE_VERSION,
 
156
  'operation' => $operation,
157
  'what' => $what,
158
  'options' => $options
@@ -169,6 +200,8 @@ class Cache
169
  /**
170
  * Check that the cache dir exists and is writeable
171
  *
 
 
172
  * @throws \Exception
173
  */
174
  public static function checkCacheDir()
@@ -177,9 +210,7 @@ class Cache
177
  self::$cacheDir = rtrim(self::$cacheDir, '/') . '/';
178
 
179
  if (! is_dir(self::$cacheDir)) {
180
- if (! mkdir(self::$cacheDir)) {
181
- throw new Exception('Cache directory couldn\'t be created: ' . self::$cacheDir);
182
- }
183
  }
184
 
185
  if (! is_writable(self::$cacheDir)) {
@@ -189,6 +220,8 @@ class Cache
189
 
190
  /**
191
  * Delete unused cached files
 
 
192
  */
193
  public static function cleanCache()
194
  {
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
13
  namespace ScssPhp\ScssPhp;
14
 
15
  use Exception;
16
+ use ScssPhp\ScssPhp\Version;
17
 
18
  /**
19
  * The scss cache manager.
30
  * SCSS cache
31
  *
32
  * @author Cedric Morin <cedric@yterium.com>
33
+ *
34
+ * @internal
35
  */
36
  class Cache
37
  {
38
  const CACHE_VERSION = 1;
39
 
40
+ /**
41
+ * directory used for storing data
42
+ *
43
+ * @var string|false
44
+ */
45
  public static $cacheDir = false;
46
 
47
+ /**
48
+ * prefix for the storing data
49
+ *
50
+ * @var string
51
+ */
52
  public static $prefix = 'scssphp_';
53
 
54
+ /**
55
+ * force a refresh : 'once' for refreshing the first hit on a cache only, true to never use the cache in this hit
56
+ *
57
+ * @var bool|string
58
+ */
59
  public static $forceRefresh = false;
60
 
61
+ /**
62
+ * specifies the number of seconds after which data cached will be seen as 'garbage' and potentially cleaned up
63
+ *
64
+ * @var int
65
+ */
66
  public static $gcLifetime = 604800;
67
 
68
+ /**
69
+ * array of already refreshed cache if $forceRefresh==='once'
70
+ *
71
+ * @var array<string, bool>
72
+ */
73
  protected static $refreshed = [];
74
 
75
  /**
76
  * Constructor
77
  *
78
  * @param array $options
79
+ *
80
+ * @phpstan-param array{cacheDir?: string, prefix?: string, forceRefresh?: string} $options
81
  */
82
  public function __construct($options)
83
  {
109
  * Get the cached result of $operation on $what,
110
  * which is known as dependant from the content of $options
111
  *
112
+ * @param string $operation parse, compile...
113
+ * @param mixed $what content key (e.g., filename to be treated)
114
+ * @param array $options any option that affect the operation result on the content
115
+ * @param int|null $lastModified last modified timestamp
116
  *
117
  * @return mixed
118
  *
122
  {
123
  $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options);
124
 
125
+ if (
126
+ ((self::$forceRefresh === false) || (self::$forceRefresh === 'once' &&
127
  isset(self::$refreshed[$fileCache]))) && file_exists($fileCache)
128
  ) {
129
  $cacheTime = filemtime($fileCache);
130
 
131
+ if (
132
+ (\is_null($lastModified) || $cacheTime > $lastModified) &&
133
  $cacheTime + self::$gcLifetime > time()
134
  ) {
135
  $c = file_get_contents($fileCache);
152
  * @param mixed $what
153
  * @param mixed $value
154
  * @param array $options
155
+ *
156
+ * @return void
157
  */
158
  public function setCache($operation, $what, $value, $options = [])
159
  {
183
  {
184
  $t = [
185
  'version' => self::CACHE_VERSION,
186
+ 'scssphpVersion' => Version::VERSION,
187
  'operation' => $operation,
188
  'what' => $what,
189
  'options' => $options
200
  /**
201
  * Check that the cache dir exists and is writeable
202
  *
203
+ * @return void
204
+ *
205
  * @throws \Exception
206
  */
207
  public static function checkCacheDir()
210
  self::$cacheDir = rtrim(self::$cacheDir, '/') . '/';
211
 
212
  if (! is_dir(self::$cacheDir)) {
213
+ throw new Exception('Cache directory doesn\'t exist: ' . self::$cacheDir);
 
 
214
  }
215
 
216
  if (! is_writable(self::$cacheDir)) {
220
 
221
  /**
222
  * Delete unused cached files
223
+ *
224
+ * @return void
225
  */
226
  public static function cleanCache()
227
  {
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Colors.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,6 +16,8 @@ namespace ScssPhp\ScssPhp;
15
  * CSS Colors
16
  *
17
  * @author Leaf Corcoran <leafot@gmail.com>
 
 
18
  */
19
  class Colors
20
  {
@@ -23,12 +26,13 @@ class Colors
23
  *
24
  * @see http://www.w3.org/TR/css3-color
25
  *
26
- * @var array
27
  */
28
  protected static $cssColors = [
29
  'aliceblue' => '240,248,255',
30
  'antiquewhite' => '250,235,215',
31
  'aqua' => '0,255,255',
 
32
  'aquamarine' => '127,255,212',
33
  'azure' => '240,255,255',
34
  'beige' => '245,245,220',
@@ -46,13 +50,12 @@ class Colors
46
  'cornflowerblue' => '100,149,237',
47
  'cornsilk' => '255,248,220',
48
  'crimson' => '220,20,60',
49
- 'cyan' => '0,255,255',
50
  'darkblue' => '0,0,139',
51
  'darkcyan' => '0,139,139',
52
  'darkgoldenrod' => '184,134,11',
53
  'darkgray' => '169,169,169',
54
- 'darkgreen' => '0,100,0',
55
  'darkgrey' => '169,169,169',
 
56
  'darkkhaki' => '189,183,107',
57
  'darkmagenta' => '139,0,139',
58
  'darkolivegreen' => '85,107,47',
@@ -75,14 +78,15 @@ class Colors
75
  'floralwhite' => '255,250,240',
76
  'forestgreen' => '34,139,34',
77
  'fuchsia' => '255,0,255',
 
78
  'gainsboro' => '220,220,220',
79
  'ghostwhite' => '248,248,255',
80
  'gold' => '255,215,0',
81
  'goldenrod' => '218,165,32',
82
  'gray' => '128,128,128',
 
83
  'green' => '0,128,0',
84
  'greenyellow' => '173,255,47',
85
- 'grey' => '128,128,128',
86
  'honeydew' => '240,255,240',
87
  'hotpink' => '255,105,180',
88
  'indianred' => '205,92,92',
@@ -98,8 +102,8 @@ class Colors
98
  'lightcyan' => '224,255,255',
99
  'lightgoldenrodyellow' => '250,250,210',
100
  'lightgray' => '211,211,211',
101
- 'lightgreen' => '144,238,144',
102
  'lightgrey' => '211,211,211',
 
103
  'lightpink' => '255,182,193',
104
  'lightsalmon' => '255,160,122',
105
  'lightseagreen' => '32,178,170',
@@ -111,7 +115,6 @@ class Colors
111
  'lime' => '0,255,0',
112
  'limegreen' => '50,205,50',
113
  'linen' => '250,240,230',
114
- 'magenta' => '255,0,255',
115
  'maroon' => '128,0,0',
116
  'mediumaquamarine' => '102,205,170',
117
  'mediumblue' => '0,0,205',
@@ -145,7 +148,6 @@ class Colors
145
  'plum' => '221,160,221',
146
  'powderblue' => '176,224,230',
147
  'purple' => '128,0,128',
148
- 'rebeccapurple' => '102,51,153',
149
  'red' => '255,0,0',
150
  'rosybrown' => '188,143,143',
151
  'royalblue' => '65,105,225',
@@ -167,7 +169,6 @@ class Colors
167
  'teal' => '0,128,128',
168
  'thistle' => '216,191,216',
169
  'tomato' => '255,99,71',
170
- 'transparent' => '0,0,0,0',
171
  'turquoise' => '64,224,208',
172
  'violet' => '238,130,238',
173
  'wheat' => '245,222,179',
@@ -175,6 +176,8 @@ class Colors
175
  'whitesmoke' => '245,245,245',
176
  'yellow' => '255,255,0',
177
  'yellowgreen' => '154,205,50',
 
 
178
  ];
179
 
180
  /**
@@ -182,7 +185,7 @@ class Colors
182
  *
183
  * @param string $colorName
184
  *
185
- * @return array|null
186
  */
187
  public static function colorNameToRGBa($colorName)
188
  {
@@ -201,10 +204,10 @@ class Colors
201
  /**
202
  * Reverse conversion : from RGBA to a color name if possible
203
  *
204
- * @param integer $r
205
- * @param integer $g
206
- * @param integer $b
207
- * @param integer $a
208
  *
209
  * @return string|null
210
  */
@@ -226,7 +229,10 @@ class Colors
226
  foreach (static::$cssColors as $name => $rgb_str) {
227
  $rgb_str = explode(',', $rgb_str);
228
 
229
- if (\count($rgb_str) == 3) {
 
 
 
230
  $reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])] = $name;
231
  }
232
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
16
  * CSS Colors
17
  *
18
  * @author Leaf Corcoran <leafot@gmail.com>
19
+ *
20
+ * @internal
21
  */
22
  class Colors
23
  {
26
  *
27
  * @see http://www.w3.org/TR/css3-color
28
  *
29
+ * @var array<string, string>
30
  */
31
  protected static $cssColors = [
32
  'aliceblue' => '240,248,255',
33
  'antiquewhite' => '250,235,215',
34
  'aqua' => '0,255,255',
35
+ 'cyan' => '0,255,255',
36
  'aquamarine' => '127,255,212',
37
  'azure' => '240,255,255',
38
  'beige' => '245,245,220',
50
  'cornflowerblue' => '100,149,237',
51
  'cornsilk' => '255,248,220',
52
  'crimson' => '220,20,60',
 
53
  'darkblue' => '0,0,139',
54
  'darkcyan' => '0,139,139',
55
  'darkgoldenrod' => '184,134,11',
56
  'darkgray' => '169,169,169',
 
57
  'darkgrey' => '169,169,169',
58
+ 'darkgreen' => '0,100,0',
59
  'darkkhaki' => '189,183,107',
60
  'darkmagenta' => '139,0,139',
61
  'darkolivegreen' => '85,107,47',
78
  'floralwhite' => '255,250,240',
79
  'forestgreen' => '34,139,34',
80
  'fuchsia' => '255,0,255',
81
+ 'magenta' => '255,0,255',
82
  'gainsboro' => '220,220,220',
83
  'ghostwhite' => '248,248,255',
84
  'gold' => '255,215,0',
85
  'goldenrod' => '218,165,32',
86
  'gray' => '128,128,128',
87
+ 'grey' => '128,128,128',
88
  'green' => '0,128,0',
89
  'greenyellow' => '173,255,47',
 
90
  'honeydew' => '240,255,240',
91
  'hotpink' => '255,105,180',
92
  'indianred' => '205,92,92',
102
  'lightcyan' => '224,255,255',
103
  'lightgoldenrodyellow' => '250,250,210',
104
  'lightgray' => '211,211,211',
 
105
  'lightgrey' => '211,211,211',
106
+ 'lightgreen' => '144,238,144',
107
  'lightpink' => '255,182,193',
108
  'lightsalmon' => '255,160,122',
109
  'lightseagreen' => '32,178,170',
115
  'lime' => '0,255,0',
116
  'limegreen' => '50,205,50',
117
  'linen' => '250,240,230',
 
118
  'maroon' => '128,0,0',
119
  'mediumaquamarine' => '102,205,170',
120
  'mediumblue' => '0,0,205',
148
  'plum' => '221,160,221',
149
  'powderblue' => '176,224,230',
150
  'purple' => '128,0,128',
 
151
  'red' => '255,0,0',
152
  'rosybrown' => '188,143,143',
153
  'royalblue' => '65,105,225',
169
  'teal' => '0,128,128',
170
  'thistle' => '216,191,216',
171
  'tomato' => '255,99,71',
 
172
  'turquoise' => '64,224,208',
173
  'violet' => '238,130,238',
174
  'wheat' => '245,222,179',
176
  'whitesmoke' => '245,245,245',
177
  'yellow' => '255,255,0',
178
  'yellowgreen' => '154,205,50',
179
+ 'rebeccapurple' => '102,51,153',
180
+ 'transparent' => '0,0,0,0',
181
  ];
182
 
183
  /**
185
  *
186
  * @param string $colorName
187
  *
188
+ * @return int[]|null
189
  */
190
  public static function colorNameToRGBa($colorName)
191
  {
204
  /**
205
  * Reverse conversion : from RGBA to a color name if possible
206
  *
207
+ * @param int $r
208
+ * @param int $g
209
+ * @param int $b
210
+ * @param int|float $a
211
  *
212
  * @return string|null
213
  */
229
  foreach (static::$cssColors as $name => $rgb_str) {
230
  $rgb_str = explode(',', $rgb_str);
231
 
232
+ if (
233
+ \count($rgb_str) == 3 &&
234
+ ! isset($reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])])
235
+ ) {
236
  $reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])] = $name;
237
  }
238
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/CompilationResult.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp;
14
+
15
+ class CompilationResult
16
+ {
17
+ /**
18
+ * @var string
19
+ */
20
+ private $css;
21
+
22
+ /**
23
+ * @var string|null
24
+ */
25
+ private $sourceMap;
26
+
27
+ /**
28
+ * @var string[]
29
+ */
30
+ private $includedFiles;
31
+
32
+ /**
33
+ * @param string $css
34
+ * @param string|null $sourceMap
35
+ * @param string[] $includedFiles
36
+ */
37
+ public function __construct($css, $sourceMap, array $includedFiles)
38
+ {
39
+ $this->css = $css;
40
+ $this->sourceMap = $sourceMap;
41
+ $this->includedFiles = $includedFiles;
42
+ }
43
+
44
+ /**
45
+ * @return string
46
+ */
47
+ public function getCss()
48
+ {
49
+ return $this->css;
50
+ }
51
+
52
+ /**
53
+ * @return string[]
54
+ */
55
+ public function getIncludedFiles()
56
+ {
57
+ return $this->includedFiles;
58
+ }
59
+
60
+ /**
61
+ * The sourceMap content, if it was generated
62
+ *
63
+ * @return null|string
64
+ */
65
+ public function getSourceMap()
66
+ {
67
+ return $this->sourceMap;
68
+ }
69
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Compiler.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -12,17 +13,31 @@
12
  namespace ScssPhp\ScssPhp;
13
 
14
  use ScssPhp\ScssPhp\Base\Range;
15
- use ScssPhp\ScssPhp\Block;
16
- use ScssPhp\ScssPhp\Cache;
17
- use ScssPhp\ScssPhp\Colors;
 
 
 
 
 
 
 
 
 
18
  use ScssPhp\ScssPhp\Compiler\Environment;
19
  use ScssPhp\ScssPhp\Exception\CompilerException;
 
 
 
 
 
20
  use ScssPhp\ScssPhp\Formatter\OutputBlock;
21
- use ScssPhp\ScssPhp\Node;
 
 
22
  use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
23
- use ScssPhp\ScssPhp\Type;
24
- use ScssPhp\ScssPhp\Parser;
25
- use ScssPhp\ScssPhp\Util;
26
 
27
  /**
28
  * The scss compiler and parser.
@@ -55,15 +70,35 @@ use ScssPhp\ScssPhp\Util;
55
  * SCSS compiler
56
  *
57
  * @author Leaf Corcoran <leafot@gmail.com>
 
 
58
  */
59
  class Compiler
60
  {
 
 
 
61
  const LINE_COMMENTS = 1;
 
 
 
62
  const DEBUG_INFO = 2;
63
 
 
 
 
64
  const WITH_RULE = 1;
 
 
 
65
  const WITH_MEDIA = 2;
 
 
 
66
  const WITH_SUPPORTS = 4;
 
 
 
67
  const WITH_ALL = 7;
68
 
69
  const SOURCE_MAP_NONE = 0;
@@ -71,7 +106,7 @@ class Compiler
71
  const SOURCE_MAP_FILE = 2;
72
 
73
  /**
74
- * @var array
75
  */
76
  protected static $operatorNames = [
77
  '+' => 'add',
@@ -87,11 +122,10 @@ class Compiler
87
 
88
  '<=' => 'lte',
89
  '>=' => 'gte',
90
- '<=>' => 'cmp',
91
  ];
92
 
93
  /**
94
- * @var array
95
  */
96
  protected static $namespaces = [
97
  'special' => '%',
@@ -101,7 +135,9 @@ class Compiler
101
 
102
  public static $true = [Type::T_KEYWORD, 'true'];
103
  public static $false = [Type::T_KEYWORD, 'false'];
 
104
  public static $NaN = [Type::T_KEYWORD, 'NaN'];
 
105
  public static $Infinity = [Type::T_KEYWORD, 'Infinity'];
106
  public static $null = [Type::T_NULL];
107
  public static $nullString = [Type::T_STRING, '', []];
@@ -112,80 +148,235 @@ class Compiler
112
  public static $emptyString = [Type::T_STRING, '"', []];
113
  public static $with = [Type::T_KEYWORD, 'with'];
114
  public static $without = [Type::T_KEYWORD, 'without'];
 
115
 
116
- protected $importPaths = [''];
 
 
 
 
 
 
117
  protected $importCache = [];
 
 
 
 
118
  protected $importedFiles = [];
 
 
 
 
 
119
  protected $userFunctions = [];
 
 
 
120
  protected $registeredVars = [];
 
 
 
121
  protected $registeredFeatures = [
122
  'extend-selector-pseudoclass' => false,
123
  'at-error' => true,
124
- 'units-level-3' => false,
125
  'global-variable-shadowing' => false,
126
  ];
127
 
 
 
 
128
  protected $encoding = null;
 
 
 
 
129
  protected $lineNumberStyle = null;
130
 
 
 
 
 
131
  protected $sourceMap = self::SOURCE_MAP_NONE;
 
 
 
 
 
132
  protected $sourceMapOptions = [];
133
 
134
  /**
135
- * @var string|\ScssPhp\ScssPhp\Formatter
 
 
 
 
 
136
  */
137
- protected $formatter = 'ScssPhp\ScssPhp\Formatter\Nested';
138
 
 
 
 
 
 
 
 
 
 
139
  protected $rootEnv;
 
 
 
140
  protected $rootBlock;
141
 
142
  /**
143
  * @var \ScssPhp\ScssPhp\Compiler\Environment
144
  */
145
  protected $env;
 
 
 
146
  protected $scope;
 
 
 
147
  protected $storeEnv;
 
 
 
 
 
148
  protected $charsetSeen;
 
 
 
149
  protected $sourceNames;
150
 
 
 
 
151
  protected $cache;
152
 
 
 
 
 
 
 
 
 
153
  protected $indentLevel;
 
 
 
154
  protected $extends;
 
 
 
155
  protected $extendsMap;
156
- protected $parsedFiles;
 
 
 
 
 
 
 
 
157
  protected $parser;
 
 
 
158
  protected $sourceIndex;
 
 
 
159
  protected $sourceLine;
 
 
 
160
  protected $sourceColumn;
161
- protected $stderr;
 
 
162
  protected $shouldEvaluate;
 
 
 
 
163
  protected $ignoreErrors;
 
 
 
164
  protected $ignoreCallStackMessage = false;
165
 
 
 
 
166
  protected $callStack = [];
167
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  /**
169
  * Constructor
170
  *
171
  * @param array|null $cacheOptions
 
172
  */
173
  public function __construct($cacheOptions = null)
174
  {
175
- $this->parsedFiles = [];
176
  $this->sourceNames = [];
177
 
178
  if ($cacheOptions) {
179
  $this->cache = new Cache($cacheOptions);
 
 
 
180
  }
181
 
182
- $this->stderr = fopen('php://stderr', 'w');
183
  }
184
 
185
  /**
186
  * Get compiler options
187
  *
188
- * @return array
 
 
189
  */
190
  public function getCompileOptions()
191
  {
@@ -196,54 +387,97 @@ class Compiler
196
  'encoding' => $this->encoding,
197
  'sourceMap' => serialize($this->sourceMap),
198
  'sourceMapOptions' => $this->sourceMapOptions,
199
- 'formatter' => $this->formatter,
 
200
  ];
201
 
202
  return $options;
203
  }
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  /**
206
  * Set an alternative error output stream, for testing purpose only
207
  *
208
  * @param resource $handle
 
 
 
 
209
  */
210
  public function setErrorOuput($handle)
211
  {
212
- $this->stderr = $handle;
 
 
213
  }
214
 
215
  /**
216
  * Compile scss
217
  *
218
- * @api
219
- *
220
- * @param string $code
221
- * @param string $path
222
  *
223
  * @return string
 
 
 
 
224
  */
225
  public function compile($code, $path = null)
226
  {
227
- if ($this->cache) {
228
- $cacheKey = ($path ? $path : "(stdin)") . ":" . md5($code);
229
- $compileOptions = $this->getCompileOptions();
230
- $cache = $this->cache->getCache("compile", $cacheKey, $compileOptions);
231
 
232
- if (\is_array($cache) && isset($cache['dependencies']) && isset($cache['out'])) {
233
- // check if any dependency file changed before accepting the cache
234
- foreach ($cache['dependencies'] as $file => $mtime) {
235
- if (! is_file($file) || filemtime($file) !== $mtime) {
236
- unset($cache);
237
- break;
238
- }
239
- }
240
 
241
- if (isset($cache)) {
242
- return $cache['out'];
243
- }
 
 
 
 
 
244
  }
245
  }
246
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
  $this->indentLevel = -1;
249
  $this->extends = [];
@@ -254,68 +488,151 @@ class Compiler
254
  $this->env = null;
255
  $this->scope = null;
256
  $this->storeEnv = null;
257
- $this->charsetSeen = null;
258
  $this->shouldEvaluate = null;
259
  $this->ignoreCallStackMessage = false;
 
 
 
260
 
261
- $this->parser = $this->parserFactory($path);
262
- $tree = $this->parser->parse($code);
263
- $this->parser = null;
 
 
 
 
 
264
 
265
- $this->formatter = new $this->formatter();
266
- $this->rootBlock = null;
267
- $this->rootEnv = $this->pushEnv($tree);
 
268
 
269
- $this->injectVariables($this->registeredVars);
270
- $this->compileRoot($tree);
271
- $this->popEnv();
272
 
273
- $sourceMapGenerator = null;
 
 
 
274
 
275
- if ($this->sourceMap) {
276
- if (\is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) {
277
- $sourceMapGenerator = $this->sourceMap;
278
- $this->sourceMap = self::SOURCE_MAP_FILE;
279
- } elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) {
280
- $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
281
  }
282
- }
283
 
284
- $out = $this->formatter->format($this->scope, $sourceMapGenerator);
 
 
 
 
 
 
 
 
 
 
285
 
286
- if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
287
- $sourceMap = $sourceMapGenerator->generateJson();
288
- $sourceMapUrl = null;
289
 
290
- switch ($this->sourceMap) {
291
- case self::SOURCE_MAP_INLINE:
292
- $sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap));
293
- break;
294
 
295
- case self::SOURCE_MAP_FILE:
296
- $sourceMapUrl = $sourceMapGenerator->saveMap($sourceMap);
297
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  }
 
 
 
 
 
299
 
300
- $out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl);
 
301
  }
302
 
 
 
303
  if ($this->cache && isset($cacheKey) && isset($compileOptions)) {
304
- $v = [
305
- 'dependencies' => $this->getParsedFiles(),
306
- 'out' => &$out,
307
- ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
 
309
- $this->cache->setCache("compile", $cacheKey, $v, $compileOptions);
 
 
 
310
  }
311
 
312
- return $out;
313
  }
314
 
315
  /**
316
  * Instantiate parser
317
  *
318
- * @param string $path
319
  *
320
  * @return \ScssPhp\ScssPhp\Parser
321
  */
@@ -328,11 +645,11 @@ class Compiler
328
  // Otherwise, the CSS will be rendered as-is. It can even be extended!
329
  $cssOnly = false;
330
 
331
- if (substr($path, '-4') === '.css') {
332
  $cssOnly = true;
333
  }
334
 
335
- $parser = new Parser($path, \count($this->sourceNames), $this->encoding, $this->cache, $cssOnly);
336
 
337
  $this->sourceNames[] = $path;
338
  $this->addParsedFile($path);
@@ -346,7 +663,7 @@ class Compiler
346
  * @param array $target
347
  * @param array $origin
348
  *
349
- * @return boolean
350
  */
351
  protected function isSelfExtend($target, $origin)
352
  {
@@ -362,16 +679,14 @@ class Compiler
362
  /**
363
  * Push extends
364
  *
365
- * @param array $target
366
  * @param array $origin
367
  * @param array|null $block
 
 
368
  */
369
  protected function pushExtends($target, $origin, $block)
370
  {
371
- if ($this->isSelfExtend($target, $origin)) {
372
- return;
373
- }
374
-
375
  $i = \count($this->extends);
376
  $this->extends[] = [$target, $origin, $block];
377
 
@@ -387,14 +702,14 @@ class Compiler
387
  /**
388
  * Make output block
389
  *
390
- * @param string $type
391
- * @param array $selectors
392
  *
393
  * @return \ScssPhp\ScssPhp\Formatter\OutputBlock
394
  */
395
  protected function makeOutputBlock($type, $selectors = null)
396
  {
397
- $out = new OutputBlock;
398
  $out->type = $type;
399
  $out->lines = [];
400
  $out->children = [];
@@ -407,9 +722,9 @@ class Compiler
407
  $out->sourceLine = $this->env->block->sourceLine;
408
  $out->sourceColumn = $this->env->block->sourceColumn;
409
  } else {
410
- $out->sourceName = null;
411
- $out->sourceLine = null;
412
- $out->sourceColumn = null;
413
  }
414
 
415
  return $out;
@@ -419,18 +734,23 @@ class Compiler
419
  * Compile root
420
  *
421
  * @param \ScssPhp\ScssPhp\Block $rootBlock
 
 
422
  */
423
  protected function compileRoot(Block $rootBlock)
424
  {
425
  $this->rootBlock = $this->scope = $this->makeOutputBlock(Type::T_ROOT);
426
 
427
  $this->compileChildrenNoReturn($rootBlock->children, $this->scope);
 
428
  $this->flattenSelectors($this->scope);
429
  $this->missingSelectors();
430
  }
431
 
432
  /**
433
  * Report missing selectors
 
 
434
  */
435
  protected function missingSelectors()
436
  {
@@ -450,7 +770,7 @@ class Compiler
450
  $origin = $this->collapseSelectors($origin);
451
 
452
  $this->sourceLine = $block[Parser::SOURCE_LINE];
453
- $this->throwError("\"$origin\" failed to @extend \"$target\". The selector \"$target\" was not found.");
454
  }
455
  }
456
 
@@ -459,6 +779,8 @@ class Compiler
459
  *
460
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
461
  * @param string $parentKey
 
 
462
  */
463
  protected function flattenSelectors(OutputBlock $block, $parentKey = null)
464
  {
@@ -502,6 +824,7 @@ class Compiler
502
  }
503
 
504
  if ($placeholderSelector && 0 === \count($block->selectors) && null !== $parentKey) {
 
505
  unset($block->parent->children[$parentKey]);
506
 
507
  return;
@@ -514,7 +837,7 @@ class Compiler
514
  }
515
 
516
  /**
517
- * Glue parts of :not( or :nth-child( ... that are in general splitted in selectors parts
518
  *
519
  * @param array $parts
520
  *
@@ -531,10 +854,11 @@ class Compiler
531
  } else {
532
  // a selector part finishing with a ) is the last part of a :not( or :nth-child(
533
  // and need to be joined to this
534
- if (\count($new) && \is_string($new[\count($new) - 1]) &&
 
535
  \strlen($part) && substr($part, -1) === ')' && strpos($part, '(') === false
536
  ) {
537
- while (\count($new)>1 && substr($new[\count($new) - 1], -1) !== '(') {
538
  $part = array_pop($new) . $part;
539
  }
540
  $new[\count($new) - 1] .= $part;
@@ -550,10 +874,12 @@ class Compiler
550
  /**
551
  * Match extends
552
  *
553
- * @param array $selector
554
- * @param array $out
555
- * @param integer $from
556
- * @param boolean $initial
 
 
557
  */
558
  protected function matchExtends($selector, &$out, $from = 0, $initial = true)
559
  {
@@ -598,7 +924,7 @@ class Compiler
598
  }
599
  }
600
 
601
- if (\count($nonBreakableBefore) and $k == \count($new)) {
602
  $k--;
603
  }
604
 
@@ -684,12 +1010,13 @@ class Compiler
684
  * @param string $part
685
  * @param array $matches
686
  *
687
- * @return boolean
688
  */
689
  protected function isPseudoSelector($part, &$matches)
690
  {
691
- if (strpos($part, ":") === 0
692
- && preg_match(",^::?([\w-]+)\((.+)\)$,", $part, $matches)
 
693
  ) {
694
  return true;
695
  }
@@ -706,6 +1033,8 @@ class Compiler
706
  *
707
  * @param array $out
708
  * @param array $extended
 
 
709
  */
710
  protected function pushOrMergeExtentedSelector(&$out, $extended)
711
  {
@@ -713,7 +1042,8 @@ class Compiler
713
  $single = reset($extended);
714
  $part = reset($single);
715
 
716
- if ($this->isPseudoSelector($part, $matchesExtended) &&
 
717
  \in_array($matchesExtended[1], [ 'slotted' ])
718
  ) {
719
  $prev = end($out);
@@ -723,11 +1053,12 @@ class Compiler
723
  $single = reset($prev);
724
  $part = reset($single);
725
 
726
- if ($this->isPseudoSelector($part, $matchesPrev) &&
 
727
  $matchesPrev[1] === $matchesExtended[1]
728
  ) {
729
  $extended = explode($matchesExtended[1] . '(', $matchesExtended[0], 2);
730
- $extended[1] = $matchesPrev[2] . ", " . $extended[1];
731
  $extended = implode($matchesExtended[1] . '(', $extended);
732
  $extended = [ [ $extended ]];
733
  array_pop($out);
@@ -741,11 +1072,11 @@ class Compiler
741
  /**
742
  * Match extends single
743
  *
744
- * @param array $rawSingle
745
- * @param array $outOrigin
746
- * @param boolean $initial
747
  *
748
- * @return boolean
749
  */
750
  protected function matchExtendsSingle($rawSingle, &$outOrigin, $initial = true)
751
  {
@@ -787,14 +1118,15 @@ class Compiler
787
  }
788
  }
789
 
790
- if ($initial &&
 
791
  $this->isPseudoSelector($part, $matches) &&
792
  ! \in_array($matches[1], [ 'not' ])
793
  ) {
794
  $buffer = $matches[2];
795
  $parser = $this->parserFactory(__METHOD__);
796
 
797
- if ($parser->parseSelector($buffer, $subSelectors)) {
798
  foreach ($subSelectors as $ksub => $subSelector) {
799
  $subExtended = [];
800
  $this->matchExtends($subSelector, $subExtended, 0, false);
@@ -809,7 +1141,7 @@ class Compiler
809
 
810
  $subSelectorsExtended = implode(', ', $subSelectorsExtended);
811
  $singleExtended = $single;
812
- $singleExtended[$k] = str_replace("(".$buffer.")", "($subSelectorsExtended)", $part);
813
  $outOrigin[] = [ $singleExtended ];
814
  $found = true;
815
  }
@@ -834,14 +1166,15 @@ class Compiler
834
 
835
  foreach ($origin as $j => $new) {
836
  // prevent infinite loop when target extends itself
837
- if ($this->isSelfExtend($single, $origin)) {
838
  return false;
839
  }
840
 
841
  $replacement = end($new);
842
 
843
  // Extending a decorated tag with another tag is not possible.
844
- if ($extendingDecoratedTag && $replacement[0] != $extendingDecoratedTag &&
 
845
  preg_match('/^[a-z0-9]+$/i', $replacement[0])
846
  ) {
847
  unset($origin[$j]);
@@ -912,11 +1245,13 @@ class Compiler
912
  $wasTag = false;
913
  $pseudo = [];
914
 
915
- while (\count($other) && strpos(end($other), ':')===0) {
916
  array_unshift($pseudo, array_pop($other));
917
  }
918
 
919
  foreach ([array_reverse($base), array_reverse($other)] as $single) {
 
 
920
  foreach ($single as $part) {
921
  if (preg_match('/^[\[:]/', $part)) {
922
  $out[] = $part;
@@ -924,14 +1259,15 @@ class Compiler
924
  } elseif (preg_match('/^[\.#]/', $part)) {
925
  array_unshift($out, $part);
926
  $wasTag = false;
927
- } elseif (preg_match('/^[^_-]/', $part)) {
928
  $tag[] = $part;
929
  $wasTag = true;
930
  } elseif ($wasTag) {
931
  $tag[\count($tag) - 1] .= $part;
932
  } else {
933
- $out[] = $part;
934
  }
 
935
  }
936
  }
937
 
@@ -950,14 +1286,18 @@ class Compiler
950
  * Compile media
951
  *
952
  * @param \ScssPhp\ScssPhp\Block $media
 
 
953
  */
954
  protected function compileMedia(Block $media)
955
  {
 
956
  $this->pushEnv($media);
957
 
958
  $mediaQueries = $this->compileMediaQuery($this->multiplyMedia($this->env));
959
 
960
- if (! empty($mediaQueries) && $mediaQueries) {
 
961
  $previousScope = $this->scope;
962
  $parentScope = $this->mediaParent($this->scope);
963
 
@@ -974,7 +1314,8 @@ class Compiler
974
  foreach ($media->children as $child) {
975
  $type = $child[0];
976
 
977
- if ($type !== Type::T_BLOCK &&
 
978
  $type !== Type::T_MEDIA &&
979
  $type !== Type::T_DIRECTIVE &&
980
  $type !== Type::T_IMPORT
@@ -985,7 +1326,7 @@ class Compiler
985
  }
986
 
987
  if ($needsWrap) {
988
- $wrapped = new Block;
989
  $wrapped->sourceName = $media->sourceName;
990
  $wrapped->sourceIndex = $media->sourceIndex;
991
  $wrapped->sourceLine = $media->sourceLine;
@@ -996,30 +1337,6 @@ class Compiler
996
  $wrapped->children = $media->children;
997
 
998
  $media->children = [[Type::T_BLOCK, $wrapped]];
999
-
1000
- if (isset($this->lineNumberStyle)) {
1001
- $annotation = $this->makeOutputBlock(Type::T_COMMENT);
1002
- $annotation->depth = 0;
1003
-
1004
- $file = $this->sourceNames[$media->sourceIndex];
1005
- $line = $media->sourceLine;
1006
-
1007
- switch ($this->lineNumberStyle) {
1008
- case static::LINE_COMMENTS:
1009
- $annotation->lines[] = '/* line ' . $line
1010
- . ($file ? ', ' . $file : '')
1011
- . ' */';
1012
- break;
1013
-
1014
- case static::DEBUG_INFO:
1015
- $annotation->lines[] = '@media -sass-debug-info{'
1016
- . ($file ? 'filename{font-family:"' . $file . '"}' : '')
1017
- . 'line{font-family:' . $line . '}}';
1018
- break;
1019
- }
1020
-
1021
- $this->scope->children[] = $annotation;
1022
- }
1023
  }
1024
 
1025
  $this->compileChildrenNoReturn($media->children, $this->scope);
@@ -1053,20 +1370,33 @@ class Compiler
1053
  /**
1054
  * Compile directive
1055
  *
1056
- * @param \ScssPhp\ScssPhp\Block|array $block
1057
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
 
 
1058
  */
1059
  protected function compileDirective($directive, OutputBlock $out)
1060
  {
1061
  if (\is_array($directive)) {
1062
- $s = '@' . $directive[0];
 
1063
 
1064
  if (! empty($directive[1])) {
1065
  $s .= ' ' . $this->compileValue($directive[1]);
1066
  }
 
 
 
 
 
1067
 
1068
- $this->appendRootDirective($s . ';', $out);
 
 
 
 
1069
  } else {
 
1070
  $s = '@' . $directive->name;
1071
 
1072
  if (! empty($directive->value)) {
@@ -1081,20 +1411,39 @@ class Compiler
1081
  }
1082
  }
1083
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1084
  /**
1085
  * Compile at-root
1086
  *
1087
  * @param \ScssPhp\ScssPhp\Block $block
 
 
1088
  */
1089
  protected function compileAtRoot(Block $block)
1090
  {
 
1091
  $env = $this->pushEnv($block);
1092
  $envs = $this->compactEnv($env);
1093
  list($with, $without) = $this->compileWith(isset($block->with) ? $block->with : null);
1094
 
1095
  // wrap inline selector
1096
  if ($block->selector) {
1097
- $wrapped = new Block;
1098
  $wrapped->sourceName = $block->sourceName;
1099
  $wrapped->sourceIndex = $block->sourceIndex;
1100
  $wrapped->sourceLine = $block->sourceLine;
@@ -1110,8 +1459,11 @@ class Compiler
1110
  }
1111
 
1112
  $selfParent = $block->selfParent;
 
1113
 
1114
- if (! $block->selfParent->selectors && isset($block->parent) && $block->parent &&
 
 
1115
  isset($block->parent->selectors) && $block->parent->selectors
1116
  ) {
1117
  $selfParent = $block->parent;
@@ -1119,13 +1471,15 @@ class Compiler
1119
 
1120
  $this->env = $this->filterWithWithout($envs, $with, $without);
1121
 
 
1122
  $saveScope = $this->scope;
1123
  $this->scope = $this->filterScopeWithWithout($saveScope, $with, $without);
1124
 
1125
  // propagate selfParent to the children where they still can be useful
1126
  $this->compileChildrenNoReturn($block->children, $this->scope, $selfParent);
1127
 
1128
- $this->scope = $this->completeScope($this->scope, $saveScope);
 
1129
  $this->scope = $saveScope;
1130
  $this->env = $this->extractEnv($envs);
1131
 
@@ -1133,25 +1487,26 @@ class Compiler
1133
  }
1134
 
1135
  /**
1136
- * Filter at-root scope depending of with/without option
1137
  *
1138
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1139
  * @param array $with
1140
  * @param array $without
1141
  *
1142
- * @return mixed
1143
  */
1144
  protected function filterScopeWithWithout($scope, $with, $without)
1145
  {
1146
  $filteredScopes = [];
1147
  $childStash = [];
1148
 
1149
- if ($scope->type === TYPE::T_ROOT) {
1150
  return $scope;
1151
  }
 
1152
 
1153
  // start from the root
1154
- while ($scope->parent && $scope->parent->type !== TYPE::T_ROOT) {
1155
  array_unshift($childStash, $scope);
1156
  $scope = $scope->parent;
1157
  }
@@ -1212,11 +1567,11 @@ class Compiler
1212
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1213
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $previousScope
1214
  *
1215
- * @return mixed
1216
  */
1217
  protected function completeScope($scope, $previousScope)
1218
  {
1219
- if (! $scope->type && (! $scope->selectors || ! \count($scope->selectors)) && \count($scope->lines)) {
1220
  $scope->selectors = $this->findScopeSelectors($previousScope, $scope->depth);
1221
  }
1222
 
@@ -1233,7 +1588,7 @@ class Compiler
1233
  * Find a selector by the depth node in the scope
1234
  *
1235
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1236
- * @param integer $depth
1237
  *
1238
  * @return array
1239
  */
@@ -1257,9 +1612,11 @@ class Compiler
1257
  /**
1258
  * Compile @at-root's with: inclusion / without: exclusion into 2 lists uses to filter scope/env later
1259
  *
1260
- * @param array $withCondition
1261
  *
1262
  * @return array
 
 
1263
  */
1264
  protected function compileWith($withCondition)
1265
  {
@@ -1268,9 +1625,21 @@ class Compiler
1268
  $without = ['rule' => true];
1269
 
1270
  if ($withCondition) {
1271
- if ($this->libMapHasKey([$withCondition, static::$with])) {
 
 
 
 
 
 
 
 
 
 
 
 
1272
  $without = []; // cancel the default
1273
- $list = $this->coerceList($this->libMapGet([$withCondition, static::$with]));
1274
 
1275
  foreach ($list[2] as $item) {
1276
  $keyword = $this->compileStringContent($this->coerceString($item));
@@ -1279,9 +1648,10 @@ class Compiler
1279
  }
1280
  }
1281
 
1282
- if ($this->libMapHasKey([$withCondition, static::$without])) {
 
1283
  $without = []; // cancel the default
1284
- $list = $this->coerceList($this->libMapGet([$withCondition, static::$without]));
1285
 
1286
  foreach ($list[2] as $item) {
1287
  $keyword = $this->compileStringContent($this->coerceString($item));
@@ -1297,11 +1667,13 @@ class Compiler
1297
  /**
1298
  * Filter env stack
1299
  *
1300
- * @param array $envs
1301
  * @param array $with
1302
  * @param array $without
1303
  *
1304
- * @return \ScssPhp\ScssPhp\Compiler\Environment
 
 
1305
  */
1306
  protected function filterWithWithout($envs, $with, $without)
1307
  {
@@ -1329,7 +1701,7 @@ class Compiler
1329
  * @param array $with
1330
  * @param array $without
1331
  *
1332
- * @return boolean
1333
  */
1334
  protected function isWith($block, $with, $without)
1335
  {
@@ -1339,8 +1711,9 @@ class Compiler
1339
  }
1340
 
1341
  if ($block->type === Type::T_DIRECTIVE) {
 
1342
  if (isset($block->name)) {
1343
- return $this->testWithWithout($block->name, $with, $without);
1344
  } elseif (isset($block->selectors) && preg_match(',@(\w+),ims', json_encode($block->selectors), $m)) {
1345
  return $this->testWithWithout($m[1], $with, $without);
1346
  } else {
@@ -1356,7 +1729,7 @@ class Compiler
1356
  $s = reset($s);
1357
  }
1358
 
1359
- if (\is_object($s) && $s instanceof Node\Number) {
1360
  return $this->testWithWithout('keyframes', $with, $without);
1361
  }
1362
  }
@@ -1374,12 +1747,11 @@ class Compiler
1374
  * @param array $with
1375
  * @param array $without
1376
  *
1377
- * @return boolean
1378
  * true if the block should be kept, false to reject
1379
  */
1380
  protected function testWithWithout($what, $with, $without)
1381
  {
1382
-
1383
  // if without, reject only if in the list (or 'all' is in the list)
1384
  if (\count($without)) {
1385
  return (isset($without[$what]) || isset($without['all'])) ? false : true;
@@ -1394,7 +1766,9 @@ class Compiler
1394
  * Compile keyframe block
1395
  *
1396
  * @param \ScssPhp\ScssPhp\Block $block
1397
- * @param array $selectors
 
 
1398
  */
1399
  protected function compileKeyframeBlock(Block $block, $selectors)
1400
  {
@@ -1408,10 +1782,12 @@ class Compiler
1408
 
1409
  $this->scope = $this->makeOutputBlock($block->type, $selectors);
1410
  $this->scope->depth = 1;
 
1411
  $this->scope->parent->children[] = $this->scope;
1412
 
1413
  $this->compileChildrenNoReturn($block->children, $this->scope);
1414
 
 
1415
  $this->scope = $this->scope->parent;
1416
  $this->env = $this->extractEnv($envs);
1417
 
@@ -1423,9 +1799,12 @@ class Compiler
1423
  *
1424
  * @param \ScssPhp\ScssPhp\Block $block
1425
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
 
 
1426
  */
1427
  protected function compileNestedPropertiesBlock(Block $block, OutputBlock $out)
1428
  {
 
1429
  $prefix = $this->compileValue($block->prefix) . '-';
1430
 
1431
  $nested = $this->makeOutputBlock($block->type);
@@ -1444,6 +1823,7 @@ class Compiler
1444
  break;
1445
 
1446
  case Type::T_NESTED_PROPERTY:
 
1447
  array_unshift($child[1]->prefix[2], $prefix);
1448
  break;
1449
  }
@@ -1456,18 +1836,21 @@ class Compiler
1456
  * Compile nested block
1457
  *
1458
  * @param \ScssPhp\ScssPhp\Block $block
1459
- * @param array $selectors
 
 
1460
  */
1461
  protected function compileNestedBlock(Block $block, $selectors)
1462
  {
1463
  $this->pushEnv($block);
1464
 
1465
  $this->scope = $this->makeOutputBlock($block->type, $selectors);
 
1466
  $this->scope->parent->children[] = $this->scope;
1467
 
1468
  // wrap assign children in a block
1469
  // except for @font-face
1470
- if ($block->type !== Type::T_DIRECTIVE || $block->name !== "font-face") {
1471
  // need wrapping?
1472
  $needWrapping = false;
1473
 
@@ -1479,7 +1862,7 @@ class Compiler
1479
  }
1480
 
1481
  if ($needWrapping) {
1482
- $wrapped = new Block;
1483
  $wrapped->sourceName = $block->sourceName;
1484
  $wrapped->sourceIndex = $block->sourceIndex;
1485
  $wrapped->sourceLine = $block->sourceLine;
@@ -1496,6 +1879,7 @@ class Compiler
1496
 
1497
  $this->compileChildrenNoReturn($block->children, $this->scope);
1498
 
 
1499
  $this->scope = $this->scope->parent;
1500
 
1501
  $this->popEnv();
@@ -1518,39 +1902,19 @@ class Compiler
1518
  * @see Compiler::compileChild()
1519
  *
1520
  * @param \ScssPhp\ScssPhp\Block $block
 
 
1521
  */
1522
  protected function compileBlock(Block $block)
1523
  {
1524
  $env = $this->pushEnv($block);
 
1525
  $env->selectors = $this->evalSelectors($block->selectors);
1526
 
1527
  $out = $this->makeOutputBlock(null);
1528
 
1529
- if (isset($this->lineNumberStyle) && \count($env->selectors) && \count($block->children)) {
1530
- $annotation = $this->makeOutputBlock(Type::T_COMMENT);
1531
- $annotation->depth = 0;
1532
-
1533
- $file = $this->sourceNames[$block->sourceIndex];
1534
- $line = $block->sourceLine;
1535
-
1536
- switch ($this->lineNumberStyle) {
1537
- case static::LINE_COMMENTS:
1538
- $annotation->lines[] = '/* line ' . $line
1539
- . ($file ? ', ' . $file : '')
1540
- . ' */';
1541
- break;
1542
-
1543
- case static::DEBUG_INFO:
1544
- $annotation->lines[] = '@media -sass-debug-info{'
1545
- . ($file ? 'filename{font-family:"' . $file . '"}' : '')
1546
- . 'line{font-family:' . $line . '}}';
1547
- break;
1548
- }
1549
-
1550
- $this->scope->children[] = $annotation;
1551
- }
1552
-
1553
- $this->scope->children[] = $out;
1554
 
1555
  if (\count($block->children)) {
1556
  $out->selectors = $this->multiplySelectors($env, $block->selfParent);
@@ -1567,6 +1931,7 @@ class Compiler
1567
 
1568
  // and revert for the following children of the same block
1569
  if ($selfParentSelectors) {
 
1570
  $block->selfParent->selectors = $selfParentSelectors;
1571
  }
1572
  }
@@ -1578,10 +1943,10 @@ class Compiler
1578
  /**
1579
  * Compile the value of a comment that can have interpolation
1580
  *
1581
- * @param array $value
1582
- * @param boolean $pushEnv
1583
  *
1584
- * @return array|mixed|string
1585
  */
1586
  protected function compileCommentValue($value, $pushEnv = false)
1587
  {
@@ -1592,17 +1957,16 @@ class Compiler
1592
  $this->pushEnv();
1593
  }
1594
 
1595
- $ignoreCallStackMessage = $this->ignoreCallStackMessage;
1596
- $this->ignoreCallStackMessage = true;
1597
-
1598
  try {
1599
  $c = $this->compileValue($value[2]);
1600
- } catch (\Exception $e) {
 
 
 
 
1601
  // ignore error in comment compilation which are only interpolation
1602
  }
1603
 
1604
- $this->ignoreCallStackMessage = $ignoreCallStackMessage;
1605
-
1606
  if ($pushEnv) {
1607
  $this->popEnv();
1608
  }
@@ -1615,12 +1979,15 @@ class Compiler
1615
  * Compile root level comment
1616
  *
1617
  * @param array $block
 
 
1618
  */
1619
  protected function compileComment($block)
1620
  {
1621
  $out = $this->makeOutputBlock(Type::T_COMMENT);
1622
  $out->lines[] = $this->compileCommentValue($block, true);
1623
 
 
1624
  $this->scope->children[] = $out;
1625
  }
1626
 
@@ -1635,15 +2002,25 @@ class Compiler
1635
  {
1636
  $this->shouldEvaluate = false;
1637
 
1638
- $selectors = array_map([$this, 'evalSelector'], $selectors);
 
 
 
 
1639
 
1640
  // after evaluating interpolates, we might need a second pass
1641
  if ($this->shouldEvaluate) {
1642
- $selectors = $this->revertSelfSelector($selectors);
1643
  $buffer = $this->collapseSelectors($selectors);
1644
  $parser = $this->parserFactory(__METHOD__);
1645
 
1646
- if ($parser->parseSelector($buffer, $newSelectors)) {
 
 
 
 
 
 
1647
  $selectors = array_map([$this, 'evalSelector'], $newSelectors);
1648
  }
1649
  }
@@ -1657,6 +2034,8 @@ class Compiler
1657
  * @param array $selector
1658
  *
1659
  * @return array
 
 
1660
  */
1661
  protected function evalSelector($selector)
1662
  {
@@ -1669,6 +2048,8 @@ class Compiler
1669
  * @param array $part
1670
  *
1671
  * @return array
 
 
1672
  */
1673
  protected function evalSelectorPart($part)
1674
  {
@@ -1676,13 +2057,14 @@ class Compiler
1676
  if (\is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) {
1677
  $p = $this->compileValue($p);
1678
 
1679
- // force re-evaluation
1680
- if (strpos($p, '&') !== false || strpos($p, ',') !== false) {
1681
  $this->shouldEvaluate = true;
1682
  }
1683
- } elseif (\is_string($p) && \strlen($p) >= 2 &&
1684
- ($first = $p[0]) && ($first === '"' || $first === "'") &&
1685
- substr($p, -1) === $first
 
1686
  ) {
1687
  $p = substr($p, 1, -1);
1688
  }
@@ -1694,14 +2076,44 @@ class Compiler
1694
  /**
1695
  * Collapse selectors
1696
  *
1697
- * @param array $selectors
1698
- * @param boolean $selectorFormat
1699
- * if false return a collapsed string
1700
- * if true return an array description of a structured selector
1701
  *
1702
  * @return string
1703
  */
1704
- protected function collapseSelectors($selectors, $selectorFormat = false)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1705
  {
1706
  $parts = [];
1707
 
@@ -1719,7 +2131,7 @@ class Compiler
1719
  }
1720
  );
1721
 
1722
- if ($selectorFormat && $this->isImmediateRelationshipCombinator($compound)) {
1723
  if (\count($output)) {
1724
  $output[\count($output) - 1] .= ' ' . $compound;
1725
  } else {
@@ -1735,43 +2147,36 @@ class Compiler
1735
  }
1736
  }
1737
 
1738
- if ($selectorFormat) {
1739
- foreach ($output as &$o) {
1740
- $o = [Type::T_STRING, '', [$o]];
1741
- }
1742
-
1743
- $output = [Type::T_LIST, ' ', $output];
1744
- } else {
1745
- $output = implode(' ', $output);
1746
  }
1747
 
1748
- $parts[] = $output;
1749
- }
1750
-
1751
- if ($selectorFormat) {
1752
- $parts = [Type::T_LIST, ',', $parts];
1753
- } else {
1754
- $parts = implode(', ', $parts);
1755
  }
1756
 
1757
- return $parts;
1758
  }
1759
 
1760
  /**
1761
  * Parse down the selector and revert [self] to "&" before a reparsing
1762
  *
1763
- * @param array $selectors
 
1764
  *
1765
  * @return array
1766
  */
1767
- protected function revertSelfSelector($selectors)
1768
  {
1769
  foreach ($selectors as &$part) {
1770
  if (\is_array($part)) {
1771
  if ($part === [Type::T_SELF]) {
1772
- $part = '&';
 
 
 
 
1773
  } else {
1774
- $part = $this->revertSelfSelector($part);
1775
  }
1776
  }
1777
  }
@@ -1791,7 +2196,8 @@ class Compiler
1791
  $joined = [];
1792
 
1793
  foreach ($single as $part) {
1794
- if (empty($joined) ||
 
1795
  ! \is_string($part) ||
1796
  preg_match('/[\[.:#%]/', $part)
1797
  ) {
@@ -1864,7 +2270,7 @@ class Compiler
1864
  *
1865
  * @param array $selector
1866
  *
1867
- * @return boolean
1868
  */
1869
  protected function hasSelectorPlaceholder($selector)
1870
  {
@@ -1883,6 +2289,11 @@ class Compiler
1883
  return false;
1884
  }
1885
 
 
 
 
 
 
1886
  protected function pushCallStack($name = '')
1887
  {
1888
  $this->callStack[] = [
@@ -1896,12 +2307,15 @@ class Compiler
1896
  if (\count($this->callStack) > 25000) {
1897
  // not displayed but you can var_dump it to deep debug
1898
  $msg = $this->callStackMessage(true, 100);
1899
- $msg = "Infinite calling loop";
1900
 
1901
- $this->throwError($msg);
1902
  }
1903
  }
1904
 
 
 
 
1905
  protected function popCallStack()
1906
  {
1907
  array_pop($this->callStack);
@@ -1914,7 +2328,7 @@ class Compiler
1914
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1915
  * @param string $traceName
1916
  *
1917
- * @return array|null
1918
  */
1919
  protected function compileChildren($stms, OutputBlock $out, $traceName = '')
1920
  {
@@ -1936,13 +2350,15 @@ class Compiler
1936
  }
1937
 
1938
  /**
1939
- * Compile children and throw exception if unexpected @return
1940
  *
1941
  * @param array $stms
1942
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1943
  * @param \ScssPhp\ScssPhp\Block $selfParent
1944
  * @param string $traceName
1945
  *
 
 
1946
  * @throws \Exception
1947
  */
1948
  protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null, $traceName = '')
@@ -1954,7 +2370,7 @@ class Compiler
1954
  $stm[1]->selfParent = $selfParent;
1955
  $ret = $this->compileChild($stm, $out);
1956
  $stm[1]->selfParent = null;
1957
- } elseif ($selfParent && \in_array($stm[0], [TYPE::T_INCLUDE, TYPE::T_EXTEND])) {
1958
  $stm['selfParent'] = $selfParent;
1959
  $ret = $this->compileChild($stm, $out);
1960
  unset($stm['selfParent']);
@@ -1963,10 +2379,7 @@ class Compiler
1963
  }
1964
 
1965
  if (isset($ret)) {
1966
- $this->throwError('@return may only be used within a function');
1967
- $this->popCallStack();
1968
-
1969
- return;
1970
  }
1971
  }
1972
 
@@ -1975,7 +2388,7 @@ class Compiler
1975
 
1976
 
1977
  /**
1978
- * evaluate media query : compile internal value keeping the structure inchanged
1979
  *
1980
  * @param array $queryList
1981
  *
@@ -1996,7 +2409,8 @@ class Compiler
1996
 
1997
  // the parser had no mean to know if media type or expression if it was an interpolation
1998
  // so you need to reparse if the T_MEDIA_TYPE looks like anything else a media type
1999
- if ($q[0] == Type::T_MEDIA_TYPE &&
 
2000
  (strpos($value, '(') !== false ||
2001
  strpos($value, ')') !== false ||
2002
  strpos($value, ':') !== false ||
@@ -2017,7 +2431,7 @@ class Compiler
2017
  $queryString = $this->compileMediaQuery([$queryList[$kql]]);
2018
  $queryString = reset($queryString);
2019
 
2020
- if (strpos($queryString, '@media ') === 0) {
2021
  $queryString = substr($queryString, 7);
2022
  $queries = [];
2023
 
@@ -2044,14 +2458,14 @@ class Compiler
2044
  *
2045
  * @param array $queryList
2046
  *
2047
- * @return array
2048
  */
2049
  protected function compileMediaQuery($queryList)
2050
  {
2051
  $start = '@media ';
2052
  $default = trim($start);
2053
  $out = [];
2054
- $current = "";
2055
 
2056
  foreach ($queryList as $query) {
2057
  $type = null;
@@ -2090,7 +2504,7 @@ class Compiler
2090
  $out[] = $start . $current;
2091
  }
2092
 
2093
- $current = "";
2094
  $type = null;
2095
  $parts = [];
2096
  }
@@ -2265,7 +2679,7 @@ class Compiler
2265
  }
2266
 
2267
  // t1 == t2, neither m1 nor m2 are "not"
2268
- return [empty($m1)? $m2 : $m1, $t1];
2269
  }
2270
 
2271
  /**
@@ -2273,25 +2687,27 @@ class Compiler
2273
  *
2274
  * @param array $rawPath
2275
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2276
- * @param boolean $once
2277
  *
2278
- * @return boolean
2279
  */
2280
  protected function compileImport($rawPath, OutputBlock $out, $once = false)
2281
  {
2282
  if ($rawPath[0] === Type::T_STRING) {
2283
  $path = $this->compileStringContent($rawPath);
2284
 
2285
- if ($path = $this->findImport($path)) {
2286
- if (! $once || ! \in_array($path, $this->importedFiles)) {
2287
- $this->importFile($path, $out);
2288
- $this->importedFiles[] = $path;
 
 
2289
  }
2290
 
2291
  return true;
2292
  }
2293
 
2294
- $this->appendRootDirective('@import ' . $this->compileValue($rawPath). ';', $out);
2295
 
2296
  return false;
2297
  }
@@ -2304,7 +2720,7 @@ class Compiler
2304
 
2305
  foreach ($rawPath[2] as $path) {
2306
  if ($path[0] !== Type::T_STRING) {
2307
- $this->appendRootDirective('@import ' . $this->compileValue($rawPath) . ';', $out);
2308
 
2309
  return false;
2310
  }
@@ -2317,19 +2733,68 @@ class Compiler
2317
  return true;
2318
  }
2319
 
2320
- $this->appendRootDirective('@import ' . $this->compileValue($rawPath) . ';', $out);
2321
 
2322
  return false;
2323
  }
2324
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2325
 
2326
  /**
2327
  * Append a root directive like @import or @charset as near as the possible from the source code
2328
  * (keeping before comments, @import and @charset coming before in the source code)
2329
  *
2330
- * @param string $line
2331
- * @param @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2332
- * @param array $allowed
 
 
2333
  */
2334
  protected function appendRootDirective($line, $out, $allowed = [Type::T_COMMENT])
2335
  {
@@ -2359,7 +2824,7 @@ class Compiler
2359
  // insert the directive as a comment
2360
  $child = $this->makeOutputBlock(Type::T_COMMENT);
2361
  $child->lines[] = $line;
2362
- $child->sourceName = $this->sourceNames[$this->sourceIndex];
2363
  $child->sourceLine = $this->sourceLine;
2364
  $child->sourceColumn = $this->sourceColumn;
2365
 
@@ -2377,25 +2842,20 @@ class Compiler
2377
  *
2378
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2379
  * @param string $type
2380
- * @param string|mixed $line
 
 
2381
  */
2382
  protected function appendOutputLine(OutputBlock $out, $type, $line)
2383
  {
2384
  $outWrite = &$out;
2385
 
2386
- if ($type === Type::T_COMMENT) {
2387
- $parent = $out->parent;
2388
-
2389
- if (end($parent->children) !== $out) {
2390
- $outWrite = &$parent->children[\count($parent->children) - 1];
2391
- }
2392
- }
2393
-
2394
  // check if it's a flat output or not
2395
  if (\count($out->children)) {
2396
  $lastChild = &$out->children[\count($out->children) - 1];
2397
 
2398
- if ($lastChild->depth === $out->depth &&
 
2399
  \is_null($lastChild->selectors) &&
2400
  ! \count($lastChild->children)
2401
  ) {
@@ -2419,7 +2879,7 @@ class Compiler
2419
  * @param array $child
2420
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2421
  *
2422
- * @return array
2423
  */
2424
  protected function compileChild($child, OutputBlock $out)
2425
  {
@@ -2427,18 +2887,19 @@ class Compiler
2427
  $this->sourceIndex = isset($child[Parser::SOURCE_INDEX]) ? $child[Parser::SOURCE_INDEX] : null;
2428
  $this->sourceLine = isset($child[Parser::SOURCE_LINE]) ? $child[Parser::SOURCE_LINE] : -1;
2429
  $this->sourceColumn = isset($child[Parser::SOURCE_COLUMN]) ? $child[Parser::SOURCE_COLUMN] : -1;
2430
- } elseif (\is_array($child) && isset($child[1]->sourceLine)) {
2431
  $this->sourceIndex = $child[1]->sourceIndex;
2432
  $this->sourceLine = $child[1]->sourceLine;
2433
  $this->sourceColumn = $child[1]->sourceColumn;
2434
  } elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) {
2435
  $this->sourceLine = $out->sourceLine;
2436
- $this->sourceIndex = array_search($out->sourceName, $this->sourceNames);
2437
  $this->sourceColumn = $out->sourceColumn;
2438
 
2439
- if ($this->sourceIndex === false) {
2440
- $this->sourceIndex = null;
2441
  }
 
2442
  }
2443
 
2444
  switch ($child[0]) {
@@ -2471,10 +2932,6 @@ class Compiler
2471
  break;
2472
 
2473
  case Type::T_CHARSET:
2474
- if (! $this->charsetSeen) {
2475
- $this->charsetSeen = true;
2476
- $this->appendRootDirective('@charset ' . $this->compileValue($child[1]) . ';', $out);
2477
- }
2478
  break;
2479
 
2480
  case Type::T_CUSTOM_PROPERTY:
@@ -2547,7 +3004,7 @@ class Compiler
2547
  break;
2548
  }
2549
 
2550
- if ($compiledName === 'font' and $value[0] === Type::T_LIST && $value[1]==',') {
2551
  // this is the case if more than one font is given: example: "font: 400 1em/1.3 arial,helvetica"
2552
  // we need to handle the first list element
2553
  $shorthandValue=&$value[2][0];
@@ -2563,7 +3020,7 @@ class Compiler
2563
  $divider = $this->reduce($divider, true);
2564
  }
2565
 
2566
- if (\intval($divider->dimension) and ! \count($divider->units)) {
2567
  $revert = false;
2568
  }
2569
  }
@@ -2576,9 +3033,10 @@ class Compiler
2576
  if ($item[0] === Type::T_EXPRESSION && $item[1] === '/') {
2577
  if ($maxShorthandDividers > 0) {
2578
  $revert = true;
 
2579
  // if the list of values is too long, this has to be a shorthand,
2580
  // otherwise it could be a real division
2581
- if (\is_null($maxListElements) or \count($shorthandValue[2]) <= $maxListElements) {
2582
  if ($shorthandDividerNeedsUnit) {
2583
  $divider = $item[3];
2584
 
@@ -2586,7 +3044,7 @@ class Compiler
2586
  $divider = $this->reduce($divider, true);
2587
  }
2588
 
2589
- if (\intval($divider->dimension) and ! \count($divider->units)) {
2590
  $revert = false;
2591
  }
2592
  }
@@ -2637,6 +3095,7 @@ class Compiler
2637
  case Type::T_MIXIN:
2638
  case Type::T_FUNCTION:
2639
  list(, $block) = $child;
 
2640
  // the block need to be able to go up to it's parent env to resolve vars
2641
  $block->parentEnv = $this->getStoreEnv();
2642
  $this->set(static::$namespaces[$block->type] . $block->name, $block, true);
@@ -2644,16 +3103,42 @@ class Compiler
2644
 
2645
  case Type::T_EXTEND:
2646
  foreach ($child[1] as $sel) {
 
 
 
 
 
 
2647
  $results = $this->evalSelectors([$sel]);
2648
 
2649
  foreach ($results as $result) {
 
 
 
 
2650
  // only use the first one
2651
- $result = current($result);
2652
  $selectors = $out->selectors;
2653
 
2654
  if (! $selectors && isset($child['selfParent'])) {
2655
  $selectors = $this->multiplySelectors($this->env, $child['selfParent']);
2656
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2657
 
2658
  $this->pushExtends($result, $selectors, $child);
2659
  }
@@ -2662,14 +3147,16 @@ class Compiler
2662
 
2663
  case Type::T_IF:
2664
  list(, $if) = $child;
 
2665
 
2666
  if ($this->isTruthy($this->reduce($if->cond, true))) {
2667
  return $this->compileChildren($if->children, $out);
2668
  }
2669
 
2670
  foreach ($if->cases as $case) {
2671
- if ($case->type === Type::T_ELSE ||
2672
- $case->type === Type::T_ELSEIF && $this->isTruthy($this->reduce($case->cond))
 
2673
  ) {
2674
  return $this->compileChildren($case->children, $out);
2675
  }
@@ -2678,6 +3165,7 @@ class Compiler
2678
 
2679
  case Type::T_EACH:
2680
  list(, $each) = $child;
 
2681
 
2682
  $list = $this->coerceList($this->reduce($each->list), ',', true);
2683
 
@@ -2697,17 +3185,11 @@ class Compiler
2697
  $ret = $this->compileChildren($each->children, $out);
2698
 
2699
  if ($ret) {
2700
- if ($ret[0] !== Type::T_CONTROL) {
2701
- $store = $this->env->store;
2702
- $this->popEnv();
2703
- $this->backPropagateEnv($store, $each->vars);
2704
-
2705
- return $ret;
2706
- }
2707
 
2708
- if ($ret[1]) {
2709
- break;
2710
- }
2711
  }
2712
  }
2713
  $store = $this->env->store;
@@ -2718,65 +3200,54 @@ class Compiler
2718
 
2719
  case Type::T_WHILE:
2720
  list(, $while) = $child;
 
2721
 
2722
  while ($this->isTruthy($this->reduce($while->cond, true))) {
2723
  $ret = $this->compileChildren($while->children, $out);
2724
 
2725
  if ($ret) {
2726
- if ($ret[0] !== Type::T_CONTROL) {
2727
- return $ret;
2728
- }
2729
-
2730
- if ($ret[1]) {
2731
- break;
2732
- }
2733
  }
2734
  }
2735
  break;
2736
 
2737
  case Type::T_FOR:
2738
  list(, $for) = $child;
 
2739
 
2740
- $start = $this->reduce($for->start, true);
2741
- $end = $this->reduce($for->end, true);
2742
 
2743
- if (! ($start[2] == $end[2] || $end->unitless())) {
2744
- $this->throwError('Incompatible units: "%s" and "%s".', $start->unitStr(), $end->unitStr());
2745
 
2746
- break;
2747
- }
2748
 
2749
- $unit = $start[2];
2750
- $start = $start[1];
2751
- $end = $end[1];
2752
 
2753
  $d = $start < $end ? 1 : -1;
2754
 
2755
  $this->pushEnv();
2756
 
2757
  for (;;) {
2758
- if ((! $for->until && $start - $d == $end) ||
 
2759
  ($for->until && $start == $end)
2760
  ) {
2761
  break;
2762
  }
2763
 
2764
- $this->set($for->var, new Node\Number($start, $unit));
2765
  $start += $d;
2766
 
2767
  $ret = $this->compileChildren($for->children, $out);
2768
 
2769
  if ($ret) {
2770
- if ($ret[0] !== Type::T_CONTROL) {
2771
- $store = $this->env->store;
2772
- $this->popEnv();
2773
- $this->backPropagateEnv($store, [$for->var]);
2774
- return $ret;
2775
- }
2776
 
2777
- if ($ret[1]) {
2778
- break;
2779
- }
2780
  }
2781
  }
2782
 
@@ -2786,12 +3257,6 @@ class Compiler
2786
 
2787
  break;
2788
 
2789
- case Type::T_BREAK:
2790
- return [Type::T_CONTROL, true];
2791
-
2792
- case Type::T_CONTINUE:
2793
- return [Type::T_CONTROL, false];
2794
-
2795
  case Type::T_RETURN:
2796
  return $this->reduce($child[1], true);
2797
 
@@ -2806,10 +3271,11 @@ class Compiler
2806
  $mixin = $this->get(static::$namespaces['mixin'] . $name, false);
2807
 
2808
  if (! $mixin) {
2809
- $this->throwError("Undefined mixin $name");
2810
- break;
2811
  }
2812
 
 
 
2813
  $callingScope = $this->getStoreEnv();
2814
 
2815
  // push scope, apply args
@@ -2820,7 +3286,7 @@ class Compiler
2820
  // and assign this fake parent to childs
2821
  $selfParent = null;
2822
 
2823
- if (isset($child['selfParent']) && isset($child['selfParent']->selectors)) {
2824
  $selfParent = $child['selfParent'];
2825
  } else {
2826
  $parentSelectors = $this->multiplySelectors($this->env);
@@ -2830,7 +3296,7 @@ class Compiler
2830
  $parent->selectors = $parentSelectors;
2831
 
2832
  foreach ($mixin->children as $k => $child) {
2833
- if (isset($child[1]) && \is_object($child[1]) && $child[1] instanceof Block) {
2834
  $mixin->children[$k][1]->parent = $parent;
2835
  }
2836
  }
@@ -2864,10 +3330,10 @@ class Compiler
2864
  if (! empty($mixin->parentEnv)) {
2865
  $this->env->declarationScopeParent = $mixin->parentEnv;
2866
  } else {
2867
- $this->throwError("@mixin $name() without parentEnv");
2868
  }
2869
 
2870
- $this->compileChildrenNoReturn($mixin->children, $out, $selfParent, $this->env->marker . " " . $name);
2871
 
2872
  $this->popEnv();
2873
  break;
@@ -2907,54 +3373,58 @@ class Compiler
2907
  case Type::T_DEBUG:
2908
  list(, $value) = $child;
2909
 
2910
- $fname = $this->sourceNames[$this->sourceIndex];
2911
  $line = $this->sourceLine;
2912
- $value = $this->compileValue($this->reduce($value, true));
2913
 
2914
- fwrite($this->stderr, "File $fname on line $line DEBUG: $value\n");
2915
  break;
2916
 
2917
  case Type::T_WARN:
2918
  list(, $value) = $child;
2919
 
2920
- $fname = $this->sourceNames[$this->sourceIndex];
2921
  $line = $this->sourceLine;
2922
- $value = $this->compileValue($this->reduce($value, true));
2923
 
2924
- fwrite($this->stderr, "File $fname on line $line WARN: $value\n");
2925
  break;
2926
 
2927
  case Type::T_ERROR:
2928
  list(, $value) = $child;
2929
 
2930
- $fname = $this->sourceNames[$this->sourceIndex];
2931
  $line = $this->sourceLine;
2932
  $value = $this->compileValue($this->reduce($value, true));
2933
 
2934
- $this->throwError("File $fname on line $line ERROR: $value\n");
2935
- break;
2936
-
2937
- case Type::T_CONTROL:
2938
- $this->throwError('@break/@continue not permitted in this scope');
2939
- break;
2940
 
2941
  default:
2942
- $this->throwError("unknown child type: $child[0]");
2943
  }
 
 
2944
  }
2945
 
2946
  /**
2947
  * Reduce expression to string
2948
  *
2949
  * @param array $exp
 
2950
  *
2951
  * @return array
2952
  */
2953
- protected function expToString($exp)
2954
  {
2955
- list(, $op, $left, $right, /* $inParens */, $whiteLeft, $whiteRight) = $exp;
 
 
2956
 
2957
- $content = [$this->reduce($left)];
 
 
 
 
2958
 
2959
  if ($whiteLeft) {
2960
  $content[] = ' ';
@@ -2968,17 +3438,21 @@ class Compiler
2968
 
2969
  $content[] = $this->reduce($right);
2970
 
 
 
 
 
2971
  return [Type::T_STRING, '', $content];
2972
  }
2973
 
2974
  /**
2975
  * Is truthy?
2976
  *
2977
- * @param array $value
2978
  *
2979
- * @return boolean
2980
  */
2981
- protected function isTruthy($value)
2982
  {
2983
  return $value !== static::$false && $value !== static::$null;
2984
  }
@@ -2988,7 +3462,7 @@ class Compiler
2988
  *
2989
  * @param string $value
2990
  *
2991
- * @return boolean
2992
  */
2993
  protected function isImmediateRelationshipCombinator($value)
2994
  {
@@ -3000,7 +3474,7 @@ class Compiler
3000
  *
3001
  * @param array $value
3002
  *
3003
- * @return boolean
3004
  */
3005
  protected function shouldEval($value)
3006
  {
@@ -3022,15 +3496,15 @@ class Compiler
3022
  /**
3023
  * Reduce value
3024
  *
3025
- * @param array $value
3026
- * @param boolean $inExp
3027
  *
3028
- * @return null|string|array|\ScssPhp\ScssPhp\Node\Number
3029
  */
3030
  protected function reduce($value, $inExp = false)
3031
  {
3032
- if (\is_null($value)) {
3033
- return null;
3034
  }
3035
 
3036
  switch ($value[0]) {
@@ -3047,8 +3521,9 @@ class Compiler
3047
  }
3048
 
3049
  // special case: looks like css shorthand
3050
- if ($opName == 'div' && ! $inParens && ! $inExp && isset($right[2]) &&
3051
- (($right[0] !== Type::T_NUMBER && $right[2] != '') ||
 
3052
  ($right[0] === Type::T_NUMBER && ! $right->unitless()))
3053
  ) {
3054
  return $this->expToString($value);
@@ -3063,76 +3538,24 @@ class Compiler
3063
  $ucLType = ucfirst($ltype);
3064
  $ucRType = ucfirst($rtype);
3065
 
 
 
3066
  // this tries:
3067
  // 1. op[op name][left type][right type]
3068
- // 2. op[left type][right type] (passing the op as first arg
3069
  // 3. op[op name]
3070
- $fn = "op${ucOpName}${ucLType}${ucRType}";
3071
-
3072
- if (\is_callable([$this, $fn]) ||
3073
- (($fn = "op${ucLType}${ucRType}") &&
3074
- \is_callable([$this, $fn]) &&
3075
- $passOp = true) ||
3076
- (($fn = "op${ucOpName}") &&
3077
- \is_callable([$this, $fn]) &&
3078
- $genOp = true)
3079
- ) {
3080
- $coerceUnit = false;
3081
-
3082
- if (! isset($genOp) &&
3083
- $left[0] === Type::T_NUMBER && $right[0] === Type::T_NUMBER
3084
- ) {
3085
- $coerceUnit = true;
3086
-
3087
- switch ($opName) {
3088
- case 'mul':
3089
- $targetUnit = $left[2];
3090
-
3091
- foreach ($right[2] as $unit => $exp) {
3092
- $targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) + $exp;
3093
- }
3094
- break;
3095
-
3096
- case 'div':
3097
- $targetUnit = $left[2];
3098
-
3099
- foreach ($right[2] as $unit => $exp) {
3100
- $targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) - $exp;
3101
- }
3102
- break;
3103
-
3104
- case 'mod':
3105
- $targetUnit = $left[2];
3106
- break;
3107
-
3108
- default:
3109
- $targetUnit = $left->unitless() ? $right[2] : $left[2];
3110
- }
3111
-
3112
- $baseUnitLeft = $left->isNormalizable();
3113
- $baseUnitRight = $right->isNormalizable();
3114
-
3115
- if ($baseUnitLeft && $baseUnitRight && $baseUnitLeft === $baseUnitRight) {
3116
- $left = $left->normalize();
3117
- $right = $right->normalize();
3118
- }
3119
- }
3120
-
3121
- $shouldEval = $inParens || $inExp;
3122
-
3123
- if (isset($passOp)) {
3124
- $out = $this->$fn($op, $left, $right, $shouldEval);
3125
- } else {
3126
- $out = $this->$fn($left, $right, $shouldEval);
3127
- }
3128
-
3129
- if (isset($out)) {
3130
- if ($coerceUnit && $out[0] === Type::T_NUMBER) {
3131
- $out = $out->coerce($targetUnit);
3132
- }
3133
 
3134
- return $out;
3135
- }
3136
  }
3137
 
3138
  return $this->expToString($value);
@@ -3143,13 +3566,13 @@ class Compiler
3143
  $inExp = $inExp || $this->shouldEval($exp);
3144
  $exp = $this->reduce($exp);
3145
 
3146
- if ($exp[0] === Type::T_NUMBER) {
3147
  switch ($op) {
3148
  case '+':
3149
- return new Node\Number($exp[1], $exp[2]);
3150
 
3151
  case '-':
3152
- return new Node\Number(-$exp[1], $exp[2]);
3153
  }
3154
  }
3155
 
@@ -3174,6 +3597,14 @@ class Compiler
3174
  foreach ($value[2] as &$item) {
3175
  $item = $this->reduce($item);
3176
  }
 
 
 
 
 
 
 
 
3177
 
3178
  return $value;
3179
 
@@ -3190,7 +3621,7 @@ class Compiler
3190
 
3191
  case Type::T_STRING:
3192
  foreach ($value[2] as &$item) {
3193
- if (\is_array($item) || $item instanceof \ArrayAccess) {
3194
  $item = $this->reduce($item);
3195
  }
3196
  }
@@ -3201,7 +3632,7 @@ class Compiler
3201
  $value[1] = $this->reduce($value[1]);
3202
 
3203
  if ($inExp) {
3204
- return $value[1];
3205
  }
3206
 
3207
  return $value;
@@ -3210,8 +3641,9 @@ class Compiler
3210
  return $this->fncall($value[1], $value[2]);
3211
 
3212
  case Type::T_SELF:
3213
- $selfSelector = $this->multiplySelectors($this->env);
3214
- $selfSelector = $this->collapseSelectors($selfSelector, true);
 
3215
 
3216
  return $selfSelector;
3217
 
@@ -3223,161 +3655,426 @@ class Compiler
3223
  /**
3224
  * Function caller
3225
  *
3226
- * @param string $name
3227
- * @param array $argValues
3228
  *
3229
- * @return array|null
3230
  */
3231
- protected function fncall($name, $argValues)
3232
  {
3233
- // SCSS @function
3234
- if ($this->callScssFunction($name, $argValues, $returnValue)) {
3235
- return $returnValue;
3236
- }
3237
 
3238
- // native PHP functions
3239
- if ($this->callNativeFunction($name, $argValues, $returnValue)) {
3240
- return $returnValue;
 
3241
  }
3242
 
3243
- // for CSS functions, simply flatten the arguments into a list
3244
- $listArgs = [];
 
 
3245
 
3246
- foreach ((array) $argValues as $arg) {
3247
- if (empty($arg[0])) {
3248
- $listArgs[] = $this->reduce($arg[1]);
 
3249
  }
3250
- }
3251
 
3252
- return [Type::T_FUNCTION, $name, [Type::T_LIST, ',', $listArgs]];
3253
- }
3254
 
3255
- /**
3256
- * Normalize name
3257
- *
3258
- * @param string $name
3259
- *
3260
- * @return string
3261
- */
3262
- protected function normalizeName($name)
3263
- {
3264
- return str_replace('-', '_', $name);
3265
- }
3266
 
3267
- /**
3268
- * Normalize value
3269
- *
3270
- * @param array $value
3271
- *
3272
- * @return array
3273
- */
3274
- public function normalizeValue($value)
3275
- {
3276
- $value = $this->coerceForExpression($this->reduce($value));
3277
 
3278
- switch ($value[0]) {
3279
- case Type::T_LIST:
3280
- $value = $this->extractInterpolation($value);
 
3281
 
3282
- if ($value[0] !== Type::T_LIST) {
3283
- return [Type::T_KEYWORD, $this->compileValue($value)];
3284
- }
 
3285
 
3286
- foreach ($value[2] as $key => $item) {
3287
- $value[2][$key] = $this->normalizeValue($item);
 
 
 
 
 
 
 
 
3288
  }
 
3289
 
3290
- if (! empty($value['enclosing'])) {
3291
- unset($value['enclosing']);
3292
  }
3293
 
3294
- return $value;
3295
-
3296
- case Type::T_STRING:
3297
- return [$value[0], '"', [$this->compileStringContent($value)]];
3298
-
3299
- case Type::T_NUMBER:
3300
- return $value->normalize();
3301
-
3302
- case Type::T_INTERPOLATE:
3303
- return [Type::T_KEYWORD, $this->compileValue($value)];
3304
 
3305
  default:
3306
- return $value;
3307
  }
3308
  }
3309
 
3310
  /**
3311
- * Add numbers
3312
- *
3313
- * @param array $left
3314
- * @param array $right
3315
  *
3316
- * @return \ScssPhp\ScssPhp\Node\Number
3317
  */
3318
- protected function opAddNumberNumber($left, $right)
3319
  {
3320
- return new Node\Number($left[1] + $right[1], $left[2]);
3321
- }
 
3322
 
3323
- /**
3324
- * Multiply numbers
3325
- *
3326
- * @param array $left
3327
- * @param array $right
3328
- *
3329
- * @return \ScssPhp\ScssPhp\Node\Number
3330
- */
3331
- protected function opMulNumberNumber($left, $right)
3332
- {
3333
- return new Node\Number($left[1] * $right[1], $left[2]);
3334
- }
3335
 
3336
- /**
3337
- * Subtract numbers
3338
- *
3339
- * @param array $left
3340
- * @param array $right
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3341
  *
3342
- * @return \ScssPhp\ScssPhp\Node\Number
3343
  */
3344
- protected function opSubNumberNumber($left, $right)
3345
  {
3346
- return new Node\Number($left[1] - $right[1], $left[2]);
3347
  }
3348
 
3349
  /**
3350
- * Divide numbers
3351
  *
3352
- * @param array $left
3353
- * @param array $right
 
3354
  *
3355
- * @return array|\ScssPhp\ScssPhp\Node\Number
3356
  */
3357
- protected function opDivNumberNumber($left, $right)
3358
  {
3359
- if ($right[1] == 0) {
3360
- return ($left[1] == 0) ? static::$NaN : static::$Infinity;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3361
  }
 
3362
 
3363
- return new Node\Number($left[1] / $right[1], $left[2]);
 
 
 
 
 
 
 
 
 
 
3364
  }
3365
 
3366
  /**
3367
- * Mod numbers
3368
  *
3369
- * @param array $left
3370
- * @param array $right
3371
  *
3372
- * @return \ScssPhp\ScssPhp\Node\Number
3373
  */
3374
- protected function opModNumberNumber($left, $right)
3375
  {
3376
- if ($right[1] == 0) {
3377
- return static::$NaN;
3378
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3379
 
3380
- return new Node\Number($left[1] % $right[1], $left[2]);
 
 
 
 
 
 
 
 
 
 
3381
  }
3382
 
3383
  /**
@@ -3416,11 +4113,11 @@ class Compiler
3416
  /**
3417
  * Boolean and
3418
  *
3419
- * @param array $left
3420
- * @param array $right
3421
- * @param boolean $shouldEval
3422
  *
3423
- * @return array|null
3424
  */
3425
  protected function opAnd($left, $right, $shouldEval)
3426
  {
@@ -3444,11 +4141,11 @@ class Compiler
3444
  /**
3445
  * Boolean or
3446
  *
3447
- * @param array $left
3448
- * @param array $right
3449
- * @param boolean $shouldEval
3450
  *
3451
- * @return array|null
3452
  */
3453
  protected function opOr($left, $right, $shouldEval)
3454
  {
@@ -3480,6 +4177,15 @@ class Compiler
3480
  */
3481
  protected function opColorColor($op, $left, $right)
3482
  {
 
 
 
 
 
 
 
 
 
3483
  $out = [Type::T_COLOR];
3484
 
3485
  foreach ([1, 2, 3] as $i) {
@@ -3500,13 +4206,16 @@ class Compiler
3500
  break;
3501
 
3502
  case '%':
 
 
 
 
3503
  $out[] = $lval % $rval;
3504
  break;
3505
 
3506
  case '/':
3507
  if ($rval == 0) {
3508
- $this->throwError("color: Can't divide by zero");
3509
- break 2;
3510
  }
3511
 
3512
  $out[] = (int) ($lval / $rval);
@@ -3519,8 +4228,7 @@ class Compiler
3519
  return $this->opNeq($left, $right);
3520
 
3521
  default:
3522
- $this->throwError("color: unknown op $op");
3523
- break 2;
3524
  }
3525
  }
3526
 
@@ -3538,13 +4246,21 @@ class Compiler
3538
  *
3539
  * @param string $op
3540
  * @param array $left
3541
- * @param array $right
3542
  *
3543
  * @return array
3544
  */
3545
- protected function opColorNumber($op, $left, $right)
3546
  {
3547
- $value = $right[1];
 
 
 
 
 
 
 
 
3548
 
3549
  return $this->opColorColor(
3550
  $op,
@@ -3557,14 +4273,22 @@ class Compiler
3557
  * Compare number and color
3558
  *
3559
  * @param string $op
3560
- * @param array $left
3561
  * @param array $right
3562
  *
3563
  * @return array
3564
  */
3565
- protected function opNumberColor($op, $left, $right)
3566
  {
3567
- $value = $left[1];
 
 
 
 
 
 
 
 
3568
 
3569
  return $this->opColorColor(
3570
  $op,
@@ -3576,8 +4300,8 @@ class Compiler
3576
  /**
3577
  * Compare number1 == number2
3578
  *
3579
- * @param array $left
3580
- * @param array $right
3581
  *
3582
  * @return array
3583
  */
@@ -3597,8 +4321,8 @@ class Compiler
3597
  /**
3598
  * Compare number1 != number2
3599
  *
3600
- * @param array $left
3601
- * @param array $right
3602
  *
3603
  * @return array
3604
  */
@@ -3616,70 +4340,81 @@ class Compiler
3616
  }
3617
 
3618
  /**
3619
- * Compare number1 >= number2
3620
  *
3621
- * @param array $left
3622
- * @param array $right
3623
  *
3624
  * @return array
3625
  */
3626
- protected function opGteNumberNumber($left, $right)
3627
  {
3628
- return $this->toBool($left[1] >= $right[1]);
3629
  }
3630
 
3631
  /**
3632
- * Compare number1 > number2
3633
  *
3634
- * @param array $left
3635
- * @param array $right
3636
  *
3637
  * @return array
3638
  */
3639
- protected function opGtNumberNumber($left, $right)
3640
  {
3641
- return $this->toBool($left[1] > $right[1]);
3642
  }
3643
 
3644
  /**
3645
- * Compare number1 <= number2
3646
  *
3647
- * @param array $left
3648
- * @param array $right
3649
  *
3650
  * @return array
3651
  */
3652
- protected function opLteNumberNumber($left, $right)
3653
  {
3654
- return $this->toBool($left[1] <= $right[1]);
3655
  }
3656
 
3657
  /**
3658
- * Compare number1 < number2
3659
  *
3660
- * @param array $left
3661
- * @param array $right
3662
  *
3663
  * @return array
3664
  */
3665
- protected function opLtNumberNumber($left, $right)
3666
  {
3667
- return $this->toBool($left[1] < $right[1]);
3668
  }
3669
 
3670
  /**
3671
- * Three-way comparison, aka spaceship operator
3672
  *
3673
- * @param array $left
3674
- * @param array $right
3675
  *
3676
- * @return \ScssPhp\ScssPhp\Node\Number
3677
  */
3678
- protected function opCmpNumberNumber($left, $right)
3679
  {
3680
- $n = $left[1] - $right[1];
 
3681
 
3682
- return new Node\Number($n ? $n / abs($n) : 0, '');
 
 
 
 
 
 
 
 
 
 
3683
  }
3684
 
3685
  /**
@@ -3687,7 +4422,7 @@ class Compiler
3687
  *
3688
  * @api
3689
  *
3690
- * @param mixed $thing
3691
  *
3692
  * @return array
3693
  */
@@ -3696,6 +4431,53 @@ class Compiler
3696
  return $thing ? static::$true : static::$false;
3697
  }
3698
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3699
  /**
3700
  * Compiles a primitive value into a CSS property value.
3701
  *
@@ -3709,17 +4491,22 @@ class Compiler
3709
  *
3710
  * @api
3711
  *
3712
- * @param array $value
 
3713
  *
3714
- * @return string|array
3715
  */
3716
- public function compileValue($value)
3717
  {
3718
  $value = $this->reduce($value);
3719
 
 
 
 
 
3720
  switch ($value[0]) {
3721
  case Type::T_KEYWORD:
3722
- return $value[1];
3723
 
3724
  case Type::T_COLOR:
3725
  // [1] - red component (either number for a %)
@@ -3743,7 +4530,7 @@ class Compiler
3743
  }
3744
 
3745
  if (is_numeric($alpha)) {
3746
- $a = new Node\Number($alpha, '');
3747
  } else {
3748
  $a = $alpha;
3749
  }
@@ -3771,65 +4558,111 @@ class Compiler
3771
 
3772
  return $h;
3773
 
3774
- case Type::T_NUMBER:
3775
- return $value->output($this);
3776
-
3777
  case Type::T_STRING:
3778
- return $value[1] . $this->compileStringContent($value) . $value[1];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3779
 
3780
  case Type::T_FUNCTION:
3781
- $args = ! empty($value[2]) ? $this->compileValue($value[2]) : '';
3782
 
3783
  return "$value[1]($args)";
3784
 
 
 
 
 
 
3785
  case Type::T_LIST:
3786
  $value = $this->extractInterpolation($value);
3787
 
3788
  if ($value[0] !== Type::T_LIST) {
3789
- return $this->compileValue($value);
3790
  }
3791
 
3792
  list(, $delim, $items) = $value;
3793
- $pre = $post = "";
3794
 
3795
  if (! empty($value['enclosing'])) {
3796
  switch ($value['enclosing']) {
3797
  case 'parent':
3798
- //$pre = "(";
3799
- //$post = ")";
3800
  break;
3801
  case 'forced_parent':
3802
- $pre = "(";
3803
- $post = ")";
3804
  break;
3805
  case 'bracket':
3806
  case 'forced_bracket':
3807
- $pre = "[";
3808
- $post = "]";
3809
  break;
3810
  }
3811
  }
3812
 
 
 
3813
  $prefix_value = '';
 
3814
  if ($delim !== ' ') {
3815
  $prefix_value = ' ';
3816
  }
3817
 
3818
  $filtered = [];
3819
 
 
3820
  foreach ($items as $item) {
 
 
 
 
 
 
 
 
 
 
 
 
3821
  if ($item[0] === Type::T_NULL) {
3822
  continue;
3823
  }
 
 
 
 
 
3824
 
3825
- $compiled = $this->compileValue($item);
3826
  if ($prefix_value && \strlen($compiled)) {
3827
  $compiled = $prefix_value . $compiled;
3828
  }
 
3829
  $filtered[] = $compiled;
3830
  }
3831
 
3832
- return $pre . substr(implode("$delim", $filtered), \strlen($prefix_value)) . $post;
3833
 
3834
  case Type::T_MAP:
3835
  $keys = $value[1];
@@ -3837,7 +4670,7 @@ class Compiler
3837
  $filtered = [];
3838
 
3839
  for ($i = 0, $s = \count($keys); $i < $s; $i++) {
3840
- $filtered[$this->compileValue($keys[$i])] = $this->compileValue($values[$i]);
3841
  }
3842
 
3843
  array_walk($filtered, function (&$value, $key) {
@@ -3857,8 +4690,9 @@ class Compiler
3857
  $delim .= ' ';
3858
  }
3859
 
3860
- $left = \count($left[2]) > 0 ?
3861
- $this->compileValue($left) . $delim . $whiteLeft: '';
 
3862
 
3863
  $delim = $right[1];
3864
 
@@ -3867,14 +4701,18 @@ class Compiler
3867
  }
3868
 
3869
  $right = \count($right[2]) > 0 ?
3870
- $whiteRight . $delim . $this->compileValue($right) : '';
3871
 
3872
- return $left . $this->compileValue($interpolate) . $right;
3873
 
3874
  case Type::T_INTERPOLATE:
3875
  // strip quotes if it's a string
3876
  $reduced = $this->reduce($value[1]);
3877
 
 
 
 
 
3878
  switch ($reduced[0]) {
3879
  case Type::T_LIST:
3880
  $reduced = $this->extractInterpolation($reduced);
@@ -3896,14 +4734,12 @@ class Compiler
3896
  continue;
3897
  }
3898
 
3899
- $temp = $this->compileValue([Type::T_KEYWORD, $item]);
3900
-
3901
- if ($temp[0] === Type::T_STRING) {
3902
- $filtered[] = $this->compileStringContent($temp);
3903
- } elseif ($temp[0] === Type::T_KEYWORD) {
3904
- $filtered[] = $temp[1];
3905
  } else {
3906
- $filtered[] = $this->compileValue($temp);
3907
  }
3908
  }
3909
 
@@ -3911,14 +4747,14 @@ class Compiler
3911
  break;
3912
 
3913
  case Type::T_STRING:
3914
- $reduced = [Type::T_KEYWORD, $this->compileStringContent($reduced)];
3915
  break;
3916
 
3917
  case Type::T_NULL:
3918
  $reduced = [Type::T_KEYWORD, ''];
3919
  }
3920
 
3921
- return $this->compileValue($reduced);
3922
 
3923
  case Type::T_NULL:
3924
  return 'null';
@@ -3927,7 +4763,29 @@ class Compiler
3927
  return $this->compileCommentValue($value);
3928
 
3929
  default:
3930
- $this->throwError("unknown value type: ".json_encode($value));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3931
  }
3932
  }
3933
 
@@ -3937,26 +4795,50 @@ class Compiler
3937
  * @param array $list
3938
  *
3939
  * @return string
 
 
3940
  */
3941
  protected function flattenList($list)
3942
  {
 
 
3943
  return $this->compileValue($list);
3944
  }
3945
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3946
  /**
3947
  * Compile string content
3948
  *
3949
  * @param array $string
 
3950
  *
3951
  * @return string
3952
  */
3953
- protected function compileStringContent($string)
3954
  {
3955
  $parts = [];
3956
 
3957
  foreach ($string[2] as $part) {
3958
- if (\is_array($part) || $part instanceof \ArrayAccess) {
3959
- $parts[] = $this->compileValue($part);
3960
  } else {
3961
  $parts[] = $part;
3962
  }
@@ -4020,8 +4902,8 @@ class Compiler
4020
  $prevSelectors = $selectors;
4021
  $selectors = [];
4022
 
4023
- foreach ($prevSelectors as $selector) {
4024
- foreach ($parentSelectors as $parent) {
4025
  if ($selfParentSelectors) {
4026
  foreach ($selfParentSelectors as $selfParent) {
4027
  // if no '&' in the selector, each call will give same result, only add once
@@ -4041,16 +4923,21 @@ class Compiler
4041
 
4042
  $selectors = array_values($selectors);
4043
 
 
 
 
 
 
4044
  return $selectors;
4045
  }
4046
 
4047
  /**
4048
  * Join selectors; looks for & to replace, or append parent before child
4049
  *
4050
- * @param array $parent
4051
- * @param array $child
4052
- * @param boolean $stillHasSelf
4053
- * @param array $selfParentSelectors
4054
 
4055
  * @return array
4056
  */
@@ -4116,7 +5003,8 @@ class Compiler
4116
  */
4117
  protected function multiplyMedia(Environment $env = null, $childQueries = null)
4118
  {
4119
- if (! isset($env) ||
 
4120
  ! empty($env->block->type) && $env->block->type !== Type::T_MEDIA
4121
  ) {
4122
  return $childQueries;
@@ -4127,6 +5015,8 @@ class Compiler
4127
  return $this->multiplyMedia($env->parent, $childQueries);
4128
  }
4129
 
 
 
4130
  $parentQueries = isset($env->block->queryList)
4131
  ? $env->block->queryList
4132
  : [[[Type::T_MEDIA_VALUE, $env->block->value]]];
@@ -4162,9 +5052,11 @@ class Compiler
4162
  /**
4163
  * Convert env linked list to stack
4164
  *
4165
- * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4166
  *
4167
- * @return array
 
 
4168
  */
4169
  protected function compactEnv(Environment $env)
4170
  {
@@ -4178,9 +5070,11 @@ class Compiler
4178
  /**
4179
  * Convert env stack to singly linked list
4180
  *
4181
- * @param array $envs
4182
  *
4183
- * @return \ScssPhp\ScssPhp\Compiler\Environment
 
 
4184
  */
4185
  protected function extractEnv($envs)
4186
  {
@@ -4201,7 +5095,7 @@ class Compiler
4201
  */
4202
  protected function pushEnv(Block $block = null)
4203
  {
4204
- $env = new Environment;
4205
  $env->parent = $this->env;
4206
  $env->parentStore = $this->storeEnv;
4207
  $env->store = [];
@@ -4216,6 +5110,8 @@ class Compiler
4216
 
4217
  /**
4218
  * Pop environment
 
 
4219
  */
4220
  protected function popEnv()
4221
  {
@@ -4226,8 +5122,10 @@ class Compiler
4226
  /**
4227
  * Propagate vars from a just poped Env (used in @each and @for)
4228
  *
4229
- * @param array $store
4230
- * @param null|array $excludedVars
 
 
4231
  */
4232
  protected function backPropagateEnv($store, $excludedVars = null)
4233
  {
@@ -4253,9 +5151,11 @@ class Compiler
4253
  *
4254
  * @param string $name
4255
  * @param mixed $value
4256
- * @param boolean $shadow
4257
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4258
  * @param mixed $valueUnreduced
 
 
4259
  */
4260
  protected function set($name, $value, $shadow = false, Environment $env = null, $valueUnreduced = null)
4261
  {
@@ -4279,6 +5179,8 @@ class Compiler
4279
  * @param mixed $value
4280
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4281
  * @param mixed $valueUnreduced
 
 
4282
  */
4283
  protected function setExisting($name, $value, Environment $env, $valueUnreduced = null)
4284
  {
@@ -4337,6 +5239,8 @@ class Compiler
4337
  * @param mixed $value
4338
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4339
  * @param mixed $valueUnreduced
 
 
4340
  */
4341
  protected function setRaw($name, $value, Environment $env, $valueUnreduced = null)
4342
  {
@@ -4350,12 +5254,12 @@ class Compiler
4350
  /**
4351
  * Get variable
4352
  *
4353
- * @api
4354
  *
4355
  * @param string $name
4356
- * @param boolean $shouldThrow
4357
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4358
- * @param boolean $unreduced
4359
  *
4360
  * @return mixed|null
4361
  */
@@ -4409,7 +5313,7 @@ class Compiler
4409
  }
4410
 
4411
  if ($shouldThrow) {
4412
- $this->throwError("Undefined variable \$$name" . ($maxDepth <= 0 ? " (infinite recursion)" : ""));
4413
  }
4414
 
4415
  // found nothing
@@ -4422,7 +5326,7 @@ class Compiler
4422
  * @param string $name
4423
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4424
  *
4425
- * @return boolean
4426
  */
4427
  protected function has($name, Environment $env = null)
4428
  {
@@ -4433,6 +5337,8 @@ class Compiler
4433
  * Inject variables
4434
  *
4435
  * @param array $args
 
 
4436
  */
4437
  protected function injectVariables(array $args)
4438
  {
@@ -4447,7 +5353,7 @@ class Compiler
4447
  $name = substr($name, 1);
4448
  }
4449
 
4450
- if (! $parser->parseValue($strValue, $value)) {
4451
  $value = $this->coerceValue($strValue);
4452
  }
4453
 
@@ -4455,16 +5361,59 @@ class Compiler
4455
  }
4456
  }
4457
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4458
  /**
4459
  * Set variables
4460
  *
4461
  * @api
4462
  *
4463
  * @param array $variables
 
 
 
 
4464
  */
4465
  public function setVariables(array $variables)
4466
  {
4467
- $this->registeredVars = array_merge($this->registeredVars, $variables);
 
 
4468
  }
4469
 
4470
  /**
@@ -4473,6 +5422,8 @@ class Compiler
4473
  * @api
4474
  *
4475
  * @param string $name
 
 
4476
  */
4477
  public function unsetVariable($name)
4478
  {
@@ -4494,13 +5445,15 @@ class Compiler
4494
  /**
4495
  * Adds to list of parsed files
4496
  *
4497
- * @api
4498
  *
4499
- * @param string $path
 
 
4500
  */
4501
  public function addParsedFile($path)
4502
  {
4503
- if (isset($path) && is_file($path)) {
4504
  $this->parsedFiles[realpath($path)] = filemtime($path);
4505
  }
4506
  }
@@ -4508,12 +5461,12 @@ class Compiler
4508
  /**
4509
  * Returns list of parsed files
4510
  *
4511
- * @api
4512
- *
4513
- * @return array
4514
  */
4515
  public function getParsedFiles()
4516
  {
 
4517
  return $this->parsedFiles;
4518
  }
4519
 
@@ -4523,6 +5476,8 @@ class Compiler
4523
  * @api
4524
  *
4525
  * @param string|callable $path
 
 
4526
  */
4527
  public function addImportPath($path)
4528
  {
@@ -4536,11 +5491,24 @@ class Compiler
4536
  *
4537
  * @api
4538
  *
4539
- * @param string|array $path
 
 
4540
  */
4541
  public function setImportPaths($path)
4542
  {
4543
- $this->importPaths = (array) $path;
 
 
 
 
 
 
 
 
 
 
 
4544
  }
4545
 
4546
  /**
@@ -4548,11 +5516,43 @@ class Compiler
4548
  *
4549
  * @api
4550
  *
4551
- * @param integer $numberPrecision
 
 
 
 
4552
  */
4553
  public function setNumberPrecision($numberPrecision)
4554
  {
4555
- Node\Number::$precision = $numberPrecision;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4556
  }
4557
 
4558
  /**
@@ -4561,10 +5561,21 @@ class Compiler
4561
  * @api
4562
  *
4563
  * @param string $formatterName
 
 
 
 
 
 
4564
  */
4565
  public function setFormatter($formatterName)
4566
  {
4567
- $this->formatter = $formatterName;
 
 
 
 
 
4568
  }
4569
 
4570
  /**
@@ -4573,10 +5584,34 @@ class Compiler
4573
  * @api
4574
  *
4575
  * @param string $lineNumberStyle
 
 
 
 
4576
  */
4577
  public function setLineNumberStyle($lineNumberStyle)
4578
  {
4579
- $this->lineNumberStyle = $lineNumberStyle;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4580
  }
4581
 
4582
  /**
@@ -4584,7 +5619,11 @@ class Compiler
4584
  *
4585
  * @api
4586
  *
4587
- * @param integer $sourceMap
 
 
 
 
4588
  */
4589
  public function setSourceMap($sourceMap)
4590
  {
@@ -4597,6 +5636,10 @@ class Compiler
4597
  * @api
4598
  *
4599
  * @param array $sourceMapOptions
 
 
 
 
4600
  */
4601
  public function setSourceMapOptions($sourceMapOptions)
4602
  {
@@ -4608,13 +5651,23 @@ class Compiler
4608
  *
4609
  * @api
4610
  *
4611
- * @param string $name
4612
- * @param callable $func
4613
- * @param array $prototype
 
 
4614
  */
4615
- public function registerFunction($name, $func, $prototype = null)
4616
  {
4617
- $this->userFunctions[$this->normalizeName($name)] = [$func, $prototype];
 
 
 
 
 
 
 
 
4618
  }
4619
 
4620
  /**
@@ -4623,6 +5676,8 @@ class Compiler
4623
  * @api
4624
  *
4625
  * @param string $name
 
 
4626
  */
4627
  public function unregisterFunction($name)
4628
  {
@@ -4635,9 +5690,15 @@ class Compiler
4635
  * @api
4636
  *
4637
  * @param string $name
 
 
 
 
4638
  */
4639
  public function addFeature($name)
4640
  {
 
 
4641
  $this->registeredFeatures[$name] = true;
4642
  }
4643
 
@@ -4646,13 +5707,28 @@ class Compiler
4646
  *
4647
  * @param string $path
4648
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
 
 
4649
  */
4650
  protected function importFile($path, OutputBlock $out)
4651
  {
4652
- $this->pushCallStack('import '.$path);
4653
  // see if tree is cached
4654
  $realPath = realpath($path);
4655
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4656
  if (isset($this->importCache[$realPath])) {
4657
  $this->handleImportLoop($realPath);
4658
 
@@ -4665,62 +5741,109 @@ class Compiler
4665
  $this->importCache[$realPath] = $tree;
4666
  }
4667
 
4668
- $pi = pathinfo($path);
 
4669
 
4670
- array_unshift($this->importPaths, $pi['dirname']);
4671
  $this->compileChildrenNoReturn($tree->children, $out);
4672
- array_shift($this->importPaths);
4673
  $this->popCallStack();
4674
  }
4675
 
4676
  /**
4677
- * Return the file path for an import url if it exists
4678
  *
4679
- * @api
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4680
  *
4681
  * @param string $url
4682
  *
4683
- * @return string|null
4684
  */
4685
- public function findImport($url)
4686
  {
4687
- $urls = [];
 
4688
 
4689
- $hasExtension = preg_match('/[.]s?css$/', $url);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4690
 
4691
- // for "normal" scss imports (ignore vanilla css and external requests)
4692
- if (! preg_match('~\.css$|^https?://|^//~', $url)) {
4693
- $isPartial = (strpos(basename($url), '_') === 0);
4694
 
4695
- // try both normal and the _partial filename
4696
- $urls = [$url . ($hasExtension ? '' : '.scss')];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4697
 
4698
- if (! $isPartial) {
4699
- $urls[] = preg_replace('~[^/]+$~', '_\0', $url) . ($hasExtension ? '' : '.scss');
 
4700
  }
 
 
4701
 
4702
- if (! $hasExtension) {
4703
- $urls[] = "$url/index.scss";
4704
- $urls[] = "$url/_index.scss";
4705
- // allow to find a plain css file, *if* no scss or partial scss is found
4706
- $urls[] .= $url . ".css";
4707
  }
4708
  }
4709
 
4710
  foreach ($this->importPaths as $dir) {
4711
  if (\is_string($dir)) {
4712
- // check urls for normal import paths
4713
- foreach ($urls as $full) {
4714
- $separator = (
4715
- ! empty($dir) &&
4716
- substr($dir, -1) !== '/' &&
4717
- substr($full, 0, 1) !== '/'
4718
- ) ? '/' : '';
4719
- $full = $dir . $separator . $full;
4720
-
4721
- if (is_file($file = $full)) {
4722
- return $file;
4723
- }
4724
  }
4725
  } elseif (\is_callable($dir)) {
4726
  // check custom callback for import path
@@ -4732,103 +5855,336 @@ class Compiler
4732
  }
4733
  }
4734
 
4735
- if ($urls) {
4736
- if (! $hasExtension or preg_match('/[.]scss$/', $url)) {
4737
- $this->throwError("`$url` file not found for @import");
 
 
 
 
4738
  }
4739
  }
4740
 
4741
- return null;
4742
  }
4743
 
4744
  /**
4745
- * Set encoding
4746
- *
4747
- * @api
4748
  *
4749
- * @param string $encoding
4750
  */
4751
- public function setEncoding($encoding)
4752
  {
4753
- $this->encoding = $encoding;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4754
  }
4755
 
4756
  /**
4757
- * Ignore errors?
4758
- *
4759
- * @api
4760
  *
4761
- * @param boolean $ignoreErrors
4762
- *
4763
- * @return \ScssPhp\ScssPhp\Compiler
4764
  */
4765
- public function setIgnoreErrors($ignoreErrors)
4766
  {
4767
- $this->ignoreErrors = $ignoreErrors;
 
 
4768
 
4769
- return $this;
 
 
 
 
 
 
 
 
 
 
4770
  }
4771
 
4772
  /**
4773
- * Throw error (exception)
4774
- *
4775
- * @api
4776
- *
4777
- * @param string $msg Message with optional sprintf()-style vararg parameters
4778
  *
4779
- * @throws \ScssPhp\ScssPhp\Exception\CompilerException
4780
  */
4781
- public function throwError($msg)
4782
  {
4783
- if ($this->ignoreErrors) {
4784
- return;
4785
- }
 
4786
 
4787
- if (\func_num_args() > 1) {
4788
- $msg = \call_user_func_array('sprintf', \func_get_args());
4789
  }
4790
 
4791
- if (! $this->ignoreCallStackMessage) {
4792
- $line = $this->sourceLine;
4793
- $column = $this->sourceColumn;
4794
 
4795
- $loc = isset($this->sourceNames[$this->sourceIndex])
4796
- ? $this->sourceNames[$this->sourceIndex] . " on line $line, at column $column"
4797
- : "line: $line, column: $column";
 
 
 
 
 
4798
 
4799
- $msg = "$msg: $loc";
4800
 
4801
- $callStackMsg = $this->callStackMessage();
 
 
4802
 
4803
- if ($callStackMsg) {
4804
- $msg .= "\nCall Stack:\n" . $callStackMsg;
4805
- }
4806
  }
4807
 
4808
- throw new CompilerException($msg);
4809
  }
4810
 
4811
  /**
4812
- * Beautify call stack for output
4813
- *
4814
- * @param boolean $all
4815
- * @param null $limit
4816
  *
4817
- * @return string
4818
  */
4819
- protected function callStackMessage($all = false, $limit = null)
4820
  {
4821
- $callStackMsg = [];
4822
- $ncall = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4823
 
4824
  if ($this->callStack) {
4825
  foreach (array_reverse($this->callStack) as $call) {
4826
  if ($all || (isset($call['n']) && $call['n'])) {
4827
- $msg = "#" . $ncall++ . " " . $call['n'] . " ";
4828
  $msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]])
4829
- ? $this->sourceNames[$call[Parser::SOURCE_INDEX]]
4830
  : '(unknown file)');
4831
- $msg .= " on line " . $call[Parser::SOURCE_LINE];
4832
 
4833
  $callStackMsg[] = $msg;
4834
 
@@ -4847,6 +6203,8 @@ class Compiler
4847
  *
4848
  * @param string $name
4849
  *
 
 
4850
  * @throws \Exception
4851
  */
4852
  protected function handleImportLoop($name)
@@ -4858,9 +6216,12 @@ class Compiler
4858
 
4859
  $file = $this->sourceNames[$env->block->sourceIndex];
4860
 
 
 
 
 
4861
  if (realpath($file) === $name) {
4862
- $this->throwError('An @import loop has been found: %s imports %s', $file, basename($file));
4863
- break;
4864
  }
4865
  }
4866
  }
@@ -4868,19 +6229,17 @@ class Compiler
4868
  /**
4869
  * Call SCSS @function
4870
  *
4871
- * @param string $name
4872
- * @param array $argValues
4873
- * @param array $returnValue
4874
  *
4875
- * @return boolean Returns true if returnValue is set; otherwise, false
4876
  */
4877
- protected function callScssFunction($name, $argValues, &$returnValue)
4878
  {
4879
- $func = $this->get(static::$namespaces['function'] . $name, false);
4880
-
4881
  if (! $func) {
4882
- return false;
4883
  }
 
4884
 
4885
  $this->pushEnv();
4886
 
@@ -4890,7 +6249,7 @@ class Compiler
4890
  }
4891
 
4892
  // throw away lines and children
4893
- $tmp = new OutputBlock;
4894
  $tmp->lines = [];
4895
  $tmp->children = [];
4896
 
@@ -4899,66 +6258,57 @@ class Compiler
4899
  if (! empty($func->parentEnv)) {
4900
  $this->env->declarationScopeParent = $func->parentEnv;
4901
  } else {
4902
- $this->throwError("@function $name() without parentEnv");
4903
  }
4904
 
4905
- $ret = $this->compileChildren($func->children, $tmp, $this->env->marker . " " . $name);
4906
 
4907
  $this->popEnv();
4908
 
4909
- $returnValue = ! isset($ret) ? static::$defaultValue : $ret;
4910
-
4911
- return true;
4912
  }
4913
 
4914
  /**
4915
  * Call built-in and registered (PHP) functions
4916
  *
4917
  * @param string $name
 
 
4918
  * @param array $args
4919
- * @param array $returnValue
4920
  *
4921
- * @return boolean Returns true if returnValue is set; otherwise, false
4922
  */
4923
- protected function callNativeFunction($name, $args, &$returnValue)
4924
  {
4925
- // try a lib function
4926
- $name = $this->normalizeName($name);
4927
- $libName = null;
4928
 
4929
- if (isset($this->userFunctions[$name])) {
4930
- // see if we can find a user function
4931
- list($f, $prototype) = $this->userFunctions[$name];
4932
- } elseif (($f = $this->getBuiltinFunction($name)) && \is_callable($f)) {
4933
- $libName = $f[1];
4934
- $prototype = isset(static::$$libName) ? static::$$libName : null;
4935
- } else {
4936
- return false;
4937
  }
 
4938
 
4939
- @list($sorted, $kwargs) = $this->sortNativeFunctionArgs($libName, $prototype, $args);
4940
-
4941
- if ($name !== 'if' && $name !== 'call') {
4942
- $inExp = true;
4943
-
4944
- if ($name === 'join') {
4945
- $inExp = false;
4946
- }
4947
-
4948
  foreach ($sorted as &$val) {
4949
- $val = $this->reduce($val, $inExp);
 
 
4950
  }
4951
  }
4952
 
4953
- $returnValue = \call_user_func($f, $sorted, $kwargs);
4954
 
4955
  if (! isset($returnValue)) {
4956
- return false;
4957
  }
4958
 
4959
- $returnValue = $this->coerceValue($returnValue);
 
 
4960
 
4961
- return true;
 
 
4962
  }
4963
 
4964
  /**
@@ -4970,6 +6320,22 @@ class Compiler
4970
  */
4971
  protected function getBuiltinFunction($name)
4972
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4973
  $libName = 'lib' . preg_replace_callback(
4974
  '/_(.)/',
4975
  function ($m) {
@@ -4977,18 +6343,31 @@ class Compiler
4977
  },
4978
  ucfirst($name)
4979
  );
 
 
4980
 
4981
- return [$this, $libName];
 
 
 
 
 
 
 
 
 
 
 
4982
  }
4983
 
4984
  /**
4985
  * Sorts keyword arguments
4986
  *
4987
  * @param string $functionName
4988
- * @param array $prototypes
4989
  * @param array $args
4990
  *
4991
- * @return array
4992
  */
4993
  protected function sortNativeFunctionArgs($functionName, $prototypes, $args)
4994
  {
@@ -4998,16 +6377,18 @@ class Compiler
4998
  $keyArgs = [];
4999
  $posArgs = [];
5000
 
 
 
 
 
5001
  // separate positional and keyword arguments
5002
  foreach ($args as $arg) {
5003
  list($key, $value) = $arg;
5004
 
5005
- $key = $key[1];
5006
-
5007
- if (empty($key)) {
5008
  $posArgs[] = empty($arg[2]) ? $value : $arg;
5009
  } else {
5010
- $keyArgs[$key] = $value;
5011
  }
5012
  }
5013
 
@@ -5018,276 +6399,479 @@ class Compiler
5018
  if (\in_array($functionName, ['libRgb', 'libRgba', 'libHsl', 'libHsla'])) {
5019
  // notation 100 127 255 / 0 is in fact a simple list of 4 values
5020
  foreach ($args as $k => $arg) {
 
 
 
5021
  if ($arg[1][0] === Type::T_LIST && \count($arg[1][2]) === 3) {
5022
- $last = end($arg[1][2]);
5023
-
5024
- if ($last[0] === Type::T_EXPRESSION && $last[1] === '/') {
5025
- array_pop($arg[1][2]);
5026
- $arg[1][2][] = $last[2];
5027
- $arg[1][2][] = $last[3];
5028
- $args[$k] = $arg;
5029
- }
5030
  }
5031
  }
5032
  }
5033
 
5034
- $finalArgs = [];
5035
 
5036
  if (! \is_array(reset($prototypes))) {
5037
  $prototypes = [$prototypes];
5038
  }
5039
 
 
 
 
 
 
 
 
 
 
5040
  $keyArgs = [];
5041
 
5042
- // trying each prototypes
5043
- $prototypeHasMatch = false;
5044
- $exceptionMessage = '';
5045
 
5046
- foreach ($prototypes as $prototype) {
5047
- $argDef = [];
 
 
 
5048
 
5049
- foreach ($prototype as $i => $p) {
5050
- $default = null;
5051
- $p = explode(':', $p, 2);
5052
- $name = array_shift($p);
5053
 
5054
- if (\count($p)) {
5055
- $p = trim(reset($p));
 
5056
 
5057
- if ($p === 'null') {
5058
- // differentiate this null from the static::$null
5059
- $default = [Type::T_KEYWORD, 'null'];
5060
- } else {
5061
- if (\is_null($parser)) {
5062
- $parser = $this->parserFactory(__METHOD__);
5063
- }
5064
 
5065
- $parser->parseValue($p, $default);
5066
- }
5067
- }
5068
 
5069
- $isVariable = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5070
 
5071
- if (substr($name, -3) === '...') {
5072
- $isVariable = true;
5073
- $name = substr($name, 0, -3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5074
  }
 
5075
 
5076
- $argDef[] = [$name, $default, $isVariable];
 
 
 
5077
  }
 
 
 
 
 
 
 
5078
 
5079
- $ignoreCallStackMessage = $this->ignoreCallStackMessage;
5080
- $this->ignoreCallStackMessage = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5081
 
5082
- try {
5083
- $vars = $this->applyArguments($argDef, $args, false, false);
 
 
 
5084
 
5085
- // ensure all args are populated
5086
- foreach ($prototype as $i => $p) {
5087
- $name = explode(':', $p)[0];
5088
 
5089
- if (! isset($finalArgs[$i])) {
5090
- $finalArgs[$i] = null;
5091
- }
5092
  }
5093
 
5094
- // apply positional args
5095
- foreach (array_values($vars) as $i => $val) {
5096
- $finalArgs[$i] = $val;
 
5097
  }
 
5098
 
5099
- $keyArgs = array_merge($keyArgs, $vars);
5100
- $prototypeHasMatch = true;
 
5101
 
5102
- // overwrite positional args with keyword args
5103
- foreach ($prototype as $i => $p) {
5104
- $name = explode(':', $p)[0];
5105
 
5106
- if (isset($keyArgs[$name])) {
5107
- $finalArgs[$i] = $keyArgs[$name];
5108
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5109
 
5110
- // special null value as default: translate to real null here
5111
- if ($finalArgs[$i] === [Type::T_KEYWORD, 'null']) {
5112
- $finalArgs[$i] = null;
5113
- }
 
 
5114
  }
5115
- // should we break if this prototype seems fulfilled?
5116
- } catch (CompilerException $e) {
5117
- $exceptionMessage = $e->getMessage();
 
5118
  }
5119
- $this->ignoreCallStackMessage = $ignoreCallStackMessage;
5120
  }
5121
 
5122
- if ($exceptionMessage && ! $prototypeHasMatch) {
5123
- $this->throwError($exceptionMessage);
5124
  }
5125
 
5126
- return [$finalArgs, $keyArgs];
 
 
 
 
 
 
 
 
5127
  }
5128
 
5129
  /**
5130
- * Apply argument values per definition
5131
  *
5132
- * @param array $argDef
5133
- * @param array $argValues
5134
- * @param boolean $storeInEnv
5135
- * @param boolean $reduce
5136
- * only used if $storeInEnv = false
5137
  *
5138
- * @return array
5139
  *
5140
- * @throws \Exception
 
 
5141
  */
5142
- protected function applyArguments($argDef, $argValues, $storeInEnv = true, $reduce = true)
5143
  {
5144
- $output = [];
5145
 
5146
- if (\is_array($argValues) && \count($argValues) && end($argValues) === static::$null) {
5147
- array_pop($argValues);
5148
- }
5149
 
5150
- if ($storeInEnv) {
5151
- $storeEnv = $this->getStoreEnv();
 
 
 
 
 
 
 
 
5152
 
5153
- $env = new Environment;
5154
- $env->store = $storeEnv->store;
5155
  }
5156
 
5157
- $hasVariable = false;
5158
- $args = [];
 
 
 
 
 
 
 
 
 
 
5159
 
5160
- foreach ($argDef as $i => $arg) {
5161
- list($name, $default, $isVariable) = $argDef[$i];
 
 
5162
 
5163
- $args[$name] = [$i, $name, $default, $isVariable];
5164
- $hasVariable |= $isVariable;
 
 
 
 
 
 
 
 
5165
  }
 
5166
 
5167
- $splatSeparator = null;
5168
- $keywordArgs = [];
5169
- $deferredKeywordArgs = [];
5170
- $remaining = [];
5171
- $hasKeywordArgument = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5172
 
5173
- // assign the keyword args
5174
- foreach ((array) $argValues as $arg) {
5175
- if (! empty($arg[0])) {
5176
  $hasKeywordArgument = true;
5177
 
5178
- $name = $arg[0][1];
5179
- if (! isset($args[$name])) {
5180
- foreach (array_keys($args) as $an) {
5181
- if (str_replace("_", "-", $an) === str_replace("_", "-", $name)) {
5182
- $name = $an;
5183
- break;
5184
- }
5185
- }
5186
- }
5187
 
5188
- if (! isset($args[$name]) || $args[$name][3]) {
5189
- if ($hasVariable) {
5190
- $deferredKeywordArgs[$name] = $arg[1];
5191
- } else {
5192
- $this->throwError("Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
5193
- break;
5194
- }
5195
- } elseif ($args[$name][0] < \count($remaining)) {
5196
- $this->throwError("The argument $%s was passed both by position and by name.", $arg[0][1]);
5197
- break;
5198
- } else {
5199
- $keywordArgs[$name] = $arg[1];
5200
  }
5201
- } elseif ($arg[2] === true) {
 
 
 
 
5202
  $val = $this->reduce($arg[1], true);
 
5203
 
5204
  if ($val[0] === Type::T_LIST) {
5205
- foreach ($val[2] as $name => $item) {
5206
- if (! is_numeric($name)) {
5207
- if (! isset($args[$name])) {
5208
- foreach (array_keys($args) as $an) {
5209
- if (str_replace("_", "-", $an) === str_replace("_", "-", $name)) {
5210
- $name = $an;
5211
- break;
5212
- }
5213
- }
 
 
 
 
 
 
 
5214
  }
5215
 
5216
- if ($hasVariable) {
5217
- $deferredKeywordArgs[$name] = $item;
5218
- } else {
5219
- $keywordArgs[$name] = $item;
 
 
 
 
 
 
 
 
 
 
 
5220
  }
 
 
 
 
5221
  } else {
5222
  if (\is_null($splatSeparator)) {
5223
  $splatSeparator = $val[1];
5224
  }
5225
 
5226
- $remaining[] = $item;
5227
- }
5228
- }
5229
- } elseif ($val[0] === Type::T_MAP) {
5230
- foreach ($val[1] as $i => $name) {
5231
- $name = $this->compileStringContent($this->coerceString($name));
5232
- $item = $val[2][$i];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5233
 
5234
- if (! is_numeric($name)) {
5235
- if (! isset($args[$name])) {
5236
- foreach (array_keys($args) as $an) {
5237
- if (str_replace("_", "-", $an) === str_replace("_", "-", $name)) {
5238
- $name = $an;
5239
- break;
5240
- }
5241
- }
5242
- }
5243
 
5244
- if ($hasVariable) {
5245
- $deferredKeywordArgs[$name] = $item;
5246
- } else {
5247
- $keywordArgs[$name] = $item;
5248
- }
5249
- } else {
5250
- if (\is_null($splatSeparator)) {
5251
- $splatSeparator = $val[1];
5252
- }
5253
 
5254
- $remaining[] = $item;
5255
- }
5256
- }
5257
- } else {
5258
- $remaining[] = $val;
5259
- }
5260
- } elseif ($hasKeywordArgument) {
5261
- $this->throwError('Positional arguments must come before keyword arguments.');
5262
- break;
5263
  } else {
5264
- $remaining[] = $arg[1];
5265
  }
5266
  }
5267
 
5268
- foreach ($args as $arg) {
5269
- list($i, $name, $default, $isVariable) = $arg;
5270
 
5271
- if ($isVariable) {
5272
- $val = [Type::T_LIST, \is_null($splatSeparator) ? ',' : $splatSeparator , [], $isVariable];
5273
 
5274
- for ($count = \count($remaining); $i < $count; $i++) {
5275
- $val[2][] = $remaining[$i];
5276
- }
5277
 
5278
- foreach ($deferredKeywordArgs as $itemName => $item) {
5279
- $val[2][$itemName] = $item;
5280
- }
5281
- } elseif (isset($remaining[$i])) {
5282
- $val = $remaining[$i];
5283
- } elseif (isset($keywordArgs[$name])) {
5284
- $val = $keywordArgs[$name];
5285
- } elseif (! empty($default)) {
5286
  continue;
 
 
 
 
 
 
5287
  } else {
5288
- $this->throwError("Missing argument $name");
5289
- break;
5290
  }
 
 
 
 
 
 
5291
 
5292
  if ($storeInEnv) {
5293
  $this->set($name, $this->reduce($val, true), true, $env);
@@ -5300,12 +6884,13 @@ class Compiler
5300
  $storeEnv->store = $env->store;
5301
  }
5302
 
5303
- foreach ($args as $arg) {
5304
- list($i, $name, $default, $isVariable) = $arg;
5305
 
5306
- if ($isVariable || isset($remaining[$i]) || isset($keywordArgs[$name]) || empty($default)) {
5307
  continue;
5308
  }
 
5309
 
5310
  if ($storeInEnv) {
5311
  $this->set($name, $this->reduce($default, true), true);
@@ -5317,16 +6902,77 @@ class Compiler
5317
  return $output;
5318
  }
5319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5320
  /**
5321
  * Coerce a php value into a scss one
5322
  *
5323
  * @param mixed $value
5324
  *
5325
- * @return array|\ScssPhp\ScssPhp\Node\Number
5326
  */
5327
  protected function coerceValue($value)
5328
  {
5329
- if (\is_array($value) || $value instanceof \ArrayAccess) {
5330
  return $value;
5331
  }
5332
 
@@ -5339,7 +6985,7 @@ class Compiler
5339
  }
5340
 
5341
  if (is_numeric($value)) {
5342
- return new Node\Number($value, '');
5343
  }
5344
 
5345
  if ($value === '') {
@@ -5357,40 +7003,66 @@ class Compiler
5357
  }
5358
 
5359
  /**
5360
- * Coerce something to map
5361
  *
5362
- * @param array $item
5363
  *
5364
- * @return array
5365
  */
5366
- protected function coerceMap($item)
5367
  {
 
 
 
 
5368
  if ($item[0] === Type::T_MAP) {
5369
  return $item;
5370
  }
5371
 
5372
- if ($item[0] === static::$emptyList[0] &&
5373
- $item[1] === static::$emptyList[1] &&
5374
- $item[2] === static::$emptyList[2]
5375
  ) {
5376
  return static::$emptyMap;
5377
  }
5378
 
5379
- return [Type::T_MAP, [$item], [static::$null]];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5380
  }
5381
 
5382
  /**
5383
  * Coerce something to list
5384
  *
5385
- * @param array $item
5386
- * @param string $delim
5387
- * @param boolean $removeTrailingNull
5388
  *
5389
  * @return array
5390
  */
5391
  protected function coerceList($item, $delim = ',', $removeTrailingNull = false)
5392
  {
5393
- if (isset($item) && $item[0] === Type::T_LIST) {
 
 
 
 
5394
  // remove trailing null from the list
5395
  if ($removeTrailingNull && end($item[2]) === static::$null) {
5396
  array_pop($item[2]);
@@ -5399,7 +7071,7 @@ class Compiler
5399
  return $item;
5400
  }
5401
 
5402
- if (isset($item) && $item[0] === Type::T_MAP) {
5403
  $keys = $item[1];
5404
  $values = $item[2];
5405
  $list = [];
@@ -5408,37 +7080,25 @@ class Compiler
5408
  $key = $keys[$i];
5409
  $value = $values[$i];
5410
 
5411
- switch ($key[0]) {
5412
- case Type::T_LIST:
5413
- case Type::T_MAP:
5414
- case Type::T_STRING:
5415
- case Type::T_NULL:
5416
- break;
5417
-
5418
- default:
5419
- $key = [Type::T_KEYWORD, $this->compileStringContent($this->coerceString($key))];
5420
- break;
5421
- }
5422
-
5423
  $list[] = [
5424
  Type::T_LIST,
5425
- '',
5426
  [$key, $value]
5427
  ];
5428
  }
5429
 
5430
- return [Type::T_LIST, ',', $list];
5431
  }
5432
 
5433
- return [Type::T_LIST, $delim, ! isset($item) ? []: [$item]];
5434
  }
5435
 
5436
  /**
5437
  * Coerce color for expression
5438
  *
5439
- * @param array $value
5440
  *
5441
- * @return array|null
5442
  */
5443
  protected function coerceForExpression($value)
5444
  {
@@ -5452,12 +7112,17 @@ class Compiler
5452
  /**
5453
  * Coerce value to color
5454
  *
5455
- * @param array $value
 
5456
  *
5457
  * @return array|null
5458
  */
5459
  protected function coerceColor($value, $inRGBFunction = false)
5460
  {
 
 
 
 
5461
  switch ($value[0]) {
5462
  case Type::T_COLOR:
5463
  for ($i = 1; $i <= 3; $i++) {
@@ -5543,7 +7208,7 @@ class Compiler
5543
  if ($color[3] === 255) {
5544
  $color[3] = 1; // fully opaque
5545
  } else {
5546
- $color[3] = round($color[3] / 255, 3);
5547
  }
5548
  }
5549
 
@@ -5566,10 +7231,10 @@ class Compiler
5566
  }
5567
 
5568
  /**
5569
- * @param integer|\ScssPhp\ScssPhp\Node\Number $value
5570
- * @param boolean $isAlpha
5571
  *
5572
- * @return integer|mixed
5573
  */
5574
  protected function compileRGBAValue($value, $isAlpha = false)
5575
  {
@@ -5581,40 +7246,31 @@ class Compiler
5581
  }
5582
 
5583
  /**
5584
- * @param mixed $value
5585
- * @param integer|float $min
5586
- * @param integer|float $max
5587
- * @param boolean $isInt
5588
- * @param boolean $clamp
5589
- * @param boolean $modulo
5590
  *
5591
- * @return integer|mixed
5592
  */
5593
- protected function compileColorPartValue($value, $min, $max, $isInt = true, $clamp = true, $modulo = false)
5594
  {
5595
  if (! is_numeric($value)) {
5596
  if (\is_array($value)) {
5597
  $reduced = $this->reduce($value);
5598
 
5599
- if (\is_object($reduced) && $value->type === Type::T_NUMBER) {
5600
  $value = $reduced;
5601
  }
5602
  }
5603
 
5604
- if (\is_object($value) && $value->type === Type::T_NUMBER) {
5605
- $num = $value->dimension;
5606
-
5607
- if (\count($value->units)) {
5608
- $unit = array_keys($value->units);
5609
- $unit = reset($unit);
5610
-
5611
- switch ($unit) {
5612
- case '%':
5613
- $num *= $max / 100;
5614
- break;
5615
- default:
5616
- break;
5617
- }
5618
  }
5619
 
5620
  $value = $num;
@@ -5628,18 +7284,7 @@ class Compiler
5628
  $value = round($value);
5629
  }
5630
 
5631
- if ($clamp) {
5632
- $value = min($max, max($min, $value));
5633
- }
5634
-
5635
- if ($modulo) {
5636
- $value = $value % $max;
5637
-
5638
- // still negative?
5639
- while ($value < $min) {
5640
- $value += $max;
5641
- }
5642
- }
5643
 
5644
  return $value;
5645
  }
@@ -5650,34 +7295,72 @@ class Compiler
5650
  /**
5651
  * Coerce value to string
5652
  *
5653
- * @param array $value
5654
  *
5655
- * @return array|null
5656
  */
5657
  protected function coerceString($value)
5658
  {
5659
  if ($value[0] === Type::T_STRING) {
 
 
5660
  return $value;
5661
  }
5662
 
5663
  return [Type::T_STRING, '', [$this->compileValue($value)]];
5664
  }
5665
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5666
  /**
5667
  * Coerce value to a percentage
5668
  *
5669
- * @param array $value
5670
  *
5671
- * @return integer|float
 
 
5672
  */
5673
  protected function coercePercent($value)
5674
  {
5675
- if ($value[0] === Type::T_NUMBER) {
5676
- if (! empty($value[2]['%'])) {
5677
- return $value[1] / 100;
 
 
5678
  }
5679
 
5680
- return $value[1];
5681
  }
5682
 
5683
  return 0;
@@ -5688,21 +7371,24 @@ class Compiler
5688
  *
5689
  * @api
5690
  *
5691
- * @param array $value
 
5692
  *
5693
  * @return array
5694
  *
5695
- * @throws \Exception
5696
  */
5697
- public function assertMap($value)
5698
  {
5699
- $value = $this->coerceMap($value);
5700
 
5701
- if ($value[0] !== Type::T_MAP) {
5702
- $this->throwError('expecting map, %s received', $value[0]);
 
 
5703
  }
5704
 
5705
- return $value;
5706
  }
5707
 
5708
  /**
@@ -5710,7 +7396,7 @@ class Compiler
5710
  *
5711
  * @api
5712
  *
5713
- * @param array $value
5714
  *
5715
  * @return array
5716
  *
@@ -5719,30 +7405,55 @@ class Compiler
5719
  public function assertList($value)
5720
  {
5721
  if ($value[0] !== Type::T_LIST) {
5722
- $this->throwError('expecting list, %s received', $value[0]);
5723
  }
 
5724
 
5725
  return $value;
5726
  }
5727
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5728
  /**
5729
  * Assert value is a color
5730
  *
5731
  * @api
5732
  *
5733
- * @param array $value
 
5734
  *
5735
  * @return array
5736
  *
5737
- * @throws \Exception
5738
  */
5739
- public function assertColor($value)
5740
  {
5741
  if ($color = $this->coerceColor($value)) {
5742
  return $color;
5743
  }
5744
 
5745
- $this->throwError('expecting color, %s received', $value[0]);
 
 
5746
  }
5747
 
5748
  /**
@@ -5750,21 +7461,64 @@ class Compiler
5750
  *
5751
  * @api
5752
  *
5753
- * @param array $value
 
5754
  *
5755
- * @return integer|float
5756
  *
5757
- * @throws \Exception
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5758
  */
5759
- public function assertNumber($value)
5760
  {
5761
- if ($value[0] !== Type::T_NUMBER) {
5762
- $this->throwError('expecting number, %s received', $value[0]);
 
5763
  }
5764
 
5765
- return $value[1];
5766
  }
5767
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5768
  /**
5769
  * Make sure a color's components don't go out of bounds
5770
  *
@@ -5782,6 +7536,10 @@ class Compiler
5782
  if ($c[$i] > 255) {
5783
  $c[$i] = 255;
5784
  }
 
 
 
 
5785
  }
5786
 
5787
  return $c;
@@ -5790,11 +7548,11 @@ class Compiler
5790
  /**
5791
  * Convert RGB to HSL
5792
  *
5793
- * @api
5794
  *
5795
- * @param integer $red
5796
- * @param integer $green
5797
- * @param integer $blue
5798
  *
5799
  * @return array
5800
  */
@@ -5819,12 +7577,12 @@ class Compiler
5819
  $h = 60 * ($green - $blue) / $d;
5820
  } elseif ($green == $max) {
5821
  $h = 60 * ($blue - $red) / $d + 120;
5822
- } elseif ($blue == $max) {
5823
  $h = 60 * ($red - $green) / $d + 240;
5824
  }
5825
  }
5826
 
5827
- return [Type::T_HSL, fmod($h, 360), $s * 100, $l / 5.1];
5828
  }
5829
 
5830
  /**
@@ -5853,7 +7611,7 @@ class Compiler
5853
  }
5854
 
5855
  if ($h * 3 < 2) {
5856
- return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
5857
  }
5858
 
5859
  return $m1;
@@ -5862,11 +7620,11 @@ class Compiler
5862
  /**
5863
  * Convert HSL to RGB
5864
  *
5865
- * @api
5866
  *
5867
- * @param integer $hue H from 0 to 360
5868
- * @param integer $saturation S from 0 to 100
5869
- * @param integer $lightness L from 0 to 100
5870
  *
5871
  * @return array
5872
  */
@@ -5883,35 +7641,133 @@ class Compiler
5883
  $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
5884
  $m1 = $l * 2 - $m2;
5885
 
5886
- $r = $this->hueToRGB($m1, $m2, $h + 1/3) * 255;
5887
  $g = $this->hueToRGB($m1, $m2, $h) * 255;
5888
- $b = $this->hueToRGB($m1, $m2, $h - 1/3) * 255;
5889
 
5890
  $out = [Type::T_COLOR, $r, $g, $b];
5891
 
5892
  return $out;
5893
  }
5894
 
5895
- // Built in functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5896
 
5897
- protected static $libCall = ['name', 'args...'];
5898
- protected function libCall($args, $kwargs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5899
  {
5900
- $name = $this->compileStringContent($this->coerceString($this->reduce(array_shift($args), true)));
5901
- $callArgs = [];
 
 
 
 
 
 
5902
 
5903
- // $kwargs['args'] is [Type::T_LIST, ',', [..]]
5904
- foreach ($kwargs['args'][2] as $varname => $arg) {
5905
- if (is_numeric($varname)) {
5906
- $varname = null;
5907
  } else {
5908
- $varname = [ 'var', $varname];
5909
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5910
 
5911
- $callArgs[] = [$varname, $arg, false];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5912
  }
5913
 
5914
- return $this->reduce([Type::T_FUNCTION_CALL, $name, $callArgs]);
 
 
 
 
5915
  }
5916
 
5917
  protected static $libIf = ['condition', 'if-true', 'if-false:'];
@@ -5931,11 +7787,8 @@ class Compiler
5931
  {
5932
  list($list, $value) = $args;
5933
 
5934
- if ($value[0] === Type::T_MAP) {
5935
- return static::$null;
5936
- }
5937
-
5938
- if ($list[0] === Type::T_MAP ||
5939
  $list[0] === Type::T_STRING ||
5940
  $list[0] === Type::T_KEYWORD ||
5941
  $list[0] === Type::T_INTERPOLATE
@@ -5943,7 +7796,23 @@ class Compiler
5943
  $list = $this->coerceList($list, ' ');
5944
  }
5945
 
5946
- if ($list[0] !== Type::T_LIST) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5947
  return static::$null;
5948
  }
5949
 
@@ -5955,7 +7824,7 @@ class Compiler
5955
 
5956
  $key = array_search($this->normalizeValue($value), $values);
5957
 
5958
- return false === $key ? static::$null : $key + 1;
5959
  }
5960
 
5961
  protected static $libRgb = [
@@ -5964,6 +7833,14 @@ class Compiler
5964
  ['channels'],
5965
  ['red', 'green', 'blue'],
5966
  ['red', 'green', 'blue', 'alpha'] ];
 
 
 
 
 
 
 
 
5967
  protected function libRgb($args, $kwargs, $funcName = 'rgb')
5968
  {
5969
  switch (\count($args)) {
@@ -5977,7 +7854,7 @@ class Compiler
5977
  $color = [Type::T_COLOR, $args[0], $args[1], $args[2]];
5978
 
5979
  if (! $color = $this->coerceColor($color)) {
5980
- $color = [Type::T_STRING, '', [$funcName .'(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
5981
  }
5982
 
5983
  return $color;
@@ -5993,7 +7870,7 @@ class Compiler
5993
  [$funcName . '(', $color[1], ', ', $color[2], ', ', $color[3], ', ', $alpha, ')']];
5994
  }
5995
  } else {
5996
- $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
5997
  }
5998
  break;
5999
 
@@ -6022,32 +7899,125 @@ class Compiler
6022
  return $this->libRgb($args, $kwargs, 'rgba');
6023
  }
6024
 
6025
- // helper function for adjust_color, change_color, and scale_color
6026
- protected function alterColor($args, $fn)
 
 
 
 
 
 
 
 
 
 
6027
  {
6028
- $color = $this->assertColor($args[0]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6029
 
6030
- foreach ([1 => 1, 2 => 2, 3 => 3, 7 => 4] as $iarg => $irgba) {
6031
- if (isset($args[$iarg])) {
6032
- $val = $this->assertNumber($args[$iarg]);
6033
 
6034
- if (! isset($color[$irgba])) {
6035
- $color[$irgba] = (($irgba < 4) ? 0 : 1);
 
 
6036
  }
 
 
 
 
 
6037
 
6038
- $color[$irgba] = \call_user_func($fn, $color[$irgba], $val, $iarg);
 
6039
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6040
  }
6041
 
6042
- if (! empty($args[4]) || ! empty($args[5]) || ! empty($args[6])) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6043
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6044
 
6045
- foreach ([4 => 1, 5 => 2, 6 => 3] as $iarg => $ihsl) {
6046
- if (! empty($args[$iarg])) {
6047
- $val = $this->assertNumber($args[$iarg]);
6048
- $hsl[$ihsl] = \call_user_func($fn, $hsl[$ihsl], $val, $iarg);
6049
- }
6050
  }
 
 
6051
 
6052
  $rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
6053
 
@@ -6058,58 +8028,54 @@ class Compiler
6058
  $color = $rgb;
6059
  }
6060
 
 
 
 
 
 
6061
  return $color;
6062
  }
6063
 
6064
- protected static $libAdjustColor = [
6065
- 'color', 'red:null', 'green:null', 'blue:null',
6066
- 'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
6067
- ];
6068
  protected function libAdjustColor($args)
6069
  {
6070
- return $this->alterColor($args, function ($base, $alter, $i) {
6071
- return $base + $alter;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6072
  });
6073
  }
6074
 
6075
- protected static $libChangeColor = [
6076
- 'color', 'red:null', 'green:null', 'blue:null',
6077
- 'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
6078
- ];
6079
  protected function libChangeColor($args)
6080
  {
6081
- return $this->alterColor($args, function ($base, $alter, $i) {
 
 
 
 
6082
  return $alter;
6083
  });
6084
  }
6085
 
6086
- protected static $libScaleColor = [
6087
- 'color', 'red:null', 'green:null', 'blue:null',
6088
- 'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
6089
- ];
6090
  protected function libScaleColor($args)
6091
  {
6092
- return $this->alterColor($args, function ($base, $scale, $i) {
6093
- // 1, 2, 3 - rgb
6094
- // 4, 5, 6 - hsl
6095
- // 7 - a
6096
- switch ($i) {
6097
- case 1:
6098
- case 2:
6099
- case 3:
6100
- $max = 255;
6101
- break;
6102
-
6103
- case 4:
6104
- $max = 360;
6105
- break;
6106
-
6107
- case 7:
6108
- $max = 1;
6109
- break;
6110
-
6111
- default:
6112
- $max = 100;
6113
  }
6114
 
6115
  $scale = $scale / 100;
@@ -6126,6 +8092,11 @@ class Compiler
6126
  protected function libIeHexStr($args)
6127
  {
6128
  $color = $this->coerceColor($args[0]);
 
 
 
 
 
6129
  $color[4] = isset($color[4]) ? round(255 * $color[4]) : 255;
6130
 
6131
  return [Type::T_STRING, '', [sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3])]];
@@ -6136,7 +8107,11 @@ class Compiler
6136
  {
6137
  $color = $this->coerceColor($args[0]);
6138
 
6139
- return $color[1];
 
 
 
 
6140
  }
6141
 
6142
  protected static $libGreen = ['color'];
@@ -6144,7 +8119,11 @@ class Compiler
6144
  {
6145
  $color = $this->coerceColor($args[0]);
6146
 
6147
- return $color[2];
 
 
 
 
6148
  }
6149
 
6150
  protected static $libBlue = ['color'];
@@ -6152,14 +8131,18 @@ class Compiler
6152
  {
6153
  $color = $this->coerceColor($args[0]);
6154
 
6155
- return $color[3];
 
 
 
 
6156
  }
6157
 
6158
  protected static $libAlpha = ['color'];
6159
  protected function libAlpha($args)
6160
  {
6161
  if ($color = $this->coerceColor($args[0])) {
6162
- return isset($color[4]) ? $color[4] : 1;
6163
  }
6164
 
6165
  // this might be the IE function, so return value unchanged
@@ -6171,7 +8154,7 @@ class Compiler
6171
  {
6172
  $value = $args[0];
6173
 
6174
- if ($value[0] === Type::T_NUMBER) {
6175
  return null;
6176
  }
6177
 
@@ -6179,76 +8162,127 @@ class Compiler
6179
  }
6180
 
6181
  // mix two colors
6182
- protected static $libMix = ['color-1', 'color-2', 'weight:0.5'];
 
 
 
6183
  protected function libMix($args)
6184
  {
6185
  list($first, $second, $weight) = $args;
6186
 
6187
- $first = $this->assertColor($first);
6188
- $second = $this->assertColor($second);
6189
-
6190
- if (! isset($weight)) {
6191
- $weight = 0.5;
6192
- } else {
6193
- $weight = $this->coercePercent($weight);
6194
- }
6195
 
6196
  $firstAlpha = isset($first[4]) ? $first[4] : 1;
6197
  $secondAlpha = isset($second[4]) ? $second[4] : 1;
6198
 
6199
- $w = $weight * 2 - 1;
6200
- $a = $firstAlpha - $secondAlpha;
6201
 
6202
- $w1 = (($w * $a === -1 ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2.0;
6203
- $w2 = 1.0 - $w1;
 
6204
 
6205
  $new = [Type::T_COLOR,
6206
- $w1 * $first[1] + $w2 * $second[1],
6207
- $w1 * $first[2] + $w2 * $second[2],
6208
- $w1 * $first[3] + $w2 * $second[3],
6209
  ];
6210
 
6211
  if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
6212
- $new[] = $firstAlpha * $weight + $secondAlpha * (1 - $weight);
6213
  }
6214
 
6215
  return $this->fixColor($new);
6216
  }
6217
 
6218
- protected static $libHsl =[
6219
  ['channels'],
 
6220
  ['hue', 'saturation', 'lightness'],
6221
  ['hue', 'saturation', 'lightness', 'alpha'] ];
 
 
 
 
 
 
 
 
6222
  protected function libHsl($args, $kwargs, $funcName = 'hsl')
6223
  {
 
 
6224
  if (\count($args) == 1) {
6225
  if ($args[0][0] !== Type::T_LIST || \count($args[0][2]) < 3 || \count($args[0][2]) > 4) {
6226
  return [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
6227
  }
6228
 
6229
  $args = $args[0][2];
 
 
 
 
 
 
 
 
 
 
 
 
6230
  }
6231
 
6232
- $hue = $this->compileColorPartValue($args[0], 0, 360, false, false, true);
6233
- $saturation = $this->compileColorPartValue($args[1], 0, 100, false);
6234
- $lightness = $this->compileColorPartValue($args[2], 0, 100, false);
 
 
 
 
 
 
 
 
 
 
 
6235
 
 
 
 
 
 
 
 
 
 
 
 
 
6236
  $alpha = null;
6237
 
6238
  if (\count($args) === 4) {
6239
  $alpha = $this->compileColorPartValue($args[3], 0, 100, false);
6240
 
6241
- if (! is_numeric($hue) || ! is_numeric($saturation) || ! is_numeric($lightness) || ! is_numeric($alpha)) {
6242
  return [Type::T_STRING, '',
6243
  [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']];
6244
  }
6245
  } else {
6246
- if (! is_numeric($hue) || ! is_numeric($saturation) || ! is_numeric($lightness)) {
6247
  return [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
6248
  }
6249
  }
6250
 
6251
- $color = $this->toRGB($hue, $saturation, $lightness);
 
 
 
 
 
 
6252
 
6253
  if (! \is_null($alpha)) {
6254
  $color[4] = $alpha;
@@ -6259,7 +8293,9 @@ class Compiler
6259
 
6260
  protected static $libHsla = [
6261
  ['channels'],
6262
- ['hue', 'saturation', 'lightness', 'alpha:1'] ];
 
 
6263
  protected function libHsla($args, $kwargs)
6264
  {
6265
  return $this->libHsl($args, $kwargs, 'hsla');
@@ -6268,34 +8304,173 @@ class Compiler
6268
  protected static $libHue = ['color'];
6269
  protected function libHue($args)
6270
  {
6271
- $color = $this->assertColor($args[0]);
6272
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6273
 
6274
- return new Node\Number($hsl[1], 'deg');
6275
  }
6276
 
6277
  protected static $libSaturation = ['color'];
6278
  protected function libSaturation($args)
6279
  {
6280
- $color = $this->assertColor($args[0]);
6281
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6282
 
6283
- return new Node\Number($hsl[2], '%');
6284
  }
6285
 
6286
  protected static $libLightness = ['color'];
6287
  protected function libLightness($args)
6288
  {
6289
- $color = $this->assertColor($args[0]);
6290
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6291
 
6292
- return new Node\Number($hsl[3], '%');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6293
  }
 
6294
 
 
 
 
 
 
 
 
6295
  protected function adjustHsl($color, $idx, $amount)
6296
  {
6297
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6298
  $hsl[$idx] += $amount;
 
 
 
 
 
 
6299
  $out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
6300
 
6301
  if (isset($color[4])) {
@@ -6308,8 +8483,8 @@ class Compiler
6308
  protected static $libAdjustHue = ['color', 'degrees'];
6309
  protected function libAdjustHue($args)
6310
  {
6311
- $color = $this->assertColor($args[0]);
6312
- $degrees = $this->assertNumber($args[1]);
6313
 
6314
  return $this->adjustHsl($color, 1, $degrees);
6315
  }
@@ -6317,7 +8492,7 @@ class Compiler
6317
  protected static $libLighten = ['color', 'amount'];
6318
  protected function libLighten($args)
6319
  {
6320
- $color = $this->assertColor($args[0]);
6321
  $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
6322
 
6323
  return $this->adjustHsl($color, 3, $amount);
@@ -6326,34 +8501,36 @@ class Compiler
6326
  protected static $libDarken = ['color', 'amount'];
6327
  protected function libDarken($args)
6328
  {
6329
- $color = $this->assertColor($args[0]);
6330
  $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
6331
 
6332
  return $this->adjustHsl($color, 3, -$amount);
6333
  }
6334
 
6335
- protected static $libSaturate = [['color', 'amount'], ['number']];
6336
  protected function libSaturate($args)
6337
  {
6338
  $value = $args[0];
6339
 
6340
- if ($value[0] === Type::T_NUMBER) {
 
 
6341
  return null;
6342
  }
6343
 
6344
- $color = $this->assertColor($value);
6345
- $amount = 100 * $this->coercePercent($args[1]);
6346
 
6347
- return $this->adjustHsl($color, 2, $amount);
6348
  }
6349
 
6350
  protected static $libDesaturate = ['color', 'amount'];
6351
  protected function libDesaturate($args)
6352
  {
6353
- $color = $this->assertColor($args[0]);
6354
- $amount = 100 * $this->coercePercent($args[1]);
6355
 
6356
- return $this->adjustHsl($color, 2, -$amount);
6357
  }
6358
 
6359
  protected static $libGrayscale = ['color'];
@@ -6361,55 +8538,51 @@ class Compiler
6361
  {
6362
  $value = $args[0];
6363
 
6364
- if ($value[0] === Type::T_NUMBER) {
6365
  return null;
6366
  }
6367
 
6368
- return $this->adjustHsl($this->assertColor($value), 2, -100);
6369
  }
6370
 
6371
  protected static $libComplement = ['color'];
6372
  protected function libComplement($args)
6373
  {
6374
- return $this->adjustHsl($this->assertColor($args[0]), 1, 180);
6375
  }
6376
 
6377
- protected static $libInvert = ['color', 'weight:1'];
6378
  protected function libInvert($args)
6379
  {
6380
- list($value, $weight) = $args;
6381
 
6382
- if (! isset($weight)) {
6383
- $weight = 1;
6384
- } else {
6385
- $weight = $this->coercePercent($weight);
6386
- }
 
6387
 
6388
- if ($value[0] === Type::T_NUMBER) {
6389
  return null;
6390
  }
6391
 
6392
- $color = $this->assertColor($value);
6393
  $inverted = $color;
6394
  $inverted[1] = 255 - $inverted[1];
6395
  $inverted[2] = 255 - $inverted[2];
6396
  $inverted[3] = 255 - $inverted[3];
6397
 
6398
- if ($weight < 1) {
6399
- return $this->libMix([$inverted, $color, [Type::T_NUMBER, $weight]]);
6400
- }
6401
-
6402
- return $inverted;
6403
  }
6404
 
6405
  // increases opacity by amount
6406
  protected static $libOpacify = ['color', 'amount'];
6407
  protected function libOpacify($args)
6408
  {
6409
- $color = $this->assertColor($args[0]);
6410
- $amount = $this->coercePercent($args[1]);
6411
 
6412
- $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
6413
  $color[4] = min(1, max(0, $color[4]));
6414
 
6415
  return $color;
@@ -6425,10 +8598,10 @@ class Compiler
6425
  protected static $libTransparentize = ['color', 'amount'];
6426
  protected function libTransparentize($args)
6427
  {
6428
- $color = $this->assertColor($args[0]);
6429
- $amount = $this->coercePercent($args[1]);
6430
 
6431
- $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
6432
  $color[4] = min(1, max(0, $color[4]));
6433
 
6434
  return $color;
@@ -6443,144 +8616,121 @@ class Compiler
6443
  protected static $libUnquote = ['string'];
6444
  protected function libUnquote($args)
6445
  {
6446
- $str = $args[0];
 
 
 
 
 
 
 
 
6447
 
6448
- if ($str[0] === Type::T_STRING) {
6449
- $str[1] = '';
 
6450
  }
6451
 
 
 
6452
  return $str;
6453
  }
6454
 
6455
  protected static $libQuote = ['string'];
6456
  protected function libQuote($args)
6457
  {
6458
- $value = $args[0];
6459
 
6460
- if ($value[0] === Type::T_STRING && ! empty($value[1])) {
6461
- return $value;
6462
- }
6463
 
6464
- return [Type::T_STRING, '"', [$value]];
6465
  }
6466
 
6467
  protected static $libPercentage = ['number'];
6468
  protected function libPercentage($args)
6469
  {
6470
- return new Node\Number($this->coercePercent($args[0]) * 100, '%');
 
 
 
6471
  }
6472
 
6473
  protected static $libRound = ['number'];
6474
  protected function libRound($args)
6475
  {
6476
- $num = $args[0];
6477
 
6478
- return new Node\Number(round($num[1]), $num[2]);
6479
  }
6480
 
6481
  protected static $libFloor = ['number'];
6482
  protected function libFloor($args)
6483
  {
6484
- $num = $args[0];
6485
 
6486
- return new Node\Number(floor($num[1]), $num[2]);
6487
  }
6488
 
6489
  protected static $libCeil = ['number'];
6490
  protected function libCeil($args)
6491
  {
6492
- $num = $args[0];
6493
 
6494
- return new Node\Number(ceil($num[1]), $num[2]);
6495
  }
6496
 
6497
  protected static $libAbs = ['number'];
6498
  protected function libAbs($args)
6499
  {
6500
- $num = $args[0];
6501
 
6502
- return new Node\Number(abs($num[1]), $num[2]);
6503
  }
6504
 
 
6505
  protected function libMin($args)
6506
  {
6507
- $numbers = $this->getNormalizedNumbers($args);
6508
- $minOriginal = null;
6509
- $minNormalized = null;
 
6510
 
6511
- foreach ($numbers as $key => $pair) {
6512
- list($original, $normalized) = $pair;
6513
 
6514
- if (\is_null($normalized) or \is_null($minNormalized)) {
6515
- if (\is_null($minOriginal) || $original[1] <= $minOriginal[1]) {
6516
- $minOriginal = $original;
6517
- $minNormalized = $normalized;
6518
- }
6519
- } elseif ($normalized[1] <= $minNormalized[1]) {
6520
- $minOriginal = $original;
6521
- $minNormalized = $normalized;
6522
  }
6523
  }
6524
 
6525
- return $minOriginal;
6526
- }
6527
-
6528
- protected function libMax($args)
6529
- {
6530
- $numbers = $this->getNormalizedNumbers($args);
6531
- $maxOriginal = null;
6532
- $maxNormalized = null;
6533
-
6534
- foreach ($numbers as $key => $pair) {
6535
- list($original, $normalized) = $pair;
6536
-
6537
- if (\is_null($normalized) or \is_null($maxNormalized)) {
6538
- if (\is_null($maxOriginal) || $original[1] >= $maxOriginal[1]) {
6539
- $maxOriginal = $original;
6540
- $maxNormalized = $normalized;
6541
- }
6542
- } elseif ($normalized[1] >= $maxNormalized[1]) {
6543
- $maxOriginal = $original;
6544
- $maxNormalized = $normalized;
6545
- }
6546
  }
6547
 
6548
- return $maxOriginal;
6549
  }
6550
 
6551
- /**
6552
- * Helper to normalize args containing numbers
6553
- *
6554
- * @param array $args
6555
- *
6556
- * @return array
6557
- */
6558
- protected function getNormalizedNumbers($args)
6559
  {
6560
- $unit = null;
6561
- $originalUnit = null;
6562
- $numbers = [];
6563
-
6564
- foreach ($args as $key => $item) {
6565
- if ($item[0] !== Type::T_NUMBER) {
6566
- $this->throwError('%s is not a number', $item[0]);
6567
- break;
6568
- }
6569
 
6570
- $number = $item->normalize();
 
6571
 
6572
- if (empty($unit)) {
6573
- $unit = $number[2];
6574
- $originalUnit = $item->unitStr();
6575
- } elseif ($number[1] && $unit !== $number[2] && ! empty($number[2])) {
6576
- $this->throwError('Incompatible units: "%s" and "%s".', $originalUnit, $item->unitStr());
6577
- break;
6578
  }
 
6579
 
6580
- $numbers[$key] = [$args[$key], empty($number[2]) ? null : $number];
 
6581
  }
6582
 
6583
- return $numbers;
6584
  }
6585
 
6586
  protected static $libLength = ['list'];
@@ -6588,34 +8738,38 @@ class Compiler
6588
  {
6589
  $list = $this->coerceList($args[0], ',', true);
6590
 
6591
- return \count($list[2]);
6592
  }
6593
 
6594
- //protected static $libListSeparator = ['list...'];
6595
  protected function libListSeparator($args)
6596
  {
6597
- if (\count($args) > 1) {
6598
- return 'comma';
6599
  }
6600
 
6601
  $list = $this->coerceList($args[0]);
6602
 
6603
- if (\count($list[2]) <= 1) {
6604
- return 'space';
6605
  }
6606
 
6607
  if ($list[1] === ',') {
6608
- return 'comma';
 
 
 
 
6609
  }
6610
 
6611
- return 'space';
6612
  }
6613
 
6614
  protected static $libNth = ['list', 'n'];
6615
  protected function libNth($args)
6616
  {
6617
  $list = $this->coerceList($args[0], ',', false);
6618
- $n = $this->assertNumber($args[1]);
6619
 
6620
  if ($n > 0) {
6621
  $n--;
@@ -6630,7 +8784,7 @@ class Compiler
6630
  protected function libSetNth($args)
6631
  {
6632
  $list = $this->coerceList($args[0]);
6633
- $n = $this->assertNumber($args[1]);
6634
 
6635
  if ($n > 0) {
6636
  $n--;
@@ -6639,39 +8793,86 @@ class Compiler
6639
  }
6640
 
6641
  if (! isset($list[2][$n])) {
6642
- $this->throwError('Invalid argument for "n"');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6643
 
6644
- return null;
 
6645
  }
6646
 
6647
- $list[2][$n] = $args[2];
6648
-
6649
- return $list;
6650
  }
6651
 
6652
- protected static $libMapGet = ['map', 'key'];
6653
- protected function libMapGet($args)
 
 
 
 
 
 
 
6654
  {
6655
- $map = $this->assertMap($args[0]);
6656
- $key = $args[1];
6657
 
6658
- if (! \is_null($key)) {
6659
- $key = $this->compileStringContent($this->coerceString($key));
6660
-
6661
- for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
6662
- if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
6663
- return $map[2][$i];
6664
- }
6665
  }
6666
  }
6667
 
6668
- return static::$null;
6669
  }
6670
 
6671
  protected static $libMapKeys = ['map'];
6672
  protected function libMapKeys($args)
6673
  {
6674
- $map = $this->assertMap($args[0]);
6675
  $keys = $map[1];
6676
 
6677
  return [Type::T_LIST, ',', $keys];
@@ -6680,20 +8881,33 @@ class Compiler
6680
  protected static $libMapValues = ['map'];
6681
  protected function libMapValues($args)
6682
  {
6683
- $map = $this->assertMap($args[0]);
6684
  $values = $map[2];
6685
 
6686
  return [Type::T_LIST, ',', $values];
6687
  }
6688
 
6689
- protected static $libMapRemove = ['map', 'key'];
 
 
 
6690
  protected function libMapRemove($args)
6691
  {
6692
- $map = $this->assertMap($args[0]);
6693
- $key = $this->compileStringContent($this->coerceString($args[1]));
 
 
 
 
 
 
 
 
 
 
6694
 
6695
  for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
6696
- if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
6697
  array_splice($map[1], $i, 1);
6698
  array_splice($map[2], $i, 1);
6699
  }
@@ -6702,11 +8916,38 @@ class Compiler
6702
  return $map;
6703
  }
6704
 
6705
- protected static $libMapHasKey = ['map', 'key'];
6706
  protected function libMapHasKey($args)
6707
  {
6708
- $map = $this->assertMap($args[0]);
6709
- $key = $this->compileStringContent($this->coerceString($args[1]));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6710
 
6711
  for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
6712
  if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
@@ -6717,23 +8958,129 @@ class Compiler
6717
  return false;
6718
  }
6719
 
6720
- protected static $libMapMerge = ['map-1', 'map-2'];
 
 
 
 
6721
  protected function libMapMerge($args)
6722
  {
6723
- $map1 = $this->assertMap($args[0]);
6724
- $map2 = $this->assertMap($args[1]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6725
 
 
 
 
 
 
 
 
 
 
 
6726
  foreach ($map2[1] as $i2 => $key2) {
6727
- $key = $this->compileStringContent($this->coerceString($key2));
6728
 
6729
- foreach ($map1[1] as $i1 => $key1) {
6730
- if ($key === $this->compileStringContent($this->coerceString($key1))) {
6731
- $map1[2][$i1] = $map2[2][$i2];
6732
- continue 2;
6733
- }
6734
  }
6735
 
6736
- $map1[1][] = $map2[1][$i2];
6737
  $map1[2][] = $map2[2][$i2];
6738
  }
6739
 
@@ -6743,12 +9090,18 @@ class Compiler
6743
  protected static $libKeywords = ['args'];
6744
  protected function libKeywords($args)
6745
  {
6746
- $this->assertList($args[0]);
 
 
 
 
 
 
6747
 
6748
  $keys = [];
6749
  $values = [];
6750
 
6751
- foreach ($args[0][2] as $name => $arg) {
6752
  $keys[] = [Type::T_KEYWORD, $name];
6753
  $values[] = $arg;
6754
  }
@@ -6763,14 +9116,25 @@ class Compiler
6763
  $this->coerceList($list, ' ');
6764
 
6765
  if (! empty($list['enclosing']) && $list['enclosing'] === 'bracket') {
6766
- return true;
6767
  }
6768
 
6769
- return false;
6770
  }
6771
 
 
 
 
 
 
 
 
 
 
6772
  protected function listSeparatorForJoin($list1, $sep)
6773
  {
 
 
6774
  if (! isset($sep)) {
6775
  return $list1[1];
6776
  }
@@ -6787,14 +9151,40 @@ class Compiler
6787
  }
6788
  }
6789
 
6790
- protected static $libJoin = ['list1', 'list2', 'separator:null', 'bracketed:auto'];
6791
  protected function libJoin($args)
6792
  {
6793
  list($list1, $list2, $sep, $bracketed) = $args;
6794
 
6795
  $list1 = $this->coerceList($list1, ' ', true);
6796
  $list2 = $this->coerceList($list2, ' ', true);
6797
- $sep = $this->listSeparatorForJoin($list1, $sep);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6798
 
6799
  if ($bracketed === static::$true) {
6800
  $bracketed = true;
@@ -6821,11 +9211,7 @@ class Compiler
6821
  }
6822
  }
6823
 
6824
- $res = [Type::T_LIST, $sep, array_merge($list1[2], $list2[2])];
6825
-
6826
- if (isset($list1['enclosing'])) {
6827
- $res['enlcosing'] = $list1['enclosing'];
6828
- }
6829
 
6830
  if ($bracketed) {
6831
  $res['enclosing'] = 'bracket';
@@ -6834,14 +9220,35 @@ class Compiler
6834
  return $res;
6835
  }
6836
 
6837
- protected static $libAppend = ['list', 'val', 'separator:null'];
6838
  protected function libAppend($args)
6839
  {
6840
  list($list1, $value, $sep) = $args;
6841
 
6842
  $list1 = $this->coerceList($list1, ' ', true);
6843
- $sep = $this->listSeparatorForJoin($list1, $sep);
6844
- $res = [Type::T_LIST, $sep, array_merge($list1[2], [$value])];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6845
 
6846
  if (isset($list1['enclosing'])) {
6847
  $res['enclosing'] = $list1['enclosing'];
@@ -6850,30 +9257,39 @@ class Compiler
6850
  return $res;
6851
  }
6852
 
 
6853
  protected function libZip($args)
6854
  {
6855
- foreach ($args as $key => $arg) {
6856
- $args[$key] = $this->coerceList($arg);
 
6857
  }
6858
 
6859
  $lists = [];
6860
- $firstList = array_shift($args);
6861
 
6862
- foreach ($firstList[2] as $key => $item) {
6863
- $list = [Type::T_LIST, '', [$item]];
 
 
6864
 
6865
- foreach ($args as $arg) {
6866
- if (isset($arg[2][$key])) {
6867
- $list[2][] = $arg[2][$key];
6868
- } else {
6869
- break 2;
 
6870
  }
 
 
6871
  }
6872
 
6873
- $lists[] = $list;
 
 
6874
  }
6875
 
6876
- return [Type::T_LIST, ',', $lists];
6877
  }
6878
 
6879
  protected static $libTypeOf = ['value'];
@@ -6881,6 +9297,16 @@ class Compiler
6881
  {
6882
  $value = $args[0];
6883
 
 
 
 
 
 
 
 
 
 
 
6884
  switch ($value[0]) {
6885
  case Type::T_KEYWORD:
6886
  if ($value === static::$true || $value === static::$false) {
@@ -6895,8 +9321,11 @@ class Compiler
6895
  case Type::T_FUNCTION:
6896
  return 'string';
6897
 
 
 
 
6898
  case Type::T_LIST:
6899
- if (isset($value[3]) && $value[3]) {
6900
  return 'arglist';
6901
  }
6902
 
@@ -6909,68 +9338,77 @@ class Compiler
6909
  protected static $libUnit = ['number'];
6910
  protected function libUnit($args)
6911
  {
6912
- $num = $args[0];
6913
-
6914
- if ($num[0] === Type::T_NUMBER) {
6915
- return [Type::T_STRING, '"', [$num->unitStr()]];
6916
- }
6917
 
6918
- return '';
6919
  }
6920
 
6921
  protected static $libUnitless = ['number'];
6922
  protected function libUnitless($args)
6923
  {
6924
- $value = $args[0];
6925
 
6926
- return $value[0] === Type::T_NUMBER && $value->unitless();
6927
  }
6928
 
6929
- protected static $libComparable = ['number-1', 'number-2'];
 
 
 
6930
  protected function libComparable($args)
6931
  {
6932
  list($number1, $number2) = $args;
6933
 
6934
- if (! isset($number1[0]) || $number1[0] !== Type::T_NUMBER ||
6935
- ! isset($number2[0]) || $number2[0] !== Type::T_NUMBER
 
6936
  ) {
6937
- $this->throwError('Invalid argument(s) for "comparable"');
6938
-
6939
- return null;
6940
  }
6941
 
6942
- $number1 = $number1->normalize();
6943
- $number2 = $number2->normalize();
6944
-
6945
- return $number1[2] === $number2[2] || $number1->unitless() || $number2->unitless();
6946
  }
6947
 
6948
  protected static $libStrIndex = ['string', 'substring'];
6949
  protected function libStrIndex($args)
6950
  {
6951
- $string = $this->coerceString($args[0]);
6952
  $stringContent = $this->compileStringContent($string);
6953
 
6954
- $substring = $this->coerceString($args[1]);
6955
  $substringContent = $this->compileStringContent($substring);
6956
 
6957
- $result = strpos($stringContent, $substringContent);
 
 
 
 
6958
 
6959
- return $result === false ? static::$null : new Node\Number($result + 1, '');
6960
  }
6961
 
6962
  protected static $libStrInsert = ['string', 'insert', 'index'];
6963
  protected function libStrInsert($args)
6964
  {
6965
- $string = $this->coerceString($args[0]);
6966
  $stringContent = $this->compileStringContent($string);
6967
 
6968
- $insert = $this->coerceString($args[1]);
6969
  $insertContent = $this->compileStringContent($insert);
6970
 
6971
- list(, $index) = $args[2];
 
 
 
 
 
 
6972
 
6973
- $string[2] = [substr_replace($stringContent, $insertContent, $index - 1, 0)];
 
 
 
 
6974
 
6975
  return $string;
6976
  }
@@ -6978,34 +9416,46 @@ class Compiler
6978
  protected static $libStrLength = ['string'];
6979
  protected function libStrLength($args)
6980
  {
6981
- $string = $this->coerceString($args[0]);
6982
  $stringContent = $this->compileStringContent($string);
6983
 
6984
- return new Node\Number(\strlen($stringContent), '');
6985
  }
6986
 
6987
  protected static $libStrSlice = ['string', 'start-at', 'end-at:-1'];
6988
  protected function libStrSlice($args)
6989
  {
6990
- if (isset($args[2]) && ! $args[2][1]) {
6991
- return static::$nullString;
 
 
 
 
 
 
 
 
 
 
6992
  }
6993
 
6994
- $string = $this->coerceString($args[0]);
6995
- $stringContent = $this->compileStringContent($string);
 
6996
 
6997
- $start = (int) $args[1][1];
 
 
 
 
6998
 
6999
- if ($start > 0) {
7000
- $start--;
7001
  }
7002
 
7003
- $end = isset($args[2]) ? (int) $args[2][1] : -1;
7004
- $length = $end < 0 ? $end + 1 : ($end > 0 ? $end - $start : $end);
7005
 
7006
- $string[2] = $length
7007
- ? [substr($stringContent, $start, $length)]
7008
- : [substr($stringContent, $start)];
7009
 
7010
  return $string;
7011
  }
@@ -7013,10 +9463,10 @@ class Compiler
7013
  protected static $libToLowerCase = ['string'];
7014
  protected function libToLowerCase($args)
7015
  {
7016
- $string = $this->coerceString($args[0]);
7017
  $stringContent = $this->compileStringContent($string);
7018
 
7019
- $string[2] = [\function_exists('mb_strtolower') ? mb_strtolower($stringContent) : strtolower($stringContent)];
7020
 
7021
  return $string;
7022
  }
@@ -7024,18 +9474,45 @@ class Compiler
7024
  protected static $libToUpperCase = ['string'];
7025
  protected function libToUpperCase($args)
7026
  {
7027
- $string = $this->coerceString($args[0]);
7028
  $stringContent = $this->compileStringContent($string);
7029
 
7030
- $string[2] = [\function_exists('mb_strtoupper') ? mb_strtoupper($stringContent) : strtoupper($stringContent)];
7031
 
7032
  return $string;
7033
  }
7034
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7035
  protected static $libFeatureExists = ['feature'];
7036
  protected function libFeatureExists($args)
7037
  {
7038
- $string = $this->coerceString($args[0]);
7039
  $name = $this->compileStringContent($string);
7040
 
7041
  return $this->toBool(
@@ -7046,18 +9523,18 @@ class Compiler
7046
  protected static $libFunctionExists = ['name'];
7047
  protected function libFunctionExists($args)
7048
  {
7049
- $string = $this->coerceString($args[0]);
7050
  $name = $this->compileStringContent($string);
7051
 
7052
  // user defined functions
7053
  if ($this->has(static::$namespaces['function'] . $name)) {
7054
- return true;
7055
  }
7056
 
7057
  $name = $this->normalizeName($name);
7058
 
7059
  if (isset($this->userFunctions[$name])) {
7060
- return true;
7061
  }
7062
 
7063
  // built-in functions
@@ -7069,30 +9546,31 @@ class Compiler
7069
  protected static $libGlobalVariableExists = ['name'];
7070
  protected function libGlobalVariableExists($args)
7071
  {
7072
- $string = $this->coerceString($args[0]);
7073
  $name = $this->compileStringContent($string);
7074
 
7075
- return $this->has($name, $this->rootEnv);
7076
  }
7077
 
7078
  protected static $libMixinExists = ['name'];
7079
  protected function libMixinExists($args)
7080
  {
7081
- $string = $this->coerceString($args[0]);
7082
  $name = $this->compileStringContent($string);
7083
 
7084
- return $this->has(static::$namespaces['mixin'] . $name);
7085
  }
7086
 
7087
  protected static $libVariableExists = ['name'];
7088
  protected function libVariableExists($args)
7089
  {
7090
- $string = $this->coerceString($args[0]);
7091
  $name = $this->compileStringContent($string);
7092
 
7093
- return $this->has($name);
7094
  }
7095
 
 
7096
  /**
7097
  * Workaround IE7's content counter bug.
7098
  *
@@ -7102,41 +9580,37 @@ class Compiler
7102
  */
7103
  protected function libCounter($args)
7104
  {
7105
- $list = array_map([$this, 'compileValue'], $args);
7106
 
7107
  return [Type::T_STRING, '', ['counter(' . implode(',', $list) . ')']];
7108
  }
7109
 
7110
- protected static $libRandom = ['limit:1'];
7111
  protected function libRandom($args)
7112
  {
7113
- if (isset($args[0])) {
7114
- $n = $this->assertNumber($args[0]);
7115
 
7116
  if ($n < 1) {
7117
- $this->throwError("\$limit must be greater than or equal to 1");
7118
-
7119
- return null;
7120
- }
7121
-
7122
- if ($n - \intval($n) > 0) {
7123
- $this->throwError("Expected \$limit to be an integer but got $n for `random`");
7124
-
7125
- return null;
7126
  }
7127
 
7128
- return new Node\Number(mt_rand(1, \intval($n)), '');
7129
  }
7130
 
7131
- return new Node\Number(mt_rand(1, mt_getrandmax()), '');
 
7132
  }
7133
 
 
7134
  protected function libUniqueId()
7135
  {
7136
  static $id;
7137
 
7138
  if (! isset($id)) {
7139
- $id = mt_rand(0, pow(36, 8));
 
 
7140
  }
7141
 
7142
  $id += mt_rand(0, 10) + 1;
@@ -7144,6 +9618,12 @@ class Compiler
7144
  return [Type::T_STRING, '', ['u' . str_pad(base_convert($id, 10, 36), 8, '0', STR_PAD_LEFT)]];
7145
  }
7146
 
 
 
 
 
 
 
7147
  protected function inspectFormatValue($value, $force_enclosing_display = false)
7148
  {
7149
  if ($value === static::$null) {
@@ -7152,6 +9632,10 @@ class Compiler
7152
 
7153
  $stringValue = [$value];
7154
 
 
 
 
 
7155
  if ($value[0] === Type::T_LIST) {
7156
  if (end($value[2]) === static::$null) {
7157
  array_pop($value[2]);
@@ -7159,13 +9643,16 @@ class Compiler
7159
  $force_enclosing_display = true;
7160
  }
7161
 
7162
- if (! empty($value['enclosing']) &&
 
7163
  ($force_enclosing_display ||
7164
  ($value['enclosing'] === 'bracket') ||
7165
  ! \count($value[2]))
7166
  ) {
7167
- $value['enclosing'] = 'forced_'.$value['enclosing'];
7168
  $force_enclosing_display = true;
 
 
7169
  }
7170
 
7171
  foreach ($value[2] as $k => $listelement) {
@@ -7189,11 +9676,13 @@ class Compiler
7189
  /**
7190
  * Preprocess selector args
7191
  *
7192
- * @param array $arg
 
 
7193
  *
7194
- * @return array|boolean
7195
  */
7196
- protected function getSelectorArg($arg)
7197
  {
7198
  static $parser = null;
7199
 
@@ -7201,19 +9690,59 @@ class Compiler
7201
  $parser = $this->parserFactory(__METHOD__);
7202
  }
7203
 
7204
- $arg = $this->libUnquote([$arg]);
 
 
 
 
 
 
 
 
7205
  $arg = $this->compileValue($arg);
7206
 
7207
  $parsedSelector = [];
7208
 
7209
- if ($parser->parseSelector($arg, $parsedSelector)) {
7210
  $selector = $this->evalSelectors($parsedSelector);
7211
  $gluedSelector = $this->glueFunctionSelectors($selector);
7212
 
 
 
 
 
 
 
 
 
 
 
7213
  return $gluedSelector;
7214
  }
7215
 
7216
- return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7217
  }
7218
 
7219
  /**
@@ -7221,11 +9750,11 @@ class Compiler
7221
  *
7222
  * @param array $selectors
7223
  *
7224
- * @return string
7225
  */
7226
  protected function formatOutputSelector($selectors)
7227
  {
7228
- $selectors = $this->collapseSelectors($selectors, true);
7229
 
7230
  return $selectors;
7231
  }
@@ -7235,10 +9764,10 @@ class Compiler
7235
  {
7236
  list($super, $sub) = $args;
7237
 
7238
- $super = $this->getSelectorArg($super);
7239
- $sub = $this->getSelectorArg($sub);
7240
 
7241
- return $this->isSuperSelector($super, $sub);
7242
  }
7243
 
7244
  /**
@@ -7247,17 +9776,35 @@ class Compiler
7247
  * @param array $super
7248
  * @param array $sub
7249
  *
7250
- * @return boolean
7251
  */
7252
  protected function isSuperSelector($super, $sub)
7253
  {
7254
  // one and only one selector for each arg
7255
- if (! $super || \count($super) !== 1) {
7256
- $this->throwError("Invalid super selector for isSuperSelector()");
 
 
 
 
 
 
 
 
 
 
 
 
 
7257
  }
7258
 
7259
- if (! $sub || \count($sub) !== 1) {
7260
- $this->throwError("Invalid sub selector for isSuperSelector()");
 
 
 
 
 
7261
  }
7262
 
7263
  $super = reset($super);
@@ -7310,7 +9857,7 @@ class Compiler
7310
  * @param array $superParts
7311
  * @param array $subParts
7312
  *
7313
- * @return boolean
7314
  */
7315
  protected function isSuperPart($superParts, $subParts)
7316
  {
@@ -7339,10 +9886,13 @@ class Compiler
7339
  $args = $args[2];
7340
 
7341
  if (\count($args) < 1) {
7342
- $this->throwError("selector-append() needs at least 1 argument");
7343
  }
7344
 
7345
- $selectors = array_map([$this, 'getSelectorArg'], $args);
 
 
 
7346
 
7347
  return $this->formatOutputSelector($this->selectorAppend($selectors));
7348
  }
@@ -7361,34 +9911,31 @@ class Compiler
7361
  $lastSelectors = array_pop($selectors);
7362
 
7363
  if (! $lastSelectors) {
7364
- $this->throwError("Invalid selector list in selector-append()");
7365
  }
7366
 
7367
  while (\count($selectors)) {
7368
  $previousSelectors = array_pop($selectors);
7369
 
7370
  if (! $previousSelectors) {
7371
- $this->throwError("Invalid selector list in selector-append()");
7372
  }
7373
 
7374
  // do the trick, happening $lastSelector to $previousSelector
7375
  $appended = [];
7376
 
7377
- foreach ($lastSelectors as $lastSelector) {
7378
- $previous = $previousSelectors;
7379
-
7380
- foreach ($lastSelector as $lastSelectorParts) {
7381
- foreach ($lastSelectorParts as $lastSelectorPart) {
7382
- foreach ($previous as $i => $previousSelector) {
7383
- foreach ($previousSelector as $j => $previousSelectorParts) {
7384
- $previous[$i][$j][] = $lastSelectorPart;
7385
  }
7386
  }
7387
  }
7388
- }
7389
 
7390
- foreach ($previous as $ps) {
7391
- $appended[] = $ps;
7392
  }
7393
  }
7394
 
@@ -7398,17 +9945,20 @@ class Compiler
7398
  return $lastSelectors;
7399
  }
7400
 
7401
- protected static $libSelectorExtend = ['selectors', 'extendee', 'extender'];
 
 
 
7402
  protected function libSelectorExtend($args)
7403
  {
7404
  list($selectors, $extendee, $extender) = $args;
7405
 
7406
- $selectors = $this->getSelectorArg($selectors);
7407
- $extendee = $this->getSelectorArg($extendee);
7408
- $extender = $this->getSelectorArg($extender);
7409
 
7410
  if (! $selectors || ! $extendee || ! $extender) {
7411
- $this->throwError("selector-extend() invalid arguments");
7412
  }
7413
 
7414
  $extended = $this->extendOrReplaceSelectors($selectors, $extendee, $extender);
@@ -7416,17 +9966,20 @@ class Compiler
7416
  return $this->formatOutputSelector($extended);
7417
  }
7418
 
7419
- protected static $libSelectorReplace = ['selectors', 'original', 'replacement'];
 
 
 
7420
  protected function libSelectorReplace($args)
7421
  {
7422
  list($selectors, $original, $replacement) = $args;
7423
 
7424
- $selectors = $this->getSelectorArg($selectors);
7425
- $original = $this->getSelectorArg($original);
7426
- $replacement = $this->getSelectorArg($replacement);
7427
 
7428
  if (! $selectors || ! $original || ! $replacement) {
7429
- $this->throwError("selector-replace() invalid arguments");
7430
  }
7431
 
7432
  $replaced = $this->extendOrReplaceSelectors($selectors, $original, $replacement, true);
@@ -7438,10 +9991,10 @@ class Compiler
7438
  * Extend/replace in selectors
7439
  * used by selector-extend and selector-replace that use the same logic
7440
  *
7441
- * @param array $selectors
7442
- * @param array $extendee
7443
- * @param array $extender
7444
- * @param boolean $replace
7445
  *
7446
  * @return array
7447
  */
@@ -7454,6 +10007,10 @@ class Compiler
7454
  $this->extendsMap = [];
7455
 
7456
  foreach ($extendee as $es) {
 
 
 
 
7457
  // only use the first one
7458
  $this->pushExtends(reset($es), $extender, null);
7459
  }
@@ -7470,7 +10027,7 @@ class Compiler
7470
  $this->matchExtends($selector, $extended);
7471
 
7472
  // if didnt match, keep the original selector if we are in a replace operation
7473
- if ($replace and \count($extended) === $n) {
7474
  $extended[] = $selector;
7475
  }
7476
  }
@@ -7489,10 +10046,16 @@ class Compiler
7489
  $args = $args[2];
7490
 
7491
  if (\count($args) < 1) {
7492
- $this->throwError("selector-nest() needs at least 1 argument");
 
 
 
 
 
7493
  }
7494
 
7495
- $selectorsMap = array_map([$this, 'getSelectorArg'], $args);
 
7496
  $envs = [];
7497
 
7498
  foreach ($selectorsMap as $selectors) {
@@ -7509,11 +10072,14 @@ class Compiler
7509
  return $this->formatOutputSelector($outputSelectors);
7510
  }
7511
 
7512
- protected static $libSelectorParse = ['selectors'];
 
 
 
7513
  protected function libSelectorParse($args)
7514
  {
7515
  $selectors = reset($args);
7516
- $selectors = $this->getSelectorArg($selectors);
7517
 
7518
  return $this->formatOutputSelector($selectors);
7519
  }
@@ -7523,11 +10089,11 @@ class Compiler
7523
  {
7524
  list($selectors1, $selectors2) = $args;
7525
 
7526
- $selectors1 = $this->getSelectorArg($selectors1);
7527
- $selectors2 = $this->getSelectorArg($selectors2);
7528
 
7529
  if (! $selectors1 || ! $selectors2) {
7530
- $this->throwError("selector-unify() invalid arguments");
7531
  }
7532
 
7533
  // only consider the first compound of each
@@ -7547,7 +10113,7 @@ class Compiler
7547
  * @param array $compound1
7548
  * @param array $compound2
7549
  *
7550
- * @return array|mixed
7551
  */
7552
  protected function unifyCompoundSelectors($compound1, $compound2)
7553
  {
@@ -7663,7 +10229,7 @@ class Compiler
7663
  * @param array $part
7664
  * @param array $compound
7665
  *
7666
- * @return array|boolean
7667
  */
7668
  protected function matchPartInCompound($part, $compound)
7669
  {
@@ -7758,7 +10324,7 @@ class Compiler
7758
  * @param string $tag1
7759
  * @param string $tag2
7760
  *
7761
- * @return array|boolean
7762
  */
7763
  protected function checkCompatibleTags($tag1, $tag2)
7764
  {
@@ -7781,9 +10347,9 @@ class Compiler
7781
  /**
7782
  * Find the html tag name in a selector parts list
7783
  *
7784
- * @param array $parts
7785
  *
7786
- * @return mixed|string
7787
  */
7788
  protected function findTagName($parts)
7789
  {
@@ -7800,7 +10366,7 @@ class Compiler
7800
  protected function libSimpleSelectors($args)
7801
  {
7802
  $selector = reset($args);
7803
- $selector = $this->getSelectorArg($selector);
7804
 
7805
  // remove selectors list layer, keeping the first one
7806
  $selector = reset($selector);
@@ -7820,7 +10386,11 @@ class Compiler
7820
  protected static $libScssphpGlob = ['pattern'];
7821
  protected function libScssphpGlob($args)
7822
  {
7823
- $string = $this->coerceString($args[0]);
 
 
 
 
7824
  $pattern = $this->compileStringContent($string);
7825
  $matches = glob($pattern);
7826
  $listParts = [];
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
13
  namespace ScssPhp\ScssPhp;
14
 
15
  use ScssPhp\ScssPhp\Base\Range;
16
+ use ScssPhp\ScssPhp\Block\AtRootBlock;
17
+ use ScssPhp\ScssPhp\Block\CallableBlock;
18
+ use ScssPhp\ScssPhp\Block\DirectiveBlock;
19
+ use ScssPhp\ScssPhp\Block\EachBlock;
20
+ use ScssPhp\ScssPhp\Block\ElseBlock;
21
+ use ScssPhp\ScssPhp\Block\ElseifBlock;
22
+ use ScssPhp\ScssPhp\Block\ForBlock;
23
+ use ScssPhp\ScssPhp\Block\IfBlock;
24
+ use ScssPhp\ScssPhp\Block\MediaBlock;
25
+ use ScssPhp\ScssPhp\Block\NestedPropertyBlock;
26
+ use ScssPhp\ScssPhp\Block\WhileBlock;
27
+ use ScssPhp\ScssPhp\Compiler\CachedResult;
28
  use ScssPhp\ScssPhp\Compiler\Environment;
29
  use ScssPhp\ScssPhp\Exception\CompilerException;
30
+ use ScssPhp\ScssPhp\Exception\ParserException;
31
+ use ScssPhp\ScssPhp\Exception\SassException;
32
+ use ScssPhp\ScssPhp\Exception\SassScriptException;
33
+ use ScssPhp\ScssPhp\Formatter\Compressed;
34
+ use ScssPhp\ScssPhp\Formatter\Expanded;
35
  use ScssPhp\ScssPhp\Formatter\OutputBlock;
36
+ use ScssPhp\ScssPhp\Logger\LoggerInterface;
37
+ use ScssPhp\ScssPhp\Logger\StreamLogger;
38
+ use ScssPhp\ScssPhp\Node\Number;
39
  use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
40
+ use ScssPhp\ScssPhp\Util\Path;
 
 
41
 
42
  /**
43
  * The scss compiler and parser.
70
  * SCSS compiler
71
  *
72
  * @author Leaf Corcoran <leafot@gmail.com>
73
+ *
74
+ * @final Extending the Compiler is deprecated
75
  */
76
  class Compiler
77
  {
78
+ /**
79
+ * @deprecated
80
+ */
81
  const LINE_COMMENTS = 1;
82
+ /**
83
+ * @deprecated
84
+ */
85
  const DEBUG_INFO = 2;
86
 
87
+ /**
88
+ * @deprecated
89
+ */
90
  const WITH_RULE = 1;
91
+ /**
92
+ * @deprecated
93
+ */
94
  const WITH_MEDIA = 2;
95
+ /**
96
+ * @deprecated
97
+ */
98
  const WITH_SUPPORTS = 4;
99
+ /**
100
+ * @deprecated
101
+ */
102
  const WITH_ALL = 7;
103
 
104
  const SOURCE_MAP_NONE = 0;
106
  const SOURCE_MAP_FILE = 2;
107
 
108
  /**
109
+ * @var array<string, string>
110
  */
111
  protected static $operatorNames = [
112
  '+' => 'add',
122
 
123
  '<=' => 'lte',
124
  '>=' => 'gte',
 
125
  ];
126
 
127
  /**
128
+ * @var array<string, string>
129
  */
130
  protected static $namespaces = [
131
  'special' => '%',
135
 
136
  public static $true = [Type::T_KEYWORD, 'true'];
137
  public static $false = [Type::T_KEYWORD, 'false'];
138
+ /** @deprecated */
139
  public static $NaN = [Type::T_KEYWORD, 'NaN'];
140
+ /** @deprecated */
141
  public static $Infinity = [Type::T_KEYWORD, 'Infinity'];
142
  public static $null = [Type::T_NULL];
143
  public static $nullString = [Type::T_STRING, '', []];
148
  public static $emptyString = [Type::T_STRING, '"', []];
149
  public static $with = [Type::T_KEYWORD, 'with'];
150
  public static $without = [Type::T_KEYWORD, 'without'];
151
+ private static $emptyArgumentList = [Type::T_LIST, '', [], []];
152
 
153
+ /**
154
+ * @var array<int, string|callable>
155
+ */
156
+ protected $importPaths = [];
157
+ /**
158
+ * @var array<string, Block>
159
+ */
160
  protected $importCache = [];
161
+
162
+ /**
163
+ * @var string[]
164
+ */
165
  protected $importedFiles = [];
166
+
167
+ /**
168
+ * @var array
169
+ * @phpstan-var array<string, array{0: callable, 1: string[]|null}>
170
+ */
171
  protected $userFunctions = [];
172
+ /**
173
+ * @var array<string, mixed>
174
+ */
175
  protected $registeredVars = [];
176
+ /**
177
+ * @var array<string, bool>
178
+ */
179
  protected $registeredFeatures = [
180
  'extend-selector-pseudoclass' => false,
181
  'at-error' => true,
182
+ 'units-level-3' => true,
183
  'global-variable-shadowing' => false,
184
  ];
185
 
186
+ /**
187
+ * @var string|null
188
+ */
189
  protected $encoding = null;
190
+ /**
191
+ * @var null
192
+ * @deprecated
193
+ */
194
  protected $lineNumberStyle = null;
195
 
196
+ /**
197
+ * @var int|SourceMapGenerator
198
+ * @phpstan-var self::SOURCE_MAP_*|SourceMapGenerator
199
+ */
200
  protected $sourceMap = self::SOURCE_MAP_NONE;
201
+
202
+ /**
203
+ * @var array
204
+ * @phpstan-var array{sourceRoot?: string, sourceMapFilename?: string|null, sourceMapURL?: string|null, sourceMapWriteTo?: string|null, outputSourceFiles?: bool, sourceMapRootpath?: string, sourceMapBasepath?: string}
205
+ */
206
  protected $sourceMapOptions = [];
207
 
208
  /**
209
+ * @var bool
210
+ */
211
+ private $charset = true;
212
+
213
+ /**
214
+ * @var Formatter
215
  */
216
+ protected $formatter;
217
 
218
+ /**
219
+ * @var string
220
+ * @phpstan-var class-string<Formatter>
221
+ */
222
+ private $configuredFormatter = Expanded::class;
223
+
224
+ /**
225
+ * @var Environment
226
+ */
227
  protected $rootEnv;
228
+ /**
229
+ * @var OutputBlock|null
230
+ */
231
  protected $rootBlock;
232
 
233
  /**
234
  * @var \ScssPhp\ScssPhp\Compiler\Environment
235
  */
236
  protected $env;
237
+ /**
238
+ * @var OutputBlock|null
239
+ */
240
  protected $scope;
241
+ /**
242
+ * @var Environment|null
243
+ */
244
  protected $storeEnv;
245
+ /**
246
+ * @var bool|null
247
+ *
248
+ * @deprecated
249
+ */
250
  protected $charsetSeen;
251
+ /**
252
+ * @var array<int, string|null>
253
+ */
254
  protected $sourceNames;
255
 
256
+ /**
257
+ * @var Cache|null
258
+ */
259
  protected $cache;
260
 
261
+ /**
262
+ * @var bool
263
+ */
264
+ protected $cacheCheckImportResolutions = false;
265
+
266
+ /**
267
+ * @var int
268
+ */
269
  protected $indentLevel;
270
+ /**
271
+ * @var array[]
272
+ */
273
  protected $extends;
274
+ /**
275
+ * @var array<string, int[]>
276
+ */
277
  protected $extendsMap;
278
+
279
+ /**
280
+ * @var array<string, int>
281
+ */
282
+ protected $parsedFiles = [];
283
+
284
+ /**
285
+ * @var Parser|null
286
+ */
287
  protected $parser;
288
+ /**
289
+ * @var int|null
290
+ */
291
  protected $sourceIndex;
292
+ /**
293
+ * @var int|null
294
+ */
295
  protected $sourceLine;
296
+ /**
297
+ * @var int|null
298
+ */
299
  protected $sourceColumn;
300
+ /**
301
+ * @var bool|null
302
+ */
303
  protected $shouldEvaluate;
304
+ /**
305
+ * @var null
306
+ * @deprecated
307
+ */
308
  protected $ignoreErrors;
309
+ /**
310
+ * @var bool
311
+ */
312
  protected $ignoreCallStackMessage = false;
313
 
314
+ /**
315
+ * @var array[]
316
+ */
317
  protected $callStack = [];
318
 
319
+ /**
320
+ * @var array
321
+ * @phpstan-var list<array{currentDir: string|null, path: string, filePath: string}>
322
+ */
323
+ private $resolvedImports = [];
324
+
325
+ /**
326
+ * The directory of the currently processed file
327
+ *
328
+ * @var string|null
329
+ */
330
+ private $currentDirectory;
331
+
332
+ /**
333
+ * The directory of the input file
334
+ *
335
+ * @var string
336
+ */
337
+ private $rootDirectory;
338
+
339
+ /**
340
+ * @var bool
341
+ */
342
+ private $legacyCwdImportPath = true;
343
+
344
+ /**
345
+ * @var LoggerInterface
346
+ */
347
+ private $logger;
348
+
349
+ /**
350
+ * @var array<string, bool>
351
+ */
352
+ private $warnedChildFunctions = [];
353
+
354
  /**
355
  * Constructor
356
  *
357
  * @param array|null $cacheOptions
358
+ * @phpstan-param array{cacheDir?: string, prefix?: string, forceRefresh?: string, checkImportResolutions?: bool}|null $cacheOptions
359
  */
360
  public function __construct($cacheOptions = null)
361
  {
 
362
  $this->sourceNames = [];
363
 
364
  if ($cacheOptions) {
365
  $this->cache = new Cache($cacheOptions);
366
+ if (!empty($cacheOptions['checkImportResolutions'])) {
367
+ $this->cacheCheckImportResolutions = true;
368
+ }
369
  }
370
 
371
+ $this->logger = new StreamLogger(fopen('php://stderr', 'w'), true);
372
  }
373
 
374
  /**
375
  * Get compiler options
376
  *
377
+ * @return array<string, mixed>
378
+ *
379
+ * @internal
380
  */
381
  public function getCompileOptions()
382
  {
387
  'encoding' => $this->encoding,
388
  'sourceMap' => serialize($this->sourceMap),
389
  'sourceMapOptions' => $this->sourceMapOptions,
390
+ 'formatter' => $this->configuredFormatter,
391
+ 'legacyImportPath' => $this->legacyCwdImportPath,
392
  ];
393
 
394
  return $options;
395
  }
396
 
397
+ /**
398
+ * Sets an alternative logger.
399
+ *
400
+ * Changing the logger in the middle of the compilation is not
401
+ * supported and will result in an undefined behavior.
402
+ *
403
+ * @param LoggerInterface $logger
404
+ *
405
+ * @return void
406
+ */
407
+ public function setLogger(LoggerInterface $logger)
408
+ {
409
+ $this->logger = $logger;
410
+ }
411
+
412
  /**
413
  * Set an alternative error output stream, for testing purpose only
414
  *
415
  * @param resource $handle
416
+ *
417
+ * @return void
418
+ *
419
+ * @deprecated Use {@see setLogger} instead
420
  */
421
  public function setErrorOuput($handle)
422
  {
423
+ @trigger_error('The method "setErrorOuput" is deprecated. Use "setLogger" instead.', E_USER_DEPRECATED);
424
+
425
+ $this->logger = new StreamLogger($handle);
426
  }
427
 
428
  /**
429
  * Compile scss
430
  *
431
+ * @param string $code
432
+ * @param string|null $path
 
 
433
  *
434
  * @return string
435
+ *
436
+ * @throws SassException when the source fails to compile
437
+ *
438
+ * @deprecated Use {@see compileString} instead.
439
  */
440
  public function compile($code, $path = null)
441
  {
442
+ @trigger_error(sprintf('The "%s" method is deprecated. Use "compileString" instead.', __METHOD__), E_USER_DEPRECATED);
 
 
 
443
 
444
+ $result = $this->compileString($code, $path);
 
 
 
 
 
 
 
445
 
446
+ $sourceMap = $result->getSourceMap();
447
+
448
+ if ($sourceMap !== null) {
449
+ if ($this->sourceMap instanceof SourceMapGenerator) {
450
+ $this->sourceMap->saveMap($sourceMap);
451
+ } elseif ($this->sourceMap === self::SOURCE_MAP_FILE) {
452
+ $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
453
+ $sourceMapGenerator->saveMap($sourceMap);
454
  }
455
  }
456
 
457
+ return $result->getCss();
458
+ }
459
+
460
+ /**
461
+ * Compile scss
462
+ *
463
+ * @param string $source
464
+ * @param string|null $path
465
+ *
466
+ * @return CompilationResult
467
+ *
468
+ * @throws SassException when the source fails to compile
469
+ */
470
+ public function compileString($source, $path = null)
471
+ {
472
+ if ($this->cache) {
473
+ $cacheKey = ($path ? $path : '(stdin)') . ':' . md5($source);
474
+ $compileOptions = $this->getCompileOptions();
475
+ $cachedResult = $this->cache->getCache('compile', $cacheKey, $compileOptions);
476
+
477
+ if ($cachedResult instanceof CachedResult && $this->isFreshCachedResult($cachedResult)) {
478
+ return $cachedResult->getResult();
479
+ }
480
+ }
481
 
482
  $this->indentLevel = -1;
483
  $this->extends = [];
488
  $this->env = null;
489
  $this->scope = null;
490
  $this->storeEnv = null;
 
491
  $this->shouldEvaluate = null;
492
  $this->ignoreCallStackMessage = false;
493
+ $this->parsedFiles = [];
494
+ $this->importedFiles = [];
495
+ $this->resolvedImports = [];
496
 
497
+ if (!\is_null($path) && is_file($path)) {
498
+ $path = realpath($path) ?: $path;
499
+ $this->currentDirectory = dirname($path);
500
+ $this->rootDirectory = $this->currentDirectory;
501
+ } else {
502
+ $this->currentDirectory = null;
503
+ $this->rootDirectory = getcwd();
504
+ }
505
 
506
+ try {
507
+ $this->parser = $this->parserFactory($path);
508
+ $tree = $this->parser->parse($source);
509
+ $this->parser = null;
510
 
511
+ $this->formatter = new $this->configuredFormatter();
512
+ $this->rootBlock = null;
513
+ $this->rootEnv = $this->pushEnv($tree);
514
 
515
+ $warnCallback = function ($message, $deprecation) {
516
+ $this->logger->warn($message, $deprecation);
517
+ };
518
+ $previousWarnCallback = Warn::setCallback($warnCallback);
519
 
520
+ try {
521
+ $this->injectVariables($this->registeredVars);
522
+ $this->compileRoot($tree);
523
+ $this->popEnv();
524
+ } finally {
525
+ Warn::setCallback($previousWarnCallback);
526
  }
 
527
 
528
+ $sourceMapGenerator = null;
529
+
530
+ if ($this->sourceMap) {
531
+ if (\is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) {
532
+ $sourceMapGenerator = $this->sourceMap;
533
+ $this->sourceMap = self::SOURCE_MAP_FILE;
534
+ } elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) {
535
+ $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
536
+ }
537
+ }
538
+ assert($this->scope !== null);
539
 
540
+ $out = $this->formatter->format($this->scope, $sourceMapGenerator);
 
 
541
 
542
+ $prefix = '';
 
 
 
543
 
544
+ if ($this->charset && strlen($out) !== Util::mbStrlen($out)) {
545
+ $prefix = '@charset "UTF-8";' . "\n";
546
+ $out = $prefix . $out;
547
+ }
548
+
549
+ $sourceMap = null;
550
+
551
+ if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
552
+ assert($sourceMapGenerator !== null);
553
+ $sourceMap = $sourceMapGenerator->generateJson($prefix);
554
+ $sourceMapUrl = null;
555
+
556
+ switch ($this->sourceMap) {
557
+ case self::SOURCE_MAP_INLINE:
558
+ $sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap));
559
+ break;
560
+
561
+ case self::SOURCE_MAP_FILE:
562
+ if (isset($this->sourceMapOptions['sourceMapURL'])) {
563
+ $sourceMapUrl = $this->sourceMapOptions['sourceMapURL'];
564
+ }
565
+ break;
566
+ }
567
+
568
+ if ($sourceMapUrl !== null) {
569
+ $out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl);
570
+ }
571
  }
572
+ } catch (SassScriptException $e) {
573
+ throw new CompilerException($this->addLocationToMessage($e->getMessage()), 0, $e);
574
+ }
575
+
576
+ $includedFiles = [];
577
 
578
+ foreach ($this->resolvedImports as $resolvedImport) {
579
+ $includedFiles[$resolvedImport['filePath']] = $resolvedImport['filePath'];
580
  }
581
 
582
+ $result = new CompilationResult($out, $sourceMap, array_values($includedFiles));
583
+
584
  if ($this->cache && isset($cacheKey) && isset($compileOptions)) {
585
+ $this->cache->setCache('compile', $cacheKey, new CachedResult($result, $this->parsedFiles, $this->resolvedImports), $compileOptions);
586
+ }
587
+
588
+ // Reset state to free memory
589
+ // TODO in 2.0, reset parsedFiles as well when the getter is removed.
590
+ $this->resolvedImports = [];
591
+ $this->importedFiles = [];
592
+
593
+ return $result;
594
+ }
595
+
596
+ /**
597
+ * @param CachedResult $result
598
+ *
599
+ * @return bool
600
+ */
601
+ private function isFreshCachedResult(CachedResult $result)
602
+ {
603
+ // check if any dependency file changed since the result was compiled
604
+ foreach ($result->getParsedFiles() as $file => $mtime) {
605
+ if (! is_file($file) || filemtime($file) !== $mtime) {
606
+ return false;
607
+ }
608
+ }
609
+
610
+ if ($this->cacheCheckImportResolutions) {
611
+ $resolvedImports = [];
612
+
613
+ foreach ($result->getResolvedImports() as $import) {
614
+ $currentDir = $import['currentDir'];
615
+ $path = $import['path'];
616
+ // store the check across all the results in memory to avoid multiple findImport() on the same path
617
+ // with same context.
618
+ // this is happening in a same hit with multiple compilations (especially with big frameworks)
619
+ if (empty($resolvedImports[$currentDir][$path])) {
620
+ $resolvedImports[$currentDir][$path] = $this->findImport($path, $currentDir);
621
+ }
622
 
623
+ if ($resolvedImports[$currentDir][$path] !== $import['filePath']) {
624
+ return false;
625
+ }
626
+ }
627
  }
628
 
629
+ return true;
630
  }
631
 
632
  /**
633
  * Instantiate parser
634
  *
635
+ * @param string|null $path
636
  *
637
  * @return \ScssPhp\ScssPhp\Parser
638
  */
645
  // Otherwise, the CSS will be rendered as-is. It can even be extended!
646
  $cssOnly = false;
647
 
648
+ if ($path !== null && substr($path, -4) === '.css') {
649
  $cssOnly = true;
650
  }
651
 
652
+ $parser = new Parser($path, \count($this->sourceNames), $this->encoding, $this->cache, $cssOnly, $this->logger);
653
 
654
  $this->sourceNames[] = $path;
655
  $this->addParsedFile($path);
663
  * @param array $target
664
  * @param array $origin
665
  *
666
+ * @return bool
667
  */
668
  protected function isSelfExtend($target, $origin)
669
  {
679
  /**
680
  * Push extends
681
  *
682
+ * @param string[] $target
683
  * @param array $origin
684
  * @param array|null $block
685
+ *
686
+ * @return void
687
  */
688
  protected function pushExtends($target, $origin, $block)
689
  {
 
 
 
 
690
  $i = \count($this->extends);
691
  $this->extends[] = [$target, $origin, $block];
692
 
702
  /**
703
  * Make output block
704
  *
705
+ * @param string|null $type
706
+ * @param string[]|null $selectors
707
  *
708
  * @return \ScssPhp\ScssPhp\Formatter\OutputBlock
709
  */
710
  protected function makeOutputBlock($type, $selectors = null)
711
  {
712
+ $out = new OutputBlock();
713
  $out->type = $type;
714
  $out->lines = [];
715
  $out->children = [];
722
  $out->sourceLine = $this->env->block->sourceLine;
723
  $out->sourceColumn = $this->env->block->sourceColumn;
724
  } else {
725
+ $out->sourceName = isset($this->sourceNames[$this->sourceIndex]) ? $this->sourceNames[$this->sourceIndex] : '(stdin)';
726
+ $out->sourceLine = $this->sourceLine;
727
+ $out->sourceColumn = $this->sourceColumn;
728
  }
729
 
730
  return $out;
734
  * Compile root
735
  *
736
  * @param \ScssPhp\ScssPhp\Block $rootBlock
737
+ *
738
+ * @return void
739
  */
740
  protected function compileRoot(Block $rootBlock)
741
  {
742
  $this->rootBlock = $this->scope = $this->makeOutputBlock(Type::T_ROOT);
743
 
744
  $this->compileChildrenNoReturn($rootBlock->children, $this->scope);
745
+ assert($this->scope !== null);
746
  $this->flattenSelectors($this->scope);
747
  $this->missingSelectors();
748
  }
749
 
750
  /**
751
  * Report missing selectors
752
+ *
753
+ * @return void
754
  */
755
  protected function missingSelectors()
756
  {
770
  $origin = $this->collapseSelectors($origin);
771
 
772
  $this->sourceLine = $block[Parser::SOURCE_LINE];
773
+ throw $this->error("\"$origin\" failed to @extend \"$target\". The selector \"$target\" was not found.");
774
  }
775
  }
776
 
779
  *
780
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
781
  * @param string $parentKey
782
+ *
783
+ * @return void
784
  */
785
  protected function flattenSelectors(OutputBlock $block, $parentKey = null)
786
  {
824
  }
825
 
826
  if ($placeholderSelector && 0 === \count($block->selectors) && null !== $parentKey) {
827
+ assert($block->parent !== null);
828
  unset($block->parent->children[$parentKey]);
829
 
830
  return;
837
  }
838
 
839
  /**
840
+ * Glue parts of :not( or :nth-child( ... that are in general split in selectors parts
841
  *
842
  * @param array $parts
843
  *
854
  } else {
855
  // a selector part finishing with a ) is the last part of a :not( or :nth-child(
856
  // and need to be joined to this
857
+ if (
858
+ \count($new) && \is_string($new[\count($new) - 1]) &&
859
  \strlen($part) && substr($part, -1) === ')' && strpos($part, '(') === false
860
  ) {
861
+ while (\count($new) > 1 && substr($new[\count($new) - 1], -1) !== '(') {
862
  $part = array_pop($new) . $part;
863
  }
864
  $new[\count($new) - 1] .= $part;
874
  /**
875
  * Match extends
876
  *
877
+ * @param array $selector
878
+ * @param array $out
879
+ * @param int $from
880
+ * @param bool $initial
881
+ *
882
+ * @return void
883
  */
884
  protected function matchExtends($selector, &$out, $from = 0, $initial = true)
885
  {
924
  }
925
  }
926
 
927
+ if (\count($nonBreakableBefore) && $k === \count($new)) {
928
  $k--;
929
  }
930
 
1010
  * @param string $part
1011
  * @param array $matches
1012
  *
1013
+ * @return bool
1014
  */
1015
  protected function isPseudoSelector($part, &$matches)
1016
  {
1017
+ if (
1018
+ strpos($part, ':') === 0 &&
1019
+ preg_match(",^::?([\w-]+)\((.+)\)$,", $part, $matches)
1020
  ) {
1021
  return true;
1022
  }
1033
  *
1034
  * @param array $out
1035
  * @param array $extended
1036
+ *
1037
+ * @return void
1038
  */
1039
  protected function pushOrMergeExtentedSelector(&$out, $extended)
1040
  {
1042
  $single = reset($extended);
1043
  $part = reset($single);
1044
 
1045
+ if (
1046
+ $this->isPseudoSelector($part, $matchesExtended) &&
1047
  \in_array($matchesExtended[1], [ 'slotted' ])
1048
  ) {
1049
  $prev = end($out);
1053
  $single = reset($prev);
1054
  $part = reset($single);
1055
 
1056
+ if (
1057
+ $this->isPseudoSelector($part, $matchesPrev) &&
1058
  $matchesPrev[1] === $matchesExtended[1]
1059
  ) {
1060
  $extended = explode($matchesExtended[1] . '(', $matchesExtended[0], 2);
1061
+ $extended[1] = $matchesPrev[2] . ', ' . $extended[1];
1062
  $extended = implode($matchesExtended[1] . '(', $extended);
1063
  $extended = [ [ $extended ]];
1064
  array_pop($out);
1072
  /**
1073
  * Match extends single
1074
  *
1075
+ * @param array $rawSingle
1076
+ * @param array $outOrigin
1077
+ * @param bool $initial
1078
  *
1079
+ * @return bool
1080
  */
1081
  protected function matchExtendsSingle($rawSingle, &$outOrigin, $initial = true)
1082
  {
1118
  }
1119
  }
1120
 
1121
+ if (
1122
+ $initial &&
1123
  $this->isPseudoSelector($part, $matches) &&
1124
  ! \in_array($matches[1], [ 'not' ])
1125
  ) {
1126
  $buffer = $matches[2];
1127
  $parser = $this->parserFactory(__METHOD__);
1128
 
1129
+ if ($parser->parseSelector($buffer, $subSelectors, false)) {
1130
  foreach ($subSelectors as $ksub => $subSelector) {
1131
  $subExtended = [];
1132
  $this->matchExtends($subSelector, $subExtended, 0, false);
1141
 
1142
  $subSelectorsExtended = implode(', ', $subSelectorsExtended);
1143
  $singleExtended = $single;
1144
+ $singleExtended[$k] = str_replace('(' . $buffer . ')', "($subSelectorsExtended)", $part);
1145
  $outOrigin[] = [ $singleExtended ];
1146
  $found = true;
1147
  }
1166
 
1167
  foreach ($origin as $j => $new) {
1168
  // prevent infinite loop when target extends itself
1169
+ if ($this->isSelfExtend($single, $origin) && ! $initial) {
1170
  return false;
1171
  }
1172
 
1173
  $replacement = end($new);
1174
 
1175
  // Extending a decorated tag with another tag is not possible.
1176
+ if (
1177
+ $extendingDecoratedTag && $replacement[0] != $extendingDecoratedTag &&
1178
  preg_match('/^[a-z0-9]+$/i', $replacement[0])
1179
  ) {
1180
  unset($origin[$j]);
1245
  $wasTag = false;
1246
  $pseudo = [];
1247
 
1248
+ while (\count($other) && strpos(end($other), ':') === 0) {
1249
  array_unshift($pseudo, array_pop($other));
1250
  }
1251
 
1252
  foreach ([array_reverse($base), array_reverse($other)] as $single) {
1253
+ $rang = count($single);
1254
+
1255
  foreach ($single as $part) {
1256
  if (preg_match('/^[\[:]/', $part)) {
1257
  $out[] = $part;
1259
  } elseif (preg_match('/^[\.#]/', $part)) {
1260
  array_unshift($out, $part);
1261
  $wasTag = false;
1262
+ } elseif (preg_match('/^[^_-]/', $part) && $rang === 1) {
1263
  $tag[] = $part;
1264
  $wasTag = true;
1265
  } elseif ($wasTag) {
1266
  $tag[\count($tag) - 1] .= $part;
1267
  } else {
1268
+ array_unshift($out, $part);
1269
  }
1270
+ $rang--;
1271
  }
1272
  }
1273
 
1286
  * Compile media
1287
  *
1288
  * @param \ScssPhp\ScssPhp\Block $media
1289
+ *
1290
+ * @return void
1291
  */
1292
  protected function compileMedia(Block $media)
1293
  {
1294
+ assert($media instanceof MediaBlock);
1295
  $this->pushEnv($media);
1296
 
1297
  $mediaQueries = $this->compileMediaQuery($this->multiplyMedia($this->env));
1298
 
1299
+ if (! empty($mediaQueries)) {
1300
+ assert($this->scope !== null);
1301
  $previousScope = $this->scope;
1302
  $parentScope = $this->mediaParent($this->scope);
1303
 
1314
  foreach ($media->children as $child) {
1315
  $type = $child[0];
1316
 
1317
+ if (
1318
+ $type !== Type::T_BLOCK &&
1319
  $type !== Type::T_MEDIA &&
1320
  $type !== Type::T_DIRECTIVE &&
1321
  $type !== Type::T_IMPORT
1326
  }
1327
 
1328
  if ($needsWrap) {
1329
+ $wrapped = new Block();
1330
  $wrapped->sourceName = $media->sourceName;
1331
  $wrapped->sourceIndex = $media->sourceIndex;
1332
  $wrapped->sourceLine = $media->sourceLine;
1337
  $wrapped->children = $media->children;
1338
 
1339
  $media->children = [[Type::T_BLOCK, $wrapped]];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1340
  }
1341
 
1342
  $this->compileChildrenNoReturn($media->children, $this->scope);
1370
  /**
1371
  * Compile directive
1372
  *
1373
+ * @param DirectiveBlock|array $directive
1374
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1375
+ *
1376
+ * @return void
1377
  */
1378
  protected function compileDirective($directive, OutputBlock $out)
1379
  {
1380
  if (\is_array($directive)) {
1381
+ $directiveName = $this->compileDirectiveName($directive[0]);
1382
+ $s = '@' . $directiveName;
1383
 
1384
  if (! empty($directive[1])) {
1385
  $s .= ' ' . $this->compileValue($directive[1]);
1386
  }
1387
+ // sass-spec compliance on newline after directives, a bit tricky :/
1388
+ $appendNewLine = (! empty($directive[2]) || strpos($s, "\n")) ? "\n" : "";
1389
+ if (\is_array($directive[0]) && empty($directive[1])) {
1390
+ $appendNewLine = "\n";
1391
+ }
1392
 
1393
+ if (empty($directive[3])) {
1394
+ $this->appendRootDirective($s . ';' . $appendNewLine, $out, [Type::T_COMMENT, Type::T_DIRECTIVE]);
1395
+ } else {
1396
+ $this->appendOutputLine($out, Type::T_DIRECTIVE, $s . ';');
1397
+ }
1398
  } else {
1399
+ $directive->name = $this->compileDirectiveName($directive->name);
1400
  $s = '@' . $directive->name;
1401
 
1402
  if (! empty($directive->value)) {
1411
  }
1412
  }
1413
 
1414
+ /**
1415
+ * directive names can include some interpolation
1416
+ *
1417
+ * @param string|array $directiveName
1418
+ * @return string
1419
+ * @throws CompilerException
1420
+ */
1421
+ protected function compileDirectiveName($directiveName)
1422
+ {
1423
+ if (is_string($directiveName)) {
1424
+ return $directiveName;
1425
+ }
1426
+
1427
+ return $this->compileValue($directiveName);
1428
+ }
1429
+
1430
  /**
1431
  * Compile at-root
1432
  *
1433
  * @param \ScssPhp\ScssPhp\Block $block
1434
+ *
1435
+ * @return void
1436
  */
1437
  protected function compileAtRoot(Block $block)
1438
  {
1439
+ assert($block instanceof AtRootBlock);
1440
  $env = $this->pushEnv($block);
1441
  $envs = $this->compactEnv($env);
1442
  list($with, $without) = $this->compileWith(isset($block->with) ? $block->with : null);
1443
 
1444
  // wrap inline selector
1445
  if ($block->selector) {
1446
+ $wrapped = new Block();
1447
  $wrapped->sourceName = $block->sourceName;
1448
  $wrapped->sourceIndex = $block->sourceIndex;
1449
  $wrapped->sourceLine = $block->sourceLine;
1459
  }
1460
 
1461
  $selfParent = $block->selfParent;
1462
+ assert($selfParent !== null, 'at-root blocks must have a selfParent set.');
1463
 
1464
+ if (
1465
+ ! $selfParent->selectors &&
1466
+ isset($block->parent) &&
1467
  isset($block->parent->selectors) && $block->parent->selectors
1468
  ) {
1469
  $selfParent = $block->parent;
1471
 
1472
  $this->env = $this->filterWithWithout($envs, $with, $without);
1473
 
1474
+ assert($this->scope !== null);
1475
  $saveScope = $this->scope;
1476
  $this->scope = $this->filterScopeWithWithout($saveScope, $with, $without);
1477
 
1478
  // propagate selfParent to the children where they still can be useful
1479
  $this->compileChildrenNoReturn($block->children, $this->scope, $selfParent);
1480
 
1481
+ assert($this->scope !== null);
1482
+ $this->completeScope($this->scope, $saveScope);
1483
  $this->scope = $saveScope;
1484
  $this->env = $this->extractEnv($envs);
1485
 
1487
  }
1488
 
1489
  /**
1490
+ * Filter at-root scope depending on with/without option
1491
  *
1492
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1493
  * @param array $with
1494
  * @param array $without
1495
  *
1496
+ * @return OutputBlock
1497
  */
1498
  protected function filterScopeWithWithout($scope, $with, $without)
1499
  {
1500
  $filteredScopes = [];
1501
  $childStash = [];
1502
 
1503
+ if ($scope->type === Type::T_ROOT) {
1504
  return $scope;
1505
  }
1506
+ assert($this->rootBlock !== null);
1507
 
1508
  // start from the root
1509
+ while ($scope->parent && $scope->parent->type !== Type::T_ROOT) {
1510
  array_unshift($childStash, $scope);
1511
  $scope = $scope->parent;
1512
  }
1567
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1568
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $previousScope
1569
  *
1570
+ * @return OutputBlock
1571
  */
1572
  protected function completeScope($scope, $previousScope)
1573
  {
1574
+ if (! $scope->type && ! $scope->selectors && \count($scope->lines)) {
1575
  $scope->selectors = $this->findScopeSelectors($previousScope, $scope->depth);
1576
  }
1577
 
1588
  * Find a selector by the depth node in the scope
1589
  *
1590
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1591
+ * @param int $depth
1592
  *
1593
  * @return array
1594
  */
1612
  /**
1613
  * Compile @at-root's with: inclusion / without: exclusion into 2 lists uses to filter scope/env later
1614
  *
1615
+ * @param array|null $withCondition
1616
  *
1617
  * @return array
1618
+ *
1619
+ * @phpstan-return array{array<string, bool>, array<string, bool>}
1620
  */
1621
  protected function compileWith($withCondition)
1622
  {
1625
  $without = ['rule' => true];
1626
 
1627
  if ($withCondition) {
1628
+ if ($withCondition[0] === Type::T_INTERPOLATE) {
1629
+ $w = $this->compileValue($withCondition);
1630
+
1631
+ $buffer = "($w)";
1632
+ $parser = $this->parserFactory(__METHOD__);
1633
+
1634
+ if ($parser->parseValue($buffer, $reParsedWith)) {
1635
+ $withCondition = $reParsedWith;
1636
+ }
1637
+ }
1638
+
1639
+ $withConfig = $this->mapGet($withCondition, static::$with);
1640
+ if ($withConfig !== null) {
1641
  $without = []; // cancel the default
1642
+ $list = $this->coerceList($withConfig);
1643
 
1644
  foreach ($list[2] as $item) {
1645
  $keyword = $this->compileStringContent($this->coerceString($item));
1648
  }
1649
  }
1650
 
1651
+ $withoutConfig = $this->mapGet($withCondition, static::$without);
1652
+ if ($withoutConfig !== null) {
1653
  $without = []; // cancel the default
1654
+ $list = $this->coerceList($withoutConfig);
1655
 
1656
  foreach ($list[2] as $item) {
1657
  $keyword = $this->compileStringContent($this->coerceString($item));
1667
  /**
1668
  * Filter env stack
1669
  *
1670
+ * @param Environment[] $envs
1671
  * @param array $with
1672
  * @param array $without
1673
  *
1674
+ * @return Environment
1675
+ *
1676
+ * @phpstan-param non-empty-array<Environment> $envs
1677
  */
1678
  protected function filterWithWithout($envs, $with, $without)
1679
  {
1701
  * @param array $with
1702
  * @param array $without
1703
  *
1704
+ * @return bool
1705
  */
1706
  protected function isWith($block, $with, $without)
1707
  {
1711
  }
1712
 
1713
  if ($block->type === Type::T_DIRECTIVE) {
1714
+ assert($block instanceof DirectiveBlock || $block instanceof OutputBlock);
1715
  if (isset($block->name)) {
1716
+ return $this->testWithWithout($this->compileDirectiveName($block->name), $with, $without);
1717
  } elseif (isset($block->selectors) && preg_match(',@(\w+),ims', json_encode($block->selectors), $m)) {
1718
  return $this->testWithWithout($m[1], $with, $without);
1719
  } else {
1729
  $s = reset($s);
1730
  }
1731
 
1732
+ if (\is_object($s) && $s instanceof Number) {
1733
  return $this->testWithWithout('keyframes', $with, $without);
1734
  }
1735
  }
1747
  * @param array $with
1748
  * @param array $without
1749
  *
1750
+ * @return bool
1751
  * true if the block should be kept, false to reject
1752
  */
1753
  protected function testWithWithout($what, $with, $without)
1754
  {
 
1755
  // if without, reject only if in the list (or 'all' is in the list)
1756
  if (\count($without)) {
1757
  return (isset($without[$what]) || isset($without['all'])) ? false : true;
1766
  * Compile keyframe block
1767
  *
1768
  * @param \ScssPhp\ScssPhp\Block $block
1769
+ * @param string[] $selectors
1770
+ *
1771
+ * @return void
1772
  */
1773
  protected function compileKeyframeBlock(Block $block, $selectors)
1774
  {
1782
 
1783
  $this->scope = $this->makeOutputBlock($block->type, $selectors);
1784
  $this->scope->depth = 1;
1785
+ assert($this->scope->parent !== null);
1786
  $this->scope->parent->children[] = $this->scope;
1787
 
1788
  $this->compileChildrenNoReturn($block->children, $this->scope);
1789
 
1790
+ assert($this->scope !== null);
1791
  $this->scope = $this->scope->parent;
1792
  $this->env = $this->extractEnv($envs);
1793
 
1799
  *
1800
  * @param \ScssPhp\ScssPhp\Block $block
1801
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1802
+ *
1803
+ * @return void
1804
  */
1805
  protected function compileNestedPropertiesBlock(Block $block, OutputBlock $out)
1806
  {
1807
+ assert($block instanceof NestedPropertyBlock);
1808
  $prefix = $this->compileValue($block->prefix) . '-';
1809
 
1810
  $nested = $this->makeOutputBlock($block->type);
1823
  break;
1824
 
1825
  case Type::T_NESTED_PROPERTY:
1826
+ assert($child[1] instanceof NestedPropertyBlock);
1827
  array_unshift($child[1]->prefix[2], $prefix);
1828
  break;
1829
  }
1836
  * Compile nested block
1837
  *
1838
  * @param \ScssPhp\ScssPhp\Block $block
1839
+ * @param string[] $selectors
1840
+ *
1841
+ * @return void
1842
  */
1843
  protected function compileNestedBlock(Block $block, $selectors)
1844
  {
1845
  $this->pushEnv($block);
1846
 
1847
  $this->scope = $this->makeOutputBlock($block->type, $selectors);
1848
+ assert($this->scope->parent !== null);
1849
  $this->scope->parent->children[] = $this->scope;
1850
 
1851
  // wrap assign children in a block
1852
  // except for @font-face
1853
+ if (!$block instanceof DirectiveBlock || $this->compileDirectiveName($block->name) !== 'font-face') {
1854
  // need wrapping?
1855
  $needWrapping = false;
1856
 
1862
  }
1863
 
1864
  if ($needWrapping) {
1865
+ $wrapped = new Block();
1866
  $wrapped->sourceName = $block->sourceName;
1867
  $wrapped->sourceIndex = $block->sourceIndex;
1868
  $wrapped->sourceLine = $block->sourceLine;
1879
 
1880
  $this->compileChildrenNoReturn($block->children, $this->scope);
1881
 
1882
+ assert($this->scope !== null);
1883
  $this->scope = $this->scope->parent;
1884
 
1885
  $this->popEnv();
1902
  * @see Compiler::compileChild()
1903
  *
1904
  * @param \ScssPhp\ScssPhp\Block $block
1905
+ *
1906
+ * @return void
1907
  */
1908
  protected function compileBlock(Block $block)
1909
  {
1910
  $env = $this->pushEnv($block);
1911
+ assert($block->selectors !== null);
1912
  $env->selectors = $this->evalSelectors($block->selectors);
1913
 
1914
  $out = $this->makeOutputBlock(null);
1915
 
1916
+ assert($this->scope !== null);
1917
+ $this->scope->children[] = $out;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1918
 
1919
  if (\count($block->children)) {
1920
  $out->selectors = $this->multiplySelectors($env, $block->selfParent);
1931
 
1932
  // and revert for the following children of the same block
1933
  if ($selfParentSelectors) {
1934
+ assert($block->selfParent !== null);
1935
  $block->selfParent->selectors = $selfParentSelectors;
1936
  }
1937
  }
1943
  /**
1944
  * Compile the value of a comment that can have interpolation
1945
  *
1946
+ * @param array $value
1947
+ * @param bool $pushEnv
1948
  *
1949
+ * @return string
1950
  */
1951
  protected function compileCommentValue($value, $pushEnv = false)
1952
  {
1957
  $this->pushEnv();
1958
  }
1959
 
 
 
 
1960
  try {
1961
  $c = $this->compileValue($value[2]);
1962
+ } catch (SassScriptException $e) {
1963
+ $this->logger->warn('Ignoring interpolation errors in multiline comments is deprecated and will be removed in ScssPhp 2.0. ' . $this->addLocationToMessage($e->getMessage()), true);
1964
+ // ignore error in comment compilation which are only interpolation
1965
+ } catch (SassException $e) {
1966
+ $this->logger->warn('Ignoring interpolation errors in multiline comments is deprecated and will be removed in ScssPhp 2.0. ' . $e->getMessage(), true);
1967
  // ignore error in comment compilation which are only interpolation
1968
  }
1969
 
 
 
1970
  if ($pushEnv) {
1971
  $this->popEnv();
1972
  }
1979
  * Compile root level comment
1980
  *
1981
  * @param array $block
1982
+ *
1983
+ * @return void
1984
  */
1985
  protected function compileComment($block)
1986
  {
1987
  $out = $this->makeOutputBlock(Type::T_COMMENT);
1988
  $out->lines[] = $this->compileCommentValue($block, true);
1989
 
1990
+ assert($this->scope !== null);
1991
  $this->scope->children[] = $out;
1992
  }
1993
 
2002
  {
2003
  $this->shouldEvaluate = false;
2004
 
2005
+ $evaluatedSelectors = [];
2006
+ foreach ($selectors as $selector) {
2007
+ $evaluatedSelectors[] = $this->evalSelector($selector);
2008
+ }
2009
+ $selectors = $evaluatedSelectors;
2010
 
2011
  // after evaluating interpolates, we might need a second pass
2012
  if ($this->shouldEvaluate) {
2013
+ $selectors = $this->replaceSelfSelector($selectors, '&');
2014
  $buffer = $this->collapseSelectors($selectors);
2015
  $parser = $this->parserFactory(__METHOD__);
2016
 
2017
+ try {
2018
+ $isValid = $parser->parseSelector($buffer, $newSelectors, true);
2019
+ } catch (ParserException $e) {
2020
+ throw $this->error($e->getMessage());
2021
+ }
2022
+
2023
+ if ($isValid) {
2024
  $selectors = array_map([$this, 'evalSelector'], $newSelectors);
2025
  }
2026
  }
2034
  * @param array $selector
2035
  *
2036
  * @return array
2037
+ *
2038
+ * @phpstan-impure
2039
  */
2040
  protected function evalSelector($selector)
2041
  {
2048
  * @param array $part
2049
  *
2050
  * @return array
2051
+ *
2052
+ * @phpstan-impure
2053
  */
2054
  protected function evalSelectorPart($part)
2055
  {
2057
  if (\is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) {
2058
  $p = $this->compileValue($p);
2059
 
2060
+ // force re-evaluation if self char or non standard char
2061
+ if (preg_match(',[^\w-],', $p)) {
2062
  $this->shouldEvaluate = true;
2063
  }
2064
+ } elseif (
2065
+ \is_string($p) && \strlen($p) >= 2 &&
2066
+ ($p[0] === '"' || $p[0] === "'") &&
2067
+ substr($p, -1) === $p[0]
2068
  ) {
2069
  $p = substr($p, 1, -1);
2070
  }
2076
  /**
2077
  * Collapse selectors
2078
  *
2079
+ * @param array $selectors
 
 
 
2080
  *
2081
  * @return string
2082
  */
2083
+ protected function collapseSelectors($selectors)
2084
+ {
2085
+ $parts = [];
2086
+
2087
+ foreach ($selectors as $selector) {
2088
+ $output = [];
2089
+
2090
+ foreach ($selector as $node) {
2091
+ $compound = '';
2092
+
2093
+ array_walk_recursive(
2094
+ $node,
2095
+ function ($value, $key) use (&$compound) {
2096
+ $compound .= $value;
2097
+ }
2098
+ );
2099
+
2100
+ $output[] = $compound;
2101
+ }
2102
+
2103
+ $parts[] = implode(' ', $output);
2104
+ }
2105
+
2106
+ return implode(', ', $parts);
2107
+ }
2108
+
2109
+ /**
2110
+ * Collapse selectors
2111
+ *
2112
+ * @param array $selectors
2113
+ *
2114
+ * @return array
2115
+ */
2116
+ private function collapseSelectorsAsList($selectors)
2117
  {
2118
  $parts = [];
2119
 
2131
  }
2132
  );
2133
 
2134
+ if ($this->isImmediateRelationshipCombinator($compound)) {
2135
  if (\count($output)) {
2136
  $output[\count($output) - 1] .= ' ' . $compound;
2137
  } else {
2147
  }
2148
  }
2149
 
2150
+ foreach ($output as &$o) {
2151
+ $o = [Type::T_STRING, '', [$o]];
 
 
 
 
 
 
2152
  }
2153
 
2154
+ $parts[] = [Type::T_LIST, ' ', $output];
 
 
 
 
 
 
2155
  }
2156
 
2157
+ return [Type::T_LIST, ',', $parts];
2158
  }
2159
 
2160
  /**
2161
  * Parse down the selector and revert [self] to "&" before a reparsing
2162
  *
2163
+ * @param array $selectors
2164
+ * @param string|null $replace
2165
  *
2166
  * @return array
2167
  */
2168
+ protected function replaceSelfSelector($selectors, $replace = null)
2169
  {
2170
  foreach ($selectors as &$part) {
2171
  if (\is_array($part)) {
2172
  if ($part === [Type::T_SELF]) {
2173
+ if (\is_null($replace)) {
2174
+ $replace = $this->reduce([Type::T_SELF]);
2175
+ $replace = $this->compileValue($replace);
2176
+ }
2177
+ $part = $replace;
2178
  } else {
2179
+ $part = $this->replaceSelfSelector($part, $replace);
2180
  }
2181
  }
2182
  }
2196
  $joined = [];
2197
 
2198
  foreach ($single as $part) {
2199
+ if (
2200
+ empty($joined) ||
2201
  ! \is_string($part) ||
2202
  preg_match('/[\[.:#%]/', $part)
2203
  ) {
2270
  *
2271
  * @param array $selector
2272
  *
2273
+ * @return bool
2274
  */
2275
  protected function hasSelectorPlaceholder($selector)
2276
  {
2289
  return false;
2290
  }
2291
 
2292
+ /**
2293
+ * @param string $name
2294
+ *
2295
+ * @return void
2296
+ */
2297
  protected function pushCallStack($name = '')
2298
  {
2299
  $this->callStack[] = [
2307
  if (\count($this->callStack) > 25000) {
2308
  // not displayed but you can var_dump it to deep debug
2309
  $msg = $this->callStackMessage(true, 100);
2310
+ $msg = 'Infinite calling loop';
2311
 
2312
+ throw $this->error($msg);
2313
  }
2314
  }
2315
 
2316
+ /**
2317
+ * @return void
2318
+ */
2319
  protected function popCallStack()
2320
  {
2321
  array_pop($this->callStack);
2328
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2329
  * @param string $traceName
2330
  *
2331
+ * @return array|Number|null
2332
  */
2333
  protected function compileChildren($stms, OutputBlock $out, $traceName = '')
2334
  {
2350
  }
2351
 
2352
  /**
2353
+ * Compile children and throw exception if unexpected `@return`
2354
  *
2355
  * @param array $stms
2356
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2357
  * @param \ScssPhp\ScssPhp\Block $selfParent
2358
  * @param string $traceName
2359
  *
2360
+ * @return void
2361
+ *
2362
  * @throws \Exception
2363
  */
2364
  protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null, $traceName = '')
2370
  $stm[1]->selfParent = $selfParent;
2371
  $ret = $this->compileChild($stm, $out);
2372
  $stm[1]->selfParent = null;
2373
+ } elseif ($selfParent && \in_array($stm[0], [Type::T_INCLUDE, Type::T_EXTEND])) {
2374
  $stm['selfParent'] = $selfParent;
2375
  $ret = $this->compileChild($stm, $out);
2376
  unset($stm['selfParent']);
2379
  }
2380
 
2381
  if (isset($ret)) {
2382
+ throw $this->error('@return may only be used within a function');
 
 
 
2383
  }
2384
  }
2385
 
2388
 
2389
 
2390
  /**
2391
+ * evaluate media query : compile internal value keeping the structure unchanged
2392
  *
2393
  * @param array $queryList
2394
  *
2409
 
2410
  // the parser had no mean to know if media type or expression if it was an interpolation
2411
  // so you need to reparse if the T_MEDIA_TYPE looks like anything else a media type
2412
+ if (
2413
+ $q[0] == Type::T_MEDIA_TYPE &&
2414
  (strpos($value, '(') !== false ||
2415
  strpos($value, ')') !== false ||
2416
  strpos($value, ':') !== false ||
2431
  $queryString = $this->compileMediaQuery([$queryList[$kql]]);
2432
  $queryString = reset($queryString);
2433
 
2434
+ if ($queryString !== false && strpos($queryString, '@media ') === 0) {
2435
  $queryString = substr($queryString, 7);
2436
  $queries = [];
2437
 
2458
  *
2459
  * @param array $queryList
2460
  *
2461
+ * @return string[]
2462
  */
2463
  protected function compileMediaQuery($queryList)
2464
  {
2465
  $start = '@media ';
2466
  $default = trim($start);
2467
  $out = [];
2468
+ $current = '';
2469
 
2470
  foreach ($queryList as $query) {
2471
  $type = null;
2504
  $out[] = $start . $current;
2505
  }
2506
 
2507
+ $current = '';
2508
  $type = null;
2509
  $parts = [];
2510
  }
2679
  }
2680
 
2681
  // t1 == t2, neither m1 nor m2 are "not"
2682
+ return [empty($m1) ? $m2 : $m1, $t1];
2683
  }
2684
 
2685
  /**
2687
  *
2688
  * @param array $rawPath
2689
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2690
+ * @param bool $once
2691
  *
2692
+ * @return bool
2693
  */
2694
  protected function compileImport($rawPath, OutputBlock $out, $once = false)
2695
  {
2696
  if ($rawPath[0] === Type::T_STRING) {
2697
  $path = $this->compileStringContent($rawPath);
2698
 
2699
+ if (strpos($path, 'url(') !== 0 && $filePath = $this->findImport($path, $this->currentDirectory)) {
2700
+ $this->registerImport($this->currentDirectory, $path, $filePath);
2701
+
2702
+ if (! $once || ! \in_array($filePath, $this->importedFiles)) {
2703
+ $this->importFile($filePath, $out);
2704
+ $this->importedFiles[] = $filePath;
2705
  }
2706
 
2707
  return true;
2708
  }
2709
 
2710
+ $this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out);
2711
 
2712
  return false;
2713
  }
2720
 
2721
  foreach ($rawPath[2] as $path) {
2722
  if ($path[0] !== Type::T_STRING) {
2723
+ $this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out);
2724
 
2725
  return false;
2726
  }
2733
  return true;
2734
  }
2735
 
2736
+ $this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out);
2737
 
2738
  return false;
2739
  }
2740
 
2741
+ /**
2742
+ * @param array $rawPath
2743
+ * @return string
2744
+ * @throws CompilerException
2745
+ */
2746
+ protected function compileImportPath($rawPath)
2747
+ {
2748
+ $path = $this->compileValue($rawPath);
2749
+
2750
+ // case url() without quotes : suppress \r \n remaining in the path
2751
+ // if this is a real string there can not be CR or LF char
2752
+ if (strpos($path, 'url(') === 0) {
2753
+ $path = str_replace(array("\r", "\n"), array('', ' '), $path);
2754
+ } else {
2755
+ // if this is a file name in a string, spaces should be escaped
2756
+ $path = $this->reduce($rawPath);
2757
+ $path = $this->escapeImportPathString($path);
2758
+ $path = $this->compileValue($path);
2759
+ }
2760
+
2761
+ return $path;
2762
+ }
2763
+
2764
+ /**
2765
+ * @param array $path
2766
+ * @return array
2767
+ * @throws CompilerException
2768
+ */
2769
+ protected function escapeImportPathString($path)
2770
+ {
2771
+ switch ($path[0]) {
2772
+ case Type::T_LIST:
2773
+ foreach ($path[2] as $k => $v) {
2774
+ $path[2][$k] = $this->escapeImportPathString($v);
2775
+ }
2776
+ break;
2777
+ case Type::T_STRING:
2778
+ if ($path[1]) {
2779
+ $path = $this->compileValue($path);
2780
+ $path = str_replace(' ', '\\ ', $path);
2781
+ $path = [Type::T_KEYWORD, $path];
2782
+ }
2783
+ break;
2784
+ }
2785
+
2786
+ return $path;
2787
+ }
2788
 
2789
  /**
2790
  * Append a root directive like @import or @charset as near as the possible from the source code
2791
  * (keeping before comments, @import and @charset coming before in the source code)
2792
  *
2793
+ * @param string $line
2794
+ * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2795
+ * @param array $allowed
2796
+ *
2797
+ * @return void
2798
  */
2799
  protected function appendRootDirective($line, $out, $allowed = [Type::T_COMMENT])
2800
  {
2824
  // insert the directive as a comment
2825
  $child = $this->makeOutputBlock(Type::T_COMMENT);
2826
  $child->lines[] = $line;
2827
+ $child->sourceName = $this->sourceNames[$this->sourceIndex] ?: '(stdin)';
2828
  $child->sourceLine = $this->sourceLine;
2829
  $child->sourceColumn = $this->sourceColumn;
2830
 
2842
  *
2843
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2844
  * @param string $type
2845
+ * @param string $line
2846
+ *
2847
+ * @return void
2848
  */
2849
  protected function appendOutputLine(OutputBlock $out, $type, $line)
2850
  {
2851
  $outWrite = &$out;
2852
 
 
 
 
 
 
 
 
 
2853
  // check if it's a flat output or not
2854
  if (\count($out->children)) {
2855
  $lastChild = &$out->children[\count($out->children) - 1];
2856
 
2857
+ if (
2858
+ $lastChild->depth === $out->depth &&
2859
  \is_null($lastChild->selectors) &&
2860
  ! \count($lastChild->children)
2861
  ) {
2879
  * @param array $child
2880
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2881
  *
2882
+ * @return array|Number|null
2883
  */
2884
  protected function compileChild($child, OutputBlock $out)
2885
  {
2887
  $this->sourceIndex = isset($child[Parser::SOURCE_INDEX]) ? $child[Parser::SOURCE_INDEX] : null;
2888
  $this->sourceLine = isset($child[Parser::SOURCE_LINE]) ? $child[Parser::SOURCE_LINE] : -1;
2889
  $this->sourceColumn = isset($child[Parser::SOURCE_COLUMN]) ? $child[Parser::SOURCE_COLUMN] : -1;
2890
+ } elseif (\is_array($child) && isset($child[1]->sourceLine) && $child[1] instanceof Block) {
2891
  $this->sourceIndex = $child[1]->sourceIndex;
2892
  $this->sourceLine = $child[1]->sourceLine;
2893
  $this->sourceColumn = $child[1]->sourceColumn;
2894
  } elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) {
2895
  $this->sourceLine = $out->sourceLine;
2896
+ $sourceIndex = array_search($out->sourceName, $this->sourceNames);
2897
  $this->sourceColumn = $out->sourceColumn;
2898
 
2899
+ if ($sourceIndex === false) {
2900
+ $sourceIndex = null;
2901
  }
2902
+ $this->sourceIndex = $sourceIndex;
2903
  }
2904
 
2905
  switch ($child[0]) {
2932
  break;
2933
 
2934
  case Type::T_CHARSET:
 
 
 
 
2935
  break;
2936
 
2937
  case Type::T_CUSTOM_PROPERTY:
3004
  break;
3005
  }
3006
 
3007
+ if ($compiledName === 'font' && $value[0] === Type::T_LIST && $value[1] === ',') {
3008
  // this is the case if more than one font is given: example: "font: 400 1em/1.3 arial,helvetica"
3009
  // we need to handle the first list element
3010
  $shorthandValue=&$value[2][0];
3020
  $divider = $this->reduce($divider, true);
3021
  }
3022
 
3023
+ if ($divider instanceof Number && \intval($divider->getDimension()) && $divider->unitless()) {
3024
  $revert = false;
3025
  }
3026
  }
3033
  if ($item[0] === Type::T_EXPRESSION && $item[1] === '/') {
3034
  if ($maxShorthandDividers > 0) {
3035
  $revert = true;
3036
+
3037
  // if the list of values is too long, this has to be a shorthand,
3038
  // otherwise it could be a real division
3039
+ if (\is_null($maxListElements) || \count($shorthandValue[2]) <= $maxListElements) {
3040
  if ($shorthandDividerNeedsUnit) {
3041
  $divider = $item[3];
3042
 
3044
  $divider = $this->reduce($divider, true);
3045
  }
3046
 
3047
+ if ($divider instanceof Number && \intval($divider->getDimension()) && $divider->unitless()) {
3048
  $revert = false;
3049
  }
3050
  }
3095
  case Type::T_MIXIN:
3096
  case Type::T_FUNCTION:
3097
  list(, $block) = $child;
3098
+ assert($block instanceof CallableBlock);
3099
  // the block need to be able to go up to it's parent env to resolve vars
3100
  $block->parentEnv = $this->getStoreEnv();
3101
  $this->set(static::$namespaces[$block->type] . $block->name, $block, true);
3103
 
3104
  case Type::T_EXTEND:
3105
  foreach ($child[1] as $sel) {
3106
+ $replacedSel = $this->replaceSelfSelector($sel);
3107
+
3108
+ if ($replacedSel !== $sel) {
3109
+ throw $this->error('Parent selectors aren\'t allowed here.');
3110
+ }
3111
+
3112
  $results = $this->evalSelectors([$sel]);
3113
 
3114
  foreach ($results as $result) {
3115
+ if (\count($result) !== 1) {
3116
+ throw $this->error('complex selectors may not be extended.');
3117
+ }
3118
+
3119
  // only use the first one
3120
+ $result = $result[0];
3121
  $selectors = $out->selectors;
3122
 
3123
  if (! $selectors && isset($child['selfParent'])) {
3124
  $selectors = $this->multiplySelectors($this->env, $child['selfParent']);
3125
  }
3126
+ assert($selectors !== null);
3127
+
3128
+ if (\count($result) > 1) {
3129
+ $replacement = implode(', ', $result);
3130
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
3131
+ $line = $this->sourceLine;
3132
+
3133
+ $message = <<<EOL
3134
+ on line $line of $fname:
3135
+ Compound selectors may no longer be extended.
3136
+ Consider `@extend $replacement` instead.
3137
+ See http://bit.ly/ExtendCompound for details.
3138
+ EOL;
3139
+
3140
+ $this->logger->warn($message);
3141
+ }
3142
 
3143
  $this->pushExtends($result, $selectors, $child);
3144
  }
3147
 
3148
  case Type::T_IF:
3149
  list(, $if) = $child;
3150
+ assert($if instanceof IfBlock);
3151
 
3152
  if ($this->isTruthy($this->reduce($if->cond, true))) {
3153
  return $this->compileChildren($if->children, $out);
3154
  }
3155
 
3156
  foreach ($if->cases as $case) {
3157
+ if (
3158
+ $case instanceof ElseBlock ||
3159
+ $case instanceof ElseifBlock && $this->isTruthy($this->reduce($case->cond))
3160
  ) {
3161
  return $this->compileChildren($case->children, $out);
3162
  }
3165
 
3166
  case Type::T_EACH:
3167
  list(, $each) = $child;
3168
+ assert($each instanceof EachBlock);
3169
 
3170
  $list = $this->coerceList($this->reduce($each->list), ',', true);
3171
 
3185
  $ret = $this->compileChildren($each->children, $out);
3186
 
3187
  if ($ret) {
3188
+ $store = $this->env->store;
3189
+ $this->popEnv();
3190
+ $this->backPropagateEnv($store, $each->vars);
 
 
 
 
3191
 
3192
+ return $ret;
 
 
3193
  }
3194
  }
3195
  $store = $this->env->store;
3200
 
3201
  case Type::T_WHILE:
3202
  list(, $while) = $child;
3203
+ assert($while instanceof WhileBlock);
3204
 
3205
  while ($this->isTruthy($this->reduce($while->cond, true))) {
3206
  $ret = $this->compileChildren($while->children, $out);
3207
 
3208
  if ($ret) {
3209
+ return $ret;
 
 
 
 
 
 
3210
  }
3211
  }
3212
  break;
3213
 
3214
  case Type::T_FOR:
3215
  list(, $for) = $child;
3216
+ assert($for instanceof ForBlock);
3217
 
3218
+ $startNumber = $this->assertNumber($this->reduce($for->start, true));
3219
+ $endNumber = $this->assertNumber($this->reduce($for->end, true));
3220
 
3221
+ $start = $this->assertInteger($startNumber);
 
3222
 
3223
+ $numeratorUnits = $startNumber->getNumeratorUnits();
3224
+ $denominatorUnits = $startNumber->getDenominatorUnits();
3225
 
3226
+ $end = $this->assertInteger($endNumber->coerce($numeratorUnits, $denominatorUnits));
 
 
3227
 
3228
  $d = $start < $end ? 1 : -1;
3229
 
3230
  $this->pushEnv();
3231
 
3232
  for (;;) {
3233
+ if (
3234
+ (! $for->until && $start - $d == $end) ||
3235
  ($for->until && $start == $end)
3236
  ) {
3237
  break;
3238
  }
3239
 
3240
+ $this->set($for->var, new Number($start, $numeratorUnits, $denominatorUnits));
3241
  $start += $d;
3242
 
3243
  $ret = $this->compileChildren($for->children, $out);
3244
 
3245
  if ($ret) {
3246
+ $store = $this->env->store;
3247
+ $this->popEnv();
3248
+ $this->backPropagateEnv($store, [$for->var]);
 
 
 
3249
 
3250
+ return $ret;
 
 
3251
  }
3252
  }
3253
 
3257
 
3258
  break;
3259
 
 
 
 
 
 
 
3260
  case Type::T_RETURN:
3261
  return $this->reduce($child[1], true);
3262
 
3271
  $mixin = $this->get(static::$namespaces['mixin'] . $name, false);
3272
 
3273
  if (! $mixin) {
3274
+ throw $this->error("Undefined mixin $name");
 
3275
  }
3276
 
3277
+ assert($mixin instanceof CallableBlock);
3278
+
3279
  $callingScope = $this->getStoreEnv();
3280
 
3281
  // push scope, apply args
3286
  // and assign this fake parent to childs
3287
  $selfParent = null;
3288
 
3289
+ if (isset($child['selfParent']) && $child['selfParent'] instanceof Block && isset($child['selfParent']->selectors)) {
3290
  $selfParent = $child['selfParent'];
3291
  } else {
3292
  $parentSelectors = $this->multiplySelectors($this->env);
3296
  $parent->selectors = $parentSelectors;
3297
 
3298
  foreach ($mixin->children as $k => $child) {
3299
+ if (isset($child[1]) && $child[1] instanceof Block) {
3300
  $mixin->children[$k][1]->parent = $parent;
3301
  }
3302
  }
3330
  if (! empty($mixin->parentEnv)) {
3331
  $this->env->declarationScopeParent = $mixin->parentEnv;
3332
  } else {
3333
+ throw $this->error("@mixin $name() without parentEnv");
3334
  }
3335
 
3336
+ $this->compileChildrenNoReturn($mixin->children, $out, $selfParent, $this->env->marker . ' ' . $name);
3337
 
3338
  $this->popEnv();
3339
  break;
3373
  case Type::T_DEBUG:
3374
  list(, $value) = $child;
3375
 
3376
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
3377
  $line = $this->sourceLine;
3378
+ $value = $this->compileDebugValue($value);
3379
 
3380
+ $this->logger->debug("$fname:$line DEBUG: $value");
3381
  break;
3382
 
3383
  case Type::T_WARN:
3384
  list(, $value) = $child;
3385
 
3386
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
3387
  $line = $this->sourceLine;
3388
+ $value = $this->compileDebugValue($value);
3389
 
3390
+ $this->logger->warn("$value\n on line $line of $fname");
3391
  break;
3392
 
3393
  case Type::T_ERROR:
3394
  list(, $value) = $child;
3395
 
3396
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
3397
  $line = $this->sourceLine;
3398
  $value = $this->compileValue($this->reduce($value, true));
3399
 
3400
+ throw $this->error("File $fname on line $line ERROR: $value\n");
 
 
 
 
 
3401
 
3402
  default:
3403
+ throw $this->error("unknown child type: $child[0]");
3404
  }
3405
+
3406
+ return null;
3407
  }
3408
 
3409
  /**
3410
  * Reduce expression to string
3411
  *
3412
  * @param array $exp
3413
+ * @param bool $keepParens
3414
  *
3415
  * @return array
3416
  */
3417
+ protected function expToString($exp, $keepParens = false)
3418
  {
3419
+ list(, $op, $left, $right, $inParens, $whiteLeft, $whiteRight) = $exp;
3420
+
3421
+ $content = [];
3422
 
3423
+ if ($keepParens && $inParens) {
3424
+ $content[] = '(';
3425
+ }
3426
+
3427
+ $content[] = $this->reduce($left);
3428
 
3429
  if ($whiteLeft) {
3430
  $content[] = ' ';
3438
 
3439
  $content[] = $this->reduce($right);
3440
 
3441
+ if ($keepParens && $inParens) {
3442
+ $content[] = ')';
3443
+ }
3444
+
3445
  return [Type::T_STRING, '', $content];
3446
  }
3447
 
3448
  /**
3449
  * Is truthy?
3450
  *
3451
+ * @param array|Number $value
3452
  *
3453
+ * @return bool
3454
  */
3455
+ public function isTruthy($value)
3456
  {
3457
  return $value !== static::$false && $value !== static::$null;
3458
  }
3462
  *
3463
  * @param string $value
3464
  *
3465
+ * @return bool
3466
  */
3467
  protected function isImmediateRelationshipCombinator($value)
3468
  {
3474
  *
3475
  * @param array $value
3476
  *
3477
+ * @return bool
3478
  */
3479
  protected function shouldEval($value)
3480
  {
3496
  /**
3497
  * Reduce value
3498
  *
3499
+ * @param array|Number $value
3500
+ * @param bool $inExp
3501
  *
3502
+ * @return array|Number
3503
  */
3504
  protected function reduce($value, $inExp = false)
3505
  {
3506
+ if ($value instanceof Number) {
3507
+ return $value;
3508
  }
3509
 
3510
  switch ($value[0]) {
3521
  }
3522
 
3523
  // special case: looks like css shorthand
3524
+ if (
3525
+ $opName == 'div' && ! $inParens && ! $inExp &&
3526
+ (($right[0] !== Type::T_NUMBER && isset($right[2]) && $right[2] != '') ||
3527
  ($right[0] === Type::T_NUMBER && ! $right->unitless()))
3528
  ) {
3529
  return $this->expToString($value);
3538
  $ucLType = ucfirst($ltype);
3539
  $ucRType = ucfirst($rtype);
3540
 
3541
+ $shouldEval = $inParens || $inExp;
3542
+
3543
  // this tries:
3544
  // 1. op[op name][left type][right type]
3545
+ // 2. op[left type][right type] (passing the op as first arg)
3546
  // 3. op[op name]
3547
+ if (\is_callable([$this, $fn = "op${ucOpName}${ucLType}${ucRType}"])) {
3548
+ $out = $this->$fn($left, $right, $shouldEval);
3549
+ } elseif (\is_callable([$this, $fn = "op${ucLType}${ucRType}"])) {
3550
+ $out = $this->$fn($op, $left, $right, $shouldEval);
3551
+ } elseif (\is_callable([$this, $fn = "op${ucOpName}"])) {
3552
+ $out = $this->$fn($left, $right, $shouldEval);
3553
+ } else {
3554
+ $out = null;
3555
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3556
 
3557
+ if (isset($out)) {
3558
+ return $out;
3559
  }
3560
 
3561
  return $this->expToString($value);
3566
  $inExp = $inExp || $this->shouldEval($exp);
3567
  $exp = $this->reduce($exp);
3568
 
3569
+ if ($exp instanceof Number) {
3570
  switch ($op) {
3571
  case '+':
3572
+ return $exp;
3573
 
3574
  case '-':
3575
+ return $exp->unaryMinus();
3576
  }
3577
  }
3578
 
3597
  foreach ($value[2] as &$item) {
3598
  $item = $this->reduce($item);
3599
  }
3600
+ unset($item);
3601
+
3602
+ if (isset($value[3]) && \is_array($value[3])) {
3603
+ foreach ($value[3] as &$item) {
3604
+ $item = $this->reduce($item);
3605
+ }
3606
+ unset($item);
3607
+ }
3608
 
3609
  return $value;
3610
 
3621
 
3622
  case Type::T_STRING:
3623
  foreach ($value[2] as &$item) {
3624
+ if (\is_array($item) || $item instanceof Number) {
3625
  $item = $this->reduce($item);
3626
  }
3627
  }
3632
  $value[1] = $this->reduce($value[1]);
3633
 
3634
  if ($inExp) {
3635
+ return [Type::T_KEYWORD, $this->compileValue($value, false)];
3636
  }
3637
 
3638
  return $value;
3641
  return $this->fncall($value[1], $value[2]);
3642
 
3643
  case Type::T_SELF:
3644
+ $selfParent = ! empty($this->env->block->selfParent) ? $this->env->block->selfParent : null;
3645
+ $selfSelector = $this->multiplySelectors($this->env, $selfParent);
3646
+ $selfSelector = $this->collapseSelectorsAsList($selfSelector);
3647
 
3648
  return $selfSelector;
3649
 
3655
  /**
3656
  * Function caller
3657
  *
3658
+ * @param string|array $functionReference
3659
+ * @param array $argValues
3660
  *
3661
+ * @return array|Number
3662
  */
3663
+ protected function fncall($functionReference, $argValues)
3664
  {
3665
+ // a string means this is a static hard reference coming from the parsing
3666
+ if (is_string($functionReference)) {
3667
+ $name = $functionReference;
 
3668
 
3669
+ $functionReference = $this->getFunctionReference($name);
3670
+ if ($functionReference === static::$null || $functionReference[0] !== Type::T_FUNCTION_REFERENCE) {
3671
+ $functionReference = [Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]];
3672
+ }
3673
  }
3674
 
3675
+ // a function type means we just want a plain css function call
3676
+ if ($functionReference[0] === Type::T_FUNCTION) {
3677
+ // for CSS functions, simply flatten the arguments into a list
3678
+ $listArgs = [];
3679
 
3680
+ foreach ((array) $argValues as $arg) {
3681
+ if (empty($arg[0]) || count($argValues) === 1) {
3682
+ $listArgs[] = $this->reduce($this->stringifyFncallArgs($arg[1]));
3683
+ }
3684
  }
 
3685
 
3686
+ return [Type::T_FUNCTION, $functionReference[1], [Type::T_LIST, ',', $listArgs]];
3687
+ }
3688
 
3689
+ if ($functionReference === static::$null || $functionReference[0] !== Type::T_FUNCTION_REFERENCE) {
3690
+ return static::$defaultValue;
3691
+ }
 
 
 
 
 
 
 
 
3692
 
 
 
 
 
 
 
 
 
 
 
3693
 
3694
+ switch ($functionReference[1]) {
3695
+ // SCSS @function
3696
+ case 'scss':
3697
+ return $this->callScssFunction($functionReference[3], $argValues);
3698
 
3699
+ // native PHP functions
3700
+ case 'user':
3701
+ case 'native':
3702
+ list(,,$name, $fn, $prototype) = $functionReference;
3703
 
3704
+ // special cases of css valid functions min/max
3705
+ $name = strtolower($name);
3706
+ if (\in_array($name, ['min', 'max']) && count($argValues) >= 1) {
3707
+ $cssFunction = $this->cssValidArg(
3708
+ [Type::T_FUNCTION_CALL, $name, $argValues],
3709
+ ['min', 'max', 'calc', 'env', 'var']
3710
+ );
3711
+ if ($cssFunction !== false) {
3712
+ return $cssFunction;
3713
+ }
3714
  }
3715
+ $returnValue = $this->callNativeFunction($name, $fn, $prototype, $argValues);
3716
 
3717
+ if (! isset($returnValue)) {
3718
+ return $this->fncall([Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]], $argValues);
3719
  }
3720
 
3721
+ return $returnValue;
 
 
 
 
 
 
 
 
 
3722
 
3723
  default:
3724
+ return static::$defaultValue;
3725
  }
3726
  }
3727
 
3728
  /**
3729
+ * @param array|Number $arg
3730
+ * @param string[] $allowed_function
3731
+ * @param bool $inFunction
 
3732
  *
3733
+ * @return array|Number|false
3734
  */
3735
+ protected function cssValidArg($arg, $allowed_function = [], $inFunction = false)
3736
  {
3737
+ if ($arg instanceof Number) {
3738
+ return $this->stringifyFncallArgs($arg);
3739
+ }
3740
 
3741
+ switch ($arg[0]) {
3742
+ case Type::T_INTERPOLATE:
3743
+ return [Type::T_KEYWORD, $this->CompileValue($arg)];
 
 
 
 
 
 
 
 
 
3744
 
3745
+ case Type::T_FUNCTION:
3746
+ if (! \in_array($arg[1], $allowed_function)) {
3747
+ return false;
3748
+ }
3749
+ if ($arg[2][0] === Type::T_LIST) {
3750
+ foreach ($arg[2][2] as $k => $subarg) {
3751
+ $arg[2][2][$k] = $this->cssValidArg($subarg, $allowed_function, $arg[1]);
3752
+ if ($arg[2][2][$k] === false) {
3753
+ return false;
3754
+ }
3755
+ }
3756
+ }
3757
+ return $arg;
3758
+
3759
+ case Type::T_FUNCTION_CALL:
3760
+ if (! \in_array($arg[1], $allowed_function)) {
3761
+ return false;
3762
+ }
3763
+ $cssArgs = [];
3764
+ foreach ($arg[2] as $argValue) {
3765
+ if ($argValue === static::$null) {
3766
+ return false;
3767
+ }
3768
+ $cssArg = $this->cssValidArg($argValue[1], $allowed_function, $arg[1]);
3769
+ if (empty($argValue[0]) && $cssArg !== false) {
3770
+ $cssArgs[] = [$argValue[0], $cssArg];
3771
+ } else {
3772
+ return false;
3773
+ }
3774
+ }
3775
+
3776
+ return $this->fncall([Type::T_FUNCTION, $arg[1], [Type::T_LIST, ',', []]], $cssArgs);
3777
+
3778
+ case Type::T_STRING:
3779
+ case Type::T_KEYWORD:
3780
+ if (!$inFunction or !\in_array($inFunction, ['calc', 'env', 'var'])) {
3781
+ return false;
3782
+ }
3783
+ return $this->stringifyFncallArgs($arg);
3784
+
3785
+ case Type::T_LIST:
3786
+ if (!$inFunction) {
3787
+ return false;
3788
+ }
3789
+ if (empty($arg['enclosing']) and $arg[1] === '') {
3790
+ foreach ($arg[2] as $k => $subarg) {
3791
+ $arg[2][$k] = $this->cssValidArg($subarg, $allowed_function, $inFunction);
3792
+ if ($arg[2][$k] === false) {
3793
+ return false;
3794
+ }
3795
+ }
3796
+ $arg[0] = Type::T_STRING;
3797
+ return $arg;
3798
+ }
3799
+ return false;
3800
+
3801
+ case Type::T_EXPRESSION:
3802
+ if (! \in_array($arg[1], ['+', '-', '/', '*'])) {
3803
+ return false;
3804
+ }
3805
+ $arg[2] = $this->cssValidArg($arg[2], $allowed_function, $inFunction);
3806
+ $arg[3] = $this->cssValidArg($arg[3], $allowed_function, $inFunction);
3807
+ if ($arg[2] === false || $arg[3] === false) {
3808
+ return false;
3809
+ }
3810
+ return $this->expToString($arg, true);
3811
+
3812
+ case Type::T_VARIABLE:
3813
+ case Type::T_SELF:
3814
+ default:
3815
+ return false;
3816
+ }
3817
+ }
3818
+
3819
+
3820
+ /**
3821
+ * Reformat fncall arguments to proper css function output
3822
+ *
3823
+ * @param array|Number $arg
3824
+ *
3825
+ * @return array|Number
3826
+ */
3827
+ protected function stringifyFncallArgs($arg)
3828
+ {
3829
+ if ($arg instanceof Number) {
3830
+ return $arg;
3831
+ }
3832
+
3833
+ switch ($arg[0]) {
3834
+ case Type::T_LIST:
3835
+ foreach ($arg[2] as $k => $v) {
3836
+ $arg[2][$k] = $this->stringifyFncallArgs($v);
3837
+ }
3838
+ break;
3839
+
3840
+ case Type::T_EXPRESSION:
3841
+ if ($arg[1] === '/') {
3842
+ $arg[2] = $this->stringifyFncallArgs($arg[2]);
3843
+ $arg[3] = $this->stringifyFncallArgs($arg[3]);
3844
+ $arg[5] = $arg[6] = false; // no space around /
3845
+ $arg = $this->expToString($arg);
3846
+ }
3847
+ break;
3848
+
3849
+ case Type::T_FUNCTION_CALL:
3850
+ $name = strtolower($arg[1]);
3851
+
3852
+ if (in_array($name, ['max', 'min', 'calc'])) {
3853
+ $args = $arg[2];
3854
+ $arg = $this->fncall([Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]], $args);
3855
+ }
3856
+ break;
3857
+ }
3858
+
3859
+ return $arg;
3860
+ }
3861
+
3862
+ /**
3863
+ * Find a function reference
3864
+ * @param string $name
3865
+ * @param bool $safeCopy
3866
+ * @return array
3867
+ */
3868
+ protected function getFunctionReference($name, $safeCopy = false)
3869
+ {
3870
+ // SCSS @function
3871
+ if ($func = $this->get(static::$namespaces['function'] . $name, false)) {
3872
+ if ($safeCopy) {
3873
+ $func = clone $func;
3874
+ }
3875
+
3876
+ return [Type::T_FUNCTION_REFERENCE, 'scss', $name, $func];
3877
+ }
3878
+
3879
+ // native PHP functions
3880
+
3881
+ // try to find a native lib function
3882
+ $normalizedName = $this->normalizeName($name);
3883
+
3884
+ if (isset($this->userFunctions[$normalizedName])) {
3885
+ // see if we can find a user function
3886
+ list($f, $prototype) = $this->userFunctions[$normalizedName];
3887
+
3888
+ return [Type::T_FUNCTION_REFERENCE, 'user', $name, $f, $prototype];
3889
+ }
3890
+
3891
+ $lowercasedName = strtolower($normalizedName);
3892
+
3893
+ // Special functions overriding a CSS function are case-insensitive. We normalize them as lowercase
3894
+ // to avoid the deprecation warning about the wrong case being used.
3895
+ if ($lowercasedName === 'min' || $lowercasedName === 'max') {
3896
+ $normalizedName = $lowercasedName;
3897
+ }
3898
+
3899
+ if (($f = $this->getBuiltinFunction($normalizedName)) && \is_callable($f)) {
3900
+ /** @var string $libName */
3901
+ $libName = $f[1];
3902
+ $prototype = isset(static::$$libName) ? static::$$libName : null;
3903
+
3904
+ // All core functions have a prototype defined. Not finding the
3905
+ // prototype can mean 2 things:
3906
+ // - the function comes from a child class (deprecated just after)
3907
+ // - the function was found with a different case, which relates to calling the
3908
+ // wrong Sass function due to our camelCase usage (`fade-in()` vs `fadein()`),
3909
+ // because PHP method names are case-insensitive while property names are
3910
+ // case-sensitive.
3911
+ if ($prototype === null || strtolower($normalizedName) !== $normalizedName) {
3912
+ $r = new \ReflectionMethod($this, $libName);
3913
+ $actualLibName = $r->name;
3914
+
3915
+ if ($actualLibName !== $libName || strtolower($normalizedName) !== $normalizedName) {
3916
+ $kebabCaseName = preg_replace('~(?<=\\w)([A-Z])~', '-$1', substr($actualLibName, 3));
3917
+ assert($kebabCaseName !== null);
3918
+ $originalName = strtolower($kebabCaseName);
3919
+ $warning = "Calling built-in functions with a non-standard name is deprecated since Scssphp 1.8.0 and will not work anymore in 2.0 (they will be treated as CSS function calls instead).\nUse \"$originalName\" instead of \"$name\".";
3920
+ @trigger_error($warning, E_USER_DEPRECATED);
3921
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
3922
+ $line = $this->sourceLine;
3923
+ Warn::deprecation("$warning\n on line $line of $fname");
3924
+
3925
+ // Use the actual function definition
3926
+ $prototype = isset(static::$$actualLibName) ? static::$$actualLibName : null;
3927
+ $f[1] = $libName = $actualLibName;
3928
+ }
3929
+ }
3930
+
3931
+ if (\get_class($this) !== __CLASS__ && !isset($this->warnedChildFunctions[$libName])) {
3932
+ $r = new \ReflectionMethod($this, $libName);
3933
+ $declaringClass = $r->getDeclaringClass()->name;
3934
+
3935
+ $needsWarning = $this->warnedChildFunctions[$libName] = $declaringClass !== __CLASS__;
3936
+
3937
+ if ($needsWarning) {
3938
+ if (method_exists(__CLASS__, $libName)) {
3939
+ @trigger_error(sprintf('Overriding the "%s" core function by extending the Compiler is deprecated and will be unsupported in 2.0. Remove the "%s::%s" method.', $normalizedName, $declaringClass, $libName), E_USER_DEPRECATED);
3940
+ } else {
3941
+ @trigger_error(sprintf('Registering custom functions by extending the Compiler and using the lib* discovery mechanism is deprecated and will be removed in 2.0. Replace the "%s::%s" method with registering the "%s" function through "Compiler::registerFunction".', $declaringClass, $libName, $normalizedName), E_USER_DEPRECATED);
3942
+ }
3943
+ }
3944
+ }
3945
+
3946
+ return [Type::T_FUNCTION_REFERENCE, 'native', $name, $f, $prototype];
3947
+ }
3948
+
3949
+ return static::$null;
3950
+ }
3951
+
3952
+
3953
+ /**
3954
+ * Normalize name
3955
+ *
3956
+ * @param string $name
3957
  *
3958
+ * @return string
3959
  */
3960
+ protected function normalizeName($name)
3961
  {
3962
+ return str_replace('-', '_', $name);
3963
  }
3964
 
3965
  /**
3966
+ * Normalize value
3967
  *
3968
+ * @internal
3969
+ *
3970
+ * @param array|Number $value
3971
  *
3972
+ * @return array|Number
3973
  */
3974
+ public function normalizeValue($value)
3975
  {
3976
+ $value = $this->coerceForExpression($this->reduce($value));
3977
+
3978
+ if ($value instanceof Number) {
3979
+ return $value;
3980
+ }
3981
+
3982
+ switch ($value[0]) {
3983
+ case Type::T_LIST:
3984
+ $value = $this->extractInterpolation($value);
3985
+
3986
+ if ($value[0] !== Type::T_LIST) {
3987
+ return [Type::T_KEYWORD, $this->compileValue($value)];
3988
+ }
3989
+
3990
+ foreach ($value[2] as $key => $item) {
3991
+ $value[2][$key] = $this->normalizeValue($item);
3992
+ }
3993
+
3994
+ if (! empty($value['enclosing'])) {
3995
+ unset($value['enclosing']);
3996
+ }
3997
+
3998
+ if ($value[1] === '' && count($value[2]) > 1) {
3999
+ $value[1] = ' ';
4000
+ }
4001
+
4002
+ return $value;
4003
+
4004
+ case Type::T_STRING:
4005
+ return [$value[0], '"', [$this->compileStringContent($value)]];
4006
+
4007
+ case Type::T_INTERPOLATE:
4008
+ return [Type::T_KEYWORD, $this->compileValue($value)];
4009
+
4010
+ default:
4011
+ return $value;
4012
  }
4013
+ }
4014
 
4015
+ /**
4016
+ * Add numbers
4017
+ *
4018
+ * @param Number $left
4019
+ * @param Number $right
4020
+ *
4021
+ * @return Number
4022
+ */
4023
+ protected function opAddNumberNumber(Number $left, Number $right)
4024
+ {
4025
+ return $left->plus($right);
4026
  }
4027
 
4028
  /**
4029
+ * Multiply numbers
4030
  *
4031
+ * @param Number $left
4032
+ * @param Number $right
4033
  *
4034
+ * @return Number
4035
  */
4036
+ protected function opMulNumberNumber(Number $left, Number $right)
4037
  {
4038
+ return $left->times($right);
4039
+ }
4040
+
4041
+ /**
4042
+ * Subtract numbers
4043
+ *
4044
+ * @param Number $left
4045
+ * @param Number $right
4046
+ *
4047
+ * @return Number
4048
+ */
4049
+ protected function opSubNumberNumber(Number $left, Number $right)
4050
+ {
4051
+ return $left->minus($right);
4052
+ }
4053
+
4054
+ /**
4055
+ * Divide numbers
4056
+ *
4057
+ * @param Number $left
4058
+ * @param Number $right
4059
+ *
4060
+ * @return Number
4061
+ */
4062
+ protected function opDivNumberNumber(Number $left, Number $right)
4063
+ {
4064
+ return $left->dividedBy($right);
4065
+ }
4066
 
4067
+ /**
4068
+ * Mod numbers
4069
+ *
4070
+ * @param Number $left
4071
+ * @param Number $right
4072
+ *
4073
+ * @return Number
4074
+ */
4075
+ protected function opModNumberNumber(Number $left, Number $right)
4076
+ {
4077
+ return $left->modulo($right);
4078
  }
4079
 
4080
  /**
4113
  /**
4114
  * Boolean and
4115
  *
4116
+ * @param array|Number $left
4117
+ * @param array|Number $right
4118
+ * @param bool $shouldEval
4119
  *
4120
+ * @return array|Number|null
4121
  */
4122
  protected function opAnd($left, $right, $shouldEval)
4123
  {
4141
  /**
4142
  * Boolean or
4143
  *
4144
+ * @param array|Number $left
4145
+ * @param array|Number $right
4146
+ * @param bool $shouldEval
4147
  *
4148
+ * @return array|Number|null
4149
  */
4150
  protected function opOr($left, $right, $shouldEval)
4151
  {
4177
  */
4178
  protected function opColorColor($op, $left, $right)
4179
  {
4180
+ if ($op !== '==' && $op !== '!=') {
4181
+ $warning = "Color arithmetic is deprecated and will be an error in future versions.\n"
4182
+ . "Consider using Sass's color functions instead.";
4183
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
4184
+ $line = $this->sourceLine;
4185
+
4186
+ Warn::deprecation("$warning\n on line $line of $fname");
4187
+ }
4188
+
4189
  $out = [Type::T_COLOR];
4190
 
4191
  foreach ([1, 2, 3] as $i) {
4206
  break;
4207
 
4208
  case '%':
4209
+ if ($rval == 0) {
4210
+ throw $this->error("color: Can't take modulo by zero");
4211
+ }
4212
+
4213
  $out[] = $lval % $rval;
4214
  break;
4215
 
4216
  case '/':
4217
  if ($rval == 0) {
4218
+ throw $this->error("color: Can't divide by zero");
 
4219
  }
4220
 
4221
  $out[] = (int) ($lval / $rval);
4228
  return $this->opNeq($left, $right);
4229
 
4230
  default:
4231
+ throw $this->error("color: unknown op $op");
 
4232
  }
4233
  }
4234
 
4246
  *
4247
  * @param string $op
4248
  * @param array $left
4249
+ * @param Number $right
4250
  *
4251
  * @return array
4252
  */
4253
+ protected function opColorNumber($op, $left, Number $right)
4254
  {
4255
+ if ($op === '==') {
4256
+ return static::$false;
4257
+ }
4258
+
4259
+ if ($op === '!=') {
4260
+ return static::$true;
4261
+ }
4262
+
4263
+ $value = $right->getDimension();
4264
 
4265
  return $this->opColorColor(
4266
  $op,
4273
  * Compare number and color
4274
  *
4275
  * @param string $op
4276
+ * @param Number $left
4277
  * @param array $right
4278
  *
4279
  * @return array
4280
  */
4281
+ protected function opNumberColor($op, Number $left, $right)
4282
  {
4283
+ if ($op === '==') {
4284
+ return static::$false;
4285
+ }
4286
+
4287
+ if ($op === '!=') {
4288
+ return static::$true;
4289
+ }
4290
+
4291
+ $value = $left->getDimension();
4292
 
4293
  return $this->opColorColor(
4294
  $op,
4300
  /**
4301
  * Compare number1 == number2
4302
  *
4303
+ * @param array|Number $left
4304
+ * @param array|Number $right
4305
  *
4306
  * @return array
4307
  */
4321
  /**
4322
  * Compare number1 != number2
4323
  *
4324
+ * @param array|Number $left
4325
+ * @param array|Number $right
4326
  *
4327
  * @return array
4328
  */
4340
  }
4341
 
4342
  /**
4343
+ * Compare number1 == number2
4344
  *
4345
+ * @param Number $left
4346
+ * @param Number $right
4347
  *
4348
  * @return array
4349
  */
4350
+ protected function opEqNumberNumber(Number $left, Number $right)
4351
  {
4352
+ return $this->toBool($left->equals($right));
4353
  }
4354
 
4355
  /**
4356
+ * Compare number1 != number2
4357
  *
4358
+ * @param Number $left
4359
+ * @param Number $right
4360
  *
4361
  * @return array
4362
  */
4363
+ protected function opNeqNumberNumber(Number $left, Number $right)
4364
  {
4365
+ return $this->toBool(!$left->equals($right));
4366
  }
4367
 
4368
  /**
4369
+ * Compare number1 >= number2
4370
  *
4371
+ * @param Number $left
4372
+ * @param Number $right
4373
  *
4374
  * @return array
4375
  */
4376
+ protected function opGteNumberNumber(Number $left, Number $right)
4377
  {
4378
+ return $this->toBool($left->greaterThanOrEqual($right));
4379
  }
4380
 
4381
  /**
4382
+ * Compare number1 > number2
4383
  *
4384
+ * @param Number $left
4385
+ * @param Number $right
4386
  *
4387
  * @return array
4388
  */
4389
+ protected function opGtNumberNumber(Number $left, Number $right)
4390
  {
4391
+ return $this->toBool($left->greaterThan($right));
4392
  }
4393
 
4394
  /**
4395
+ * Compare number1 <= number2
4396
  *
4397
+ * @param Number $left
4398
+ * @param Number $right
4399
  *
4400
+ * @return array
4401
  */
4402
+ protected function opLteNumberNumber(Number $left, Number $right)
4403
  {
4404
+ return $this->toBool($left->lessThanOrEqual($right));
4405
+ }
4406
 
4407
+ /**
4408
+ * Compare number1 < number2
4409
+ *
4410
+ * @param Number $left
4411
+ * @param Number $right
4412
+ *
4413
+ * @return array
4414
+ */
4415
+ protected function opLtNumberNumber(Number $left, Number $right)
4416
+ {
4417
+ return $this->toBool($left->lessThan($right));
4418
  }
4419
 
4420
  /**
4422
  *
4423
  * @api
4424
  *
4425
+ * @param bool $thing
4426
  *
4427
  * @return array
4428
  */
4431
  return $thing ? static::$true : static::$false;
4432
  }
4433
 
4434
+ /**
4435
+ * Escape non printable chars in strings output as in dart-sass
4436
+ *
4437
+ * @internal
4438
+ *
4439
+ * @param string $string
4440
+ * @param bool $inKeyword
4441
+ *
4442
+ * @return string
4443
+ */
4444
+ public function escapeNonPrintableChars($string, $inKeyword = false)
4445
+ {
4446
+ static $replacement = [];
4447
+ if (empty($replacement[$inKeyword])) {
4448
+ for ($i = 0; $i < 32; $i++) {
4449
+ if ($i !== 9 || $inKeyword) {
4450
+ $replacement[$inKeyword][chr($i)] = '\\' . dechex($i) . ($inKeyword ? ' ' : chr(0));
4451
+ }
4452
+ }
4453
+ }
4454
+ $string = str_replace(array_keys($replacement[$inKeyword]), array_values($replacement[$inKeyword]), $string);
4455
+ // chr(0) is not a possible char from the input, so any chr(0) comes from our escaping replacement
4456
+ if (strpos($string, chr(0)) !== false) {
4457
+ if (substr($string, -1) === chr(0)) {
4458
+ $string = substr($string, 0, -1);
4459
+ }
4460
+ $string = str_replace(
4461
+ [chr(0) . '\\',chr(0) . ' '],
4462
+ [ '\\', ' '],
4463
+ $string
4464
+ );
4465
+ if (strpos($string, chr(0)) !== false) {
4466
+ $parts = explode(chr(0), $string);
4467
+ $string = array_shift($parts);
4468
+ while (count($parts)) {
4469
+ $next = array_shift($parts);
4470
+ if (strpos("0123456789abcdefABCDEF" . chr(9), $next[0]) !== false) {
4471
+ $string .= " ";
4472
+ }
4473
+ $string .= $next;
4474
+ }
4475
+ }
4476
+ }
4477
+
4478
+ return $string;
4479
+ }
4480
+
4481
  /**
4482
  * Compiles a primitive value into a CSS property value.
4483
  *
4491
  *
4492
  * @api
4493
  *
4494
+ * @param array|Number $value
4495
+ * @param bool $quote
4496
  *
4497
+ * @return string
4498
  */
4499
+ public function compileValue($value, $quote = true)
4500
  {
4501
  $value = $this->reduce($value);
4502
 
4503
+ if ($value instanceof Number) {
4504
+ return $value->output($this);
4505
+ }
4506
+
4507
  switch ($value[0]) {
4508
  case Type::T_KEYWORD:
4509
+ return $this->escapeNonPrintableChars($value[1], true);
4510
 
4511
  case Type::T_COLOR:
4512
  // [1] - red component (either number for a %)
4530
  }
4531
 
4532
  if (is_numeric($alpha)) {
4533
+ $a = new Number($alpha, '');
4534
  } else {
4535
  $a = $alpha;
4536
  }
4558
 
4559
  return $h;
4560
 
 
 
 
4561
  case Type::T_STRING:
4562
+ $content = $this->compileStringContent($value, $quote);
4563
+
4564
+ if ($value[1] && $quote) {
4565
+ $content = str_replace('\\', '\\\\', $content);
4566
+
4567
+ $content = $this->escapeNonPrintableChars($content);
4568
+
4569
+ // force double quote as string quote for the output in certain cases
4570
+ if (
4571
+ $value[1] === "'" &&
4572
+ (strpos($content, '"') === false or strpos($content, "'") !== false)
4573
+ ) {
4574
+ $value[1] = '"';
4575
+ } elseif (
4576
+ $value[1] === '"' &&
4577
+ (strpos($content, '"') !== false and strpos($content, "'") === false)
4578
+ ) {
4579
+ $value[1] = "'";
4580
+ }
4581
+
4582
+ $content = str_replace($value[1], '\\' . $value[1], $content);
4583
+ }
4584
+
4585
+ return $value[1] . $content . $value[1];
4586
 
4587
  case Type::T_FUNCTION:
4588
+ $args = ! empty($value[2]) ? $this->compileValue($value[2], $quote) : '';
4589
 
4590
  return "$value[1]($args)";
4591
 
4592
+ case Type::T_FUNCTION_REFERENCE:
4593
+ $name = ! empty($value[2]) ? $value[2] : '';
4594
+
4595
+ return "get-function(\"$name\")";
4596
+
4597
  case Type::T_LIST:
4598
  $value = $this->extractInterpolation($value);
4599
 
4600
  if ($value[0] !== Type::T_LIST) {
4601
+ return $this->compileValue($value, $quote);
4602
  }
4603
 
4604
  list(, $delim, $items) = $value;
4605
+ $pre = $post = '';
4606
 
4607
  if (! empty($value['enclosing'])) {
4608
  switch ($value['enclosing']) {
4609
  case 'parent':
4610
+ //$pre = '(';
4611
+ //$post = ')';
4612
  break;
4613
  case 'forced_parent':
4614
+ $pre = '(';
4615
+ $post = ')';
4616
  break;
4617
  case 'bracket':
4618
  case 'forced_bracket':
4619
+ $pre = '[';
4620
+ $post = ']';
4621
  break;
4622
  }
4623
  }
4624
 
4625
+ $separator = $delim === '/' ? ' /' : $delim;
4626
+
4627
  $prefix_value = '';
4628
+
4629
  if ($delim !== ' ') {
4630
  $prefix_value = ' ';
4631
  }
4632
 
4633
  $filtered = [];
4634
 
4635
+ $same_string_quote = null;
4636
  foreach ($items as $item) {
4637
+ if (\is_null($same_string_quote)) {
4638
+ $same_string_quote = false;
4639
+ if ($item[0] === Type::T_STRING) {
4640
+ $same_string_quote = $item[1];
4641
+ foreach ($items as $ii) {
4642
+ if ($ii[0] !== Type::T_STRING) {
4643
+ $same_string_quote = false;
4644
+ break;
4645
+ }
4646
+ }
4647
+ }
4648
+ }
4649
  if ($item[0] === Type::T_NULL) {
4650
  continue;
4651
  }
4652
+ if ($same_string_quote === '"' && $item[0] === Type::T_STRING && $item[1]) {
4653
+ $item[1] = $same_string_quote;
4654
+ }
4655
+
4656
+ $compiled = $this->compileValue($item, $quote);
4657
 
 
4658
  if ($prefix_value && \strlen($compiled)) {
4659
  $compiled = $prefix_value . $compiled;
4660
  }
4661
+
4662
  $filtered[] = $compiled;
4663
  }
4664
 
4665
+ return $pre . substr(implode($separator, $filtered), \strlen($prefix_value)) . $post;
4666
 
4667
  case Type::T_MAP:
4668
  $keys = $value[1];
4670
  $filtered = [];
4671
 
4672
  for ($i = 0, $s = \count($keys); $i < $s; $i++) {
4673
+ $filtered[$this->compileValue($keys[$i], $quote)] = $this->compileValue($values[$i], $quote);
4674
  }
4675
 
4676
  array_walk($filtered, function (&$value, $key) {
4690
  $delim .= ' ';
4691
  }
4692
 
4693
+ $left = \count($left[2]) > 0
4694
+ ? $this->compileValue($left, $quote) . $delim . $whiteLeft
4695
+ : '';
4696
 
4697
  $delim = $right[1];
4698
 
4701
  }
4702
 
4703
  $right = \count($right[2]) > 0 ?
4704
+ $whiteRight . $delim . $this->compileValue($right, $quote) : '';
4705
 
4706
+ return $left . $this->compileValue($interpolate, $quote) . $right;
4707
 
4708
  case Type::T_INTERPOLATE:
4709
  // strip quotes if it's a string
4710
  $reduced = $this->reduce($value[1]);
4711
 
4712
+ if ($reduced instanceof Number) {
4713
+ return $this->compileValue($reduced, $quote);
4714
+ }
4715
+
4716
  switch ($reduced[0]) {
4717
  case Type::T_LIST:
4718
  $reduced = $this->extractInterpolation($reduced);
4734
  continue;
4735
  }
4736
 
4737
+ if ($item[0] === Type::T_STRING) {
4738
+ $filtered[] = $this->compileStringContent($item, $quote);
4739
+ } elseif ($item[0] === Type::T_KEYWORD) {
4740
+ $filtered[] = $item[1];
 
 
4741
  } else {
4742
+ $filtered[] = $this->compileValue($item, $quote);
4743
  }
4744
  }
4745
 
4747
  break;
4748
 
4749
  case Type::T_STRING:
4750
+ $reduced = [Type::T_STRING, '', [$this->compileStringContent($reduced)]];
4751
  break;
4752
 
4753
  case Type::T_NULL:
4754
  $reduced = [Type::T_KEYWORD, ''];
4755
  }
4756
 
4757
+ return $this->compileValue($reduced, $quote);
4758
 
4759
  case Type::T_NULL:
4760
  return 'null';
4763
  return $this->compileCommentValue($value);
4764
 
4765
  default:
4766
+ throw $this->error('unknown value type: ' . json_encode($value));
4767
+ }
4768
+ }
4769
+
4770
+ /**
4771
+ * @param array|Number $value
4772
+ *
4773
+ * @return string
4774
+ */
4775
+ protected function compileDebugValue($value)
4776
+ {
4777
+ $value = $this->reduce($value, true);
4778
+
4779
+ if ($value instanceof Number) {
4780
+ return $this->compileValue($value);
4781
+ }
4782
+
4783
+ switch ($value[0]) {
4784
+ case Type::T_STRING:
4785
+ return $this->compileStringContent($value);
4786
+
4787
+ default:
4788
+ return $this->compileValue($value);
4789
  }
4790
  }
4791
 
4795
  * @param array $list
4796
  *
4797
  * @return string
4798
+ *
4799
+ * @deprecated
4800
  */
4801
  protected function flattenList($list)
4802
  {
4803
+ @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
4804
+
4805
  return $this->compileValue($list);
4806
  }
4807
 
4808
+ /**
4809
+ * Gets the text of a Sass string
4810
+ *
4811
+ * Calling this method on anything else than a SassString is unsupported. Use {@see assertString} first
4812
+ * to ensure that the value is indeed a string.
4813
+ *
4814
+ * @param array $value
4815
+ *
4816
+ * @return string
4817
+ */
4818
+ public function getStringText(array $value)
4819
+ {
4820
+ if ($value[0] !== Type::T_STRING) {
4821
+ throw new \InvalidArgumentException('The argument is not a sass string. Did you forgot to use "assertString"?');
4822
+ }
4823
+
4824
+ return $this->compileStringContent($value);
4825
+ }
4826
+
4827
  /**
4828
  * Compile string content
4829
  *
4830
  * @param array $string
4831
+ * @param bool $quote
4832
  *
4833
  * @return string
4834
  */
4835
+ protected function compileStringContent($string, $quote = true)
4836
  {
4837
  $parts = [];
4838
 
4839
  foreach ($string[2] as $part) {
4840
+ if (\is_array($part) || $part instanceof Number) {
4841
+ $parts[] = $this->compileValue($part, $quote);
4842
  } else {
4843
  $parts[] = $part;
4844
  }
4902
  $prevSelectors = $selectors;
4903
  $selectors = [];
4904
 
4905
+ foreach ($parentSelectors as $parent) {
4906
+ foreach ($prevSelectors as $selector) {
4907
  if ($selfParentSelectors) {
4908
  foreach ($selfParentSelectors as $selfParent) {
4909
  // if no '&' in the selector, each call will give same result, only add once
4923
 
4924
  $selectors = array_values($selectors);
4925
 
4926
+ // case we are just starting a at-root : nothing to multiply but parentSelectors
4927
+ if (! $selectors && $selfParentSelectors) {
4928
+ $selectors = $selfParentSelectors;
4929
+ }
4930
+
4931
  return $selectors;
4932
  }
4933
 
4934
  /**
4935
  * Join selectors; looks for & to replace, or append parent before child
4936
  *
4937
+ * @param array $parent
4938
+ * @param array $child
4939
+ * @param bool $stillHasSelf
4940
+ * @param array $selfParentSelectors
4941
 
4942
  * @return array
4943
  */
5003
  */
5004
  protected function multiplyMedia(Environment $env = null, $childQueries = null)
5005
  {
5006
+ if (
5007
+ ! isset($env) ||
5008
  ! empty($env->block->type) && $env->block->type !== Type::T_MEDIA
5009
  ) {
5010
  return $childQueries;
5015
  return $this->multiplyMedia($env->parent, $childQueries);
5016
  }
5017
 
5018
+ assert($env->block instanceof MediaBlock);
5019
+
5020
  $parentQueries = isset($env->block->queryList)
5021
  ? $env->block->queryList
5022
  : [[[Type::T_MEDIA_VALUE, $env->block->value]]];
5052
  /**
5053
  * Convert env linked list to stack
5054
  *
5055
+ * @param Environment $env
5056
  *
5057
+ * @return Environment[]
5058
+ *
5059
+ * @phpstan-return non-empty-array<Environment>
5060
  */
5061
  protected function compactEnv(Environment $env)
5062
  {
5070
  /**
5071
  * Convert env stack to singly linked list
5072
  *
5073
+ * @param Environment[] $envs
5074
  *
5075
+ * @return Environment
5076
+ *
5077
+ * @phpstan-param non-empty-array<Environment> $envs
5078
  */
5079
  protected function extractEnv($envs)
5080
  {
5095
  */
5096
  protected function pushEnv(Block $block = null)
5097
  {
5098
+ $env = new Environment();
5099
  $env->parent = $this->env;
5100
  $env->parentStore = $this->storeEnv;
5101
  $env->store = [];
5110
 
5111
  /**
5112
  * Pop environment
5113
+ *
5114
+ * @return void
5115
  */
5116
  protected function popEnv()
5117
  {
5122
  /**
5123
  * Propagate vars from a just poped Env (used in @each and @for)
5124
  *
5125
+ * @param array $store
5126
+ * @param null|string[] $excludedVars
5127
+ *
5128
+ * @return void
5129
  */
5130
  protected function backPropagateEnv($store, $excludedVars = null)
5131
  {
5151
  *
5152
  * @param string $name
5153
  * @param mixed $value
5154
+ * @param bool $shadow
5155
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
5156
  * @param mixed $valueUnreduced
5157
+ *
5158
+ * @return void
5159
  */
5160
  protected function set($name, $value, $shadow = false, Environment $env = null, $valueUnreduced = null)
5161
  {
5179
  * @param mixed $value
5180
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
5181
  * @param mixed $valueUnreduced
5182
+ *
5183
+ * @return void
5184
  */
5185
  protected function setExisting($name, $value, Environment $env, $valueUnreduced = null)
5186
  {
5239
  * @param mixed $value
5240
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
5241
  * @param mixed $valueUnreduced
5242
+ *
5243
+ * @return void
5244
  */
5245
  protected function setRaw($name, $value, Environment $env, $valueUnreduced = null)
5246
  {
5254
  /**
5255
  * Get variable
5256
  *
5257
+ * @internal
5258
  *
5259
  * @param string $name
5260
+ * @param bool $shouldThrow
5261
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
5262
+ * @param bool $unreduced
5263
  *
5264
  * @return mixed|null
5265
  */
5313
  }
5314
 
5315
  if ($shouldThrow) {
5316
+ throw $this->error("Undefined variable \$$name" . ($maxDepth <= 0 ? ' (infinite recursion)' : ''));
5317
  }
5318
 
5319
  // found nothing
5326
  * @param string $name
5327
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
5328
  *
5329
+ * @return bool
5330
  */
5331
  protected function has($name, Environment $env = null)
5332
  {
5337
  * Inject variables
5338
  *
5339
  * @param array $args
5340
+ *
5341
+ * @return void
5342
  */
5343
  protected function injectVariables(array $args)
5344
  {
5353
  $name = substr($name, 1);
5354
  }
5355
 
5356
+ if (!\is_string($strValue) || ! $parser->parseValue($strValue, $value)) {
5357
  $value = $this->coerceValue($strValue);
5358
  }
5359
 
5361
  }
5362
  }
5363
 
5364
+ /**
5365
+ * Replaces variables.
5366
+ *
5367
+ * @param array<string, mixed> $variables
5368
+ *
5369
+ * @return void
5370
+ */
5371
+ public function replaceVariables(array $variables)
5372
+ {
5373
+ $this->registeredVars = [];
5374
+ $this->addVariables($variables);
5375
+ }
5376
+
5377
+ /**
5378
+ * Replaces variables.
5379
+ *
5380
+ * @param array<string, mixed> $variables
5381
+ *
5382
+ * @return void
5383
+ */
5384
+ public function addVariables(array $variables)
5385
+ {
5386
+ $triggerWarning = false;
5387
+
5388
+ foreach ($variables as $name => $value) {
5389
+ if (!$value instanceof Number && !\is_array($value)) {
5390
+ $triggerWarning = true;
5391
+ }
5392
+
5393
+ $this->registeredVars[$name] = $value;
5394
+ }
5395
+
5396
+ if ($triggerWarning) {
5397
+ @trigger_error('Passing raw values to as custom variables to the Compiler is deprecated. Use "\ScssPhp\ScssPhp\ValueConverter::parseValue" or "\ScssPhp\ScssPhp\ValueConverter::fromPhp" to convert them instead.', E_USER_DEPRECATED);
5398
+ }
5399
+ }
5400
+
5401
  /**
5402
  * Set variables
5403
  *
5404
  * @api
5405
  *
5406
  * @param array $variables
5407
+ *
5408
+ * @return void
5409
+ *
5410
+ * @deprecated Use "addVariables" or "replaceVariables" instead.
5411
  */
5412
  public function setVariables(array $variables)
5413
  {
5414
+ @trigger_error('The method "setVariables" of the Compiler is deprecated. Use the "addVariables" method for the equivalent behavior or "replaceVariables" if merging with previous variables was not desired.');
5415
+
5416
+ $this->addVariables($variables);
5417
  }
5418
 
5419
  /**
5422
  * @api
5423
  *
5424
  * @param string $name
5425
+ *
5426
+ * @return void
5427
  */
5428
  public function unsetVariable($name)
5429
  {
5445
  /**
5446
  * Adds to list of parsed files
5447
  *
5448
+ * @internal
5449
  *
5450
+ * @param string|null $path
5451
+ *
5452
+ * @return void
5453
  */
5454
  public function addParsedFile($path)
5455
  {
5456
+ if (! \is_null($path) && is_file($path)) {
5457
  $this->parsedFiles[realpath($path)] = filemtime($path);
5458
  }
5459
  }
5461
  /**
5462
  * Returns list of parsed files
5463
  *
5464
+ * @deprecated
5465
+ * @return array<string, int>
 
5466
  */
5467
  public function getParsedFiles()
5468
  {
5469
+ @trigger_error('The method "getParsedFiles" of the Compiler is deprecated. Use the "getIncludedFiles" method on the CompilationResult instance returned by compileString() instead. Be careful that the signature of the method is different.', E_USER_DEPRECATED);
5470
  return $this->parsedFiles;
5471
  }
5472
 
5476
  * @api
5477
  *
5478
  * @param string|callable $path
5479
+ *
5480
+ * @return void
5481
  */
5482
  public function addImportPath($path)
5483
  {
5491
  *
5492
  * @api
5493
  *
5494
+ * @param string|array<string|callable> $path
5495
+ *
5496
+ * @return void
5497
  */
5498
  public function setImportPaths($path)
5499
  {
5500
+ $paths = (array) $path;
5501
+ $actualImportPaths = array_filter($paths, function ($path) {
5502
+ return $path !== '';
5503
+ });
5504
+
5505
+ $this->legacyCwdImportPath = \count($actualImportPaths) !== \count($paths);
5506
+
5507
+ if ($this->legacyCwdImportPath) {
5508
+ @trigger_error('Passing an empty string in the import paths to refer to the current working directory is deprecated. If that\'s the intended behavior, the value of "getcwd()" should be used directly instead. If this was used for resolving relative imports of the input alongside "chdir" with the source directory, the path of the input file should be passed to "compileString()" instead.', E_USER_DEPRECATED);
5509
+ }
5510
+
5511
+ $this->importPaths = $actualImportPaths;
5512
  }
5513
 
5514
  /**
5516
  *
5517
  * @api
5518
  *
5519
+ * @param int $numberPrecision
5520
+ *
5521
+ * @return void
5522
+ *
5523
+ * @deprecated The number precision is not configurable anymore. The default is enough for all browsers.
5524
  */
5525
  public function setNumberPrecision($numberPrecision)
5526
  {
5527
+ @trigger_error('The number precision is not configurable anymore. '
5528
+ . 'The default is enough for all browsers.', E_USER_DEPRECATED);
5529
+ }
5530
+
5531
+ /**
5532
+ * Sets the output style.
5533
+ *
5534
+ * @api
5535
+ *
5536
+ * @param string $style One of the OutputStyle constants
5537
+ *
5538
+ * @return void
5539
+ *
5540
+ * @phpstan-param OutputStyle::* $style
5541
+ */
5542
+ public function setOutputStyle($style)
5543
+ {
5544
+ switch ($style) {
5545
+ case OutputStyle::EXPANDED:
5546
+ $this->configuredFormatter = Expanded::class;
5547
+ break;
5548
+
5549
+ case OutputStyle::COMPRESSED:
5550
+ $this->configuredFormatter = Compressed::class;
5551
+ break;
5552
+
5553
+ default:
5554
+ throw new \InvalidArgumentException(sprintf('Invalid output style "%s".', $style));
5555
+ }
5556
  }
5557
 
5558
  /**
5561
  * @api
5562
  *
5563
  * @param string $formatterName
5564
+ *
5565
+ * @return void
5566
+ *
5567
+ * @deprecated Use {@see setOutputStyle} instead.
5568
+ *
5569
+ * @phpstan-param class-string<Formatter> $formatterName
5570
  */
5571
  public function setFormatter($formatterName)
5572
  {
5573
+ if (!\in_array($formatterName, [Expanded::class, Compressed::class], true)) {
5574
+ @trigger_error('Formatters other than Expanded and Compressed are deprecated.', E_USER_DEPRECATED);
5575
+ }
5576
+ @trigger_error('The method "setFormatter" is deprecated. Use "setOutputStyle" instead.', E_USER_DEPRECATED);
5577
+
5578
+ $this->configuredFormatter = $formatterName;
5579
  }
5580
 
5581
  /**
5584
  * @api
5585
  *
5586
  * @param string $lineNumberStyle
5587
+ *
5588
+ * @return void
5589
+ *
5590
+ * @deprecated The line number output is not supported anymore. Use source maps instead.
5591
  */
5592
  public function setLineNumberStyle($lineNumberStyle)
5593
  {
5594
+ @trigger_error('The line number output is not supported anymore. '
5595
+ . 'Use source maps instead.', E_USER_DEPRECATED);
5596
+ }
5597
+
5598
+ /**
5599
+ * Configures the handling of non-ASCII outputs.
5600
+ *
5601
+ * If $charset is `true`, this will include a `@charset` declaration or a
5602
+ * UTF-8 [byte-order mark][] if the stylesheet contains any non-ASCII
5603
+ * characters. Otherwise, it will never include a `@charset` declaration or a
5604
+ * byte-order mark.
5605
+ *
5606
+ * [byte-order mark]: https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8
5607
+ *
5608
+ * @param bool $charset
5609
+ *
5610
+ * @return void
5611
+ */
5612
+ public function setCharset($charset)
5613
+ {
5614
+ $this->charset = $charset;
5615
  }
5616
 
5617
  /**
5619
  *
5620
  * @api
5621
  *
5622
+ * @param int $sourceMap
5623
+ *
5624
+ * @return void
5625
+ *
5626
+ * @phpstan-param self::SOURCE_MAP_* $sourceMap
5627
  */
5628
  public function setSourceMap($sourceMap)
5629
  {
5636
  * @api
5637
  *
5638
  * @param array $sourceMapOptions
5639
+ *
5640
+ * @phpstan-param array{sourceRoot?: string, sourceMapFilename?: string|null, sourceMapURL?: string|null, sourceMapWriteTo?: string|null, outputSourceFiles?: bool, sourceMapRootpath?: string, sourceMapBasepath?: string} $sourceMapOptions
5641
+ *
5642
+ * @return void
5643
  */
5644
  public function setSourceMapOptions($sourceMapOptions)
5645
  {
5651
  *
5652
  * @api
5653
  *
5654
+ * @param string $name
5655
+ * @param callable $callback
5656
+ * @param string[]|null $argumentDeclaration
5657
+ *
5658
+ * @return void
5659
  */
5660
+ public function registerFunction($name, $callback, $argumentDeclaration = null)
5661
  {
5662
+ if (self::isNativeFunction($name)) {
5663
+ @trigger_error(sprintf('The "%s" function is a core sass function. Overriding it with a custom implementation through "%s" is deprecated and won\'t be supported in ScssPhp 2.0 anymore.', $name, __METHOD__), E_USER_DEPRECATED);
5664
+ }
5665
+
5666
+ if ($argumentDeclaration === null) {
5667
+ @trigger_error('Omitting the argument declaration when registering custom function is deprecated and won\'t be supported in ScssPhp 2.0 anymore.', E_USER_DEPRECATED);
5668
+ }
5669
+
5670
+ $this->userFunctions[$this->normalizeName($name)] = [$callback, $argumentDeclaration];
5671
  }
5672
 
5673
  /**
5676
  * @api
5677
  *
5678
  * @param string $name
5679
+ *
5680
+ * @return void
5681
  */
5682
  public function unregisterFunction($name)
5683
  {
5690
  * @api
5691
  *
5692
  * @param string $name
5693
+ *
5694
+ * @return void
5695
+ *
5696
+ * @deprecated Registering additional features is deprecated.
5697
  */
5698
  public function addFeature($name)
5699
  {
5700
+ @trigger_error('Registering additional features is deprecated.', E_USER_DEPRECATED);
5701
+
5702
  $this->registeredFeatures[$name] = true;
5703
  }
5704
 
5707
  *
5708
  * @param string $path
5709
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
5710
+ *
5711
+ * @return void
5712
  */
5713
  protected function importFile($path, OutputBlock $out)
5714
  {
5715
+ $this->pushCallStack('import ' . $this->getPrettyPath($path));
5716
  // see if tree is cached
5717
  $realPath = realpath($path);
5718
 
5719
+ if ($realPath === false) {
5720
+ $realPath = $path;
5721
+ }
5722
+
5723
+ if (substr($path, -5) === '.sass') {
5724
+ $this->sourceIndex = \count($this->sourceNames);
5725
+ $this->sourceNames[] = $path;
5726
+ $this->sourceLine = 1;
5727
+ $this->sourceColumn = 1;
5728
+
5729
+ throw $this->error('The Sass indented syntax is not implemented.');
5730
+ }
5731
+
5732
  if (isset($this->importCache[$realPath])) {
5733
  $this->handleImportLoop($realPath);
5734
 
5741
  $this->importCache[$realPath] = $tree;
5742
  }
5743
 
5744
+ $currentDirectory = $this->currentDirectory;
5745
+ $this->currentDirectory = dirname($path);
5746
 
 
5747
  $this->compileChildrenNoReturn($tree->children, $out);
5748
+ $this->currentDirectory = $currentDirectory;
5749
  $this->popCallStack();
5750
  }
5751
 
5752
  /**
5753
+ * Save the imported files with their resolving path context
5754
  *
5755
+ * @param string|null $currentDirectory
5756
+ * @param string $path
5757
+ * @param string $filePath
5758
+ *
5759
+ * @return void
5760
+ */
5761
+ private function registerImport($currentDirectory, $path, $filePath)
5762
+ {
5763
+ $this->resolvedImports[] = ['currentDir' => $currentDirectory, 'path' => $path, 'filePath' => $filePath];
5764
+ }
5765
+
5766
+ /**
5767
+ * Detects whether the import is a CSS import.
5768
+ *
5769
+ * For legacy reasons, custom importers are called for those, allowing them
5770
+ * to replace them with an actual Sass import. However this behavior is
5771
+ * deprecated. Custom importers are expected to return null when they receive
5772
+ * a CSS import.
5773
  *
5774
  * @param string $url
5775
  *
5776
+ * @return bool
5777
  */
5778
+ public static function isCssImport($url)
5779
  {
5780
+ return 1 === preg_match('~\.css$|^https?://|^//~', $url);
5781
+ }
5782
 
5783
+ /**
5784
+ * Return the file path for an import url if it exists
5785
+ *
5786
+ * @internal
5787
+ *
5788
+ * @param string $url
5789
+ * @param string|null $currentDir
5790
+ *
5791
+ * @return string|null
5792
+ */
5793
+ public function findImport($url, $currentDir = null)
5794
+ {
5795
+ // Vanilla css and external requests. These are not meant to be Sass imports.
5796
+ // Callback importers are still called for BC.
5797
+ if (self::isCssImport($url)) {
5798
+ foreach ($this->importPaths as $dir) {
5799
+ if (\is_string($dir)) {
5800
+ continue;
5801
+ }
5802
 
5803
+ if (\is_callable($dir)) {
5804
+ // check custom callback for import path
5805
+ $file = \call_user_func($dir, $url);
5806
 
5807
+ if (! \is_null($file)) {
5808
+ if (\is_array($dir)) {
5809
+ $callableDescription = (\is_object($dir[0]) ? \get_class($dir[0]) : $dir[0]).'::'.$dir[1];
5810
+ } elseif ($dir instanceof \Closure) {
5811
+ $r = new \ReflectionFunction($dir);
5812
+ if (false !== strpos($r->name, '{closure}')) {
5813
+ $callableDescription = sprintf('closure{%s:%s}', $r->getFileName(), $r->getStartLine());
5814
+ } elseif ($class = $r->getClosureScopeClass()) {
5815
+ $callableDescription = $class->name.'::'.$r->name;
5816
+ } else {
5817
+ $callableDescription = $r->name;
5818
+ }
5819
+ } elseif (\is_object($dir)) {
5820
+ $callableDescription = \get_class($dir) . '::__invoke';
5821
+ } else {
5822
+ $callableDescription = 'callable'; // Fallback if we don't have a dedicated description
5823
+ }
5824
+ @trigger_error(sprintf('Returning a file to import for CSS or external references in custom importer callables is deprecated and will not be supported anymore in ScssPhp 2.0. This behavior is not compliant with the Sass specification. Update your "%s" importer.', $callableDescription), E_USER_DEPRECATED);
5825
 
5826
+ return $file;
5827
+ }
5828
+ }
5829
  }
5830
+ return null;
5831
+ }
5832
 
5833
+ if (!\is_null($currentDir)) {
5834
+ $relativePath = $this->resolveImportPath($url, $currentDir);
5835
+
5836
+ if (!\is_null($relativePath)) {
5837
+ return $relativePath;
5838
  }
5839
  }
5840
 
5841
  foreach ($this->importPaths as $dir) {
5842
  if (\is_string($dir)) {
5843
+ $path = $this->resolveImportPath($url, $dir);
5844
+
5845
+ if (!\is_null($path)) {
5846
+ return $path;
 
 
 
 
 
 
 
 
5847
  }
5848
  } elseif (\is_callable($dir)) {
5849
  // check custom callback for import path
5855
  }
5856
  }
5857
 
5858
+ if ($this->legacyCwdImportPath) {
5859
+ $path = $this->resolveImportPath($url, getcwd());
5860
+
5861
+ if (!\is_null($path)) {
5862
+ @trigger_error('Resolving imports relatively to the current working directory is deprecated. If that\'s the intended behavior, the value of "getcwd()" should be added as an import path explicitly instead. If this was used for resolving relative imports of the input alongside "chdir" with the source directory, the path of the input file should be passed to "compileString()" instead.', E_USER_DEPRECATED);
5863
+
5864
+ return $path;
5865
  }
5866
  }
5867
 
5868
+ throw $this->error("`$url` file not found for @import");
5869
  }
5870
 
5871
  /**
5872
+ * @param string $url
5873
+ * @param string $baseDir
 
5874
  *
5875
+ * @return string|null
5876
  */
5877
+ private function resolveImportPath($url, $baseDir)
5878
  {
5879
+ $path = Path::join($baseDir, $url);
5880
+
5881
+ $hasExtension = preg_match('/.s[ac]ss$/', $url);
5882
+
5883
+ if ($hasExtension) {
5884
+ return $this->checkImportPathConflicts($this->tryImportPath($path));
5885
+ }
5886
+
5887
+ $result = $this->checkImportPathConflicts($this->tryImportPathWithExtensions($path));
5888
+
5889
+ if (!\is_null($result)) {
5890
+ return $result;
5891
+ }
5892
+
5893
+ return $this->tryImportPathAsDirectory($path);
5894
  }
5895
 
5896
  /**
5897
+ * @param string[] $paths
 
 
5898
  *
5899
+ * @return string|null
 
 
5900
  */
5901
+ private function checkImportPathConflicts(array $paths)
5902
  {
5903
+ if (\count($paths) === 0) {
5904
+ return null;
5905
+ }
5906
 
5907
+ if (\count($paths) === 1) {
5908
+ return $paths[0];
5909
+ }
5910
+
5911
+ $formattedPrettyPaths = [];
5912
+
5913
+ foreach ($paths as $path) {
5914
+ $formattedPrettyPaths[] = ' ' . $this->getPrettyPath($path);
5915
+ }
5916
+
5917
+ throw $this->error("It's not clear which file to import. Found:\n" . implode("\n", $formattedPrettyPaths));
5918
  }
5919
 
5920
  /**
5921
+ * @param string $path
 
 
 
 
5922
  *
5923
+ * @return string[]
5924
  */
5925
+ private function tryImportPathWithExtensions($path)
5926
  {
5927
+ $result = array_merge(
5928
+ $this->tryImportPath($path.'.sass'),
5929
+ $this->tryImportPath($path.'.scss')
5930
+ );
5931
 
5932
+ if ($result) {
5933
+ return $result;
5934
  }
5935
 
5936
+ return $this->tryImportPath($path.'.css');
5937
+ }
 
5938
 
5939
+ /**
5940
+ * @param string $path
5941
+ *
5942
+ * @return string[]
5943
+ */
5944
+ private function tryImportPath($path)
5945
+ {
5946
+ $partial = dirname($path).'/_'.basename($path);
5947
 
5948
+ $candidates = [];
5949
 
5950
+ if (is_file($partial)) {
5951
+ $candidates[] = $partial;
5952
+ }
5953
 
5954
+ if (is_file($path)) {
5955
+ $candidates[] = $path;
 
5956
  }
5957
 
5958
+ return $candidates;
5959
  }
5960
 
5961
  /**
5962
+ * @param string $path
 
 
 
5963
  *
5964
+ * @return string|null
5965
  */
5966
+ private function tryImportPathAsDirectory($path)
5967
  {
5968
+ if (!is_dir($path)) {
5969
+ return null;
5970
+ }
5971
+
5972
+ return $this->checkImportPathConflicts($this->tryImportPathWithExtensions($path.'/index'));
5973
+ }
5974
+
5975
+ /**
5976
+ * @param string|null $path
5977
+ *
5978
+ * @return string
5979
+ */
5980
+ private function getPrettyPath($path)
5981
+ {
5982
+ if ($path === null) {
5983
+ return '(unknown file)';
5984
+ }
5985
+
5986
+ $normalizedPath = $path;
5987
+ $normalizedRootDirectory = $this->rootDirectory.'/';
5988
+
5989
+ if (\DIRECTORY_SEPARATOR === '\\') {
5990
+ $normalizedRootDirectory = str_replace('\\', '/', $normalizedRootDirectory);
5991
+ $normalizedPath = str_replace('\\', '/', $path);
5992
+ }
5993
+
5994
+ if (0 === strpos($normalizedPath, $normalizedRootDirectory)) {
5995
+ return substr($path, \strlen($normalizedRootDirectory));
5996
+ }
5997
+
5998
+ return $path;
5999
+ }
6000
+
6001
+ /**
6002
+ * Set encoding
6003
+ *
6004
+ * @api
6005
+ *
6006
+ * @param string|null $encoding
6007
+ *
6008
+ * @return void
6009
+ *
6010
+ * @deprecated Non-compliant support for other encodings than UTF-8 is deprecated.
6011
+ */
6012
+ public function setEncoding($encoding)
6013
+ {
6014
+ if (!$encoding || strtolower($encoding) === 'utf-8') {
6015
+ @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
6016
+ } else {
6017
+ @trigger_error(sprintf('The "%s" method is deprecated. Parsing will only support UTF-8 in ScssPhp 2.0. The non-UTF-8 parsing of ScssPhp 1.x is not spec compliant.', __METHOD__), E_USER_DEPRECATED);
6018
+ }
6019
+
6020
+ $this->encoding = $encoding;
6021
+ }
6022
+
6023
+ /**
6024
+ * Ignore errors?
6025
+ *
6026
+ * @api
6027
+ *
6028
+ * @param bool $ignoreErrors
6029
+ *
6030
+ * @return \ScssPhp\ScssPhp\Compiler
6031
+ *
6032
+ * @deprecated Ignoring Sass errors is not longer supported.
6033
+ */
6034
+ public function setIgnoreErrors($ignoreErrors)
6035
+ {
6036
+ @trigger_error('Ignoring Sass errors is not longer supported.', E_USER_DEPRECATED);
6037
+
6038
+ return $this;
6039
+ }
6040
+
6041
+ /**
6042
+ * Get source position
6043
+ *
6044
+ * @api
6045
+ *
6046
+ * @return array
6047
+ *
6048
+ * @deprecated
6049
+ */
6050
+ public function getSourcePosition()
6051
+ {
6052
+ @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
6053
+
6054
+ $sourceFile = isset($this->sourceNames[$this->sourceIndex]) ? $this->sourceNames[$this->sourceIndex] : '';
6055
+
6056
+ return [$sourceFile, $this->sourceLine, $this->sourceColumn];
6057
+ }
6058
+
6059
+ /**
6060
+ * Throw error (exception)
6061
+ *
6062
+ * @api
6063
+ *
6064
+ * @param string $msg Message with optional sprintf()-style vararg parameters
6065
+ *
6066
+ * @return never
6067
+ *
6068
+ * @throws \ScssPhp\ScssPhp\Exception\CompilerException
6069
+ *
6070
+ * @deprecated use "error" and throw the exception in the caller instead.
6071
+ */
6072
+ public function throwError($msg)
6073
+ {
6074
+ @trigger_error(
6075
+ 'The method "throwError" is deprecated. Use "error" and throw the exception in the caller instead',
6076
+ E_USER_DEPRECATED
6077
+ );
6078
+
6079
+ throw $this->error(...func_get_args());
6080
+ }
6081
+
6082
+ /**
6083
+ * Build an error (exception)
6084
+ *
6085
+ * @internal
6086
+ *
6087
+ * @param string $msg Message with optional sprintf()-style vararg parameters
6088
+ * @param bool|float|int|string|null ...$args
6089
+ *
6090
+ * @return CompilerException
6091
+ */
6092
+ public function error($msg, ...$args)
6093
+ {
6094
+ if ($args) {
6095
+ $msg = sprintf($msg, ...$args);
6096
+ }
6097
+
6098
+ if (! $this->ignoreCallStackMessage) {
6099
+ $msg = $this->addLocationToMessage($msg);
6100
+ }
6101
+
6102
+ return new CompilerException($msg);
6103
+ }
6104
+
6105
+ /**
6106
+ * @param string $msg
6107
+ *
6108
+ * @return string
6109
+ */
6110
+ private function addLocationToMessage($msg)
6111
+ {
6112
+ $line = $this->sourceLine;
6113
+ $column = $this->sourceColumn;
6114
+
6115
+ $loc = isset($this->sourceNames[$this->sourceIndex])
6116
+ ? $this->getPrettyPath($this->sourceNames[$this->sourceIndex]) . " on line $line, at column $column"
6117
+ : "line: $line, column: $column";
6118
+
6119
+ $msg = "$msg: $loc";
6120
+
6121
+ $callStackMsg = $this->callStackMessage();
6122
+
6123
+ if ($callStackMsg) {
6124
+ $msg .= "\nCall Stack:\n" . $callStackMsg;
6125
+ }
6126
+
6127
+ return $msg;
6128
+ }
6129
+
6130
+ /**
6131
+ * @param string $functionName
6132
+ * @param array $ExpectedArgs
6133
+ * @param int $nbActual
6134
+ * @return CompilerException
6135
+ *
6136
+ * @deprecated
6137
+ */
6138
+ public function errorArgsNumber($functionName, $ExpectedArgs, $nbActual)
6139
+ {
6140
+ @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
6141
+
6142
+ $nbExpected = \count($ExpectedArgs);
6143
+
6144
+ if ($nbActual > $nbExpected) {
6145
+ return $this->error(
6146
+ 'Error: Only %d arguments allowed in %s(), but %d were passed.',
6147
+ $nbExpected,
6148
+ $functionName,
6149
+ $nbActual
6150
+ );
6151
+ } else {
6152
+ $missing = [];
6153
+
6154
+ while (count($ExpectedArgs) && count($ExpectedArgs) > $nbActual) {
6155
+ array_unshift($missing, array_pop($ExpectedArgs));
6156
+ }
6157
+
6158
+ return $this->error(
6159
+ 'Error: %s() argument%s %s missing.',
6160
+ $functionName,
6161
+ count($missing) > 1 ? 's' : '',
6162
+ implode(', ', $missing)
6163
+ );
6164
+ }
6165
+ }
6166
+
6167
+ /**
6168
+ * Beautify call stack for output
6169
+ *
6170
+ * @param bool $all
6171
+ * @param int|null $limit
6172
+ *
6173
+ * @return string
6174
+ */
6175
+ protected function callStackMessage($all = false, $limit = null)
6176
+ {
6177
+ $callStackMsg = [];
6178
+ $ncall = 0;
6179
 
6180
  if ($this->callStack) {
6181
  foreach (array_reverse($this->callStack) as $call) {
6182
  if ($all || (isset($call['n']) && $call['n'])) {
6183
+ $msg = '#' . $ncall++ . ' ' . $call['n'] . ' ';
6184
  $msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]])
6185
+ ? $this->getPrettyPath($this->sourceNames[$call[Parser::SOURCE_INDEX]])
6186
  : '(unknown file)');
6187
+ $msg .= ' on line ' . $call[Parser::SOURCE_LINE];
6188
 
6189
  $callStackMsg[] = $msg;
6190
 
6203
  *
6204
  * @param string $name
6205
  *
6206
+ * @return void
6207
+ *
6208
  * @throws \Exception
6209
  */
6210
  protected function handleImportLoop($name)
6216
 
6217
  $file = $this->sourceNames[$env->block->sourceIndex];
6218
 
6219
+ if ($file === null) {
6220
+ continue;
6221
+ }
6222
+
6223
  if (realpath($file) === $name) {
6224
+ throw $this->error('An @import loop has been found: %s imports %s', $file, basename($file));
 
6225
  }
6226
  }
6227
  }
6229
  /**
6230
  * Call SCSS @function
6231
  *
6232
+ * @param CallableBlock|null $func
6233
+ * @param array $argValues
 
6234
  *
6235
+ * @return array|Number
6236
  */
6237
+ protected function callScssFunction($func, $argValues)
6238
  {
 
 
6239
  if (! $func) {
6240
+ return static::$defaultValue;
6241
  }
6242
+ $name = $func->name;
6243
 
6244
  $this->pushEnv();
6245
 
6249
  }
6250
 
6251
  // throw away lines and children
6252
+ $tmp = new OutputBlock();
6253
  $tmp->lines = [];
6254
  $tmp->children = [];
6255
 
6258
  if (! empty($func->parentEnv)) {
6259
  $this->env->declarationScopeParent = $func->parentEnv;
6260
  } else {
6261
+ throw $this->error("@function $name() without parentEnv");
6262
  }
6263
 
6264
+ $ret = $this->compileChildren($func->children, $tmp, $this->env->marker . ' ' . $name);
6265
 
6266
  $this->popEnv();
6267
 
6268
+ return ! isset($ret) ? static::$defaultValue : $ret;
 
 
6269
  }
6270
 
6271
  /**
6272
  * Call built-in and registered (PHP) functions
6273
  *
6274
  * @param string $name
6275
+ * @param callable $function
6276
+ * @param array $prototype
6277
  * @param array $args
 
6278
  *
6279
+ * @return array|Number|null
6280
  */
6281
+ protected function callNativeFunction($name, $function, $prototype, $args)
6282
  {
6283
+ $libName = (is_array($function) ? end($function) : null);
6284
+ $sorted_kwargs = $this->sortNativeFunctionArgs($libName, $prototype, $args);
 
6285
 
6286
+ if (\is_null($sorted_kwargs)) {
6287
+ return null;
 
 
 
 
 
 
6288
  }
6289
+ @list($sorted, $kwargs) = $sorted_kwargs;
6290
 
6291
+ if ($name !== 'if') {
 
 
 
 
 
 
 
 
6292
  foreach ($sorted as &$val) {
6293
+ if ($val !== null) {
6294
+ $val = $this->reduce($val, true);
6295
+ }
6296
  }
6297
  }
6298
 
6299
+ $returnValue = \call_user_func($function, $sorted, $kwargs);
6300
 
6301
  if (! isset($returnValue)) {
6302
+ return null;
6303
  }
6304
 
6305
+ if (\is_array($returnValue) || $returnValue instanceof Number) {
6306
+ return $returnValue;
6307
+ }
6308
 
6309
+ @trigger_error(sprintf('Returning a PHP value from the "%s" custom function is deprecated. A sass value must be returned instead.', $name), E_USER_DEPRECATED);
6310
+
6311
+ return $this->coerceValue($returnValue);
6312
  }
6313
 
6314
  /**
6320
  */
6321
  protected function getBuiltinFunction($name)
6322
  {
6323
+ $libName = self::normalizeNativeFunctionName($name);
6324
+ return [$this, $libName];
6325
+ }
6326
+
6327
+ /**
6328
+ * Normalize native function name
6329
+ *
6330
+ * @internal
6331
+ *
6332
+ * @param string $name
6333
+ *
6334
+ * @return string
6335
+ */
6336
+ public static function normalizeNativeFunctionName($name)
6337
+ {
6338
+ $name = str_replace("-", "_", $name);
6339
  $libName = 'lib' . preg_replace_callback(
6340
  '/_(.)/',
6341
  function ($m) {
6343
  },
6344
  ucfirst($name)
6345
  );
6346
+ return $libName;
6347
+ }
6348
 
6349
+ /**
6350
+ * Check if a function is a native built-in scss function, for css parsing
6351
+ *
6352
+ * @internal
6353
+ *
6354
+ * @param string $name
6355
+ *
6356
+ * @return bool
6357
+ */
6358
+ public static function isNativeFunction($name)
6359
+ {
6360
+ return method_exists(Compiler::class, self::normalizeNativeFunctionName($name));
6361
  }
6362
 
6363
  /**
6364
  * Sorts keyword arguments
6365
  *
6366
  * @param string $functionName
6367
+ * @param array|null $prototypes
6368
  * @param array $args
6369
  *
6370
+ * @return array|null
6371
  */
6372
  protected function sortNativeFunctionArgs($functionName, $prototypes, $args)
6373
  {
6377
  $keyArgs = [];
6378
  $posArgs = [];
6379
 
6380
+ if (\is_array($args) && \count($args) && \end($args) === static::$null) {
6381
+ array_pop($args);
6382
+ }
6383
+
6384
  // separate positional and keyword arguments
6385
  foreach ($args as $arg) {
6386
  list($key, $value) = $arg;
6387
 
6388
+ if (empty($key) or empty($key[1])) {
 
 
6389
  $posArgs[] = empty($arg[2]) ? $value : $arg;
6390
  } else {
6391
+ $keyArgs[$key[1]] = $value;
6392
  }
6393
  }
6394
 
6399
  if (\in_array($functionName, ['libRgb', 'libRgba', 'libHsl', 'libHsla'])) {
6400
  // notation 100 127 255 / 0 is in fact a simple list of 4 values
6401
  foreach ($args as $k => $arg) {
6402
+ if (!isset($arg[1])) {
6403
+ continue; // This happens when using a trailing comma
6404
+ }
6405
  if ($arg[1][0] === Type::T_LIST && \count($arg[1][2]) === 3) {
6406
+ $args[$k][1][2] = $this->extractSlashAlphaInColorFunction($arg[1][2]);
 
 
 
 
 
 
 
6407
  }
6408
  }
6409
  }
6410
 
6411
+ list($positionalArgs, $namedArgs, $names, $separator, $hasSplat) = $this->evaluateArguments($args, false);
6412
 
6413
  if (! \is_array(reset($prototypes))) {
6414
  $prototypes = [$prototypes];
6415
  }
6416
 
6417
+ $parsedPrototypes = array_map([$this, 'parseFunctionPrototype'], $prototypes);
6418
+ assert(!empty($parsedPrototypes));
6419
+ $matchedPrototype = $this->selectFunctionPrototype($parsedPrototypes, \count($positionalArgs), $names);
6420
+
6421
+ $this->verifyPrototype($matchedPrototype, \count($positionalArgs), $names, $hasSplat);
6422
+
6423
+ $vars = $this->applyArgumentsToDeclaration($matchedPrototype, $positionalArgs, $namedArgs, $separator);
6424
+
6425
+ $finalArgs = [];
6426
  $keyArgs = [];
6427
 
6428
+ foreach ($matchedPrototype['arguments'] as $argument) {
6429
+ list($normalizedName, $originalName, $default) = $argument;
 
6430
 
6431
+ if (isset($vars[$normalizedName])) {
6432
+ $value = $vars[$normalizedName];
6433
+ } else {
6434
+ $value = $default;
6435
+ }
6436
 
6437
+ // special null value as default: translate to real null here
6438
+ if ($value === [Type::T_KEYWORD, 'null']) {
6439
+ $value = null;
6440
+ }
6441
 
6442
+ $finalArgs[] = $value;
6443
+ $keyArgs[$originalName] = $value;
6444
+ }
6445
 
6446
+ if ($matchedPrototype['rest_argument'] !== null) {
6447
+ $value = $vars[$matchedPrototype['rest_argument']];
 
 
 
 
 
6448
 
6449
+ $finalArgs[] = $value;
6450
+ $keyArgs[$matchedPrototype['rest_argument']] = $value;
6451
+ }
6452
 
6453
+ return [$finalArgs, $keyArgs];
6454
+ }
6455
+
6456
+ /**
6457
+ * Parses a function prototype to the internal representation of arguments.
6458
+ *
6459
+ * The input is an array of strings describing each argument, as supported
6460
+ * in {@see registerFunction}. Argument names don't include the `$`.
6461
+ * The output contains the list of positional argument, with their normalized
6462
+ * name (underscores are replaced by dashes), their original name (to be used
6463
+ * in case of error reporting) and their default value. The output also contains
6464
+ * the normalized name of the rest argument, or null if the function prototype
6465
+ * is not variadic.
6466
+ *
6467
+ * @param string[] $prototype
6468
+ *
6469
+ * @return array
6470
+ * @phpstan-return array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null}
6471
+ */
6472
+ private function parseFunctionPrototype(array $prototype)
6473
+ {
6474
+ static $parser = null;
6475
+
6476
+ $arguments = [];
6477
+ $restArgument = null;
6478
 
6479
+ foreach ($prototype as $p) {
6480
+ if (null !== $restArgument) {
6481
+ throw new \InvalidArgumentException('The argument declaration is invalid. The rest argument must be the last one.');
6482
+ }
6483
+
6484
+ $default = null;
6485
+ $p = explode(':', $p, 2);
6486
+ $name = str_replace('_', '-', $p[0]);
6487
+
6488
+ if (isset($p[1])) {
6489
+ $defaultSource = trim($p[1]);
6490
+
6491
+ if ($defaultSource === 'null') {
6492
+ // differentiate this null from the static::$null
6493
+ $default = [Type::T_KEYWORD, 'null'];
6494
+ } else {
6495
+ if (\is_null($parser)) {
6496
+ $parser = $this->parserFactory(__METHOD__);
6497
+ }
6498
+
6499
+ $parser->parseValue($defaultSource, $default);
6500
  }
6501
+ }
6502
 
6503
+ if (substr($name, -3) === '...') {
6504
+ $restArgument = substr($name, 0, -3);
6505
+ } else {
6506
+ $arguments[] = [$name, $p[0], $default];
6507
  }
6508
+ }
6509
+
6510
+ return [
6511
+ 'arguments' => $arguments,
6512
+ 'rest_argument' => $restArgument,
6513
+ ];
6514
+ }
6515
 
6516
+ /**
6517
+ * Returns the function prototype for the given positional and named arguments.
6518
+ *
6519
+ * If no exact match is found, finds the closest approximation. Note that this
6520
+ * doesn't guarantee that $positional and $names are valid for the returned
6521
+ * prototype.
6522
+ *
6523
+ * @param array[] $prototypes
6524
+ * @param int $positional
6525
+ * @param array<string, string> $names A set of names, as both keys and values
6526
+ *
6527
+ * @return array
6528
+ *
6529
+ * @phpstan-param non-empty-list<array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null}> $prototypes
6530
+ * @phpstan-return array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null}
6531
+ */
6532
+ private function selectFunctionPrototype(array $prototypes, $positional, array $names)
6533
+ {
6534
+ $fuzzyMatch = null;
6535
+ $minMismatchDistance = null;
6536
 
6537
+ foreach ($prototypes as $prototype) {
6538
+ // Ideally, find an exact match.
6539
+ if ($this->checkPrototypeMatches($prototype, $positional, $names)) {
6540
+ return $prototype;
6541
+ }
6542
 
6543
+ $mismatchDistance = \count($prototype['arguments']) - $positional;
 
 
6544
 
6545
+ if ($minMismatchDistance !== null) {
6546
+ if (abs($mismatchDistance) > abs($minMismatchDistance)) {
6547
+ continue;
6548
  }
6549
 
6550
+ // If two overloads have the same mismatch distance, favor the overload
6551
+ // that has more arguments.
6552
+ if (abs($mismatchDistance) === abs($minMismatchDistance) && $mismatchDistance < 0) {
6553
+ continue;
6554
  }
6555
+ }
6556
 
6557
+ $minMismatchDistance = $mismatchDistance;
6558
+ $fuzzyMatch = $prototype;
6559
+ }
6560
 
6561
+ return $fuzzyMatch;
6562
+ }
 
6563
 
6564
+ /**
6565
+ * Checks whether the argument invocation matches the callable prototype.
6566
+ *
6567
+ * The rules are similar to {@see verifyPrototype}. The boolean return value
6568
+ * avoids the overhead of building and catching exceptions when the reason of
6569
+ * not matching the prototype does not need to be known.
6570
+ *
6571
+ * @param array $prototype
6572
+ * @param int $positional
6573
+ * @param array<string, string> $names
6574
+ *
6575
+ * @return bool
6576
+ *
6577
+ * @phpstan-param array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null} $prototype
6578
+ */
6579
+ private function checkPrototypeMatches(array $prototype, $positional, array $names)
6580
+ {
6581
+ $nameUsed = 0;
6582
 
6583
+ foreach ($prototype['arguments'] as $i => $argument) {
6584
+ list ($name, $originalName, $default) = $argument;
6585
+
6586
+ if ($i < $positional) {
6587
+ if (isset($names[$name])) {
6588
+ return false;
6589
  }
6590
+ } elseif (isset($names[$name])) {
6591
+ $nameUsed++;
6592
+ } elseif ($default === null) {
6593
+ return false;
6594
  }
 
6595
  }
6596
 
6597
+ if ($prototype['rest_argument'] !== null) {
6598
+ return true;
6599
  }
6600
 
6601
+ if ($positional > \count($prototype['arguments'])) {
6602
+ return false;
6603
+ }
6604
+
6605
+ if ($nameUsed < \count($names)) {
6606
+ return false;
6607
+ }
6608
+
6609
+ return true;
6610
  }
6611
 
6612
  /**
6613
+ * Verifies that the argument invocation is valid for the callable prototype.
6614
  *
6615
+ * @param array $prototype
6616
+ * @param int $positional
6617
+ * @param array<string, string> $names
6618
+ * @param bool $hasSplat
 
6619
  *
6620
+ * @return void
6621
  *
6622
+ * @throws SassScriptException
6623
+ *
6624
+ * @phpstan-param array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null} $prototype
6625
  */
6626
+ private function verifyPrototype(array $prototype, $positional, array $names, $hasSplat)
6627
  {
6628
+ $nameUsed = 0;
6629
 
6630
+ foreach ($prototype['arguments'] as $i => $argument) {
6631
+ list ($name, $originalName, $default) = $argument;
 
6632
 
6633
+ if ($i < $positional) {
6634
+ if (isset($names[$name])) {
6635
+ throw new SassScriptException(sprintf('Argument $%s was passed both by position and by name.', $originalName));
6636
+ }
6637
+ } elseif (isset($names[$name])) {
6638
+ $nameUsed++;
6639
+ } elseif ($default === null) {
6640
+ throw new SassScriptException(sprintf('Missing argument $%s', $originalName));
6641
+ }
6642
+ }
6643
 
6644
+ if ($prototype['rest_argument'] !== null) {
6645
+ return;
6646
  }
6647
 
6648
+ if ($positional > \count($prototype['arguments'])) {
6649
+ $message = sprintf(
6650
+ 'Only %d %sargument%s allowed, but %d %s passed.',
6651
+ \count($prototype['arguments']),
6652
+ empty($names) ? '' : 'positional ',
6653
+ \count($prototype['arguments']) === 1 ? '' : 's',
6654
+ $positional,
6655
+ $positional === 1 ? 'was' : 'were'
6656
+ );
6657
+ if (!$hasSplat) {
6658
+ throw new SassScriptException($message);
6659
+ }
6660
 
6661
+ $message = $this->addLocationToMessage($message);
6662
+ $message .= "\nThis will be an error in future versions of Sass.";
6663
+ $this->logger->warn($message, true);
6664
+ }
6665
 
6666
+ if ($nameUsed < \count($names)) {
6667
+ $unknownNames = array_values(array_diff($names, array_column($prototype['arguments'], 0)));
6668
+ $lastName = array_pop($unknownNames);
6669
+ $message = sprintf(
6670
+ 'No argument%s named $%s%s.',
6671
+ $unknownNames ? 's' : '',
6672
+ $unknownNames ? implode(', $', $unknownNames) . ' or $' : '',
6673
+ $lastName
6674
+ );
6675
+ throw new SassScriptException($message);
6676
  }
6677
+ }
6678
 
6679
+ /**
6680
+ * Evaluates the argument from the invocation.
6681
+ *
6682
+ * This returns several things about this invocation:
6683
+ * - the list of positional arguments
6684
+ * - the map of named arguments, indexed by normalized names
6685
+ * - the set of names used in the arguments (that's an array using the normalized names as keys for O(1) access)
6686
+ * - the separator used by the list using the splat operator, if any
6687
+ * - a boolean indicator whether any splat argument (list or map) was used, to support the incomplete error reporting.
6688
+ *
6689
+ * @param array[] $args
6690
+ * @param bool $reduce Whether arguments should be reduced to their value
6691
+ *
6692
+ * @return array
6693
+ *
6694
+ * @throws SassScriptException
6695
+ *
6696
+ * @phpstan-return array{0: list<array|Number>, 1: array<string, array|Number>, 2: array<string, string>, 3: string|null, 4: bool}
6697
+ */
6698
+ private function evaluateArguments(array $args, $reduce = true)
6699
+ {
6700
+ // this represents trailing commas
6701
+ if (\count($args) && end($args) === static::$null) {
6702
+ array_pop($args);
6703
+ }
6704
+
6705
+ $splatSeparator = null;
6706
+ $keywordArgs = [];
6707
+ $names = [];
6708
+ $positionalArgs = [];
6709
+ $hasKeywordArgument = false;
6710
+ $hasSplat = false;
6711
 
6712
+ foreach ($args as $arg) {
6713
+ if (!empty($arg[0])) {
 
6714
  $hasKeywordArgument = true;
6715
 
6716
+ assert(\is_string($arg[0][1]));
6717
+ $name = str_replace('_', '-', $arg[0][1]);
 
 
 
 
 
 
 
6718
 
6719
+ if (isset($keywordArgs[$name])) {
6720
+ throw new SassScriptException(sprintf('Duplicate named argument $%s.', $arg[0][1]));
 
 
 
 
 
 
 
 
 
 
6721
  }
6722
+
6723
+ $keywordArgs[$name] = $this->maybeReduce($reduce, $arg[1]);
6724
+ $names[$name] = $name;
6725
+ } elseif (! empty($arg[2])) {
6726
+ // $arg[2] means a var followed by ... in the arg ($list... )
6727
  $val = $this->reduce($arg[1], true);
6728
+ $hasSplat = true;
6729
 
6730
  if ($val[0] === Type::T_LIST) {
6731
+ foreach ($val[2] as $item) {
6732
+ if (\is_null($splatSeparator)) {
6733
+ $splatSeparator = $val[1];
6734
+ }
6735
+
6736
+ $positionalArgs[] = $this->maybeReduce($reduce, $item);
6737
+ }
6738
+
6739
+ if (isset($val[3]) && \is_array($val[3])) {
6740
+ foreach ($val[3] as $name => $item) {
6741
+ assert(\is_string($name));
6742
+
6743
+ $normalizedName = str_replace('_', '-', $name);
6744
+
6745
+ if (isset($keywordArgs[$normalizedName])) {
6746
+ throw new SassScriptException(sprintf('Duplicate named argument $%s.', $name));
6747
  }
6748
 
6749
+ $keywordArgs[$normalizedName] = $this->maybeReduce($reduce, $item);
6750
+ $names[$normalizedName] = $normalizedName;
6751
+ $hasKeywordArgument = true;
6752
+ }
6753
+ }
6754
+ } elseif ($val[0] === Type::T_MAP) {
6755
+ foreach ($val[1] as $i => $name) {
6756
+ $name = $this->compileStringContent($this->coerceString($name));
6757
+ $item = $val[2][$i];
6758
+
6759
+ if (! is_numeric($name)) {
6760
+ $normalizedName = str_replace('_', '-', $name);
6761
+
6762
+ if (isset($keywordArgs[$normalizedName])) {
6763
+ throw new SassScriptException(sprintf('Duplicate named argument $%s.', $name));
6764
  }
6765
+
6766
+ $keywordArgs[$normalizedName] = $this->maybeReduce($reduce, $item);
6767
+ $names[$normalizedName] = $normalizedName;
6768
+ $hasKeywordArgument = true;
6769
  } else {
6770
  if (\is_null($splatSeparator)) {
6771
  $splatSeparator = $val[1];
6772
  }
6773
 
6774
+ $positionalArgs[] = $this->maybeReduce($reduce, $item);
6775
+ }
6776
+ }
6777
+ } elseif ($val[0] !== Type::T_NULL) { // values other than null are treated a single-element lists, while null is the empty list
6778
+ $positionalArgs[] = $this->maybeReduce($reduce, $val);
6779
+ }
6780
+ } elseif ($hasKeywordArgument) {
6781
+ throw new SassScriptException('Positional arguments must come before keyword arguments.');
6782
+ } else {
6783
+ $positionalArgs[] = $this->maybeReduce($reduce, $arg[1]);
6784
+ }
6785
+ }
6786
+
6787
+ return [$positionalArgs, $keywordArgs, $names, $splatSeparator, $hasSplat];
6788
+ }
6789
+
6790
+ /**
6791
+ * @param bool $reduce
6792
+ * @param array|Number $value
6793
+ *
6794
+ * @return array|Number
6795
+ */
6796
+ private function maybeReduce($reduce, $value)
6797
+ {
6798
+ if ($reduce) {
6799
+ return $this->reduce($value, true);
6800
+ }
6801
+
6802
+ return $value;
6803
+ }
6804
+
6805
+ /**
6806
+ * Apply argument values per definition
6807
+ *
6808
+ * @param array[] $argDef
6809
+ * @param array|null $argValues
6810
+ * @param bool $storeInEnv
6811
+ * @param bool $reduce only used if $storeInEnv = false
6812
+ *
6813
+ * @return array<string, array|Number>
6814
+ *
6815
+ * @phpstan-param list<array{0: string, 1: array|Number|null, 2: bool}> $argDef
6816
+ *
6817
+ * @throws \Exception
6818
+ */
6819
+ protected function applyArguments($argDef, $argValues, $storeInEnv = true, $reduce = true)
6820
+ {
6821
+ $output = [];
6822
+
6823
+ if (\is_null($argValues)) {
6824
+ $argValues = [];
6825
+ }
6826
+
6827
+ if ($storeInEnv) {
6828
+ $storeEnv = $this->getStoreEnv();
6829
+
6830
+ $env = new Environment();
6831
+ $env->store = $storeEnv->store;
6832
+ }
6833
 
6834
+ $prototype = ['arguments' => [], 'rest_argument' => null];
6835
+ $originalRestArgumentName = null;
 
 
 
 
 
 
 
6836
 
6837
+ foreach ($argDef as $arg) {
6838
+ list($name, $default, $isVariable) = $arg;
6839
+ $normalizedName = str_replace('_', '-', $name);
 
 
 
 
 
 
6840
 
6841
+ if ($isVariable) {
6842
+ $originalRestArgumentName = $name;
6843
+ $prototype['rest_argument'] = $normalizedName;
 
 
 
 
 
 
6844
  } else {
6845
+ $prototype['arguments'][] = [$normalizedName, $name, !empty($default) ? $default : null];
6846
  }
6847
  }
6848
 
6849
+ list($positionalArgs, $namedArgs, $names, $splatSeparator, $hasSplat) = $this->evaluateArguments($argValues, $reduce);
 
6850
 
6851
+ $this->verifyPrototype($prototype, \count($positionalArgs), $names, $hasSplat);
 
6852
 
6853
+ $vars = $this->applyArgumentsToDeclaration($prototype, $positionalArgs, $namedArgs, $splatSeparator);
 
 
6854
 
6855
+ foreach ($prototype['arguments'] as $argument) {
6856
+ list($normalizedName, $name) = $argument;
6857
+
6858
+ if (!isset($vars[$normalizedName])) {
 
 
 
 
6859
  continue;
6860
+ }
6861
+
6862
+ $val = $vars[$normalizedName];
6863
+
6864
+ if ($storeInEnv) {
6865
+ $this->set($name, $this->reduce($val, true), true, $env);
6866
  } else {
6867
+ $output[$name] = ($reduce ? $this->reduce($val, true) : $val);
 
6868
  }
6869
+ }
6870
+
6871
+ if ($prototype['rest_argument'] !== null) {
6872
+ assert($originalRestArgumentName !== null);
6873
+ $name = $originalRestArgumentName;
6874
+ $val = $vars[$prototype['rest_argument']];
6875
 
6876
  if ($storeInEnv) {
6877
  $this->set($name, $this->reduce($val, true), true, $env);
6884
  $storeEnv->store = $env->store;
6885
  }
6886
 
6887
+ foreach ($prototype['arguments'] as $argument) {
6888
+ list($normalizedName, $name, $default) = $argument;
6889
 
6890
+ if (isset($vars[$normalizedName])) {
6891
  continue;
6892
  }
6893
+ assert($default !== null);
6894
 
6895
  if ($storeInEnv) {
6896
  $this->set($name, $this->reduce($default, true), true);
6902
  return $output;
6903
  }
6904
 
6905
+ /**
6906
+ * Apply argument values per definition.
6907
+ *
6908
+ * This method assumes that the arguments are valid for the provided prototype.
6909
+ * The validation with {@see verifyPrototype} must have been run before calling
6910
+ * it.
6911
+ * Arguments are returned as a map from the normalized argument names to the
6912
+ * value. Additional arguments are collected in a sass argument list available
6913
+ * under the name of the rest argument in the result.
6914
+ *
6915
+ * Defaults are not applied as they are resolved in a different environment.
6916
+ *
6917
+ * @param array $prototype
6918
+ * @param array<array|Number> $positionalArgs
6919
+ * @param array<string, array|Number> $namedArgs
6920
+ * @param string|null $splatSeparator
6921
+ *
6922
+ * @return array<string, array|Number>
6923
+ *
6924
+ * @phpstan-param array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null} $prototype
6925
+ */
6926
+ private function applyArgumentsToDeclaration(array $prototype, array $positionalArgs, array $namedArgs, $splatSeparator)
6927
+ {
6928
+ $output = [];
6929
+ $minLength = min(\count($positionalArgs), \count($prototype['arguments']));
6930
+
6931
+ for ($i = 0; $i < $minLength; $i++) {
6932
+ list($name) = $prototype['arguments'][$i];
6933
+ $val = $positionalArgs[$i];
6934
+
6935
+ $output[$name] = $val;
6936
+ }
6937
+
6938
+ $restNamed = $namedArgs;
6939
+
6940
+ for ($i = \count($positionalArgs); $i < \count($prototype['arguments']); $i++) {
6941
+ $argument = $prototype['arguments'][$i];
6942
+ list($name) = $argument;
6943
+
6944
+ if (isset($namedArgs[$name])) {
6945
+ $val = $namedArgs[$name];
6946
+ unset($restNamed[$name]);
6947
+ } else {
6948
+ continue;
6949
+ }
6950
+
6951
+ $output[$name] = $val;
6952
+ }
6953
+
6954
+ if ($prototype['rest_argument'] !== null) {
6955
+ $name = $prototype['rest_argument'];
6956
+ $rest = array_values(array_slice($positionalArgs, \count($prototype['arguments'])));
6957
+
6958
+ $val = [Type::T_LIST, \is_null($splatSeparator) ? ',' : $splatSeparator , $rest, $restNamed];
6959
+
6960
+ $output[$name] = $val;
6961
+ }
6962
+
6963
+ return $output;
6964
+ }
6965
+
6966
  /**
6967
  * Coerce a php value into a scss one
6968
  *
6969
  * @param mixed $value
6970
  *
6971
+ * @return array|Number
6972
  */
6973
  protected function coerceValue($value)
6974
  {
6975
+ if (\is_array($value) || $value instanceof Number) {
6976
  return $value;
6977
  }
6978
 
6985
  }
6986
 
6987
  if (is_numeric($value)) {
6988
+ return new Number($value, '');
6989
  }
6990
 
6991
  if ($value === '') {
7003
  }
7004
 
7005
  /**
7006
+ * Tries to convert an item to a Sass map
7007
  *
7008
+ * @param Number|array $item
7009
  *
7010
+ * @return array|null
7011
  */
7012
+ private function tryMap($item)
7013
  {
7014
+ if ($item instanceof Number) {
7015
+ return null;
7016
+ }
7017
+
7018
  if ($item[0] === Type::T_MAP) {
7019
  return $item;
7020
  }
7021
 
7022
+ if (
7023
+ $item[0] === Type::T_LIST &&
7024
+ $item[2] === []
7025
  ) {
7026
  return static::$emptyMap;
7027
  }
7028
 
7029
+ return null;
7030
+ }
7031
+
7032
+ /**
7033
+ * Coerce something to map
7034
+ *
7035
+ * @param array|Number $item
7036
+ *
7037
+ * @return array|Number
7038
+ */
7039
+ protected function coerceMap($item)
7040
+ {
7041
+ $map = $this->tryMap($item);
7042
+
7043
+ if ($map !== null) {
7044
+ return $map;
7045
+ }
7046
+
7047
+ return $item;
7048
  }
7049
 
7050
  /**
7051
  * Coerce something to list
7052
  *
7053
+ * @param array|Number $item
7054
+ * @param string $delim
7055
+ * @param bool $removeTrailingNull
7056
  *
7057
  * @return array
7058
  */
7059
  protected function coerceList($item, $delim = ',', $removeTrailingNull = false)
7060
  {
7061
+ if ($item instanceof Number) {
7062
+ return [Type::T_LIST, '', [$item]];
7063
+ }
7064
+
7065
+ if ($item[0] === Type::T_LIST) {
7066
  // remove trailing null from the list
7067
  if ($removeTrailingNull && end($item[2]) === static::$null) {
7068
  array_pop($item[2]);
7071
  return $item;
7072
  }
7073
 
7074
+ if ($item[0] === Type::T_MAP) {
7075
  $keys = $item[1];
7076
  $values = $item[2];
7077
  $list = [];
7080
  $key = $keys[$i];
7081
  $value = $values[$i];
7082
 
 
 
 
 
 
 
 
 
 
 
 
 
7083
  $list[] = [
7084
  Type::T_LIST,
7085
+ ' ',
7086
  [$key, $value]
7087
  ];
7088
  }
7089
 
7090
+ return [Type::T_LIST, $list ? ',' : '', $list];
7091
  }
7092
 
7093
+ return [Type::T_LIST, '', [$item]];
7094
  }
7095
 
7096
  /**
7097
  * Coerce color for expression
7098
  *
7099
+ * @param array|Number $value
7100
  *
7101
+ * @return array|Number
7102
  */
7103
  protected function coerceForExpression($value)
7104
  {
7112
  /**
7113
  * Coerce value to color
7114
  *
7115
+ * @param array|Number $value
7116
+ * @param bool $inRGBFunction
7117
  *
7118
  * @return array|null
7119
  */
7120
  protected function coerceColor($value, $inRGBFunction = false)
7121
  {
7122
+ if ($value instanceof Number) {
7123
+ return null;
7124
+ }
7125
+
7126
  switch ($value[0]) {
7127
  case Type::T_COLOR:
7128
  for ($i = 1; $i <= 3; $i++) {
7208
  if ($color[3] === 255) {
7209
  $color[3] = 1; // fully opaque
7210
  } else {
7211
+ $color[3] = round($color[3] / 255, Number::PRECISION);
7212
  }
7213
  }
7214
 
7231
  }
7232
 
7233
  /**
7234
+ * @param int|Number $value
7235
+ * @param bool $isAlpha
7236
  *
7237
+ * @return int|mixed
7238
  */
7239
  protected function compileRGBAValue($value, $isAlpha = false)
7240
  {
7246
  }
7247
 
7248
  /**
7249
+ * @param mixed $value
7250
+ * @param int|float $min
7251
+ * @param int|float $max
7252
+ * @param bool $isInt
 
 
7253
  *
7254
+ * @return int|mixed
7255
  */
7256
+ protected function compileColorPartValue($value, $min, $max, $isInt = true)
7257
  {
7258
  if (! is_numeric($value)) {
7259
  if (\is_array($value)) {
7260
  $reduced = $this->reduce($value);
7261
 
7262
+ if ($reduced instanceof Number) {
7263
  $value = $reduced;
7264
  }
7265
  }
7266
 
7267
+ if ($value instanceof Number) {
7268
+ if ($value->unitless()) {
7269
+ $num = $value->getDimension();
7270
+ } elseif ($value->hasUnit('%')) {
7271
+ $num = $max * $value->getDimension() / 100;
7272
+ } else {
7273
+ throw $this->error('Expected %s to have no units or "%%".', $value);
 
 
 
 
 
 
 
7274
  }
7275
 
7276
  $value = $num;
7284
  $value = round($value);
7285
  }
7286
 
7287
+ $value = min($max, max($min, $value));
 
 
 
 
 
 
 
 
 
 
 
7288
 
7289
  return $value;
7290
  }
7295
  /**
7296
  * Coerce value to string
7297
  *
7298
+ * @param array|Number $value
7299
  *
7300
+ * @return array
7301
  */
7302
  protected function coerceString($value)
7303
  {
7304
  if ($value[0] === Type::T_STRING) {
7305
+ assert(\is_array($value));
7306
+
7307
  return $value;
7308
  }
7309
 
7310
  return [Type::T_STRING, '', [$this->compileValue($value)]];
7311
  }
7312
 
7313
+ /**
7314
+ * Assert value is a string
7315
+ *
7316
+ * This method deals with internal implementation details of the value
7317
+ * representation where unquoted strings can sometimes be stored under
7318
+ * other types.
7319
+ * The returned value is always using the T_STRING type.
7320
+ *
7321
+ * @api
7322
+ *
7323
+ * @param array|Number $value
7324
+ * @param string|null $varName
7325
+ *
7326
+ * @return array
7327
+ *
7328
+ * @throws SassScriptException
7329
+ */
7330
+ public function assertString($value, $varName = null)
7331
+ {
7332
+ // case of url(...) parsed a a function
7333
+ if ($value[0] === Type::T_FUNCTION) {
7334
+ $value = $this->coerceString($value);
7335
+ }
7336
+
7337
+ if (! \in_array($value[0], [Type::T_STRING, Type::T_KEYWORD])) {
7338
+ $value = $this->compileValue($value);
7339
+ throw SassScriptException::forArgument("$value is not a string.", $varName);
7340
+ }
7341
+
7342
+ return $this->coerceString($value);
7343
+ }
7344
+
7345
  /**
7346
  * Coerce value to a percentage
7347
  *
7348
+ * @param array|Number $value
7349
  *
7350
+ * @return int|float
7351
+ *
7352
+ * @deprecated
7353
  */
7354
  protected function coercePercent($value)
7355
  {
7356
+ @trigger_error(sprintf('"%s" is deprecated since 1.7.0.', __METHOD__), E_USER_DEPRECATED);
7357
+
7358
+ if ($value instanceof Number) {
7359
+ if ($value->hasUnit('%')) {
7360
+ return $value->getDimension() / 100;
7361
  }
7362
 
7363
+ return $value->getDimension();
7364
  }
7365
 
7366
  return 0;
7371
  *
7372
  * @api
7373
  *
7374
+ * @param array|Number $value
7375
+ * @param string|null $varName
7376
  *
7377
  * @return array
7378
  *
7379
+ * @throws SassScriptException
7380
  */
7381
+ public function assertMap($value, $varName = null)
7382
  {
7383
+ $map = $this->tryMap($value);
7384
 
7385
+ if ($map === null) {
7386
+ $value = $this->compileValue($value);
7387
+
7388
+ throw SassScriptException::forArgument("$value is not a map.", $varName);
7389
  }
7390
 
7391
+ return $map;
7392
  }
7393
 
7394
  /**
7396
  *
7397
  * @api
7398
  *
7399
+ * @param array|Number $value
7400
  *
7401
  * @return array
7402
  *
7405
  public function assertList($value)
7406
  {
7407
  if ($value[0] !== Type::T_LIST) {
7408
+ throw $this->error('expecting list, %s received', $value[0]);
7409
  }
7410
+ assert(\is_array($value));
7411
 
7412
  return $value;
7413
  }
7414
 
7415
+ /**
7416
+ * Gets the keywords of an argument list.
7417
+ *
7418
+ * Keys in the returned array are normalized names (underscores are replaced with dashes)
7419
+ * without the leading `$`.
7420
+ * Calling this helper with anything that an argument list received for a rest argument
7421
+ * of the function argument declaration is not supported.
7422
+ *
7423
+ * @param array|Number $value
7424
+ *
7425
+ * @return array<string, array|Number>
7426
+ */
7427
+ public function getArgumentListKeywords($value)
7428
+ {
7429
+ if ($value[0] !== Type::T_LIST || !isset($value[3]) || !\is_array($value[3])) {
7430
+ throw new \InvalidArgumentException('The argument is not a sass argument list.');
7431
+ }
7432
+
7433
+ return $value[3];
7434
+ }
7435
+
7436
  /**
7437
  * Assert value is a color
7438
  *
7439
  * @api
7440
  *
7441
+ * @param array|Number $value
7442
+ * @param string|null $varName
7443
  *
7444
  * @return array
7445
  *
7446
+ * @throws SassScriptException
7447
  */
7448
+ public function assertColor($value, $varName = null)
7449
  {
7450
  if ($color = $this->coerceColor($value)) {
7451
  return $color;
7452
  }
7453
 
7454
+ $value = $this->compileValue($value);
7455
+
7456
+ throw SassScriptException::forArgument("$value is not a color.", $varName);
7457
  }
7458
 
7459
  /**
7461
  *
7462
  * @api
7463
  *
7464
+ * @param array|Number $value
7465
+ * @param string|null $varName
7466
  *
7467
+ * @return Number
7468
  *
7469
+ * @throws SassScriptException
7470
+ */
7471
+ public function assertNumber($value, $varName = null)
7472
+ {
7473
+ if (!$value instanceof Number) {
7474
+ $value = $this->compileValue($value);
7475
+ throw SassScriptException::forArgument("$value is not a number.", $varName);
7476
+ }
7477
+
7478
+ return $value;
7479
+ }
7480
+
7481
+ /**
7482
+ * Assert value is a integer
7483
+ *
7484
+ * @api
7485
+ *
7486
+ * @param array|Number $value
7487
+ * @param string|null $varName
7488
+ *
7489
+ * @return int
7490
+ *
7491
+ * @throws SassScriptException
7492
  */
7493
+ public function assertInteger($value, $varName = null)
7494
  {
7495
+ $value = $this->assertNumber($value, $varName)->getDimension();
7496
+ if (round($value - \intval($value), Number::PRECISION) > 0) {
7497
+ throw SassScriptException::forArgument("$value is not an integer.", $varName);
7498
  }
7499
 
7500
+ return intval($value);
7501
  }
7502
 
7503
+ /**
7504
+ * Extract the ... / alpha on the last argument of channel arg
7505
+ * in color functions
7506
+ *
7507
+ * @param array $args
7508
+ * @return array
7509
+ */
7510
+ private function extractSlashAlphaInColorFunction($args)
7511
+ {
7512
+ $last = end($args);
7513
+ if (\count($args) === 3 && $last[0] === Type::T_EXPRESSION && $last[1] === '/') {
7514
+ array_pop($args);
7515
+ $args[] = $last[2];
7516
+ $args[] = $last[3];
7517
+ }
7518
+ return $args;
7519
+ }
7520
+
7521
+
7522
  /**
7523
  * Make sure a color's components don't go out of bounds
7524
  *
7536
  if ($c[$i] > 255) {
7537
  $c[$i] = 255;
7538
  }
7539
+
7540
+ if (!\is_int($c[$i])) {
7541
+ $c[$i] = round($c[$i]);
7542
+ }
7543
  }
7544
 
7545
  return $c;
7548
  /**
7549
  * Convert RGB to HSL
7550
  *
7551
+ * @internal
7552
  *
7553
+ * @param int $red
7554
+ * @param int $green
7555
+ * @param int $blue
7556
  *
7557
  * @return array
7558
  */
7577
  $h = 60 * ($green - $blue) / $d;
7578
  } elseif ($green == $max) {
7579
  $h = 60 * ($blue - $red) / $d + 120;
7580
+ } else {
7581
  $h = 60 * ($red - $green) / $d + 240;
7582
  }
7583
  }
7584
 
7585
+ return [Type::T_HSL, fmod($h + 360, 360), $s * 100, $l / 5.1];
7586
  }
7587
 
7588
  /**
7611
  }
7612
 
7613
  if ($h * 3 < 2) {
7614
+ return $m1 + ($m2 - $m1) * (2 / 3 - $h) * 6;
7615
  }
7616
 
7617
  return $m1;
7620
  /**
7621
  * Convert HSL to RGB
7622
  *
7623
+ * @internal
7624
  *
7625
+ * @param int|float $hue H from 0 to 360
7626
+ * @param int|float $saturation S from 0 to 100
7627
+ * @param int|float $lightness L from 0 to 100
7628
  *
7629
  * @return array
7630
  */
7641
  $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
7642
  $m1 = $l * 2 - $m2;
7643
 
7644
+ $r = $this->hueToRGB($m1, $m2, $h + 1 / 3) * 255;
7645
  $g = $this->hueToRGB($m1, $m2, $h) * 255;
7646
+ $b = $this->hueToRGB($m1, $m2, $h - 1 / 3) * 255;
7647
 
7648
  $out = [Type::T_COLOR, $r, $g, $b];
7649
 
7650
  return $out;
7651
  }
7652
 
7653
+ /**
7654
+ * Convert HWB to RGB
7655
+ * https://www.w3.org/TR/css-color-4/#hwb-to-rgb
7656
+ *
7657
+ * @api
7658
+ *
7659
+ * @param int|float $hue H from 0 to 360
7660
+ * @param int|float $whiteness W from 0 to 100
7661
+ * @param int|float $blackness B from 0 to 100
7662
+ *
7663
+ * @return array
7664
+ */
7665
+ private function HWBtoRGB($hue, $whiteness, $blackness)
7666
+ {
7667
+ $w = min(100, max(0, $whiteness)) / 100;
7668
+ $b = min(100, max(0, $blackness)) / 100;
7669
 
7670
+ $sum = $w + $b;
7671
+ if ($sum > 1.0) {
7672
+ $w = $w / $sum;
7673
+ $b = $b / $sum;
7674
+ }
7675
+ $b = min(1.0 - $w, $b);
7676
+
7677
+ $rgb = $this->toRGB($hue, 100, 50);
7678
+ for($i = 1; $i < 4; $i++) {
7679
+ $rgb[$i] *= (1.0 - $w - $b);
7680
+ $rgb[$i] = round($rgb[$i] + 255 * $w + 0.0001);
7681
+ }
7682
+
7683
+ return $rgb;
7684
+ }
7685
+
7686
+ /**
7687
+ * Convert RGB to HWB
7688
+ *
7689
+ * @api
7690
+ *
7691
+ * @param int $red
7692
+ * @param int $green
7693
+ * @param int $blue
7694
+ *
7695
+ * @return array
7696
+ */
7697
+ private function RGBtoHWB($red, $green, $blue)
7698
  {
7699
+ $min = min($red, $green, $blue);
7700
+ $max = max($red, $green, $blue);
7701
+
7702
+ $d = $max - $min;
7703
+
7704
+ if ((int) $d === 0) {
7705
+ $h = 0;
7706
+ } else {
7707
 
7708
+ if ($red == $max) {
7709
+ $h = 60 * ($green - $blue) / $d;
7710
+ } elseif ($green == $max) {
7711
+ $h = 60 * ($blue - $red) / $d + 120;
7712
  } else {
7713
+ $h = 60 * ($red - $green) / $d + 240;
7714
  }
7715
+ }
7716
+
7717
+ return [Type::T_HWB, fmod($h, 360), $min / 255 * 100, 100 - $max / 255 *100];
7718
+ }
7719
+
7720
+
7721
+ // Built in functions
7722
+
7723
+ protected static $libCall = ['function', 'args...'];
7724
+ protected function libCall($args)
7725
+ {
7726
+ $functionReference = $args[0];
7727
+
7728
+ if (in_array($functionReference[0], [Type::T_STRING, Type::T_KEYWORD])) {
7729
+ $name = $this->compileStringContent($this->coerceString($functionReference));
7730
+ $warning = "Passing a string to call() is deprecated and will be illegal\n"
7731
+ . "in Sass 4.0. Use call(function-reference($name)) instead.";
7732
+ Warn::deprecation($warning);
7733
+ $functionReference = $this->libGetFunction([$this->assertString($functionReference, 'function')]);
7734
+ }
7735
+
7736
+ if ($functionReference === static::$null) {
7737
+ return static::$null;
7738
+ }
7739
+
7740
+ if (! in_array($functionReference[0], [Type::T_FUNCTION_REFERENCE, Type::T_FUNCTION])) {
7741
+ throw $this->error('Function reference expected, got ' . $functionReference[0]);
7742
+ }
7743
 
7744
+ $callArgs = [
7745
+ [null, $args[1], true]
7746
+ ];
7747
+
7748
+ return $this->reduce([Type::T_FUNCTION_CALL, $functionReference, $callArgs]);
7749
+ }
7750
+
7751
+
7752
+ protected static $libGetFunction = [
7753
+ ['name'],
7754
+ ['name', 'css']
7755
+ ];
7756
+ protected function libGetFunction($args)
7757
+ {
7758
+ $name = $this->compileStringContent($this->assertString(array_shift($args), 'name'));
7759
+ $isCss = false;
7760
+
7761
+ if (count($args)) {
7762
+ $isCss = array_shift($args);
7763
+ $isCss = (($isCss === static::$true) ? true : false);
7764
  }
7765
 
7766
+ if ($isCss) {
7767
+ return [Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]];
7768
+ }
7769
+
7770
+ return $this->getFunctionReference($name, true);
7771
  }
7772
 
7773
  protected static $libIf = ['condition', 'if-true', 'if-false:'];
7787
  {
7788
  list($list, $value) = $args;
7789
 
7790
+ if (
7791
+ $list[0] === Type::T_MAP ||
 
 
 
7792
  $list[0] === Type::T_STRING ||
7793
  $list[0] === Type::T_KEYWORD ||
7794
  $list[0] === Type::T_INTERPOLATE
7796
  $list = $this->coerceList($list, ' ');
7797
  }
7798
 
7799
+ if ($list[0] !== Type::T_LIST) {
7800
+ return static::$null;
7801
+ }
7802
+
7803
+ // Numbers are represented with value objects, for which the PHP equality operator does not
7804
+ // match the Sass rules (and we cannot overload it). As they are the only type of values
7805
+ // represented with a value object for now, they require a special case.
7806
+ if ($value instanceof Number) {
7807
+ $key = 0;
7808
+ foreach ($list[2] as $item) {
7809
+ $key++;
7810
+ $itemValue = $this->normalizeValue($item);
7811
+
7812
+ if ($itemValue instanceof Number && $value->equals($itemValue)) {
7813
+ return new Number($key, '');
7814
+ }
7815
+ }
7816
  return static::$null;
7817
  }
7818
 
7824
 
7825
  $key = array_search($this->normalizeValue($value), $values);
7826
 
7827
+ return false === $key ? static::$null : new Number($key + 1, '');
7828
  }
7829
 
7830
  protected static $libRgb = [
7833
  ['channels'],
7834
  ['red', 'green', 'blue'],
7835
  ['red', 'green', 'blue', 'alpha'] ];
7836
+
7837
+ /**
7838
+ * @param array $args
7839
+ * @param array $kwargs
7840
+ * @param string $funcName
7841
+ *
7842
+ * @return array
7843
+ */
7844
  protected function libRgb($args, $kwargs, $funcName = 'rgb')
7845
  {
7846
  switch (\count($args)) {
7854
  $color = [Type::T_COLOR, $args[0], $args[1], $args[2]];
7855
 
7856
  if (! $color = $this->coerceColor($color)) {
7857
+ $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
7858
  }
7859
 
7860
  return $color;
7870
  [$funcName . '(', $color[1], ', ', $color[2], ', ', $color[3], ', ', $alpha, ')']];
7871
  }
7872
  } else {
7873
+ $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ')']];
7874
  }
7875
  break;
7876
 
7899
  return $this->libRgb($args, $kwargs, 'rgba');
7900
  }
7901
 
7902
+ /**
7903
+ * Helper function for adjust_color, change_color, and scale_color
7904
+ *
7905
+ * @param array<array|Number> $args
7906
+ * @param string $operation
7907
+ * @param callable $fn
7908
+ *
7909
+ * @return array
7910
+ *
7911
+ * @phpstan-param callable(float|int, float|int|null, float|int): (float|int) $fn
7912
+ */
7913
+ protected function alterColor(array $args, $operation, $fn)
7914
  {
7915
+ $color = $this->assertColor($args[0], 'color');
7916
+
7917
+ if ($args[1][2]) {
7918
+ throw new SassScriptException('Only one positional argument is allowed. All other arguments must be passed by name.');
7919
+ }
7920
+
7921
+ $kwargs = $this->getArgumentListKeywords($args[1]);
7922
+
7923
+ $scale = $operation === 'scale';
7924
+ $change = $operation === 'change';
7925
+
7926
+ /** @phpstan-var callable(string, float|int, bool=, bool=): (float|int|null) $getParam */
7927
+ $getParam = function ($name, $max, $checkPercent = false, $assertPercent = false) use (&$kwargs, $scale, $change) {
7928
+ if (!isset($kwargs[$name])) {
7929
+ return null;
7930
+ }
7931
 
7932
+ $number = $this->assertNumber($kwargs[$name], $name);
7933
+ unset($kwargs[$name]);
 
7934
 
7935
+ if (!$scale && $checkPercent) {
7936
+ if (!$number->hasUnit('%')) {
7937
+ $warning = $this->error("{$name} Passing a number `$number` without unit % is deprecated.");
7938
+ $this->logger->warn($warning->getMessage(), true);
7939
  }
7940
+ }
7941
+
7942
+ if ($scale || $assertPercent) {
7943
+ $number->assertUnit('%', $name);
7944
+ }
7945
 
7946
+ if ($scale) {
7947
+ $max = 100;
7948
  }
7949
+
7950
+ return $number->valueInRange($change ? 0 : -$max, $max, $name);
7951
+ };
7952
+
7953
+ $alpha = $getParam('alpha', 1);
7954
+ $red = $getParam('red', 255);
7955
+ $green = $getParam('green', 255);
7956
+ $blue = $getParam('blue', 255);
7957
+
7958
+ if ($scale || !isset($kwargs['hue'])) {
7959
+ $hue = null;
7960
+ } else {
7961
+ $hueNumber = $this->assertNumber($kwargs['hue'], 'hue');
7962
+ unset($kwargs['hue']);
7963
+ $hue = $hueNumber->getDimension();
7964
+ }
7965
+ $saturation = $getParam('saturation', 100, true);
7966
+ $lightness = $getParam('lightness', 100, true);
7967
+ $whiteness = $getParam('whiteness', 100, false, true);
7968
+ $blackness = $getParam('blackness', 100, false, true);
7969
+
7970
+ if (!empty($kwargs)) {
7971
+ $unknownNames = array_keys($kwargs);
7972
+ $lastName = array_pop($unknownNames);
7973
+ $message = sprintf(
7974
+ 'No argument%s named $%s%s.',
7975
+ $unknownNames ? 's' : '',
7976
+ $unknownNames ? implode(', $', $unknownNames) . ' or $' : '',
7977
+ $lastName
7978
+ );
7979
+ throw new SassScriptException($message);
7980
+ }
7981
+
7982
+ $hasRgb = $red !== null || $green !== null || $blue !== null;
7983
+ $hasSL = $saturation !== null || $lightness !== null;
7984
+ $hasWB = $whiteness !== null || $blackness !== null;
7985
+
7986
+ if ($hasRgb && ($hasSL || $hasWB || $hue !== null)) {
7987
+ throw new SassScriptException(sprintf('RGB parameters may not be passed along with %s parameters.', $hasWB ? 'HWB' : 'HSL'));
7988
+ }
7989
+
7990
+ if ($hasWB && $hasSL) {
7991
+ throw new SassScriptException('HSL parameters may not be passed along with HWB parameters.');
7992
  }
7993
 
7994
+ if ($hasRgb) {
7995
+ $color[1] = round($fn($color[1], $red, 255));
7996
+ $color[2] = round($fn($color[2], $green, 255));
7997
+ $color[3] = round($fn($color[3], $blue, 255));
7998
+ } elseif ($hasWB) {
7999
+ $hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]);
8000
+ if ($hue !== null) {
8001
+ $hwb[1] = $change ? $hue : $hwb[1] + $hue;
8002
+ }
8003
+ $hwb[2] = $fn($hwb[2], $whiteness, 100);
8004
+ $hwb[3] = $fn($hwb[3], $blackness, 100);
8005
+
8006
+ $rgb = $this->HWBtoRGB($hwb[1], $hwb[2], $hwb[3]);
8007
+
8008
+ if (isset($color[4])) {
8009
+ $rgb[4] = $color[4];
8010
+ }
8011
+
8012
+ $color = $rgb;
8013
+ } elseif ($hue !== null || $hasSL) {
8014
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
8015
 
8016
+ if ($hue !== null) {
8017
+ $hsl[1] = $change ? $hue : $hsl[1] + $hue;
 
 
 
8018
  }
8019
+ $hsl[2] = $fn($hsl[2], $saturation, 100);
8020
+ $hsl[3] = $fn($hsl[3], $lightness, 100);
8021
 
8022
  $rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
8023
 
8028
  $color = $rgb;
8029
  }
8030
 
8031
+ if ($alpha !== null) {
8032
+ $existingAlpha = isset($color[4]) ? $color[4] : 1;
8033
+ $color[4] = $fn($existingAlpha, $alpha, 1);
8034
+ }
8035
+
8036
  return $color;
8037
  }
8038
 
8039
+ protected static $libAdjustColor = ['color', 'kwargs...'];
 
 
 
8040
  protected function libAdjustColor($args)
8041
  {
8042
+ return $this->alterColor($args, 'adjust', function ($base, $alter, $max) {
8043
+ if ($alter === null) {
8044
+ return $base;
8045
+ }
8046
+
8047
+ $new = $base + $alter;
8048
+
8049
+ if ($new < 0) {
8050
+ return 0;
8051
+ }
8052
+
8053
+ if ($new > $max) {
8054
+ return $max;
8055
+ }
8056
+
8057
+ return $new;
8058
  });
8059
  }
8060
 
8061
+ protected static $libChangeColor = ['color', 'kwargs...'];
 
 
 
8062
  protected function libChangeColor($args)
8063
  {
8064
+ return $this->alterColor($args,'change', function ($base, $alter, $max) {
8065
+ if ($alter === null) {
8066
+ return $base;
8067
+ }
8068
+
8069
  return $alter;
8070
  });
8071
  }
8072
 
8073
+ protected static $libScaleColor = ['color', 'kwargs...'];
 
 
 
8074
  protected function libScaleColor($args)
8075
  {
8076
+ return $this->alterColor($args, 'scale', function ($base, $scale, $max) {
8077
+ if ($scale === null) {
8078
+ return $base;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8079
  }
8080
 
8081
  $scale = $scale / 100;
8092
  protected function libIeHexStr($args)
8093
  {
8094
  $color = $this->coerceColor($args[0]);
8095
+
8096
+ if (\is_null($color)) {
8097
+ throw $this->error('Error: argument `$color` of `ie-hex-str($color)` must be a color');
8098
+ }
8099
+
8100
  $color[4] = isset($color[4]) ? round(255 * $color[4]) : 255;
8101
 
8102
  return [Type::T_STRING, '', [sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3])]];
8107
  {
8108
  $color = $this->coerceColor($args[0]);
8109
 
8110
+ if (\is_null($color)) {
8111
+ throw $this->error('Error: argument `$color` of `red($color)` must be a color');
8112
+ }
8113
+
8114
+ return new Number((int) $color[1], '');
8115
  }
8116
 
8117
  protected static $libGreen = ['color'];
8119
  {
8120
  $color = $this->coerceColor($args[0]);
8121
 
8122
+ if (\is_null($color)) {
8123
+ throw $this->error('Error: argument `$color` of `green($color)` must be a color');
8124
+ }
8125
+
8126
+ return new Number((int) $color[2], '');
8127
  }
8128
 
8129
  protected static $libBlue = ['color'];
8131
  {
8132
  $color = $this->coerceColor($args[0]);
8133
 
8134
+ if (\is_null($color)) {
8135
+ throw $this->error('Error: argument `$color` of `blue($color)` must be a color');
8136
+ }
8137
+
8138
+ return new Number((int) $color[3], '');
8139
  }
8140
 
8141
  protected static $libAlpha = ['color'];
8142
  protected function libAlpha($args)
8143
  {
8144
  if ($color = $this->coerceColor($args[0])) {
8145
+ return new Number(isset($color[4]) ? $color[4] : 1, '');
8146
  }
8147
 
8148
  // this might be the IE function, so return value unchanged
8154
  {
8155
  $value = $args[0];
8156
 
8157
+ if ($value instanceof Number) {
8158
  return null;
8159
  }
8160
 
8162
  }
8163
 
8164
  // mix two colors
8165
+ protected static $libMix = [
8166
+ ['color1', 'color2', 'weight:50%'],
8167
+ ['color-1', 'color-2', 'weight:50%']
8168
+ ];
8169
  protected function libMix($args)
8170
  {
8171
  list($first, $second, $weight) = $args;
8172
 
8173
+ $first = $this->assertColor($first, 'color1');
8174
+ $second = $this->assertColor($second, 'color2');
8175
+ $weightScale = $this->assertNumber($weight, 'weight')->valueInRange(0, 100, 'weight') / 100;
 
 
 
 
 
8176
 
8177
  $firstAlpha = isset($first[4]) ? $first[4] : 1;
8178
  $secondAlpha = isset($second[4]) ? $second[4] : 1;
8179
 
8180
+ $normalizedWeight = $weightScale * 2 - 1;
8181
+ $alphaDistance = $firstAlpha - $secondAlpha;
8182
 
8183
+ $combinedWeight = $normalizedWeight * $alphaDistance == -1 ? $normalizedWeight : ($normalizedWeight + $alphaDistance) / (1 + $normalizedWeight * $alphaDistance);
8184
+ $weight1 = ($combinedWeight + 1) / 2.0;
8185
+ $weight2 = 1.0 - $weight1;
8186
 
8187
  $new = [Type::T_COLOR,
8188
+ $weight1 * $first[1] + $weight2 * $second[1],
8189
+ $weight1 * $first[2] + $weight2 * $second[2],
8190
+ $weight1 * $first[3] + $weight2 * $second[3],
8191
  ];
8192
 
8193
  if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
8194
+ $new[] = $firstAlpha * $weightScale + $secondAlpha * (1 - $weightScale);
8195
  }
8196
 
8197
  return $this->fixColor($new);
8198
  }
8199
 
8200
+ protected static $libHsl = [
8201
  ['channels'],
8202
+ ['hue', 'saturation'],
8203
  ['hue', 'saturation', 'lightness'],
8204
  ['hue', 'saturation', 'lightness', 'alpha'] ];
8205
+
8206
+ /**
8207
+ * @param array $args
8208
+ * @param array $kwargs
8209
+ * @param string $funcName
8210
+ *
8211
+ * @return array|null
8212
+ */
8213
  protected function libHsl($args, $kwargs, $funcName = 'hsl')
8214
  {
8215
+ $args_to_check = $args;
8216
+
8217
  if (\count($args) == 1) {
8218
  if ($args[0][0] !== Type::T_LIST || \count($args[0][2]) < 3 || \count($args[0][2]) > 4) {
8219
  return [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
8220
  }
8221
 
8222
  $args = $args[0][2];
8223
+ $args_to_check = $kwargs['channels'][2];
8224
+ }
8225
+
8226
+ if (\count($args) === 2) {
8227
+ // if var() is used as an argument, return as a css function
8228
+ foreach ($args as $arg) {
8229
+ if ($arg[0] === Type::T_FUNCTION && in_array($arg[1], ['var'])) {
8230
+ return null;
8231
+ }
8232
+ }
8233
+
8234
+ throw new SassScriptException('Missing argument $lightness.');
8235
  }
8236
 
8237
+ foreach ($kwargs as $arg) {
8238
+ if (in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) && in_array($arg[1], ['min', 'max'])) {
8239
+ return null;
8240
+ }
8241
+ }
8242
+
8243
+ foreach ($args_to_check as $k => $arg) {
8244
+ if (in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) && in_array($arg[1], ['min', 'max'])) {
8245
+ if (count($kwargs) > 1 || ($k >= 2 && count($args) === 4)) {
8246
+ return null;
8247
+ }
8248
+
8249
+ $args[$k] = $this->stringifyFncallArgs($arg);
8250
+ }
8251
 
8252
+ if (
8253
+ $k >= 2 && count($args) === 4 &&
8254
+ in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) &&
8255
+ in_array($arg[1], ['calc','env'])
8256
+ ) {
8257
+ return null;
8258
+ }
8259
+ }
8260
+
8261
+ $hue = $this->reduce($args[0]);
8262
+ $saturation = $this->reduce($args[1]);
8263
+ $lightness = $this->reduce($args[2]);
8264
  $alpha = null;
8265
 
8266
  if (\count($args) === 4) {
8267
  $alpha = $this->compileColorPartValue($args[3], 0, 100, false);
8268
 
8269
+ if (!$hue instanceof Number || !$saturation instanceof Number || ! $lightness instanceof Number || ! is_numeric($alpha)) {
8270
  return [Type::T_STRING, '',
8271
  [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']];
8272
  }
8273
  } else {
8274
+ if (!$hue instanceof Number || !$saturation instanceof Number || ! $lightness instanceof Number) {
8275
  return [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
8276
  }
8277
  }
8278
 
8279
+ $hueValue = fmod($hue->getDimension(), 360);
8280
+
8281
+ while ($hueValue < 0) {
8282
+ $hueValue += 360;
8283
+ }
8284
+
8285
+ $color = $this->toRGB($hueValue, max(0, min($saturation->getDimension(), 100)), max(0, min($lightness->getDimension(), 100)));
8286
 
8287
  if (! \is_null($alpha)) {
8288
  $color[4] = $alpha;
8293
 
8294
  protected static $libHsla = [
8295
  ['channels'],
8296
+ ['hue', 'saturation'],
8297
+ ['hue', 'saturation', 'lightness'],
8298
+ ['hue', 'saturation', 'lightness', 'alpha']];
8299
  protected function libHsla($args, $kwargs)
8300
  {
8301
  return $this->libHsl($args, $kwargs, 'hsla');
8304
  protected static $libHue = ['color'];
8305
  protected function libHue($args)
8306
  {
8307
+ $color = $this->assertColor($args[0], 'color');
8308
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
8309
 
8310
+ return new Number($hsl[1], 'deg');
8311
  }
8312
 
8313
  protected static $libSaturation = ['color'];
8314
  protected function libSaturation($args)
8315
  {
8316
+ $color = $this->assertColor($args[0], 'color');
8317
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
8318
 
8319
+ return new Number($hsl[2], '%');
8320
  }
8321
 
8322
  protected static $libLightness = ['color'];
8323
  protected function libLightness($args)
8324
  {
8325
+ $color = $this->assertColor($args[0], 'color');
8326
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
8327
 
8328
+ return new Number($hsl[3], '%');
8329
+ }
8330
+
8331
+ /*
8332
+ * Todo : a integrer dans le futur module color
8333
+ protected static $libHwb = [
8334
+ ['channels'],
8335
+ ['hue', 'whiteness', 'blackness'],
8336
+ ['hue', 'whiteness', 'blackness', 'alpha'] ];
8337
+ protected function libHwb($args, $kwargs, $funcName = 'hwb')
8338
+ {
8339
+ $args_to_check = $args;
8340
+
8341
+ if (\count($args) == 1) {
8342
+ if ($args[0][0] !== Type::T_LIST) {
8343
+ throw $this->error("Missing elements \$whiteness and \$blackness");
8344
+ }
8345
+
8346
+ if (\trim($args[0][1])) {
8347
+ throw $this->error("\$channels must be a space-separated list.");
8348
+ }
8349
+
8350
+ if (! empty($args[0]['enclosing'])) {
8351
+ throw $this->error("\$channels must be an unbracketed list.");
8352
+ }
8353
+
8354
+ $args = $args[0][2];
8355
+ if (\count($args) > 3) {
8356
+ throw $this->error("hwb() : Only 3 elements are allowed but ". \count($args) . "were passed");
8357
+ }
8358
+
8359
+ $args_to_check = $this->extractSlashAlphaInColorFunction($kwargs['channels'][2]);
8360
+ if (\count($args_to_check) !== \count($kwargs['channels'][2])) {
8361
+ $args = $args_to_check;
8362
+ }
8363
+ }
8364
+
8365
+ if (\count($args_to_check) < 2) {
8366
+ throw $this->error("Missing elements \$whiteness and \$blackness");
8367
+ }
8368
+ if (\count($args_to_check) < 3) {
8369
+ throw $this->error("Missing element \$blackness");
8370
+ }
8371
+ if (\count($args_to_check) > 4) {
8372
+ throw $this->error("hwb() : Only 4 elements are allowed but ". \count($args) . "were passed");
8373
+ }
8374
+
8375
+ foreach ($kwargs as $k => $arg) {
8376
+ if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
8377
+ return null;
8378
+ }
8379
+ }
8380
+
8381
+ foreach ($args_to_check as $k => $arg) {
8382
+ if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
8383
+ if (count($kwargs) > 1 || ($k >= 2 && count($args) === 4)) {
8384
+ return null;
8385
+ }
8386
+
8387
+ $args[$k] = $this->stringifyFncallArgs($arg);
8388
+ }
8389
+
8390
+ if (
8391
+ $k >= 2 && count($args) === 4 &&
8392
+ in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) &&
8393
+ in_array($arg[1], ['calc','env'])
8394
+ ) {
8395
+ return null;
8396
+ }
8397
+ }
8398
+
8399
+ $hue = $this->reduce($args[0]);
8400
+ $whiteness = $this->reduce($args[1]);
8401
+ $blackness = $this->reduce($args[2]);
8402
+ $alpha = null;
8403
+
8404
+ if (\count($args) === 4) {
8405
+ $alpha = $this->compileColorPartValue($args[3], 0, 1, false);
8406
+
8407
+ if (! \is_numeric($alpha)) {
8408
+ $val = $this->compileValue($args[3]);
8409
+ throw $this->error("\$alpha: $val is not a number");
8410
+ }
8411
+ }
8412
+
8413
+ $this->assertNumber($hue, 'hue');
8414
+ $this->assertUnit($whiteness, ['%'], 'whiteness');
8415
+ $this->assertUnit($blackness, ['%'], 'blackness');
8416
+
8417
+ $this->assertRange($whiteness, 0, 100, "0% and 100%", "whiteness");
8418
+ $this->assertRange($blackness, 0, 100, "0% and 100%", "blackness");
8419
+
8420
+ $w = $whiteness->getDimension();
8421
+ $b = $blackness->getDimension();
8422
+
8423
+ $hueValue = $hue->getDimension() % 360;
8424
+
8425
+ while ($hueValue < 0) {
8426
+ $hueValue += 360;
8427
+ }
8428
+
8429
+ $color = $this->HWBtoRGB($hueValue, $w, $b);
8430
+
8431
+ if (! \is_null($alpha)) {
8432
+ $color[4] = $alpha;
8433
+ }
8434
+
8435
+ return $color;
8436
+ }
8437
+
8438
+ protected static $libWhiteness = ['color'];
8439
+ protected function libWhiteness($args, $kwargs, $funcName = 'whiteness') {
8440
+
8441
+ $color = $this->assertColor($args[0]);
8442
+ $hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]);
8443
+
8444
+ return new Number($hwb[2], '%');
8445
+ }
8446
+
8447
+ protected static $libBlackness = ['color'];
8448
+ protected function libBlackness($args, $kwargs, $funcName = 'blackness') {
8449
+
8450
+ $color = $this->assertColor($args[0]);
8451
+ $hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]);
8452
+
8453
+ return new Number($hwb[3], '%');
8454
  }
8455
+ */
8456
 
8457
+ /**
8458
+ * @param array $color
8459
+ * @param int $idx
8460
+ * @param int|float $amount
8461
+ *
8462
+ * @return array
8463
+ */
8464
  protected function adjustHsl($color, $idx, $amount)
8465
  {
8466
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
8467
  $hsl[$idx] += $amount;
8468
+
8469
+ if ($idx !== 1) {
8470
+ // Clamp the saturation and lightness
8471
+ $hsl[$idx] = min(max(0, $hsl[$idx]), 100);
8472
+ }
8473
+
8474
  $out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
8475
 
8476
  if (isset($color[4])) {
8483
  protected static $libAdjustHue = ['color', 'degrees'];
8484
  protected function libAdjustHue($args)
8485
  {
8486
+ $color = $this->assertColor($args[0], 'color');
8487
+ $degrees = $this->assertNumber($args[1], 'degrees')->getDimension();
8488
 
8489
  return $this->adjustHsl($color, 1, $degrees);
8490
  }
8492
  protected static $libLighten = ['color', 'amount'];
8493
  protected function libLighten($args)
8494
  {
8495
+ $color = $this->assertColor($args[0], 'color');
8496
  $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
8497
 
8498
  return $this->adjustHsl($color, 3, $amount);
8501
  protected static $libDarken = ['color', 'amount'];
8502
  protected function libDarken($args)
8503
  {
8504
+ $color = $this->assertColor($args[0], 'color');
8505
  $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
8506
 
8507
  return $this->adjustHsl($color, 3, -$amount);
8508
  }
8509
 
8510
+ protected static $libSaturate = [['color', 'amount'], ['amount']];
8511
  protected function libSaturate($args)
8512
  {
8513
  $value = $args[0];
8514
 
8515
+ if (count($args) === 1) {
8516
+ $this->assertNumber($args[0], 'amount');
8517
+
8518
  return null;
8519
  }
8520
 
8521
+ $color = $this->assertColor($args[0], 'color');
8522
+ $amount = $this->assertNumber($args[1], 'amount');
8523
 
8524
+ return $this->adjustHsl($color, 2, $amount->valueInRange(0, 100, 'amount'));
8525
  }
8526
 
8527
  protected static $libDesaturate = ['color', 'amount'];
8528
  protected function libDesaturate($args)
8529
  {
8530
+ $color = $this->assertColor($args[0], 'color');
8531
+ $amount = $this->assertNumber($args[1], 'amount');
8532
 
8533
+ return $this->adjustHsl($color, 2, -$amount->valueInRange(0, 100, 'amount'));
8534
  }
8535
 
8536
  protected static $libGrayscale = ['color'];
8538
  {
8539
  $value = $args[0];
8540
 
8541
+ if ($value instanceof Number) {
8542
  return null;
8543
  }
8544
 
8545
+ return $this->adjustHsl($this->assertColor($value, 'color'), 2, -100);
8546
  }
8547
 
8548
  protected static $libComplement = ['color'];
8549
  protected function libComplement($args)
8550
  {
8551
+ return $this->adjustHsl($this->assertColor($args[0], 'color'), 1, 180);
8552
  }
8553
 
8554
+ protected static $libInvert = ['color', 'weight:100%'];
8555
  protected function libInvert($args)
8556
  {
8557
+ $value = $args[0];
8558
 
8559
+ $weight = $this->assertNumber($args[1], 'weight');
8560
+
8561
+ if ($value instanceof Number) {
8562
+ if ($weight->getDimension() != 100 || !$weight->hasUnit('%')) {
8563
+ throw new SassScriptException('Only one argument may be passed to the plain-CSS invert() function.');
8564
+ }
8565
 
 
8566
  return null;
8567
  }
8568
 
8569
+ $color = $this->assertColor($value, 'color');
8570
  $inverted = $color;
8571
  $inverted[1] = 255 - $inverted[1];
8572
  $inverted[2] = 255 - $inverted[2];
8573
  $inverted[3] = 255 - $inverted[3];
8574
 
8575
+ return $this->libMix([$inverted, $color, $weight]);
 
 
 
 
8576
  }
8577
 
8578
  // increases opacity by amount
8579
  protected static $libOpacify = ['color', 'amount'];
8580
  protected function libOpacify($args)
8581
  {
8582
+ $color = $this->assertColor($args[0], 'color');
8583
+ $amount = $this->assertNumber($args[1], 'amount');
8584
 
8585
+ $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount->valueInRange(0, 1, 'amount');
8586
  $color[4] = min(1, max(0, $color[4]));
8587
 
8588
  return $color;
8598
  protected static $libTransparentize = ['color', 'amount'];
8599
  protected function libTransparentize($args)
8600
  {
8601
+ $color = $this->assertColor($args[0], 'color');
8602
+ $amount = $this->assertNumber($args[1], 'amount');
8603
 
8604
+ $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount->valueInRange(0, 1, 'amount');
8605
  $color[4] = min(1, max(0, $color[4]));
8606
 
8607
  return $color;
8616
  protected static $libUnquote = ['string'];
8617
  protected function libUnquote($args)
8618
  {
8619
+ try {
8620
+ $str = $this->assertString($args[0], 'string');
8621
+ } catch (SassScriptException $e) {
8622
+ $value = $this->compileValue($args[0]);
8623
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
8624
+ $line = $this->sourceLine;
8625
+
8626
+ $message = "Passing $value, a non-string value, to unquote()
8627
+ will be an error in future versions of Sass.\n on line $line of $fname";
8628
 
8629
+ $this->logger->warn($message, true);
8630
+
8631
+ return $args[0];
8632
  }
8633
 
8634
+ $str[1] = '';
8635
+
8636
  return $str;
8637
  }
8638
 
8639
  protected static $libQuote = ['string'];
8640
  protected function libQuote($args)
8641
  {
8642
+ $value = $this->assertString($args[0], 'string');
8643
 
8644
+ $value[1] = '"';
 
 
8645
 
8646
+ return $value;
8647
  }
8648
 
8649
  protected static $libPercentage = ['number'];
8650
  protected function libPercentage($args)
8651
  {
8652
+ $num = $this->assertNumber($args[0], 'number');
8653
+ $num->assertNoUnits('number');
8654
+
8655
+ return new Number($num->getDimension() * 100, '%');
8656
  }
8657
 
8658
  protected static $libRound = ['number'];
8659
  protected function libRound($args)
8660
  {
8661
+ $num = $this->assertNumber($args[0], 'number');
8662
 
8663
+ return new Number(round($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
8664
  }
8665
 
8666
  protected static $libFloor = ['number'];
8667
  protected function libFloor($args)
8668
  {
8669
+ $num = $this->assertNumber($args[0], 'number');
8670
 
8671
+ return new Number(floor($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
8672
  }
8673
 
8674
  protected static $libCeil = ['number'];
8675
  protected function libCeil($args)
8676
  {
8677
+ $num = $this->assertNumber($args[0], 'number');
8678
 
8679
+ return new Number(ceil($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
8680
  }
8681
 
8682
  protected static $libAbs = ['number'];
8683
  protected function libAbs($args)
8684
  {
8685
+ $num = $this->assertNumber($args[0], 'number');
8686
 
8687
+ return new Number(abs($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
8688
  }
8689
 
8690
+ protected static $libMin = ['numbers...'];
8691
  protected function libMin($args)
8692
  {
8693
+ /**
8694
+ * @var Number|null
8695
+ */
8696
+ $min = null;
8697
 
8698
+ foreach ($args[0][2] as $arg) {
8699
+ $number = $this->assertNumber($arg);
8700
 
8701
+ if (\is_null($min) || $min->greaterThan($number)) {
8702
+ $min = $number;
 
 
 
 
 
 
8703
  }
8704
  }
8705
 
8706
+ if (!\is_null($min)) {
8707
+ return $min;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8708
  }
8709
 
8710
+ throw $this->error('At least one argument must be passed.');
8711
  }
8712
 
8713
+ protected static $libMax = ['numbers...'];
8714
+ protected function libMax($args)
 
 
 
 
 
 
8715
  {
8716
+ /**
8717
+ * @var Number|null
8718
+ */
8719
+ $max = null;
 
 
 
 
 
8720
 
8721
+ foreach ($args[0][2] as $arg) {
8722
+ $number = $this->assertNumber($arg);
8723
 
8724
+ if (\is_null($max) || $max->lessThan($number)) {
8725
+ $max = $number;
 
 
 
 
8726
  }
8727
+ }
8728
 
8729
+ if (!\is_null($max)) {
8730
+ return $max;
8731
  }
8732
 
8733
+ throw $this->error('At least one argument must be passed.');
8734
  }
8735
 
8736
  protected static $libLength = ['list'];
8738
  {
8739
  $list = $this->coerceList($args[0], ',', true);
8740
 
8741
+ return new Number(\count($list[2]), '');
8742
  }
8743
 
8744
+ protected static $libListSeparator = ['list'];
8745
  protected function libListSeparator($args)
8746
  {
8747
+ if (! \in_array($args[0][0], [Type::T_LIST, Type::T_MAP])) {
8748
+ return [Type::T_KEYWORD, 'space'];
8749
  }
8750
 
8751
  $list = $this->coerceList($args[0]);
8752
 
8753
+ if ($list[1] === '' && \count($list[2]) <= 1 && empty($list['enclosing'])) {
8754
+ return [Type::T_KEYWORD, 'space'];
8755
  }
8756
 
8757
  if ($list[1] === ',') {
8758
+ return [Type::T_KEYWORD, 'comma'];
8759
+ }
8760
+
8761
+ if ($list[1] === '/') {
8762
+ return [Type::T_KEYWORD, 'slash'];
8763
  }
8764
 
8765
+ return [Type::T_KEYWORD, 'space'];
8766
  }
8767
 
8768
  protected static $libNth = ['list', 'n'];
8769
  protected function libNth($args)
8770
  {
8771
  $list = $this->coerceList($args[0], ',', false);
8772
+ $n = $this->assertNumber($args[1])->getDimension();
8773
 
8774
  if ($n > 0) {
8775
  $n--;
8784
  protected function libSetNth($args)
8785
  {
8786
  $list = $this->coerceList($args[0]);
8787
+ $n = $this->assertNumber($args[1])->getDimension();
8788
 
8789
  if ($n > 0) {
8790
  $n--;
8793
  }
8794
 
8795
  if (! isset($list[2][$n])) {
8796
+ throw $this->error('Invalid argument for "n"');
8797
+ }
8798
+
8799
+ $list[2][$n] = $args[2];
8800
+
8801
+ return $list;
8802
+ }
8803
+
8804
+ protected static $libMapGet = ['map', 'key', 'keys...'];
8805
+ protected function libMapGet($args)
8806
+ {
8807
+ $map = $this->assertMap($args[0], 'map');
8808
+ if (!isset($args[2])) {
8809
+ // BC layer for usages of the function from PHP code rather than from the Sass function
8810
+ $args[2] = self::$emptyArgumentList;
8811
+ }
8812
+ $keys = array_merge([$args[1]], $args[2][2]);
8813
+ $value = static::$null;
8814
+
8815
+ foreach ($keys as $key) {
8816
+ if (!\is_array($map) || $map[0] !== Type::T_MAP) {
8817
+ return static::$null;
8818
+ }
8819
+
8820
+ $map = $this->mapGet($map, $key);
8821
+
8822
+ if ($map === null) {
8823
+ return static::$null;
8824
+ }
8825
+
8826
+ $value = $map;
8827
+ }
8828
+
8829
+ return $value;
8830
+ }
8831
+
8832
+ /**
8833
+ * Gets the value corresponding to that key in the map
8834
+ *
8835
+ * @param array $map
8836
+ * @param Number|array $key
8837
+ *
8838
+ * @return Number|array|null
8839
+ */
8840
+ private function mapGet(array $map, $key)
8841
+ {
8842
+ $index = $this->mapGetEntryIndex($map, $key);
8843
 
8844
+ if ($index !== null) {
8845
+ return $map[2][$index];
8846
  }
8847
 
8848
+ return null;
 
 
8849
  }
8850
 
8851
+ /**
8852
+ * Gets the index corresponding to that key in the map entries
8853
+ *
8854
+ * @param array $map
8855
+ * @param Number|array $key
8856
+ *
8857
+ * @return int|null
8858
+ */
8859
+ private function mapGetEntryIndex(array $map, $key)
8860
  {
8861
+ $key = $this->compileStringContent($this->coerceString($key));
 
8862
 
8863
+ for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
8864
+ if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
8865
+ return $i;
 
 
 
 
8866
  }
8867
  }
8868
 
8869
+ return null;
8870
  }
8871
 
8872
  protected static $libMapKeys = ['map'];
8873
  protected function libMapKeys($args)
8874
  {
8875
+ $map = $this->assertMap($args[0], 'map');
8876
  $keys = $map[1];
8877
 
8878
  return [Type::T_LIST, ',', $keys];
8881
  protected static $libMapValues = ['map'];
8882
  protected function libMapValues($args)
8883
  {
8884
+ $map = $this->assertMap($args[0], 'map');
8885
  $values = $map[2];
8886
 
8887
  return [Type::T_LIST, ',', $values];
8888
  }
8889
 
8890
+ protected static $libMapRemove = [
8891
+ ['map'],
8892
+ ['map', 'key', 'keys...'],
8893
+ ];
8894
  protected function libMapRemove($args)
8895
  {
8896
+ $map = $this->assertMap($args[0], 'map');
8897
+
8898
+ if (\count($args) === 1) {
8899
+ return $map;
8900
+ }
8901
+
8902
+ $keys = [];
8903
+ $keys[] = $this->compileStringContent($this->coerceString($args[1]));
8904
+
8905
+ foreach ($args[2][2] as $key) {
8906
+ $keys[] = $this->compileStringContent($this->coerceString($key));
8907
+ }
8908
 
8909
  for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
8910
+ if (in_array($this->compileStringContent($this->coerceString($map[1][$i])), $keys)) {
8911
  array_splice($map[1], $i, 1);
8912
  array_splice($map[2], $i, 1);
8913
  }
8916
  return $map;
8917
  }
8918
 
8919
+ protected static $libMapHasKey = ['map', 'key', 'keys...'];
8920
  protected function libMapHasKey($args)
8921
  {
8922
+ $map = $this->assertMap($args[0], 'map');
8923
+ if (!isset($args[2])) {
8924
+ // BC layer for usages of the function from PHP code rather than from the Sass function
8925
+ $args[2] = self::$emptyArgumentList;
8926
+ }
8927
+ $keys = array_merge([$args[1]], $args[2][2]);
8928
+ $lastKey = array_pop($keys);
8929
+
8930
+ foreach ($keys as $key) {
8931
+ $value = $this->mapGet($map, $key);
8932
+
8933
+ if ($value === null || $value instanceof Number || $value[0] !== Type::T_MAP) {
8934
+ return self::$false;
8935
+ }
8936
+
8937
+ $map = $value;
8938
+ }
8939
+
8940
+ return $this->toBool($this->mapHasKey($map, $lastKey));
8941
+ }
8942
+
8943
+ /**
8944
+ * @param array|Number $keyValue
8945
+ *
8946
+ * @return bool
8947
+ */
8948
+ private function mapHasKey(array $map, $keyValue)
8949
+ {
8950
+ $key = $this->compileStringContent($this->coerceString($keyValue));
8951
 
8952
  for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
8953
  if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
8958
  return false;
8959
  }
8960
 
8961
+ protected static $libMapMerge = [
8962
+ ['map1', 'map2'],
8963
+ ['map-1', 'map-2'],
8964
+ ['map1', 'args...']
8965
+ ];
8966
  protected function libMapMerge($args)
8967
  {
8968
+ $map1 = $this->assertMap($args[0], 'map1');
8969
+ $map2 = $args[1];
8970
+ $keys = [];
8971
+ if ($map2[0] === Type::T_LIST && isset($map2[3]) && \is_array($map2[3])) {
8972
+ // This is an argument list for the variadic signature
8973
+ if (\count($map2[2]) === 0) {
8974
+ throw new SassScriptException('Expected $args to contain a key.');
8975
+ }
8976
+ if (\count($map2[2]) === 1) {
8977
+ throw new SassScriptException('Expected $args to contain a value.');
8978
+ }
8979
+ $keys = $map2[2];
8980
+ $map2 = array_pop($keys);
8981
+ }
8982
+ $map2 = $this->assertMap($map2, 'map2');
8983
+
8984
+ return $this->modifyMap($map1, $keys, function ($oldValue) use ($map2) {
8985
+ $nestedMap = $this->tryMap($oldValue);
8986
+
8987
+ if ($nestedMap === null) {
8988
+ return $map2;
8989
+ }
8990
+
8991
+ return $this->mergeMaps($nestedMap, $map2);
8992
+ });
8993
+ }
8994
+
8995
+ /**
8996
+ * @param array $map
8997
+ * @param array $keys
8998
+ * @param callable $modify
8999
+ * @param bool $addNesting
9000
+ *
9001
+ * @return Number|array
9002
+ *
9003
+ * @phpstan-param array<Number|array> $keys
9004
+ * @phpstan-param callable(Number|array): (Number|array) $modify
9005
+ */
9006
+ private function modifyMap(array $map, array $keys, callable $modify, $addNesting = true)
9007
+ {
9008
+ if ($keys === []) {
9009
+ return $modify($map);
9010
+ }
9011
+
9012
+ return $this->modifyNestedMap($map, $keys, $modify, $addNesting);
9013
+ }
9014
+
9015
+ /**
9016
+ * @param array $map
9017
+ * @param array $keys
9018
+ * @param callable $modify
9019
+ * @param bool $addNesting
9020
+ *
9021
+ * @return array
9022
+ *
9023
+ * @phpstan-param non-empty-array<Number|array> $keys
9024
+ * @phpstan-param callable(Number|array): (Number|array) $modify
9025
+ */
9026
+ private function modifyNestedMap(array $map, array $keys, callable $modify, $addNesting)
9027
+ {
9028
+ $key = array_shift($keys);
9029
+
9030
+ $nestedValueIndex = $this->mapGetEntryIndex($map, $key);
9031
+
9032
+ if ($keys === []) {
9033
+ if ($nestedValueIndex !== null) {
9034
+ $map[2][$nestedValueIndex] = $modify($map[2][$nestedValueIndex]);
9035
+ } else {
9036
+ $map[1][] = $key;
9037
+ $map[2][] = $modify(self::$null);
9038
+ }
9039
+
9040
+ return $map;
9041
+ }
9042
+
9043
+ $nestedMap = $nestedValueIndex !== null ? $this->tryMap($map[2][$nestedValueIndex]) : null;
9044
+
9045
+ if ($nestedMap === null && !$addNesting) {
9046
+ return $map;
9047
+ }
9048
+
9049
+ if ($nestedMap === null) {
9050
+ $nestedMap = self::$emptyMap;
9051
+ }
9052
+
9053
+ $newNestedMap = $this->modifyNestedMap($nestedMap, $keys, $modify, $addNesting);
9054
+
9055
+ if ($nestedValueIndex !== null) {
9056
+ $map[2][$nestedValueIndex] = $newNestedMap;
9057
+ } else {
9058
+ $map[1][] = $key;
9059
+ $map[2][] = $newNestedMap;
9060
+ }
9061
+
9062
+ return $map;
9063
+ }
9064
 
9065
+ /**
9066
+ * Merges 2 Sass maps together
9067
+ *
9068
+ * @param array $map1
9069
+ * @param array $map2
9070
+ *
9071
+ * @return array
9072
+ */
9073
+ private function mergeMaps(array $map1, array $map2)
9074
+ {
9075
  foreach ($map2[1] as $i2 => $key2) {
9076
+ $map1EntryIndex = $this->mapGetEntryIndex($map1, $key2);
9077
 
9078
+ if ($map1EntryIndex !== null) {
9079
+ $map1[2][$map1EntryIndex] = $map2[2][$i2];
9080
+ continue;
 
 
9081
  }
9082
 
9083
+ $map1[1][] = $key2;
9084
  $map1[2][] = $map2[2][$i2];
9085
  }
9086
 
9090
  protected static $libKeywords = ['args'];
9091
  protected function libKeywords($args)
9092
  {
9093
+ $value = $args[0];
9094
+
9095
+ if ($value[0] !== Type::T_LIST || !isset($value[3]) || !\is_array($value[3])) {
9096
+ $compiledValue = $this->compileValue($value);
9097
+
9098
+ throw SassScriptException::forArgument($compiledValue . ' is not an argument list.', 'args');
9099
+ }
9100
 
9101
  $keys = [];
9102
  $values = [];
9103
 
9104
+ foreach ($this->getArgumentListKeywords($value) as $name => $arg) {
9105
  $keys[] = [Type::T_KEYWORD, $name];
9106
  $values[] = $arg;
9107
  }
9116
  $this->coerceList($list, ' ');
9117
 
9118
  if (! empty($list['enclosing']) && $list['enclosing'] === 'bracket') {
9119
+ return self::$true;
9120
  }
9121
 
9122
+ return self::$false;
9123
  }
9124
 
9125
+ /**
9126
+ * @param array $list1
9127
+ * @param array|Number|null $sep
9128
+ *
9129
+ * @return string
9130
+ * @throws CompilerException
9131
+ *
9132
+ * @deprecated
9133
+ */
9134
  protected function listSeparatorForJoin($list1, $sep)
9135
  {
9136
+ @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
9137
+
9138
  if (! isset($sep)) {
9139
  return $list1[1];
9140
  }
9151
  }
9152
  }
9153
 
9154
+ protected static $libJoin = ['list1', 'list2', 'separator:auto', 'bracketed:auto'];
9155
  protected function libJoin($args)
9156
  {
9157
  list($list1, $list2, $sep, $bracketed) = $args;
9158
 
9159
  $list1 = $this->coerceList($list1, ' ', true);
9160
  $list2 = $this->coerceList($list2, ' ', true);
9161
+
9162
+ switch ($this->compileStringContent($this->assertString($sep, 'separator'))) {
9163
+ case 'comma':
9164
+ $separator = ',';
9165
+ break;
9166
+
9167
+ case 'space':
9168
+ $separator = ' ';
9169
+ break;
9170
+
9171
+ case 'slash':
9172
+ $separator = '/';
9173
+ break;
9174
+
9175
+ case 'auto':
9176
+ if ($list1[1] !== '' || count($list1[2]) > 1 || !empty($list1['enclosing']) && $list1['enclosing'] !== 'parent') {
9177
+ $separator = $list1[1] ?: ' ';
9178
+ } elseif ($list2[1] !== '' || count($list2[2]) > 1 || !empty($list2['enclosing']) && $list2['enclosing'] !== 'parent') {
9179
+ $separator = $list2[1] ?: ' ';
9180
+ } else {
9181
+ $separator = ' ';
9182
+ }
9183
+ break;
9184
+
9185
+ default:
9186
+ throw SassScriptException::forArgument('Must be "space", "comma", "slash", or "auto".', 'separator');
9187
+ }
9188
 
9189
  if ($bracketed === static::$true) {
9190
  $bracketed = true;
9211
  }
9212
  }
9213
 
9214
+ $res = [Type::T_LIST, $separator, array_merge($list1[2], $list2[2])];
 
 
 
 
9215
 
9216
  if ($bracketed) {
9217
  $res['enclosing'] = 'bracket';
9220
  return $res;
9221
  }
9222
 
9223
+ protected static $libAppend = ['list', 'val', 'separator:auto'];
9224
  protected function libAppend($args)
9225
  {
9226
  list($list1, $value, $sep) = $args;
9227
 
9228
  $list1 = $this->coerceList($list1, ' ', true);
9229
+
9230
+ switch ($this->compileStringContent($this->assertString($sep, 'separator'))) {
9231
+ case 'comma':
9232
+ $separator = ',';
9233
+ break;
9234
+
9235
+ case 'space':
9236
+ $separator = ' ';
9237
+ break;
9238
+
9239
+ case 'slash':
9240
+ $separator = '/';
9241
+ break;
9242
+
9243
+ case 'auto':
9244
+ $separator = $list1[1] === '' && \count($list1[2]) <= 1 && (empty($list1['enclosing']) || $list1['enclosing'] === 'parent') ? ' ' : $list1[1];
9245
+ break;
9246
+
9247
+ default:
9248
+ throw SassScriptException::forArgument('Must be "space", "comma", "slash", or "auto".', 'separator');
9249
+ }
9250
+
9251
+ $res = [Type::T_LIST, $separator, array_merge($list1[2], [$value])];
9252
 
9253
  if (isset($list1['enclosing'])) {
9254
  $res['enclosing'] = $list1['enclosing'];
9257
  return $res;
9258
  }
9259
 
9260
+ protected static $libZip = ['lists...'];
9261
  protected function libZip($args)
9262
  {
9263
+ $argLists = [];
9264
+ foreach ($args[0][2] as $arg) {
9265
+ $argLists[] = $this->coerceList($arg);
9266
  }
9267
 
9268
  $lists = [];
9269
+ $firstList = array_shift($argLists);
9270
 
9271
+ $result = [Type::T_LIST, ',', $lists];
9272
+ if (! \is_null($firstList)) {
9273
+ foreach ($firstList[2] as $key => $item) {
9274
+ $list = [Type::T_LIST, ' ', [$item]];
9275
 
9276
+ foreach ($argLists as $arg) {
9277
+ if (isset($arg[2][$key])) {
9278
+ $list[2][] = $arg[2][$key];
9279
+ } else {
9280
+ break 2;
9281
+ }
9282
  }
9283
+
9284
+ $lists[] = $list;
9285
  }
9286
 
9287
+ $result[2] = $lists;
9288
+ } else {
9289
+ $result['enclosing'] = 'parent';
9290
  }
9291
 
9292
+ return $result;
9293
  }
9294
 
9295
  protected static $libTypeOf = ['value'];
9297
  {
9298
  $value = $args[0];
9299
 
9300
+ return [Type::T_KEYWORD, $this->getTypeOf($value)];
9301
+ }
9302
+
9303
+ /**
9304
+ * @param array|Number $value
9305
+ *
9306
+ * @return string
9307
+ */
9308
+ private function getTypeOf($value)
9309
+ {
9310
  switch ($value[0]) {
9311
  case Type::T_KEYWORD:
9312
  if ($value === static::$true || $value === static::$false) {
9321
  case Type::T_FUNCTION:
9322
  return 'string';
9323
 
9324
+ case Type::T_FUNCTION_REFERENCE:
9325
+ return 'function';
9326
+
9327
  case Type::T_LIST:
9328
+ if (isset($value[3]) && \is_array($value[3])) {
9329
  return 'arglist';
9330
  }
9331
 
9338
  protected static $libUnit = ['number'];
9339
  protected function libUnit($args)
9340
  {
9341
+ $num = $this->assertNumber($args[0], 'number');
 
 
 
 
9342
 
9343
+ return [Type::T_STRING, '"', [$num->unitStr()]];
9344
  }
9345
 
9346
  protected static $libUnitless = ['number'];
9347
  protected function libUnitless($args)
9348
  {
9349
+ $value = $this->assertNumber($args[0], 'number');
9350
 
9351
+ return $this->toBool($value->unitless());
9352
  }
9353
 
9354
+ protected static $libComparable = [
9355
+ ['number1', 'number2'],
9356
+ ['number-1', 'number-2']
9357
+ ];
9358
  protected function libComparable($args)
9359
  {
9360
  list($number1, $number2) = $args;
9361
 
9362
+ if (
9363
+ ! $number1 instanceof Number ||
9364
+ ! $number2 instanceof Number
9365
  ) {
9366
+ throw $this->error('Invalid argument(s) for "comparable"');
 
 
9367
  }
9368
 
9369
+ return $this->toBool($number1->isComparableTo($number2));
 
 
 
9370
  }
9371
 
9372
  protected static $libStrIndex = ['string', 'substring'];
9373
  protected function libStrIndex($args)
9374
  {
9375
+ $string = $this->assertString($args[0], 'string');
9376
  $stringContent = $this->compileStringContent($string);
9377
 
9378
+ $substring = $this->assertString($args[1], 'substring');
9379
  $substringContent = $this->compileStringContent($substring);
9380
 
9381
+ if (! \strlen($substringContent)) {
9382
+ $result = 0;
9383
+ } else {
9384
+ $result = Util::mbStrpos($stringContent, $substringContent);
9385
+ }
9386
 
9387
+ return $result === false ? static::$null : new Number($result + 1, '');
9388
  }
9389
 
9390
  protected static $libStrInsert = ['string', 'insert', 'index'];
9391
  protected function libStrInsert($args)
9392
  {
9393
+ $string = $this->assertString($args[0], 'string');
9394
  $stringContent = $this->compileStringContent($string);
9395
 
9396
+ $insert = $this->assertString($args[1], 'insert');
9397
  $insertContent = $this->compileStringContent($insert);
9398
 
9399
+ $index = $this->assertInteger($args[2], 'index');
9400
+ if ($index > 0) {
9401
+ $index = $index - 1;
9402
+ }
9403
+ if ($index < 0) {
9404
+ $index = max(Util::mbStrlen($stringContent) + 1 + $index, 0);
9405
+ }
9406
 
9407
+ $string[2] = [
9408
+ Util::mbSubstr($stringContent, 0, $index),
9409
+ $insertContent,
9410
+ Util::mbSubstr($stringContent, $index)
9411
+ ];
9412
 
9413
  return $string;
9414
  }
9416
  protected static $libStrLength = ['string'];
9417
  protected function libStrLength($args)
9418
  {
9419
+ $string = $this->assertString($args[0], 'string');
9420
  $stringContent = $this->compileStringContent($string);
9421
 
9422
+ return new Number(Util::mbStrlen($stringContent), '');
9423
  }
9424
 
9425
  protected static $libStrSlice = ['string', 'start-at', 'end-at:-1'];
9426
  protected function libStrSlice($args)
9427
  {
9428
+ $string = $this->assertString($args[0], 'string');
9429
+ $stringContent = $this->compileStringContent($string);
9430
+
9431
+ $start = $this->assertNumber($args[1], 'start-at');
9432
+ $start->assertNoUnits('start-at');
9433
+ $startInt = $this->assertInteger($start, 'start-at');
9434
+ $end = $this->assertNumber($args[2], 'end-at');
9435
+ $end->assertNoUnits('end-at');
9436
+ $endInt = $this->assertInteger($end, 'end-at');
9437
+
9438
+ if ($endInt === 0) {
9439
+ return [Type::T_STRING, $string[1], []];
9440
  }
9441
 
9442
+ if ($startInt > 0) {
9443
+ $startInt--;
9444
+ }
9445
 
9446
+ if ($endInt < 0) {
9447
+ $endInt = Util::mbStrlen($stringContent) + $endInt;
9448
+ } else {
9449
+ $endInt--;
9450
+ }
9451
 
9452
+ if ($endInt < $startInt) {
9453
+ return [Type::T_STRING, $string[1], []];
9454
  }
9455
 
9456
+ $length = $endInt - $startInt + 1; // The end of the slice is inclusive
 
9457
 
9458
+ $string[2] = [Util::mbSubstr($stringContent, $startInt, $length)];
 
 
9459
 
9460
  return $string;
9461
  }
9463
  protected static $libToLowerCase = ['string'];
9464
  protected function libToLowerCase($args)
9465
  {
9466
+ $string = $this->assertString($args[0], 'string');
9467
  $stringContent = $this->compileStringContent($string);
9468
 
9469
+ $string[2] = [$this->stringTransformAsciiOnly($stringContent, 'strtolower')];
9470
 
9471
  return $string;
9472
  }
9474
  protected static $libToUpperCase = ['string'];
9475
  protected function libToUpperCase($args)
9476
  {
9477
+ $string = $this->assertString($args[0], 'string');
9478
  $stringContent = $this->compileStringContent($string);
9479
 
9480
+ $string[2] = [$this->stringTransformAsciiOnly($stringContent, 'strtoupper')];
9481
 
9482
  return $string;
9483
  }
9484
 
9485
+ /**
9486
+ * Apply a filter on a string content, only on ascii chars
9487
+ * let extended chars untouched
9488
+ *
9489
+ * @param string $stringContent
9490
+ * @param callable $filter
9491
+ * @return string
9492
+ */
9493
+ protected function stringTransformAsciiOnly($stringContent, $filter)
9494
+ {
9495
+ $mblength = Util::mbStrlen($stringContent);
9496
+ if ($mblength === strlen($stringContent)) {
9497
+ return $filter($stringContent);
9498
+ }
9499
+ $filteredString = "";
9500
+ for ($i = 0; $i < $mblength; $i++) {
9501
+ $char = Util::mbSubstr($stringContent, $i, 1);
9502
+ if (strlen($char) > 1) {
9503
+ $filteredString .= $char;
9504
+ } else {
9505
+ $filteredString .= $filter($char);
9506
+ }
9507
+ }
9508
+
9509
+ return $filteredString;
9510
+ }
9511
+
9512
  protected static $libFeatureExists = ['feature'];
9513
  protected function libFeatureExists($args)
9514
  {
9515
+ $string = $this->assertString($args[0], 'feature');
9516
  $name = $this->compileStringContent($string);
9517
 
9518
  return $this->toBool(
9523
  protected static $libFunctionExists = ['name'];
9524
  protected function libFunctionExists($args)
9525
  {
9526
+ $string = $this->assertString($args[0], 'name');
9527
  $name = $this->compileStringContent($string);
9528
 
9529
  // user defined functions
9530
  if ($this->has(static::$namespaces['function'] . $name)) {
9531
+ return self::$true;
9532
  }
9533
 
9534
  $name = $this->normalizeName($name);
9535
 
9536
  if (isset($this->userFunctions[$name])) {
9537
+ return self::$true;
9538
  }
9539
 
9540
  // built-in functions
9546
  protected static $libGlobalVariableExists = ['name'];
9547
  protected function libGlobalVariableExists($args)
9548
  {
9549
+ $string = $this->assertString($args[0], 'name');
9550
  $name = $this->compileStringContent($string);
9551
 
9552
+ return $this->toBool($this->has($name, $this->rootEnv));
9553
  }
9554
 
9555
  protected static $libMixinExists = ['name'];
9556
  protected function libMixinExists($args)
9557
  {
9558
+ $string = $this->assertString($args[0], 'name');
9559
  $name = $this->compileStringContent($string);
9560
 
9561
+ return $this->toBool($this->has(static::$namespaces['mixin'] . $name));
9562
  }
9563
 
9564
  protected static $libVariableExists = ['name'];
9565
  protected function libVariableExists($args)
9566
  {
9567
+ $string = $this->assertString($args[0], 'name');
9568
  $name = $this->compileStringContent($string);
9569
 
9570
+ return $this->toBool($this->has($name));
9571
  }
9572
 
9573
+ protected static $libCounter = ['args...'];
9574
  /**
9575
  * Workaround IE7's content counter bug.
9576
  *
9580
  */
9581
  protected function libCounter($args)
9582
  {
9583
+ $list = array_map([$this, 'compileValue'], $args[0][2]);
9584
 
9585
  return [Type::T_STRING, '', ['counter(' . implode(',', $list) . ')']];
9586
  }
9587
 
9588
+ protected static $libRandom = ['limit:null'];
9589
  protected function libRandom($args)
9590
  {
9591
+ if (isset($args[0]) && $args[0] !== static::$null) {
9592
+ $n = $this->assertInteger($args[0], 'limit');
9593
 
9594
  if ($n < 1) {
9595
+ throw new SassScriptException("\$limit: Must be greater than 0, was $n.");
 
 
 
 
 
 
 
 
9596
  }
9597
 
9598
+ return new Number(mt_rand(1, $n), '');
9599
  }
9600
 
9601
+ $max = mt_getrandmax();
9602
+ return new Number(mt_rand(0, $max - 1) / $max, '');
9603
  }
9604
 
9605
+ protected static $libUniqueId = [];
9606
  protected function libUniqueId()
9607
  {
9608
  static $id;
9609
 
9610
  if (! isset($id)) {
9611
+ $id = PHP_INT_SIZE === 4
9612
+ ? mt_rand(0, pow(36, 5)) . str_pad(mt_rand(0, pow(36, 5)) % 10000000, 7, '0', STR_PAD_LEFT)
9613
+ : mt_rand(0, pow(36, 8));
9614
  }
9615
 
9616
  $id += mt_rand(0, 10) + 1;
9618
  return [Type::T_STRING, '', ['u' . str_pad(base_convert($id, 10, 36), 8, '0', STR_PAD_LEFT)]];
9619
  }
9620
 
9621
+ /**
9622
+ * @param array|Number $value
9623
+ * @param bool $force_enclosing_display
9624
+ *
9625
+ * @return array
9626
+ */
9627
  protected function inspectFormatValue($value, $force_enclosing_display = false)
9628
  {
9629
  if ($value === static::$null) {
9632
 
9633
  $stringValue = [$value];
9634
 
9635
+ if ($value instanceof Number) {
9636
+ return [Type::T_STRING, '', $stringValue];
9637
+ }
9638
+
9639
  if ($value[0] === Type::T_LIST) {
9640
  if (end($value[2]) === static::$null) {
9641
  array_pop($value[2]);
9643
  $force_enclosing_display = true;
9644
  }
9645
 
9646
+ if (
9647
+ ! empty($value['enclosing']) &&
9648
  ($force_enclosing_display ||
9649
  ($value['enclosing'] === 'bracket') ||
9650
  ! \count($value[2]))
9651
  ) {
9652
+ $value['enclosing'] = 'forced_' . $value['enclosing'];
9653
  $force_enclosing_display = true;
9654
+ } elseif (! \count($value[2])) {
9655
+ $value['enclosing'] = 'forced_parent';
9656
  }
9657
 
9658
  foreach ($value[2] as $k => $listelement) {
9676
  /**
9677
  * Preprocess selector args
9678
  *
9679
+ * @param array $arg
9680
+ * @param string|null $varname
9681
+ * @param bool $allowParent
9682
  *
9683
+ * @return array
9684
  */
9685
+ protected function getSelectorArg($arg, $varname = null, $allowParent = false)
9686
  {
9687
  static $parser = null;
9688
 
9690
  $parser = $this->parserFactory(__METHOD__);
9691
  }
9692
 
9693
+ if (! $this->checkSelectorArgType($arg)) {
9694
+ $var_value = $this->compileValue($arg);
9695
+ throw SassScriptException::forArgument("$var_value is not a valid selector: it must be a string, a list of strings, or a list of lists of strings", $varname);
9696
+ }
9697
+
9698
+
9699
+ if ($arg[0] === Type::T_STRING) {
9700
+ $arg[1] = '';
9701
+ }
9702
  $arg = $this->compileValue($arg);
9703
 
9704
  $parsedSelector = [];
9705
 
9706
+ if ($parser->parseSelector($arg, $parsedSelector, true)) {
9707
  $selector = $this->evalSelectors($parsedSelector);
9708
  $gluedSelector = $this->glueFunctionSelectors($selector);
9709
 
9710
+ if (! $allowParent) {
9711
+ foreach ($gluedSelector as $selector) {
9712
+ foreach ($selector as $s) {
9713
+ if (in_array(static::$selfSelector, $s)) {
9714
+ throw SassScriptException::forArgument("Parent selectors aren't allowed here.", $varname);
9715
+ }
9716
+ }
9717
+ }
9718
+ }
9719
+
9720
  return $gluedSelector;
9721
  }
9722
 
9723
+ throw SassScriptException::forArgument("expected more input, invalid selector.", $varname);
9724
+ }
9725
+
9726
+ /**
9727
+ * Check variable type for getSelectorArg() function
9728
+ * @param array $arg
9729
+ * @param int $maxDepth
9730
+ * @return bool
9731
+ */
9732
+ protected function checkSelectorArgType($arg, $maxDepth = 2)
9733
+ {
9734
+ if ($arg[0] === Type::T_LIST && $maxDepth > 0) {
9735
+ foreach ($arg[2] as $elt) {
9736
+ if (! $this->checkSelectorArgType($elt, $maxDepth - 1)) {
9737
+ return false;
9738
+ }
9739
+ }
9740
+ return true;
9741
+ }
9742
+ if (!in_array($arg[0], [Type::T_STRING, Type::T_KEYWORD])) {
9743
+ return false;
9744
+ }
9745
+ return true;
9746
  }
9747
 
9748
  /**
9750
  *
9751
  * @param array $selectors
9752
  *
9753
+ * @return array
9754
  */
9755
  protected function formatOutputSelector($selectors)
9756
  {
9757
+ $selectors = $this->collapseSelectorsAsList($selectors);
9758
 
9759
  return $selectors;
9760
  }
9764
  {
9765
  list($super, $sub) = $args;
9766
 
9767
+ $super = $this->getSelectorArg($super, 'super');
9768
+ $sub = $this->getSelectorArg($sub, 'sub');
9769
 
9770
+ return $this->toBool($this->isSuperSelector($super, $sub));
9771
  }
9772
 
9773
  /**
9776
  * @param array $super
9777
  * @param array $sub
9778
  *
9779
+ * @return bool
9780
  */
9781
  protected function isSuperSelector($super, $sub)
9782
  {
9783
  // one and only one selector for each arg
9784
+ if (! $super) {
9785
+ throw $this->error('Invalid super selector for isSuperSelector()');
9786
+ }
9787
+
9788
+ if (! $sub) {
9789
+ throw $this->error('Invalid sub selector for isSuperSelector()');
9790
+ }
9791
+
9792
+ if (count($sub) > 1) {
9793
+ foreach ($sub as $s) {
9794
+ if (! $this->isSuperSelector($super, [$s])) {
9795
+ return false;
9796
+ }
9797
+ }
9798
+ return true;
9799
  }
9800
 
9801
+ if (count($super) > 1) {
9802
+ foreach ($super as $s) {
9803
+ if ($this->isSuperSelector([$s], $sub)) {
9804
+ return true;
9805
+ }
9806
+ }
9807
+ return false;
9808
  }
9809
 
9810
  $super = reset($super);
9857
  * @param array $superParts
9858
  * @param array $subParts
9859
  *
9860
+ * @return bool
9861
  */
9862
  protected function isSuperPart($superParts, $subParts)
9863
  {
9886
  $args = $args[2];
9887
 
9888
  if (\count($args) < 1) {
9889
+ throw $this->error('selector-append() needs at least 1 argument');
9890
  }
9891
 
9892
+ $selectors = [];
9893
+ foreach ($args as $arg) {
9894
+ $selectors[] = $this->getSelectorArg($arg, 'selector');
9895
+ }
9896
 
9897
  return $this->formatOutputSelector($this->selectorAppend($selectors));
9898
  }
9911
  $lastSelectors = array_pop($selectors);
9912
 
9913
  if (! $lastSelectors) {
9914
+ throw $this->error('Invalid selector list in selector-append()');
9915
  }
9916
 
9917
  while (\count($selectors)) {
9918
  $previousSelectors = array_pop($selectors);
9919
 
9920
  if (! $previousSelectors) {
9921
+ throw $this->error('Invalid selector list in selector-append()');
9922
  }
9923
 
9924
  // do the trick, happening $lastSelector to $previousSelector
9925
  $appended = [];
9926
 
9927
+ foreach ($previousSelectors as $previousSelector) {
9928
+ foreach ($lastSelectors as $lastSelector) {
9929
+ $previous = $previousSelector;
9930
+ foreach ($previousSelector as $j => $previousSelectorParts) {
9931
+ foreach ($lastSelector as $lastSelectorParts) {
9932
+ foreach ($lastSelectorParts as $lastSelectorPart) {
9933
+ $previous[$j][] = $lastSelectorPart;
 
9934
  }
9935
  }
9936
  }
 
9937
 
9938
+ $appended[] = $previous;
 
9939
  }
9940
  }
9941
 
9945
  return $lastSelectors;
9946
  }
9947
 
9948
+ protected static $libSelectorExtend = [
9949
+ ['selector', 'extendee', 'extender'],
9950
+ ['selectors', 'extendee', 'extender']
9951
+ ];
9952
  protected function libSelectorExtend($args)
9953
  {
9954
  list($selectors, $extendee, $extender) = $args;
9955
 
9956
+ $selectors = $this->getSelectorArg($selectors, 'selector');
9957
+ $extendee = $this->getSelectorArg($extendee, 'extendee');
9958
+ $extender = $this->getSelectorArg($extender, 'extender');
9959
 
9960
  if (! $selectors || ! $extendee || ! $extender) {
9961
+ throw $this->error('selector-extend() invalid arguments');
9962
  }
9963
 
9964
  $extended = $this->extendOrReplaceSelectors($selectors, $extendee, $extender);
9966
  return $this->formatOutputSelector($extended);
9967
  }
9968
 
9969
+ protected static $libSelectorReplace = [
9970
+ ['selector', 'original', 'replacement'],
9971
+ ['selectors', 'original', 'replacement']
9972
+ ];
9973
  protected function libSelectorReplace($args)
9974
  {
9975
  list($selectors, $original, $replacement) = $args;
9976
 
9977
+ $selectors = $this->getSelectorArg($selectors, 'selector');
9978
+ $original = $this->getSelectorArg($original, 'original');
9979
+ $replacement = $this->getSelectorArg($replacement, 'replacement');
9980
 
9981
  if (! $selectors || ! $original || ! $replacement) {
9982
+ throw $this->error('selector-replace() invalid arguments');
9983
  }
9984
 
9985
  $replaced = $this->extendOrReplaceSelectors($selectors, $original, $replacement, true);
9991
  * Extend/replace in selectors
9992
  * used by selector-extend and selector-replace that use the same logic
9993
  *
9994
+ * @param array $selectors
9995
+ * @param array $extendee
9996
+ * @param array $extender
9997
+ * @param bool $replace
9998
  *
9999
  * @return array
10000
  */
10007
  $this->extendsMap = [];
10008
 
10009
  foreach ($extendee as $es) {
10010
+ if (\count($es) !== 1) {
10011
+ throw $this->error('Can\'t extend complex selector.');
10012
+ }
10013
+
10014
  // only use the first one
10015
  $this->pushExtends(reset($es), $extender, null);
10016
  }
10027
  $this->matchExtends($selector, $extended);
10028
 
10029
  // if didnt match, keep the original selector if we are in a replace operation
10030
+ if ($replace && \count($extended) === $n) {
10031
  $extended[] = $selector;
10032
  }
10033
  }
10046
  $args = $args[2];
10047
 
10048
  if (\count($args) < 1) {
10049
+ throw $this->error('selector-nest() needs at least 1 argument');
10050
+ }
10051
+
10052
+ $selectorsMap = [];
10053
+ foreach ($args as $arg) {
10054
+ $selectorsMap[] = $this->getSelectorArg($arg, 'selector', true);
10055
  }
10056
 
10057
+ assert(!empty($selectorsMap));
10058
+
10059
  $envs = [];
10060
 
10061
  foreach ($selectorsMap as $selectors) {
10072
  return $this->formatOutputSelector($outputSelectors);
10073
  }
10074
 
10075
+ protected static $libSelectorParse = [
10076
+ ['selector'],
10077
+ ['selectors']
10078
+ ];
10079
  protected function libSelectorParse($args)
10080
  {
10081
  $selectors = reset($args);
10082
+ $selectors = $this->getSelectorArg($selectors, 'selector');
10083
 
10084
  return $this->formatOutputSelector($selectors);
10085
  }
10089
  {
10090
  list($selectors1, $selectors2) = $args;
10091
 
10092
+ $selectors1 = $this->getSelectorArg($selectors1, 'selectors1');
10093
+ $selectors2 = $this->getSelectorArg($selectors2, 'selectors2');
10094
 
10095
  if (! $selectors1 || ! $selectors2) {
10096
+ throw $this->error('selector-unify() invalid arguments');
10097
  }
10098
 
10099
  // only consider the first compound of each
10113
  * @param array $compound1
10114
  * @param array $compound2
10115
  *
10116
+ * @return array
10117
  */
10118
  protected function unifyCompoundSelectors($compound1, $compound2)
10119
  {
10229
  * @param array $part
10230
  * @param array $compound
10231
  *
10232
+ * @return array|false
10233
  */
10234
  protected function matchPartInCompound($part, $compound)
10235
  {
10324
  * @param string $tag1
10325
  * @param string $tag2
10326
  *
10327
+ * @return array|false
10328
  */
10329
  protected function checkCompatibleTags($tag1, $tag2)
10330
  {
10347
  /**
10348
  * Find the html tag name in a selector parts list
10349
  *
10350
+ * @param string[] $parts
10351
  *
10352
+ * @return string
10353
  */
10354
  protected function findTagName($parts)
10355
  {
10366
  protected function libSimpleSelectors($args)
10367
  {
10368
  $selector = reset($args);
10369
+ $selector = $this->getSelectorArg($selector, 'selector');
10370
 
10371
  // remove selectors list layer, keeping the first one
10372
  $selector = reset($selector);
10386
  protected static $libScssphpGlob = ['pattern'];
10387
  protected function libScssphpGlob($args)
10388
  {
10389
+ @trigger_error(sprintf('The "scssphp-glob" function is deprecated an will be removed in ScssPhp 2.0. Register your own alternative through "%s::registerFunction', __CLASS__), E_USER_DEPRECATED);
10390
+
10391
+ $this->logger->warn('The "scssphp-glob" function is deprecated an will be removed in ScssPhp 2.0.', true);
10392
+
10393
+ $string = $this->assertString($args[0], 'pattern');
10394
  $pattern = $this->compileStringContent($string);
10395
  $matches = glob($pattern);
10396
  $listParts = [];
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Compiler/CachedResult.php ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Compiler;
14
+
15
+ use ScssPhp\ScssPhp\CompilationResult;
16
+
17
+ /**
18
+ * @internal
19
+ */
20
+ class CachedResult
21
+ {
22
+ /**
23
+ * @var CompilationResult
24
+ */
25
+ private $result;
26
+
27
+ /**
28
+ * @var array<string, int>
29
+ */
30
+ private $parsedFiles;
31
+
32
+ /**
33
+ * @var array
34
+ * @phpstan-var list<array{currentDir: string|null, path: string, filePath: string}>
35
+ */
36
+ private $resolvedImports;
37
+
38
+ /**
39
+ * @param CompilationResult $result
40
+ * @param array<string, int> $parsedFiles
41
+ * @param array $resolvedImports
42
+ *
43
+ * @phpstan-param list<array{currentDir: string|null, path: string, filePath: string}> $resolvedImports
44
+ */
45
+ public function __construct(CompilationResult $result, array $parsedFiles, array $resolvedImports)
46
+ {
47
+ $this->result = $result;
48
+ $this->parsedFiles = $parsedFiles;
49
+ $this->resolvedImports = $resolvedImports;
50
+ }
51
+
52
+ /**
53
+ * @return CompilationResult
54
+ */
55
+ public function getResult()
56
+ {
57
+ return $this->result;
58
+ }
59
+
60
+ /**
61
+ * @return array<string, int>
62
+ */
63
+ public function getParsedFiles()
64
+ {
65
+ return $this->parsedFiles;
66
+ }
67
+
68
+ /**
69
+ * @return array
70
+ *
71
+ * @phpstan-return list<array{currentDir: string|null, path: string, filePath: string}>
72
+ */
73
+ public function getResolvedImports()
74
+ {
75
+ return $this->resolvedImports;
76
+ }
77
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Compiler/Environment.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,19 +16,41 @@ namespace ScssPhp\ScssPhp\Compiler;
15
  * Compiler environment
16
  *
17
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
18
  */
19
  class Environment
20
  {
21
  /**
22
- * @var \ScssPhp\ScssPhp\Block
23
  */
24
  public $block;
25
 
26
  /**
27
- * @var \ScssPhp\ScssPhp\Compiler\Environment
28
  */
29
  public $parent;
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  /**
32
  * @var array
33
  */
@@ -39,7 +62,7 @@ class Environment
39
  public $storeUnreduced;
40
 
41
  /**
42
- * @var integer
43
  */
44
  public $depth;
45
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
16
  * Compiler environment
17
  *
18
  * @author Anthon Pang <anthon.pang@gmail.com>
19
+ *
20
+ * @internal
21
  */
22
  class Environment
23
  {
24
  /**
25
+ * @var \ScssPhp\ScssPhp\Block|null
26
  */
27
  public $block;
28
 
29
  /**
30
+ * @var \ScssPhp\ScssPhp\Compiler\Environment|null
31
  */
32
  public $parent;
33
 
34
+ /**
35
+ * @var Environment|null
36
+ */
37
+ public $declarationScopeParent;
38
+
39
+ /**
40
+ * @var Environment|null
41
+ */
42
+ public $parentStore;
43
+
44
+ /**
45
+ * @var array|null
46
+ */
47
+ public $selectors;
48
+
49
+ /**
50
+ * @var string|null
51
+ */
52
+ public $marker;
53
+
54
  /**
55
  * @var array
56
  */
62
  public $storeUnreduced;
63
 
64
  /**
65
+ * @var int
66
  */
67
  public $depth;
68
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Exception/CompilerException.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,7 +16,9 @@ namespace ScssPhp\ScssPhp\Exception;
15
  * Compiler exception
16
  *
17
  * @author Oleksandr Savchenko <traveltino@gmail.com>
 
 
18
  */
19
- class CompilerException extends \Exception
20
  {
21
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
16
  * Compiler exception
17
  *
18
  * @author Oleksandr Savchenko <traveltino@gmail.com>
19
+ *
20
+ * @internal
21
  */
22
+ class CompilerException extends \Exception implements SassException
23
  {
24
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Exception/ParserException.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,7 +16,43 @@ namespace ScssPhp\ScssPhp\Exception;
15
  * Parser Exception
16
  *
17
  * @author Oleksandr Savchenko <traveltino@gmail.com>
 
 
18
  */
19
- class ParserException extends \Exception
20
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
16
  * Parser Exception
17
  *
18
  * @author Oleksandr Savchenko <traveltino@gmail.com>
19
+ *
20
+ * @internal
21
  */
22
+ class ParserException extends \Exception implements SassException
23
  {
24
+ /**
25
+ * @var array|null
26
+ * @phpstan-var array{string, int, int}|null
27
+ */
28
+ private $sourcePosition;
29
+
30
+ /**
31
+ * Get source position
32
+ *
33
+ * @api
34
+ *
35
+ * @return array|null
36
+ * @phpstan-return array{string, int, int}|null
37
+ */
38
+ public function getSourcePosition()
39
+ {
40
+ return $this->sourcePosition;
41
+ }
42
+
43
+ /**
44
+ * Set source position
45
+ *
46
+ * @api
47
+ *
48
+ * @param array $sourcePosition
49
+ *
50
+ * @return void
51
+ *
52
+ * @phpstan-param array{string, int, int} $sourcePosition
53
+ */
54
+ public function setSourcePosition($sourcePosition)
55
+ {
56
+ $this->sourcePosition = $sourcePosition;
57
+ }
58
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Exception/RangeException.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,7 +16,9 @@ namespace ScssPhp\ScssPhp\Exception;
15
  * Range exception
16
  *
17
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
18
  */
19
- class RangeException extends \Exception
20
  {
21
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
16
  * Range exception
17
  *
18
  * @author Anthon Pang <anthon.pang@gmail.com>
19
+ *
20
+ * @internal
21
  */
22
+ class RangeException extends \Exception implements SassException
23
  {
24
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Exception/SassException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ScssPhp\ScssPhp\Exception;
4
+
5
+ interface SassException
6
+ {
7
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Exception/SassScriptException.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ScssPhp\ScssPhp\Exception;
4
+
5
+ /**
6
+ * An exception thrown by SassScript.
7
+ *
8
+ * This class does not implement SassException on purpose, as it should
9
+ * never be returned to the outside code. The compilation will catch it
10
+ * and replace it with a SassException reporting the location of the
11
+ * error.
12
+ */
13
+ class SassScriptException extends \Exception
14
+ {
15
+ /**
16
+ * Creates a SassScriptException with support for an argument name.
17
+ *
18
+ * This helper ensures a consistent handling of argument names in the
19
+ * error message, without duplicating it.
20
+ *
21
+ * @param string $message
22
+ * @param string|null $name The argument name, without $
23
+ *
24
+ * @return SassScriptException
25
+ */
26
+ public static function forArgument($message, $name = null)
27
+ {
28
+ $varDisplay = !\is_null($name) ? "\${$name}: " : '';
29
+
30
+ return new self($varDisplay . $message);
31
+ }
32
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Exception/ServerException.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -11,11 +12,15 @@
11
 
12
  namespace ScssPhp\ScssPhp\Exception;
13
 
 
 
14
  /**
15
  * Server Exception
16
  *
17
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
18
  */
19
- class ServerException extends \Exception
20
  {
21
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
12
 
13
  namespace ScssPhp\ScssPhp\Exception;
14
 
15
+ @trigger_error(sprintf('The "%s" class is deprecated.', ServerException::class), E_USER_DEPRECATED);
16
+
17
  /**
18
  * Server Exception
19
  *
20
  * @author Anthon Pang <anthon.pang@gmail.com>
21
+ *
22
+ * @deprecated The Scssphp server should define its own exception instead.
23
  */
24
+ class ServerException extends \Exception implements SassException
25
  {
26
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -18,11 +19,13 @@ use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
18
  * Base formatter
19
  *
20
  * @author Leaf Corcoran <leafot@gmail.com>
 
 
21
  */
22
  abstract class Formatter
23
  {
24
  /**
25
- * @var integer
26
  */
27
  public $indentLevel;
28
 
@@ -57,7 +60,7 @@ abstract class Formatter
57
  public $assignSeparator;
58
 
59
  /**
60
- * @var boolean
61
  */
62
  public $keepSemicolons;
63
 
@@ -67,17 +70,17 @@ abstract class Formatter
67
  protected $currentBlock;
68
 
69
  /**
70
- * @var integer
71
  */
72
  protected $currentLine;
73
 
74
  /**
75
- * @var integer
76
  */
77
  protected $currentColumn;
78
 
79
  /**
80
- * @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator
81
  */
82
  protected $sourceMapGenerator;
83
 
@@ -138,6 +141,8 @@ abstract class Formatter
138
  * Output lines inside a block
139
  *
140
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 
 
141
  */
142
  protected function blockLines(OutputBlock $block)
143
  {
@@ -155,9 +160,13 @@ abstract class Formatter
155
  * Output block selectors
156
  *
157
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 
 
158
  */
159
  protected function blockSelectors(OutputBlock $block)
160
  {
 
 
161
  $inner = $this->indentStr();
162
 
163
  $this->write($inner
@@ -169,6 +178,8 @@ abstract class Formatter
169
  * Output block children
170
  *
171
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 
 
172
  */
173
  protected function blockChildren(OutputBlock $block)
174
  {
@@ -181,6 +192,8 @@ abstract class Formatter
181
  * Output non-empty block
182
  *
183
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
 
 
184
  */
185
  protected function block(OutputBlock $block)
186
  {
@@ -226,7 +239,7 @@ abstract class Formatter
226
  *
227
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
228
  *
229
- * @return boolean
230
  */
231
  protected function testEmptyChildren($block)
232
  {
@@ -273,9 +286,18 @@ abstract class Formatter
273
 
274
  ob_start();
275
 
276
- $this->block($block);
 
 
 
 
 
 
 
 
277
 
278
  $out = ob_get_clean();
 
279
 
280
  return $out;
281
  }
@@ -284,6 +306,8 @@ abstract class Formatter
284
  * Output content
285
  *
286
  * @param string $str
 
 
287
  */
288
  protected function write($str)
289
  {
@@ -297,7 +321,8 @@ abstract class Formatter
297
  * Maybe Strip semi-colon appended by property(); it's a separator, not a terminator
298
  * will be striped for real before a closing, otherwise displayed unchanged starting the next write
299
  */
300
- if (! $this->keepSemicolons &&
 
301
  $str &&
302
  (strpos($str, ';') !== false) &&
303
  (substr($str, -1) === ';')
@@ -308,22 +333,43 @@ abstract class Formatter
308
  }
309
 
310
  if ($this->sourceMapGenerator) {
311
- $this->sourceMapGenerator->addMapping(
312
- $this->currentLine,
313
- $this->currentColumn,
314
- $this->currentBlock->sourceLine,
315
- //columns from parser are off by one
316
- $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
317
- $this->currentBlock->sourceName
318
- );
319
-
320
  $lines = explode("\n", $str);
321
- $lineCount = \count($lines);
322
- $this->currentLine += $lineCount-1;
323
-
324
  $lastLine = array_pop($lines);
325
 
326
- $this->currentColumn = ($lineCount === 1 ? $this->currentColumn : 0) + \strlen($lastLine);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  }
328
 
329
  echo $str;
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
19
  * Base formatter
20
  *
21
  * @author Leaf Corcoran <leafot@gmail.com>
22
+ *
23
+ * @internal
24
  */
25
  abstract class Formatter
26
  {
27
  /**
28
+ * @var int
29
  */
30
  public $indentLevel;
31
 
60
  public $assignSeparator;
61
 
62
  /**
63
+ * @var bool
64
  */
65
  public $keepSemicolons;
66
 
70
  protected $currentBlock;
71
 
72
  /**
73
+ * @var int
74
  */
75
  protected $currentLine;
76
 
77
  /**
78
+ * @var int
79
  */
80
  protected $currentColumn;
81
 
82
  /**
83
+ * @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null
84
  */
85
  protected $sourceMapGenerator;
86
 
141
  * Output lines inside a block
142
  *
143
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
144
+ *
145
+ * @return void
146
  */
147
  protected function blockLines(OutputBlock $block)
148
  {
160
  * Output block selectors
161
  *
162
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
163
+ *
164
+ * @return void
165
  */
166
  protected function blockSelectors(OutputBlock $block)
167
  {
168
+ assert(! empty($block->selectors));
169
+
170
  $inner = $this->indentStr();
171
 
172
  $this->write($inner
178
  * Output block children
179
  *
180
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
181
+ *
182
+ * @return void
183
  */
184
  protected function blockChildren(OutputBlock $block)
185
  {
192
  * Output non-empty block
193
  *
194
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
195
+ *
196
+ * @return void
197
  */
198
  protected function block(OutputBlock $block)
199
  {
239
  *
240
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
241
  *
242
+ * @return bool
243
  */
244
  protected function testEmptyChildren($block)
245
  {
286
 
287
  ob_start();
288
 
289
+ try {
290
+ $this->block($block);
291
+ } catch (\Exception $e) {
292
+ ob_end_clean();
293
+ throw $e;
294
+ } catch (\Throwable $e) {
295
+ ob_end_clean();
296
+ throw $e;
297
+ }
298
 
299
  $out = ob_get_clean();
300
+ assert($out !== false);
301
 
302
  return $out;
303
  }
306
  * Output content
307
  *
308
  * @param string $str
309
+ *
310
+ * @return void
311
  */
312
  protected function write($str)
313
  {
321
  * Maybe Strip semi-colon appended by property(); it's a separator, not a terminator
322
  * will be striped for real before a closing, otherwise displayed unchanged starting the next write
323
  */
324
+ if (
325
+ ! $this->keepSemicolons &&
326
  $str &&
327
  (strpos($str, ';') !== false) &&
328
  (substr($str, -1) === ';')
333
  }
334
 
335
  if ($this->sourceMapGenerator) {
 
 
 
 
 
 
 
 
 
336
  $lines = explode("\n", $str);
 
 
 
337
  $lastLine = array_pop($lines);
338
 
339
+ foreach ($lines as $line) {
340
+ // If the written line starts is empty, adding a mapping would add it for
341
+ // a non-existent column as we are at the end of the line
342
+ if ($line !== '') {
343
+ assert($this->currentBlock->sourceLine !== null);
344
+ assert($this->currentBlock->sourceName !== null);
345
+ $this->sourceMapGenerator->addMapping(
346
+ $this->currentLine,
347
+ $this->currentColumn,
348
+ $this->currentBlock->sourceLine,
349
+ //columns from parser are off by one
350
+ $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
351
+ $this->currentBlock->sourceName
352
+ );
353
+ }
354
+
355
+ $this->currentLine++;
356
+ $this->currentColumn = 0;
357
+ }
358
+
359
+ if ($lastLine !== '') {
360
+ assert($this->currentBlock->sourceLine !== null);
361
+ assert($this->currentBlock->sourceName !== null);
362
+ $this->sourceMapGenerator->addMapping(
363
+ $this->currentLine,
364
+ $this->currentColumn,
365
+ $this->currentBlock->sourceLine,
366
+ //columns from parser are off by one
367
+ $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
368
+ $this->currentBlock->sourceName
369
+ );
370
+ }
371
+
372
+ $this->currentColumn += \strlen($lastLine);
373
  }
374
 
375
  echo $str;
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/Compact.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -17,6 +18,10 @@ use ScssPhp\ScssPhp\Formatter;
17
  * Compact formatter
18
  *
19
  * @author Leaf Corcoran <leafot@gmail.com>
 
 
 
 
20
  */
21
  class Compact extends Formatter
22
  {
@@ -25,6 +30,8 @@ class Compact extends Formatter
25
  */
26
  public function __construct()
27
  {
 
 
28
  $this->indentLevel = 0;
29
  $this->indentChar = '';
30
  $this->break = '';
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
18
  * Compact formatter
19
  *
20
  * @author Leaf Corcoran <leafot@gmail.com>
21
+ *
22
+ * @deprecated since 1.4.0. Use the Compressed formatter instead.
23
+ *
24
+ * @internal
25
  */
26
  class Compact extends Formatter
27
  {
30
  */
31
  public function __construct()
32
  {
33
+ @trigger_error('The Compact formatter is deprecated since 1.4.0. Use the Compressed formatter instead.', E_USER_DEPRECATED);
34
+
35
  $this->indentLevel = 0;
36
  $this->indentChar = '';
37
  $this->break = '';
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/Compressed.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -12,12 +13,13 @@
12
  namespace ScssPhp\ScssPhp\Formatter;
13
 
14
  use ScssPhp\ScssPhp\Formatter;
15
- use ScssPhp\ScssPhp\Formatter\OutputBlock;
16
 
17
  /**
18
  * Compressed formatter
19
  *
20
  * @author Leaf Corcoran <leafot@gmail.com>
 
 
21
  */
22
  class Compressed extends Formatter
23
  {
@@ -48,8 +50,6 @@ class Compressed extends Formatter
48
  foreach ($block->lines as $index => $line) {
49
  if (substr($line, 0, 2) === '/*' && substr($line, 2, 1) !== '!') {
50
  unset($block->lines[$index]);
51
- } elseif (substr($line, 0, 3) === '/*!') {
52
- $block->lines[$index] = '/*' . substr($line, 3);
53
  }
54
  }
55
 
@@ -67,6 +67,8 @@ class Compressed extends Formatter
67
  */
68
  protected function blockSelectors(OutputBlock $block)
69
  {
 
 
70
  $inner = $this->indentStr();
71
 
72
  $this->write(
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
13
  namespace ScssPhp\ScssPhp\Formatter;
14
 
15
  use ScssPhp\ScssPhp\Formatter;
 
16
 
17
  /**
18
  * Compressed formatter
19
  *
20
  * @author Leaf Corcoran <leafot@gmail.com>
21
+ *
22
+ * @internal
23
  */
24
  class Compressed extends Formatter
25
  {
50
  foreach ($block->lines as $index => $line) {
51
  if (substr($line, 0, 2) === '/*' && substr($line, 2, 1) !== '!') {
52
  unset($block->lines[$index]);
 
 
53
  }
54
  }
55
 
67
  */
68
  protected function blockSelectors(OutputBlock $block)
69
  {
70
+ assert(! empty($block->selectors));
71
+
72
  $inner = $this->indentStr();
73
 
74
  $this->write(
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/Crunched.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -12,12 +13,15 @@
12
  namespace ScssPhp\ScssPhp\Formatter;
13
 
14
  use ScssPhp\ScssPhp\Formatter;
15
- use ScssPhp\ScssPhp\Formatter\OutputBlock;
16
 
17
  /**
18
  * Crunched formatter
19
  *
20
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
 
 
21
  */
22
  class Crunched extends Formatter
23
  {
@@ -26,6 +30,8 @@ class Crunched extends Formatter
26
  */
27
  public function __construct()
28
  {
 
 
29
  $this->indentLevel = 0;
30
  $this->indentChar = ' ';
31
  $this->break = '';
@@ -65,6 +71,8 @@ class Crunched extends Formatter
65
  */
66
  protected function blockSelectors(OutputBlock $block)
67
  {
 
 
68
  $inner = $this->indentStr();
69
 
70
  $this->write(
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
13
  namespace ScssPhp\ScssPhp\Formatter;
14
 
15
  use ScssPhp\ScssPhp\Formatter;
 
16
 
17
  /**
18
  * Crunched formatter
19
  *
20
  * @author Anthon Pang <anthon.pang@gmail.com>
21
+ *
22
+ * @deprecated since 1.4.0. Use the Compressed formatter instead.
23
+ *
24
+ * @internal
25
  */
26
  class Crunched extends Formatter
27
  {
30
  */
31
  public function __construct()
32
  {
33
+ @trigger_error('The Crunched formatter is deprecated since 1.4.0. Use the Compressed formatter instead.', E_USER_DEPRECATED);
34
+
35
  $this->indentLevel = 0;
36
  $this->indentChar = ' ';
37
  $this->break = '';
71
  */
72
  protected function blockSelectors(OutputBlock $block)
73
  {
74
+ assert(! empty($block->selectors));
75
+
76
  $inner = $this->indentStr();
77
 
78
  $this->write(
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/Debug.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -12,12 +13,15 @@
12
  namespace ScssPhp\ScssPhp\Formatter;
13
 
14
  use ScssPhp\ScssPhp\Formatter;
15
- use ScssPhp\ScssPhp\Formatter\OutputBlock;
16
 
17
  /**
18
  * Debug formatter
19
  *
20
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
 
 
21
  */
22
  class Debug extends Formatter
23
  {
@@ -26,6 +30,8 @@ class Debug extends Formatter
26
  */
27
  public function __construct()
28
  {
 
 
29
  $this->indentLevel = 0;
30
  $this->indentChar = '';
31
  $this->break = "\n";
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
13
  namespace ScssPhp\ScssPhp\Formatter;
14
 
15
  use ScssPhp\ScssPhp\Formatter;
 
16
 
17
  /**
18
  * Debug formatter
19
  *
20
  * @author Anthon Pang <anthon.pang@gmail.com>
21
+ *
22
+ * @deprecated since 1.4.0.
23
+ *
24
+ * @internal
25
  */
26
  class Debug extends Formatter
27
  {
30
  */
31
  public function __construct()
32
  {
33
+ @trigger_error('The Debug formatter is deprecated since 1.4.0.', E_USER_DEPRECATED);
34
+
35
  $this->indentLevel = 0;
36
  $this->indentChar = '';
37
  $this->break = "\n";
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/Expanded.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -12,12 +13,13 @@
12
  namespace ScssPhp\ScssPhp\Formatter;
13
 
14
  use ScssPhp\ScssPhp\Formatter;
15
- use ScssPhp\ScssPhp\Formatter\OutputBlock;
16
 
17
  /**
18
  * Expanded formatter
19
  *
20
  * @author Leaf Corcoran <leafot@gmail.com>
 
 
21
  */
22
  class Expanded extends Formatter
23
  {
@@ -55,7 +57,9 @@ class Expanded extends Formatter
55
 
56
  foreach ($block->lines as $index => $line) {
57
  if (substr($line, 0, 2) === '/*') {
58
- $block->lines[$index] = preg_replace('/[\r\n]+/', $glue, $line);
 
 
59
  }
60
  }
61
 
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
13
  namespace ScssPhp\ScssPhp\Formatter;
14
 
15
  use ScssPhp\ScssPhp\Formatter;
 
16
 
17
  /**
18
  * Expanded formatter
19
  *
20
  * @author Leaf Corcoran <leafot@gmail.com>
21
+ *
22
+ * @internal
23
  */
24
  class Expanded extends Formatter
25
  {
57
 
58
  foreach ($block->lines as $index => $line) {
59
  if (substr($line, 0, 2) === '/*') {
60
+ $replacedLine = preg_replace('/\r\n?|\n|\f/', $this->break, $line);
61
+ assert($replacedLine !== null);
62
+ $block->lines[$index] = $replacedLine;
63
  }
64
  }
65
 
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/Nested.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -12,18 +13,21 @@
12
  namespace ScssPhp\ScssPhp\Formatter;
13
 
14
  use ScssPhp\ScssPhp\Formatter;
15
- use ScssPhp\ScssPhp\Formatter\OutputBlock;
16
  use ScssPhp\ScssPhp\Type;
17
 
18
  /**
19
  * Nested formatter
20
  *
21
  * @author Leaf Corcoran <leafot@gmail.com>
 
 
 
 
22
  */
23
  class Nested extends Formatter
24
  {
25
  /**
26
- * @var integer
27
  */
28
  private $depth;
29
 
@@ -32,6 +36,8 @@ class Nested extends Formatter
32
  */
33
  public function __construct()
34
  {
 
 
35
  $this->indentLevel = 0;
36
  $this->indentChar = ' ';
37
  $this->break = "\n";
@@ -62,7 +68,9 @@ class Nested extends Formatter
62
 
63
  foreach ($block->lines as $index => $line) {
64
  if (substr($line, 0, 2) === '/*') {
65
- $block->lines[$index] = preg_replace('/[\r\n]+/', $glue, $line);
 
 
66
  }
67
  }
68
 
@@ -97,7 +105,8 @@ class Nested extends Formatter
97
  array_pop($depths);
98
  $this->depth--;
99
 
100
- if (! $this->depth && ($block->depth <= 1 || (! $this->indentLevel && $block->type === Type::T_COMMENT)) &&
 
101
  (($block->selectors && ! $isMediaOrDirective) || $previousHasSelector)
102
  ) {
103
  $downLevel = $this->break;
@@ -214,7 +223,7 @@ class Nested extends Formatter
214
  *
215
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
216
  *
217
- * @return boolean
218
  */
219
  private function hasFlatChild($block)
220
  {
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
13
  namespace ScssPhp\ScssPhp\Formatter;
14
 
15
  use ScssPhp\ScssPhp\Formatter;
 
16
  use ScssPhp\ScssPhp\Type;
17
 
18
  /**
19
  * Nested formatter
20
  *
21
  * @author Leaf Corcoran <leafot@gmail.com>
22
+ *
23
+ * @deprecated since 1.4.0. Use the Expanded formatter instead.
24
+ *
25
+ * @internal
26
  */
27
  class Nested extends Formatter
28
  {
29
  /**
30
+ * @var int
31
  */
32
  private $depth;
33
 
36
  */
37
  public function __construct()
38
  {
39
+ @trigger_error('The Nested formatter is deprecated since 1.4.0. Use the Expanded formatter instead.', E_USER_DEPRECATED);
40
+
41
  $this->indentLevel = 0;
42
  $this->indentChar = ' ';
43
  $this->break = "\n";
68
 
69
  foreach ($block->lines as $index => $line) {
70
  if (substr($line, 0, 2) === '/*') {
71
+ $replacedLine = preg_replace('/\r\n?|\n|\f/', $this->break, $line);
72
+ assert($replacedLine !== null);
73
+ $block->lines[$index] = $replacedLine;
74
  }
75
  }
76
 
105
  array_pop($depths);
106
  $this->depth--;
107
 
108
+ if (
109
+ ! $this->depth && ($block->depth <= 1 || (! $this->indentLevel && $block->type === Type::T_COMMENT)) &&
110
  (($block->selectors && ! $isMediaOrDirective) || $previousHasSelector)
111
  ) {
112
  $downLevel = $this->break;
223
  *
224
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
225
  *
226
+ * @return bool
227
  */
228
  private function hasFlatChild($block)
229
  {
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Formatter/OutputBlock.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,51 +16,53 @@ namespace ScssPhp\ScssPhp\Formatter;
15
  * Output block
16
  *
17
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
18
  */
19
  class OutputBlock
20
  {
21
  /**
22
- * @var string
23
  */
24
  public $type;
25
 
26
  /**
27
- * @var integer
28
  */
29
  public $depth;
30
 
31
  /**
32
- * @var array
33
  */
34
  public $selectors;
35
 
36
  /**
37
- * @var array
38
  */
39
  public $lines;
40
 
41
  /**
42
- * @var array
43
  */
44
  public $children;
45
 
46
  /**
47
- * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
48
  */
49
  public $parent;
50
 
51
  /**
52
- * @var string
53
  */
54
  public $sourceName;
55
 
56
  /**
57
- * @var integer
58
  */
59
  public $sourceLine;
60
 
61
  /**
62
- * @var integer
63
  */
64
  public $sourceColumn;
65
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
16
  * Output block
17
  *
18
  * @author Anthon Pang <anthon.pang@gmail.com>
19
+ *
20
+ * @internal
21
  */
22
  class OutputBlock
23
  {
24
  /**
25
+ * @var string|null
26
  */
27
  public $type;
28
 
29
  /**
30
+ * @var int
31
  */
32
  public $depth;
33
 
34
  /**
35
+ * @var array|null
36
  */
37
  public $selectors;
38
 
39
  /**
40
+ * @var string[]
41
  */
42
  public $lines;
43
 
44
  /**
45
+ * @var OutputBlock[]
46
  */
47
  public $children;
48
 
49
  /**
50
+ * @var OutputBlock|null
51
  */
52
  public $parent;
53
 
54
  /**
55
+ * @var string|null
56
  */
57
  public $sourceName;
58
 
59
  /**
60
+ * @var int|null
61
  */
62
  public $sourceLine;
63
 
64
  /**
65
+ * @var int|null
66
  */
67
  public $sourceColumn;
68
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Logger/LoggerInterface.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Logger;
14
+
15
+ /**
16
+ * Interface implemented by loggers for warnings and debug messages.
17
+ *
18
+ * The official Sass implementation recommends that loggers report the
19
+ * messages immediately rather than waiting for the end of the
20
+ * compilation, to provide a better debugging experience when the
21
+ * compilation does not end (error or infinite loop after the warning
22
+ * for instance).
23
+ */
24
+ interface LoggerInterface
25
+ {
26
+ /**
27
+ * Emits a warning with the given message.
28
+ *
29
+ * If $deprecation is true, it indicates that this is a deprecation
30
+ * warning. Implementations should surface all this information to
31
+ * the end user.
32
+ *
33
+ * @param string $message
34
+ * @param bool $deprecation
35
+ *
36
+ * @return void
37
+ */
38
+ public function warn($message, $deprecation = false);
39
+
40
+ /**
41
+ * Emits a debugging message.
42
+ *
43
+ * @param string $message
44
+ *
45
+ * @return void
46
+ */
47
+ public function debug($message);
48
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Logger/QuietLogger.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Logger;
14
+
15
+ /**
16
+ * A logger that silently ignores all messages.
17
+ */
18
+ class QuietLogger implements LoggerInterface
19
+ {
20
+ public function warn($message, $deprecation = false)
21
+ {
22
+ }
23
+
24
+ public function debug($message)
25
+ {
26
+ }
27
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Logger/StreamLogger.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp\Logger;
14
+
15
+ /**
16
+ * A logger that prints to a PHP stream (for instance stderr)
17
+ */
18
+ class StreamLogger implements LoggerInterface
19
+ {
20
+ private $stream;
21
+ private $closeOnDestruct;
22
+
23
+ /**
24
+ * @param resource $stream A stream resource
25
+ * @param bool $closeOnDestruct If true, takes ownership of the stream and close it on destruct to avoid leaks.
26
+ */
27
+ public function __construct($stream, $closeOnDestruct = false)
28
+ {
29
+ $this->stream = $stream;
30
+ $this->closeOnDestruct = $closeOnDestruct;
31
+ }
32
+
33
+ /**
34
+ * @internal
35
+ */
36
+ public function __destruct()
37
+ {
38
+ if ($this->closeOnDestruct) {
39
+ fclose($this->stream);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * @inheritDoc
45
+ */
46
+ public function warn($message, $deprecation = false)
47
+ {
48
+ $prefix = ($deprecation ? 'DEPRECATION ' : '') . 'WARNING: ';
49
+
50
+ fwrite($this->stream, $prefix . $message . "\n\n");
51
+ }
52
+
53
+ /**
54
+ * @inheritDoc
55
+ */
56
+ public function debug($message)
57
+ {
58
+ fwrite($this->stream, $message . "\n");
59
+ }
60
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Node.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,6 +16,8 @@ namespace ScssPhp\ScssPhp;
15
  * Base node
16
  *
17
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
18
  */
19
  abstract class Node
20
  {
@@ -24,17 +27,17 @@ abstract class Node
24
  public $type;
25
 
26
  /**
27
- * @var integer
28
  */
29
  public $sourceIndex;
30
 
31
  /**
32
- * @var integer
33
  */
34
  public $sourceLine;
35
 
36
  /**
37
- * @var integer
38
  */
39
  public $sourceColumn;
40
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
16
  * Base node
17
  *
18
  * @author Anthon Pang <anthon.pang@gmail.com>
19
+ *
20
+ * @internal
21
  */
22
  abstract class Node
23
  {
27
  public $type;
28
 
29
  /**
30
+ * @var int
31
  */
32
  public $sourceIndex;
33
 
34
  /**
35
+ * @var int|null
36
  */
37
  public $sourceLine;
38
 
39
  /**
40
+ * @var int|null
41
  */
42
  public $sourceColumn;
43
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Node/Number.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -11,9 +12,13 @@
11
 
12
  namespace ScssPhp\ScssPhp\Node;
13
 
 
14
  use ScssPhp\ScssPhp\Compiler;
 
 
15
  use ScssPhp\ScssPhp\Node;
16
  use ScssPhp\ScssPhp\Type;
 
17
 
18
  /**
19
  * Dimension + optional units
@@ -25,18 +30,24 @@ use ScssPhp\ScssPhp\Type;
25
  * }}
26
  *
27
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
28
  */
29
  class Number extends Node implements \ArrayAccess
30
  {
 
 
31
  /**
32
- * @var integer
 
33
  */
34
- public static $precision = 10;
35
 
36
  /**
37
  * @see http://www.w3.org/TR/2012/WD-css3-values-20120308/
38
  *
39
  * @var array
 
40
  */
41
  protected static $unitTable = [
42
  'in' => [
@@ -64,87 +75,81 @@ class Number extends Node implements \ArrayAccess
64
  ],
65
  'dpi' => [
66
  'dpi' => 1,
67
- 'dpcm' => 1/2.54,
68
- 'dppx' => 1/96,
69
  ],
70
  ];
71
 
72
  /**
73
- * @var integer|float
74
  */
75
- public $dimension;
76
 
77
  /**
78
- * @var array
 
79
  */
80
- public $units;
81
 
82
  /**
83
- * Initialize number
84
- *
85
- * @param mixed $dimension
86
- * @param mixed $initialUnit
87
  */
88
- public function __construct($dimension, $initialUnit)
89
- {
90
- $this->type = Type::T_NUMBER;
91
- $this->dimension = $dimension;
92
- $this->units = \is_array($initialUnit)
93
- ? $initialUnit
94
- : ($initialUnit ? [$initialUnit => 1]
95
- : []);
96
- }
97
 
98
  /**
99
- * Coerce number to target units
100
  *
101
- * @param array $units
 
 
102
  *
103
- * @return \ScssPhp\ScssPhp\Node\Number
 
104
  */
105
- public function coerce($units)
106
  {
107
- if ($this->unitless()) {
108
- return new Number($this->dimension, $units);
109
- }
110
-
111
- $dimension = $this->dimension;
112
-
113
- if (\count($units)) {
114
- $baseUnit = array_keys($units);
115
- $baseUnit = reset($baseUnit);
116
- $baseUnit = $this->findBaseUnit($baseUnit);
117
- if ($baseUnit && isset(static::$unitTable[$baseUnit])) {
118
- foreach (static::$unitTable[$baseUnit] as $unit => $conv) {
119
- $from = isset($this->units[$unit]) ? $this->units[$unit] : 0;
120
- $to = isset($units[$unit]) ? $units[$unit] : 0;
121
- $factor = pow($conv, $from - $to);
122
- $dimension /= $factor;
123
- }
124
- }
125
  }
126
 
127
- return new Number($dimension, $units);
 
 
128
  }
129
 
130
  /**
131
- * Normalize number
132
- *
133
- * @return \ScssPhp\ScssPhp\Node\Number
134
  */
135
- public function normalize()
136
  {
137
- $dimension = $this->dimension;
138
- $units = [];
139
 
140
- $this->normalizeUnits($dimension, $units);
 
 
 
 
 
 
141
 
142
- return new Number($dimension, $units);
 
 
 
 
 
143
  }
144
 
145
  /**
146
- * {@inheritdoc}
147
  */
 
148
  public function offsetExists($offset)
149
  {
150
  if ($offset === -3) {
@@ -155,7 +160,8 @@ class Number extends Node implements \ArrayAccess
155
  return ! \is_null($this->sourceLine);
156
  }
157
 
158
- if ($offset === -1 ||
 
159
  $offset === 0 ||
160
  $offset === 1 ||
161
  $offset === 2
@@ -167,8 +173,9 @@ class Number extends Node implements \ArrayAccess
167
  }
168
 
169
  /**
170
- * {@inheritdoc}
171
  */
 
172
  public function offsetGet($offset)
173
  {
174
  switch ($offset) {
@@ -182,114 +189,346 @@ class Number extends Node implements \ArrayAccess
182
  return $this->sourceIndex;
183
 
184
  case 0:
185
- return $this->type;
186
 
187
  case 1:
188
  return $this->dimension;
189
 
190
  case 2:
191
- return $this->units;
192
  }
193
  }
194
 
195
  /**
196
- * {@inheritdoc}
197
  */
 
198
  public function offsetSet($offset, $value)
199
  {
200
- if ($offset === 1) {
201
- $this->dimension = $value;
202
- } elseif ($offset === 2) {
203
- $this->units = $value;
204
- } elseif ($offset == -1) {
205
- $this->sourceIndex = $value;
206
- } elseif ($offset == -2) {
207
- $this->sourceLine = $value;
208
- } elseif ($offset == -3) {
209
- $this->sourceColumn = $value;
210
- }
211
  }
212
 
213
  /**
214
- * {@inheritdoc}
215
  */
 
216
  public function offsetUnset($offset)
217
  {
218
- if ($offset === 1) {
219
- $this->dimension = null;
220
- } elseif ($offset === 2) {
221
- $this->units = null;
222
- } elseif ($offset === -1) {
223
- $this->sourceIndex = null;
224
- } elseif ($offset === -2) {
225
- $this->sourceLine = null;
226
- } elseif ($offset === -3) {
227
- $this->sourceColumn = null;
228
- }
229
  }
230
 
231
  /**
232
  * Returns true if the number is unitless
233
  *
234
- * @return boolean
235
  */
236
  public function unitless()
237
  {
238
- return ! array_sum($this->units);
239
  }
240
 
241
  /**
242
- * Test if a number can be normalized in a base unit
243
- * ie if its units are homogeneous
 
244
  *
245
- * @return boolean
246
  */
247
- public function isNormalizable()
 
 
 
 
 
 
 
 
 
 
248
  {
249
  if ($this->unitless()) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  return false;
251
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
- $baseUnit = null;
 
 
 
 
 
 
 
 
 
 
254
 
255
- foreach ($this->units as $unit => $exp) {
256
- $b = $this->findBaseUnit($unit);
 
 
 
 
 
257
 
258
- if (\is_null($baseUnit)) {
259
- $baseUnit = $b;
 
 
 
 
 
 
 
 
260
  }
261
 
262
- if (\is_null($b) or $b !== $baseUnit) {
263
- return false;
 
 
264
  }
265
- }
266
 
267
- return $baseUnit;
 
 
 
 
 
268
  }
269
 
270
  /**
271
- * Returns unit(s) as the product of numerator units divided by the product of denominator units
272
  *
273
- * @return string
274
  */
275
- public function unitStr()
276
  {
277
- $numerators = [];
278
- $denominators = [];
279
 
280
- foreach ($this->units as $unit => $unitSize) {
281
- if ($unitSize > 0) {
282
- $numerators = array_pad($numerators, \count($numerators) + $unitSize, $unit);
283
- continue;
 
 
 
 
 
 
 
 
 
 
284
  }
 
 
 
285
 
286
- if ($unitSize < 0) {
287
- $denominators = array_pad($denominators, \count($denominators) - $unitSize, $unit);
288
- continue;
289
- }
 
 
 
 
 
 
 
 
 
290
  }
291
 
292
- return implode('*', $numerators) . (\count($denominators) ? '/' . implode('*', $denominators) : '');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  }
294
 
295
  /**
@@ -301,37 +540,31 @@ class Number extends Node implements \ArrayAccess
301
  */
302
  public function output(Compiler $compiler = null)
303
  {
304
- $dimension = round($this->dimension, static::$precision);
305
-
306
- $units = array_filter($this->units, function ($unitSize) {
307
- return $unitSize;
308
- });
309
-
310
- if (\count($units) > 1 && array_sum($units) === 0) {
311
- $dimension = $this->dimension;
312
- $units = [];
313
 
314
- $this->normalizeUnits($dimension, $units);
 
 
315
 
316
- $dimension = round($dimension, static::$precision);
317
- $units = array_filter($units, function ($unitSize) {
318
- return $unitSize;
319
- });
320
  }
321
 
322
- $unitSize = array_sum($units);
 
 
323
 
324
- if ($compiler && ($unitSize > 1 || $unitSize < 0 || \count($units) > 1)) {
325
- $this->units = $units;
326
  $unit = $this->unitStr();
 
 
327
  } else {
328
- reset($units);
329
- $unit = key($units);
330
  }
331
 
332
- $dimension = number_format($dimension, static::$precision, '.', '');
333
 
334
- return (static::$precision ? rtrim(rtrim($dimension, '0'), '.') : $dimension) . $unit;
335
  }
336
 
337
  /**
@@ -343,48 +576,229 @@ class Number extends Node implements \ArrayAccess
343
  }
344
 
345
  /**
346
- * Normalize units
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  *
348
- * @param integer|float $dimension
349
- * @param array $units
350
- * @param string $baseUnit
351
  */
352
- private function normalizeUnits(&$dimension, &$units, $baseUnit = null)
353
  {
354
- $dimension = $this->dimension;
355
- $units = [];
 
 
 
 
 
 
 
 
 
 
 
 
356
 
357
- foreach ($this->units as $unit => $exp) {
358
- if (! $baseUnit) {
359
- $baseUnit = $this->findBaseUnit($unit);
 
 
 
 
360
  }
361
 
362
- if ($baseUnit && isset(static::$unitTable[$baseUnit][$unit])) {
363
- $factor = pow(static::$unitTable[$baseUnit][$unit], $exp);
 
 
 
 
 
 
364
 
365
- $unit = $baseUnit;
366
- $dimension /= $factor;
 
 
 
 
 
 
 
 
 
367
  }
368
 
369
- $units[$unit] = $exp + (isset($units[$unit]) ? $units[$unit] : 0);
 
 
 
 
 
 
 
 
 
 
 
 
370
  }
 
 
371
  }
372
 
373
  /**
374
- * Find the base unit family for a given unit
 
 
 
 
375
  *
376
- * @param string $unit
377
  *
378
- * @return string|null
 
 
 
379
  */
380
- private function findBaseUnit($unit)
381
  {
382
- foreach (static::$unitTable as $baseUnit => $unitVariants) {
383
- if (isset($unitVariants[$unit])) {
384
- return $baseUnit;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  }
386
  }
387
 
388
  return null;
389
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
12
 
13
  namespace ScssPhp\ScssPhp\Node;
14
 
15
+ use ScssPhp\ScssPhp\Base\Range;
16
  use ScssPhp\ScssPhp\Compiler;
17
+ use ScssPhp\ScssPhp\Exception\RangeException;
18
+ use ScssPhp\ScssPhp\Exception\SassScriptException;
19
  use ScssPhp\ScssPhp\Node;
20
  use ScssPhp\ScssPhp\Type;
21
+ use ScssPhp\ScssPhp\Util;
22
 
23
  /**
24
  * Dimension + optional units
30
  * }}
31
  *
32
  * @author Anthon Pang <anthon.pang@gmail.com>
33
+ *
34
+ * @template-implements \ArrayAccess<int, mixed>
35
  */
36
  class Number extends Node implements \ArrayAccess
37
  {
38
+ const PRECISION = 10;
39
+
40
  /**
41
+ * @var int
42
+ * @deprecated use {Number::PRECISION} instead to read the precision. Configuring it is not supported anymore.
43
  */
44
+ public static $precision = self::PRECISION;
45
 
46
  /**
47
  * @see http://www.w3.org/TR/2012/WD-css3-values-20120308/
48
  *
49
  * @var array
50
+ * @phpstan-var array<string, array<string, float|int>>
51
  */
52
  protected static $unitTable = [
53
  'in' => [
75
  ],
76
  'dpi' => [
77
  'dpi' => 1,
78
+ 'dpcm' => 1 / 2.54,
79
+ 'dppx' => 1 / 96,
80
  ],
81
  ];
82
 
83
  /**
84
+ * @var int|float
85
  */
86
+ private $dimension;
87
 
88
  /**
89
+ * @var string[]
90
+ * @phpstan-var list<string>
91
  */
92
+ private $numeratorUnits;
93
 
94
  /**
95
+ * @var string[]
96
+ * @phpstan-var list<string>
 
 
97
  */
98
+ private $denominatorUnits;
 
 
 
 
 
 
 
 
99
 
100
  /**
101
+ * Initialize number
102
  *
103
+ * @param int|float $dimension
104
+ * @param string[]|string $numeratorUnits
105
+ * @param string[] $denominatorUnits
106
  *
107
+ * @phpstan-param list<string>|string $numeratorUnits
108
+ * @phpstan-param list<string> $denominatorUnits
109
  */
110
+ public function __construct($dimension, $numeratorUnits, array $denominatorUnits = [])
111
  {
112
+ if (is_string($numeratorUnits)) {
113
+ $numeratorUnits = $numeratorUnits ? [$numeratorUnits] : [];
114
+ } elseif (isset($numeratorUnits['numerator_units'], $numeratorUnits['denominator_units'])) {
115
+ // TODO get rid of this once `$number[2]` is not used anymore
116
+ $denominatorUnits = $numeratorUnits['denominator_units'];
117
+ $numeratorUnits = $numeratorUnits['numerator_units'];
 
 
 
 
 
 
 
 
 
 
 
 
118
  }
119
 
120
+ $this->dimension = $dimension;
121
+ $this->numeratorUnits = $numeratorUnits;
122
+ $this->denominatorUnits = $denominatorUnits;
123
  }
124
 
125
  /**
126
+ * @return float|int
 
 
127
  */
128
+ public function getDimension()
129
  {
130
+ return $this->dimension;
131
+ }
132
 
133
+ /**
134
+ * @return string[]
135
+ */
136
+ public function getNumeratorUnits()
137
+ {
138
+ return $this->numeratorUnits;
139
+ }
140
 
141
+ /**
142
+ * @return string[]
143
+ */
144
+ public function getDenominatorUnits()
145
+ {
146
+ return $this->denominatorUnits;
147
  }
148
 
149
  /**
150
+ * @return bool
151
  */
152
+ #[\ReturnTypeWillChange]
153
  public function offsetExists($offset)
154
  {
155
  if ($offset === -3) {
160
  return ! \is_null($this->sourceLine);
161
  }
162
 
163
+ if (
164
+ $offset === -1 ||
165
  $offset === 0 ||
166
  $offset === 1 ||
167
  $offset === 2
173
  }
174
 
175
  /**
176
+ * @return mixed
177
  */
178
+ #[\ReturnTypeWillChange]
179
  public function offsetGet($offset)
180
  {
181
  switch ($offset) {
189
  return $this->sourceIndex;
190
 
191
  case 0:
192
+ return Type::T_NUMBER;
193
 
194
  case 1:
195
  return $this->dimension;
196
 
197
  case 2:
198
+ return array('numerator_units' => $this->numeratorUnits, 'denominator_units' => $this->denominatorUnits);
199
  }
200
  }
201
 
202
  /**
203
+ * @return void
204
  */
205
+ #[\ReturnTypeWillChange]
206
  public function offsetSet($offset, $value)
207
  {
208
+ throw new \BadMethodCallException('Number is immutable');
 
 
 
 
 
 
 
 
 
 
209
  }
210
 
211
  /**
212
+ * @return void
213
  */
214
+ #[\ReturnTypeWillChange]
215
  public function offsetUnset($offset)
216
  {
217
+ throw new \BadMethodCallException('Number is immutable');
 
 
 
 
 
 
 
 
 
 
218
  }
219
 
220
  /**
221
  * Returns true if the number is unitless
222
  *
223
+ * @return bool
224
  */
225
  public function unitless()
226
  {
227
+ return \count($this->numeratorUnits) === 0 && \count($this->denominatorUnits) === 0;
228
  }
229
 
230
  /**
231
+ * Checks whether the number has exactly this unit
232
+ *
233
+ * @param string $unit
234
  *
235
+ * @return bool
236
  */
237
+ public function hasUnit($unit)
238
+ {
239
+ return \count($this->numeratorUnits) === 1 && \count($this->denominatorUnits) === 0 && $this->numeratorUnits[0] === $unit;
240
+ }
241
+
242
+ /**
243
+ * Returns unit(s) as the product of numerator units divided by the product of denominator units
244
+ *
245
+ * @return string
246
+ */
247
+ public function unitStr()
248
  {
249
  if ($this->unitless()) {
250
+ return '';
251
+ }
252
+
253
+ return self::getUnitString($this->numeratorUnits, $this->denominatorUnits);
254
+ }
255
+
256
+ /**
257
+ * @param float|int $min
258
+ * @param float|int $max
259
+ * @param string|null $name
260
+ *
261
+ * @return float|int
262
+ * @throws SassScriptException
263
+ */
264
+ public function valueInRange($min, $max, $name = null)
265
+ {
266
+ try {
267
+ return Util::checkRange('', new Range($min, $max), $this);
268
+ } catch (RangeException $e) {
269
+ throw SassScriptException::forArgument(sprintf('Expected %s to be within %s%s and %s%3$s', $this, $min, $this->unitStr(), $max), $name);
270
+ }
271
+ }
272
+
273
+ /**
274
+ * @param string|null $varName
275
+ *
276
+ * @return void
277
+ */
278
+ public function assertNoUnits($varName = null)
279
+ {
280
+ if ($this->unitless()) {
281
+ return;
282
+ }
283
+
284
+ throw SassScriptException::forArgument(sprintf('Expected %s to have no units.', $this), $varName);
285
+ }
286
+
287
+ /**
288
+ * @param string $unit
289
+ * @param string|null $varName
290
+ *
291
+ * @return void
292
+ */
293
+ public function assertUnit($unit, $varName = null)
294
+ {
295
+ if ($this->hasUnit($unit)) {
296
+ return;
297
+ }
298
+
299
+ throw SassScriptException::forArgument(sprintf('Expected %s to have unit "%s".', $this, $unit), $varName);
300
+ }
301
+
302
+ /**
303
+ * @param Number $other
304
+ *
305
+ * @return void
306
+ */
307
+ public function assertSameUnitOrUnitless(Number $other)
308
+ {
309
+ if ($other->unitless()) {
310
+ return;
311
+ }
312
+
313
+ if ($this->numeratorUnits === $other->numeratorUnits && $this->denominatorUnits === $other->denominatorUnits) {
314
+ return;
315
+ }
316
+
317
+ throw new SassScriptException(sprintf(
318
+ 'Incompatible units %s and %s.',
319
+ self::getUnitString($this->numeratorUnits, $this->denominatorUnits),
320
+ self::getUnitString($other->numeratorUnits, $other->denominatorUnits)
321
+ ));
322
+ }
323
+
324
+ /**
325
+ * Returns a copy of this number, converted to the units represented by $newNumeratorUnits and $newDenominatorUnits.
326
+ *
327
+ * This does not throw an error if this number is unitless and
328
+ * $newNumeratorUnits/$newDenominatorUnits are not empty, or vice versa. Instead,
329
+ * it treats all unitless numbers as convertible to and from all units without
330
+ * changing the value.
331
+ *
332
+ * @param string[] $newNumeratorUnits
333
+ * @param string[] $newDenominatorUnits
334
+ *
335
+ * @return Number
336
+ *
337
+ * @phpstan-param list<string> $newNumeratorUnits
338
+ * @phpstan-param list<string> $newDenominatorUnits
339
+ *
340
+ * @throws SassScriptException if this number's units are not compatible with $newNumeratorUnits and $newDenominatorUnits
341
+ */
342
+ public function coerce(array $newNumeratorUnits, array $newDenominatorUnits)
343
+ {
344
+ return new Number($this->valueInUnits($newNumeratorUnits, $newDenominatorUnits), $newNumeratorUnits, $newDenominatorUnits);
345
+ }
346
+
347
+ /**
348
+ * @param Number $other
349
+ *
350
+ * @return bool
351
+ */
352
+ public function isComparableTo(Number $other)
353
+ {
354
+ if ($this->unitless() || $other->unitless()) {
355
+ return true;
356
+ }
357
+
358
+ try {
359
+ $this->greaterThan($other);
360
+ return true;
361
+ } catch (SassScriptException $e) {
362
  return false;
363
  }
364
+ }
365
+
366
+ /**
367
+ * @param Number $other
368
+ *
369
+ * @return bool
370
+ */
371
+ public function lessThan(Number $other)
372
+ {
373
+ return $this->coerceUnits($other, function ($num1, $num2) {
374
+ return $num1 < $num2;
375
+ });
376
+ }
377
+
378
+ /**
379
+ * @param Number $other
380
+ *
381
+ * @return bool
382
+ */
383
+ public function lessThanOrEqual(Number $other)
384
+ {
385
+ return $this->coerceUnits($other, function ($num1, $num2) {
386
+ return $num1 <= $num2;
387
+ });
388
+ }
389
+
390
+ /**
391
+ * @param Number $other
392
+ *
393
+ * @return bool
394
+ */
395
+ public function greaterThan(Number $other)
396
+ {
397
+ return $this->coerceUnits($other, function ($num1, $num2) {
398
+ return $num1 > $num2;
399
+ });
400
+ }
401
+
402
+ /**
403
+ * @param Number $other
404
+ *
405
+ * @return bool
406
+ */
407
+ public function greaterThanOrEqual(Number $other)
408
+ {
409
+ return $this->coerceUnits($other, function ($num1, $num2) {
410
+ return $num1 >= $num2;
411
+ });
412
+ }
413
+
414
+ /**
415
+ * @param Number $other
416
+ *
417
+ * @return Number
418
+ */
419
+ public function plus(Number $other)
420
+ {
421
+ return $this->coerceNumber($other, function ($num1, $num2) {
422
+ return $num1 + $num2;
423
+ });
424
+ }
425
 
426
+ /**
427
+ * @param Number $other
428
+ *
429
+ * @return Number
430
+ */
431
+ public function minus(Number $other)
432
+ {
433
+ return $this->coerceNumber($other, function ($num1, $num2) {
434
+ return $num1 - $num2;
435
+ });
436
+ }
437
 
438
+ /**
439
+ * @return Number
440
+ */
441
+ public function unaryMinus()
442
+ {
443
+ return new Number(-$this->dimension, $this->numeratorUnits, $this->denominatorUnits);
444
+ }
445
 
446
+ /**
447
+ * @param Number $other
448
+ *
449
+ * @return Number
450
+ */
451
+ public function modulo(Number $other)
452
+ {
453
+ return $this->coerceNumber($other, function ($num1, $num2) {
454
+ if ($num2 == 0) {
455
+ return NAN;
456
  }
457
 
458
+ $result = fmod($num1, $num2);
459
+
460
+ if ($result == 0) {
461
+ return 0;
462
  }
 
463
 
464
+ if ($num2 < 0 xor $num1 < 0) {
465
+ $result += $num2;
466
+ }
467
+
468
+ return $result;
469
+ });
470
  }
471
 
472
  /**
473
+ * @param Number $other
474
  *
475
+ * @return Number
476
  */
477
+ public function times(Number $other)
478
  {
479
+ return $this->multiplyUnits($this->dimension * $other->dimension, $this->numeratorUnits, $this->denominatorUnits, $other->numeratorUnits, $other->denominatorUnits);
480
+ }
481
 
482
+ /**
483
+ * @param Number $other
484
+ *
485
+ * @return Number
486
+ */
487
+ public function dividedBy(Number $other)
488
+ {
489
+ if ($other->dimension == 0) {
490
+ if ($this->dimension == 0) {
491
+ $value = NAN;
492
+ } elseif ($this->dimension > 0) {
493
+ $value = INF;
494
+ } else {
495
+ $value = -INF;
496
  }
497
+ } else {
498
+ $value = $this->dimension / $other->dimension;
499
+ }
500
 
501
+ return $this->multiplyUnits($value, $this->numeratorUnits, $this->denominatorUnits, $other->denominatorUnits, $other->numeratorUnits);
502
+ }
503
+
504
+ /**
505
+ * @param Number $other
506
+ *
507
+ * @return bool
508
+ */
509
+ public function equals(Number $other)
510
+ {
511
+ // Unitless numbers are convertable to unit numbers, but not equal, so we special-case unitless here.
512
+ if ($this->unitless() !== $other->unitless()) {
513
+ return false;
514
  }
515
 
516
+ // In Sass, neither NaN nor Infinity are equal to themselves, while PHP defines INF==INF
517
+ if (is_nan($this->dimension) || is_nan($other->dimension) || !is_finite($this->dimension) || !is_finite($other->dimension)) {
518
+ return false;
519
+ }
520
+
521
+ if ($this->unitless()) {
522
+ return round($this->dimension, self::PRECISION) == round($other->dimension, self::PRECISION);
523
+ }
524
+
525
+ try {
526
+ return $this->coerceUnits($other, function ($num1, $num2) {
527
+ return round($num1,self::PRECISION) == round($num2, self::PRECISION);
528
+ });
529
+ } catch (SassScriptException $e) {
530
+ return false;
531
+ }
532
  }
533
 
534
  /**
540
  */
541
  public function output(Compiler $compiler = null)
542
  {
543
+ $dimension = round($this->dimension, self::PRECISION);
 
 
 
 
 
 
 
 
544
 
545
+ if (is_nan($dimension)) {
546
+ return 'NaN';
547
+ }
548
 
549
+ if ($dimension === INF) {
550
+ return 'Infinity';
 
 
551
  }
552
 
553
+ if ($dimension === -INF) {
554
+ return '-Infinity';
555
+ }
556
 
557
+ if ($compiler) {
 
558
  $unit = $this->unitStr();
559
+ } elseif (isset($this->numeratorUnits[0])) {
560
+ $unit = $this->numeratorUnits[0];
561
  } else {
562
+ $unit = '';
 
563
  }
564
 
565
+ $dimension = number_format($dimension, self::PRECISION, '.', '');
566
 
567
+ return rtrim(rtrim($dimension, '0'), '.') . $unit;
568
  }
569
 
570
  /**
576
  }
577
 
578
  /**
579
+ * @param Number $other
580
+ * @param callable $operation
581
+ *
582
+ * @return Number
583
+ *
584
+ * @phpstan-param callable(int|float, int|float): (int|float) $operation
585
+ */
586
+ private function coerceNumber(Number $other, $operation)
587
+ {
588
+ $result = $this->coerceUnits($other, $operation);
589
+
590
+ if (!$this->unitless()) {
591
+ return new Number($result, $this->numeratorUnits, $this->denominatorUnits);
592
+ }
593
+
594
+ return new Number($result, $other->numeratorUnits, $other->denominatorUnits);
595
+ }
596
+
597
+ /**
598
+ * @param Number $other
599
+ * @param callable $operation
600
+ *
601
+ * @return mixed
602
+ *
603
+ * @phpstan-template T
604
+ * @phpstan-param callable(int|float, int|float): T $operation
605
+ * @phpstan-return T
606
+ */
607
+ private function coerceUnits(Number $other, $operation)
608
+ {
609
+ if (!$this->unitless()) {
610
+ $num1 = $this->dimension;
611
+ $num2 = $other->valueInUnits($this->numeratorUnits, $this->denominatorUnits);
612
+ } else {
613
+ $num1 = $this->valueInUnits($other->numeratorUnits, $other->denominatorUnits);
614
+ $num2 = $other->dimension;
615
+ }
616
+
617
+ return \call_user_func($operation, $num1, $num2);
618
+ }
619
+
620
+ /**
621
+ * @param string[] $numeratorUnits
622
+ * @param string[] $denominatorUnits
623
+ *
624
+ * @return int|float
625
+ *
626
+ * @phpstan-param list<string> $numeratorUnits
627
+ * @phpstan-param list<string> $denominatorUnits
628
  *
629
+ * @throws SassScriptException if this number's units are not compatible with $numeratorUnits and $denominatorUnits
 
 
630
  */
631
+ private function valueInUnits(array $numeratorUnits, array $denominatorUnits)
632
  {
633
+ if (
634
+ $this->unitless()
635
+ || (\count($numeratorUnits) === 0 && \count($denominatorUnits) === 0)
636
+ || ($this->numeratorUnits === $numeratorUnits && $this->denominatorUnits === $denominatorUnits)
637
+ ) {
638
+ return $this->dimension;
639
+ }
640
+
641
+ $value = $this->dimension;
642
+ $oldNumerators = $this->numeratorUnits;
643
+
644
+ foreach ($numeratorUnits as $newNumerator) {
645
+ foreach ($oldNumerators as $key => $oldNumerator) {
646
+ $conversionFactor = self::getConversionFactor($newNumerator, $oldNumerator);
647
 
648
+ if (\is_null($conversionFactor)) {
649
+ continue;
650
+ }
651
+
652
+ $value *= $conversionFactor;
653
+ unset($oldNumerators[$key]);
654
+ continue 2;
655
  }
656
 
657
+ throw new SassScriptException(sprintf(
658
+ 'Incompatible units %s and %s.',
659
+ self::getUnitString($this->numeratorUnits, $this->denominatorUnits),
660
+ self::getUnitString($numeratorUnits, $denominatorUnits)
661
+ ));
662
+ }
663
+
664
+ $oldDenominators = $this->denominatorUnits;
665
 
666
+ foreach ($denominatorUnits as $newDenominator) {
667
+ foreach ($oldDenominators as $key => $oldDenominator) {
668
+ $conversionFactor = self::getConversionFactor($newDenominator, $oldDenominator);
669
+
670
+ if (\is_null($conversionFactor)) {
671
+ continue;
672
+ }
673
+
674
+ $value /= $conversionFactor;
675
+ unset($oldDenominators[$key]);
676
+ continue 2;
677
  }
678
 
679
+ throw new SassScriptException(sprintf(
680
+ 'Incompatible units %s and %s.',
681
+ self::getUnitString($this->numeratorUnits, $this->denominatorUnits),
682
+ self::getUnitString($numeratorUnits, $denominatorUnits)
683
+ ));
684
+ }
685
+
686
+ if (\count($oldNumerators) || \count($oldDenominators)) {
687
+ throw new SassScriptException(sprintf(
688
+ 'Incompatible units %s and %s.',
689
+ self::getUnitString($this->numeratorUnits, $this->denominatorUnits),
690
+ self::getUnitString($numeratorUnits, $denominatorUnits)
691
+ ));
692
  }
693
+
694
+ return $value;
695
  }
696
 
697
  /**
698
+ * @param int|float $value
699
+ * @param string[] $numerators1
700
+ * @param string[] $denominators1
701
+ * @param string[] $numerators2
702
+ * @param string[] $denominators2
703
  *
704
+ * @return Number
705
  *
706
+ * @phpstan-param list<string> $numerators1
707
+ * @phpstan-param list<string> $denominators1
708
+ * @phpstan-param list<string> $numerators2
709
+ * @phpstan-param list<string> $denominators2
710
  */
711
+ private function multiplyUnits($value, array $numerators1, array $denominators1, array $numerators2, array $denominators2)
712
  {
713
+ $newNumerators = array();
714
+
715
+ foreach ($numerators1 as $numerator) {
716
+ foreach ($denominators2 as $key => $denominator) {
717
+ $conversionFactor = self::getConversionFactor($numerator, $denominator);
718
+
719
+ if (\is_null($conversionFactor)) {
720
+ continue;
721
+ }
722
+
723
+ $value /= $conversionFactor;
724
+ unset($denominators2[$key]);
725
+ continue 2;
726
+ }
727
+
728
+ $newNumerators[] = $numerator;
729
+ }
730
+
731
+ foreach ($numerators2 as $numerator) {
732
+ foreach ($denominators1 as $key => $denominator) {
733
+ $conversionFactor = self::getConversionFactor($numerator, $denominator);
734
+
735
+ if (\is_null($conversionFactor)) {
736
+ continue;
737
+ }
738
+
739
+ $value /= $conversionFactor;
740
+ unset($denominators1[$key]);
741
+ continue 2;
742
+ }
743
+
744
+ $newNumerators[] = $numerator;
745
+ }
746
+
747
+ $newDenominators = array_values(array_merge($denominators1, $denominators2));
748
+
749
+ return new Number($value, $newNumerators, $newDenominators);
750
+ }
751
+
752
+ /**
753
+ * Returns the number of [unit1]s per [unit2].
754
+ *
755
+ * Equivalently, `1unit1 * conversionFactor(unit1, unit2) = 1unit2`.
756
+ *
757
+ * @param string $unit1
758
+ * @param string $unit2
759
+ *
760
+ * @return float|int|null
761
+ */
762
+ private static function getConversionFactor($unit1, $unit2)
763
+ {
764
+ if ($unit1 === $unit2) {
765
+ return 1;
766
+ }
767
+
768
+ foreach (static::$unitTable as $unitVariants) {
769
+ if (isset($unitVariants[$unit1]) && isset($unitVariants[$unit2])) {
770
+ return $unitVariants[$unit1] / $unitVariants[$unit2];
771
  }
772
  }
773
 
774
  return null;
775
  }
776
+
777
+ /**
778
+ * Returns unit(s) as the product of numerator units divided by the product of denominator units
779
+ *
780
+ * @param string[] $numerators
781
+ * @param string[] $denominators
782
+ *
783
+ * @phpstan-param list<string> $numerators
784
+ * @phpstan-param list<string> $denominators
785
+ *
786
+ * @return string
787
+ */
788
+ private static function getUnitString(array $numerators, array $denominators)
789
+ {
790
+ if (!\count($numerators)) {
791
+ if (\count($denominators) === 0) {
792
+ return 'no units';
793
+ }
794
+
795
+ if (\count($denominators) === 1) {
796
+ return $denominators[0] . '^-1';
797
+ }
798
+
799
+ return '(' . implode('*', $denominators) . ')^-1';
800
+ }
801
+
802
+ return implode('*', $numerators) . (\count($denominators) ? '/' . implode('*', $denominators) : '');
803
+ }
804
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/OutputStyle.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ScssPhp\ScssPhp;
4
+
5
+ final class OutputStyle
6
+ {
7
+ const EXPANDED = 'expanded';
8
+ const COMPRESSED = 'compressed';
9
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Parser.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -11,17 +12,29 @@
11
 
12
  namespace ScssPhp\ScssPhp;
13
 
14
- use ScssPhp\ScssPhp\Block;
15
- use ScssPhp\ScssPhp\Cache;
16
- use ScssPhp\ScssPhp\Compiler;
 
 
 
 
 
 
 
 
 
17
  use ScssPhp\ScssPhp\Exception\ParserException;
18
- use ScssPhp\ScssPhp\Node;
19
- use ScssPhp\ScssPhp\Type;
 
20
 
21
  /**
22
  * Parser
23
  *
24
  * @author Leaf Corcoran <leafot@gmail.com>
 
 
25
  */
26
  class Parser
27
  {
@@ -30,7 +43,7 @@ class Parser
30
  const SOURCE_COLUMN = -3;
31
 
32
  /**
33
- * @var array
34
  */
35
  protected static $precedence = [
36
  '=' => 0,
@@ -38,7 +51,6 @@ class Parser
38
  'and' => 2,
39
  '==' => 3,
40
  '!=' => 3,
41
- '<=>' => 3,
42
  '<=' => 4,
43
  '>=' => 4,
44
  '<' => 4,
@@ -50,54 +62,97 @@ class Parser
50
  '%' => 6,
51
  ];
52
 
 
 
 
53
  protected static $commentPattern;
 
 
 
54
  protected static $operatorPattern;
 
 
 
55
  protected static $whitePattern;
56
 
 
 
 
57
  protected $cache;
58
 
59
  private $sourceName;
60
  private $sourceIndex;
 
 
 
61
  private $sourcePositions;
62
- private $charset;
 
 
 
 
63
  private $count;
 
 
 
64
  private $env;
 
 
 
65
  private $inParens;
 
 
 
66
  private $eatWhiteDefault;
 
 
 
67
  private $discardComments;
68
  private $allowVars;
 
 
 
69
  private $buffer;
70
  private $utf8;
 
 
 
71
  private $encoding;
72
  private $patternModifiers;
73
  private $commentsSeen;
74
 
75
  private $cssOnly;
76
 
 
 
 
 
 
77
  /**
78
  * Constructor
79
  *
80
  * @api
81
  *
82
- * @param string $sourceName
83
- * @param integer $sourceIndex
84
- * @param string $encoding
85
- * @param \ScssPhp\ScssPhp\Cache $cache
 
 
86
  */
87
- public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8', $cache = null, $cssOnly = false)
88
  {
89
  $this->sourceName = $sourceName ?: '(stdin)';
90
  $this->sourceIndex = $sourceIndex;
91
- $this->charset = null;
92
  $this->utf8 = ! $encoding || strtolower($encoding) === 'utf-8';
93
  $this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais';
94
  $this->commentsSeen = [];
95
- $this->commentsSeen = [];
96
  $this->allowVars = true;
97
  $this->cssOnly = $cssOnly;
 
98
 
99
  if (empty(static::$operatorPattern)) {
100
- static::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=\>|\<\=?|and|or)';
101
 
102
  $commentSingle = '\/\/';
103
  $commentMultiLeft = '\/\*';
@@ -109,9 +164,7 @@ class Parser
109
  : '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisS';
110
  }
111
 
112
- if ($cache) {
113
- $this->cache = $cache;
114
- }
115
  }
116
 
117
  /**
@@ -133,9 +186,32 @@ class Parser
133
  *
134
  * @param string $msg
135
  *
136
- * @throws \ScssPhp\ScssPhp\Exception\ParserException
 
 
 
 
137
  */
138
  public function throwParseError($msg = 'parse error')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  {
140
  list($line, $column) = $this->getSourcePosition($this->count);
141
 
@@ -143,15 +219,21 @@ class Parser
143
  ? "line: $line, column: $column"
144
  : "$this->sourceName on line $line, at column $column";
145
 
146
- if ($this->peek("(.*?)(\n|$)", $m, $this->count)) {
147
  $this->restoreEncoding();
148
 
149
- throw new ParserException("$msg: failed at `$m[1]` $loc");
 
 
 
150
  }
151
 
152
  $this->restoreEncoding();
153
 
154
- throw new ParserException("$msg: $loc");
 
 
 
155
  }
156
 
157
  /**
@@ -161,17 +243,16 @@ class Parser
161
  *
162
  * @param string $buffer
163
  *
164
- * @return \ScssPhp\ScssPhp\Block
165
  */
166
  public function parse($buffer)
167
  {
168
  if ($this->cache) {
169
- $cacheKey = $this->sourceName . ":" . md5($buffer);
170
  $parseOptions = [
171
- 'charset' => $this->charset,
172
  'utf8' => $this->utf8,
173
  ];
174
- $v = $this->cache->getCache("parse", $cacheKey, $parseOptions);
175
 
176
  if (! \is_null($v)) {
177
  return $v;
@@ -202,21 +283,18 @@ class Parser
202
  }
203
 
204
  if ($this->count !== \strlen($this->buffer)) {
205
- $this->throwParseError();
206
  }
207
 
208
  if (! empty($this->env->parent)) {
209
- $this->throwParseError('unclosed block');
210
- }
211
-
212
- if ($this->charset) {
213
- array_unshift($this->env->children, $this->charset);
214
  }
215
 
216
  $this->restoreEncoding();
 
217
 
218
  if ($this->cache) {
219
- $this->cache->setCache("parse", $cacheKey, $this->env, $parseOptions);
220
  }
221
 
222
  return $this->env;
@@ -230,7 +308,7 @@ class Parser
230
  * @param string $buffer
231
  * @param string|array $out
232
  *
233
- * @return boolean
234
  */
235
  public function parseValue($buffer, &$out)
236
  {
@@ -241,6 +319,7 @@ class Parser
241
  $this->buffer = (string) $buffer;
242
 
243
  $this->saveEncoding();
 
244
 
245
  $list = $this->valueList($out);
246
 
@@ -256,10 +335,11 @@ class Parser
256
  *
257
  * @param string $buffer
258
  * @param string|array $out
 
259
  *
260
- * @return boolean
261
  */
262
- public function parseSelector($buffer, &$out)
263
  {
264
  $this->count = 0;
265
  $this->env = null;
@@ -268,11 +348,21 @@ class Parser
268
  $this->buffer = (string) $buffer;
269
 
270
  $this->saveEncoding();
 
 
 
 
 
 
271
 
272
  $selector = $this->selectors($out);
273
 
274
  $this->restoreEncoding();
275
 
 
 
 
 
276
  return $selector;
277
  }
278
 
@@ -281,10 +371,10 @@ class Parser
281
  *
282
  * @api
283
  *
284
- * @param string $buffer
285
- * @param string|array $out
286
  *
287
- * @return boolean
288
  */
289
  public function parseMediaQueryList($buffer, &$out)
290
  {
@@ -295,6 +385,7 @@ class Parser
295
  $this->buffer = (string) $buffer;
296
 
297
  $this->saveEncoding();
 
298
 
299
  $isMediaQuery = $this->mediaQueryList($out);
300
 
@@ -340,7 +431,7 @@ class Parser
340
  * position into $s. Then if a chain fails, use $this->seek($s) to
341
  * go back where we started.
342
  *
343
- * @return boolean
344
  */
345
  protected function parseChunk()
346
  {
@@ -348,19 +439,19 @@ class Parser
348
 
349
  // the directives
350
  if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') {
351
- if ($this->literal('@at-root', 8) &&
 
352
  ($this->selectors($selector) || true) &&
353
  ($this->map($with) || true) &&
354
- (($this->matchChar('(')
355
- && $this->interpolation($with)
356
- && $this->matchChar(')')) || true) &&
357
  $this->matchChar('{', false)
358
  ) {
359
- if ($this->cssOnly) {
360
- $this->throwParseError("SCSS syntax not allowed in CSS file");
361
- }
362
 
363
- $atRoot = $this->pushSpecialBlock(Type::T_AT_ROOT, $s);
 
364
  $atRoot->selector = $selector;
365
  $atRoot->with = $with;
366
 
@@ -369,8 +460,13 @@ class Parser
369
 
370
  $this->seek($s);
371
 
372
- if ($this->literal('@media', 6) && $this->mediaQueryList($mediaQueryList) && $this->matchChar('{', false)) {
373
- $media = $this->pushSpecialBlock(Type::T_MEDIA, $s);
 
 
 
 
 
374
  $media->queryList = $mediaQueryList[2];
375
 
376
  return true;
@@ -378,16 +474,16 @@ class Parser
378
 
379
  $this->seek($s);
380
 
381
- if ($this->literal('@mixin', 6) &&
 
382
  $this->keyword($mixinName) &&
383
  ($this->argumentDef($args) || true) &&
384
  $this->matchChar('{', false)
385
  ) {
386
- if ($this->cssOnly) {
387
- $this->throwParseError("SCSS syntax not allowed in CSS file");
388
- }
389
 
390
- $mixin = $this->pushSpecialBlock(Type::T_MIXIN, $s);
 
391
  $mixin->name = $mixinName;
392
  $mixin->args = $args;
393
 
@@ -396,20 +492,19 @@ class Parser
396
 
397
  $this->seek($s);
398
 
399
- if ($this->literal('@include', 8) &&
400
- $this->keyword($mixinName) &&
401
- ($this->matchChar('(') &&
 
402
  ($this->argValues($argValues) || true) &&
403
  $this->matchChar(')') || true) &&
404
- ($this->end() ||
405
- ($this->literal('using', 5) &&
406
- $this->argumentDef($argUsing) &&
407
- ($this->end() || $this->matchChar('{') && $hasBlock = true)) ||
408
- $this->matchChar('{') && $hasBlock = true)
409
  ) {
410
- if ($this->cssOnly) {
411
- $this->throwParseError("SCSS syntax not allowed in CSS file");
412
- }
413
 
414
  $child = [
415
  Type::T_INCLUDE,
@@ -420,7 +515,8 @@ class Parser
420
  ];
421
 
422
  if (! empty($hasBlock)) {
423
- $include = $this->pushSpecialBlock(Type::T_INCLUDE, $s);
 
424
  $include->child = $child;
425
  } else {
426
  $this->append($child, $s);
@@ -431,13 +527,16 @@ class Parser
431
 
432
  $this->seek($s);
433
 
434
- if ($this->literal('@scssphp-import-once', 20) &&
 
435
  $this->valueList($importPath) &&
436
  $this->end()
437
  ) {
438
- if ($this->cssOnly) {
439
- $this->throwParseError("SCSS syntax not allowed in CSS file");
440
- }
 
 
441
 
442
  $this->append([Type::T_SCSSPHP_IMPORT_ONCE, $importPath], $s);
443
 
@@ -446,10 +545,18 @@ class Parser
446
 
447
  $this->seek($s);
448
 
449
- if ($this->literal('@import', 7) &&
 
450
  $this->valueList($importPath) &&
 
451
  $this->end()
452
  ) {
 
 
 
 
 
 
453
  $this->append([Type::T_IMPORT, $importPath], $s);
454
 
455
  return true;
@@ -457,12 +564,15 @@ class Parser
457
 
458
  $this->seek($s);
459
 
460
- if ($this->literal('@import', 7) &&
 
461
  $this->url($importPath) &&
462
  $this->end()
463
  ) {
464
  if ($this->cssOnly) {
465
- $this->throwParseError("SCSS syntax not allowed in CSS file");
 
 
466
  }
467
 
468
  $this->append([Type::T_IMPORT, $importPath], $s);
@@ -472,13 +582,12 @@ class Parser
472
 
473
  $this->seek($s);
474
 
475
- if ($this->literal('@extend', 7) &&
 
476
  $this->selectors($selectors) &&
477
  $this->end()
478
  ) {
479
- if ($this->cssOnly) {
480
- $this->throwParseError("SCSS syntax not allowed in CSS file");
481
- }
482
 
483
  // check for '!flag'
484
  $optional = $this->stripOptionalFlag($selectors);
@@ -489,16 +598,16 @@ class Parser
489
 
490
  $this->seek($s);
491
 
492
- if ($this->literal('@function', 9) &&
 
493
  $this->keyword($fnName) &&
494
  $this->argumentDef($args) &&
495
  $this->matchChar('{', false)
496
  ) {
497
- if ($this->cssOnly) {
498
- $this->throwParseError("SCSS syntax not allowed in CSS file");
499
- }
500
 
501
- $func = $this->pushSpecialBlock(Type::T_FUNCTION, $s);
 
502
  $func->name = $fnName;
503
  $func->args = $args;
504
 
@@ -507,34 +616,12 @@ class Parser
507
 
508
  $this->seek($s);
509
 
510
- if ($this->literal('@break', 6) && $this->end()) {
511
- if ($this->cssOnly) {
512
- $this->throwParseError("SCSS syntax not allowed in CSS file");
513
- }
514
-
515
- $this->append([Type::T_BREAK], $s);
516
-
517
- return true;
518
- }
519
-
520
- $this->seek($s);
521
-
522
- if ($this->literal('@continue', 9) && $this->end()) {
523
- if ($this->cssOnly) {
524
- $this->throwParseError("SCSS syntax not allowed in CSS file");
525
- }
526
-
527
- $this->append([Type::T_CONTINUE], $s);
528
-
529
- return true;
530
- }
531
-
532
- $this->seek($s);
533
-
534
- if ($this->literal('@return', 7) && ($this->valueList($retVal) || true) && $this->end()) {
535
- if ($this->cssOnly) {
536
- $this->throwParseError("SCSS syntax not allowed in CSS file");
537
- }
538
 
539
  $this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s);
540
 
@@ -543,17 +630,17 @@ class Parser
543
 
544
  $this->seek($s);
545
 
546
- if ($this->literal('@each', 5) &&
 
547
  $this->genericList($varNames, 'variable', ',', false) &&
548
  $this->literal('in', 2) &&
549
  $this->valueList($list) &&
550
  $this->matchChar('{', false)
551
  ) {
552
- if ($this->cssOnly) {
553
- $this->throwParseError("SCSS syntax not allowed in CSS file");
554
- }
555
 
556
- $each = $this->pushSpecialBlock(Type::T_EACH, $s);
 
557
 
558
  foreach ($varNames[2] as $varName) {
559
  $each->vars[] = $varName[1];
@@ -566,15 +653,24 @@ class Parser
566
 
567
  $this->seek($s);
568
 
569
- if ($this->literal('@while', 6) &&
 
570
  $this->expression($cond) &&
571
  $this->matchChar('{', false)
572
  ) {
573
- if ($this->cssOnly) {
574
- $this->throwParseError("SCSS syntax not allowed in CSS file");
 
 
 
 
 
 
 
575
  }
576
 
577
- $while = $this->pushSpecialBlock(Type::T_WHILE, $s);
 
578
  $while->cond = $cond;
579
 
580
  return true;
@@ -582,7 +678,8 @@ class Parser
582
 
583
  $this->seek($s);
584
 
585
- if ($this->literal('@for', 4) &&
 
586
  $this->variable($varName) &&
587
  $this->literal('from', 4) &&
588
  $this->expression($start) &&
@@ -591,11 +688,10 @@ class Parser
591
  $this->expression($end) &&
592
  $this->matchChar('{', false)
593
  ) {
594
- if ($this->cssOnly) {
595
- $this->throwParseError("SCSS syntax not allowed in CSS file");
596
- }
597
 
598
- $for = $this->pushSpecialBlock(Type::T_FOR, $s);
 
599
  $for->var = $varName[1];
600
  $for->start = $start;
601
  $for->end = $end;
@@ -606,17 +702,21 @@ class Parser
606
 
607
  $this->seek($s);
608
 
609
- if ($this->literal('@if', 3) && $this->valueList($cond) && $this->matchChar('{', false)) {
610
- if ($this->cssOnly) {
611
- $this->throwParseError("SCSS syntax not allowed in CSS file");
612
- }
 
613
 
614
- $if = $this->pushSpecialBlock(Type::T_IF, $s);
 
615
 
616
- while ($cond[0] === Type::T_LIST
617
- && ! empty($cond['enclosing'])
618
- && $cond['enclosing'] === 'parent'
619
- && \count($cond[2]) == 1) {
 
 
620
  $cond = reset($cond[2]);
621
  }
622
 
@@ -628,13 +728,11 @@ class Parser
628
 
629
  $this->seek($s);
630
 
631
- if ($this->literal('@debug', 6) &&
632
- $this->valueList($value) &&
633
- $this->end()
634
  ) {
635
- if ($this->cssOnly) {
636
- $this->throwParseError("SCSS syntax not allowed in CSS file");
637
- }
638
 
639
  $this->append([Type::T_DEBUG, $value], $s);
640
 
@@ -643,13 +741,11 @@ class Parser
643
 
644
  $this->seek($s);
645
 
646
- if ($this->literal('@warn', 5) &&
647
- $this->valueList($value) &&
648
- $this->end()
649
  ) {
650
- if ($this->cssOnly) {
651
- $this->throwParseError("SCSS syntax not allowed in CSS file");
652
- }
653
 
654
  $this->append([Type::T_WARN, $value], $s);
655
 
@@ -658,13 +754,11 @@ class Parser
658
 
659
  $this->seek($s);
660
 
661
- if ($this->literal('@error', 6) &&
662
- $this->valueList($value) &&
663
- $this->end()
664
  ) {
665
- if ($this->cssOnly) {
666
- $this->throwParseError("SCSS syntax not allowed in CSS file");
667
- }
668
 
669
  $this->append([Type::T_ERROR, $value], $s);
670
 
@@ -673,16 +767,15 @@ class Parser
673
 
674
  $this->seek($s);
675
 
676
- if ($this->literal('@content', 8) &&
 
677
  ($this->end() ||
678
  $this->matchChar('(') &&
679
  $this->argValues($argContent) &&
680
  $this->matchChar(')') &&
681
  $this->end())
682
  ) {
683
- if ($this->cssOnly) {
684
- $this->throwParseError("SCSS syntax not allowed in CSS file");
685
- }
686
 
687
  $this->append([Type::T_MIXIN_CONTENT, isset($argContent) ? $argContent : null], $s);
688
 
@@ -695,17 +788,21 @@ class Parser
695
 
696
  if (isset($last) && $last[0] === Type::T_IF) {
697
  list(, $if) = $last;
 
698
 
699
  if ($this->literal('@else', 5)) {
700
  if ($this->matchChar('{', false)) {
701
- $else = $this->pushSpecialBlock(Type::T_ELSE, $s);
702
- } elseif ($this->literal('if', 2) && $this->valueList($cond) && $this->matchChar('{', false)) {
703
- $else = $this->pushSpecialBlock(Type::T_ELSEIF, $s);
 
 
 
704
  $else->cond = $cond;
705
  }
706
 
707
  if (isset($else)) {
708
- $else->dontAppend = true;
709
  $if->cases[] = $else;
710
 
711
  return true;
@@ -716,32 +813,23 @@ class Parser
716
  }
717
 
718
  // only retain the first @charset directive encountered
719
- if ($this->literal('@charset', 8) &&
 
720
  $this->valueList($charset) &&
721
  $this->end()
722
  ) {
723
- if (! isset($this->charset)) {
724
- $statement = [Type::T_CHARSET, $charset];
725
-
726
- list($line, $column) = $this->getSourcePosition($s);
727
-
728
- $statement[static::SOURCE_LINE] = $line;
729
- $statement[static::SOURCE_COLUMN] = $column;
730
- $statement[static::SOURCE_INDEX] = $this->sourceIndex;
731
-
732
- $this->charset = $statement;
733
- }
734
-
735
  return true;
736
  }
737
 
738
  $this->seek($s);
739
 
740
- if ($this->literal('@supports', 9) &&
741
- ($t1=$this->supportsQuery($supportQuery)) &&
742
- ($t2=$this->matchChar('{', false))
 
743
  ) {
744
- $directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s);
 
745
  $directive->name = 'supports';
746
  $directive->value = $supportQuery;
747
 
@@ -751,18 +839,26 @@ class Parser
751
  $this->seek($s);
752
 
753
  // doesn't match built in directive, do generic one
754
- if ($this->matchChar('@', false) &&
755
- $this->keyword($dirName) &&
 
756
  $this->directiveValue($dirValue, '{')
757
  ) {
 
 
 
 
 
758
  if ($dirName === 'media') {
759
- $directive = $this->pushSpecialBlock(Type::T_MEDIA, $s);
760
  } else {
761
- $directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s);
762
  $directive->name = $dirName;
763
  }
 
764
 
765
  if (isset($dirValue)) {
 
766
  $directive->value = $dirValue;
767
  }
768
 
@@ -772,12 +868,38 @@ class Parser
772
  $this->seek($s);
773
 
774
  // maybe it's a generic blockless directive
775
- if ($this->matchChar('@', false) &&
776
- $this->keyword($dirName) &&
777
- $this->directiveValue($dirValue) &&
778
- $this->end()
 
779
  ) {
780
- $this->append([Type::T_DIRECTIVE, [$dirName, $dirValue]], $s);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
781
 
782
  return true;
783
  }
@@ -787,15 +909,19 @@ class Parser
787
  return false;
788
  }
789
 
 
 
 
 
 
790
  // custom properties : right part is static
791
- if (($this->customProperty($name) || ($this->cssOnly && $this->propertyName($name))) &&
792
- $this->matchChar(':', false)
793
- ) {
794
  $start = $this->count;
795
 
796
  // but can be complex and finish with ; or }
797
  foreach ([';','}'] as $ending) {
798
- if ($this->openString($ending, $stringValue, '(', ')', false) &&
 
799
  $this->end()
800
  ) {
801
  $end = $this->count;
@@ -810,7 +936,8 @@ class Parser
810
  if ($p && $p < $end) {
811
  $this->seek($start);
812
 
813
- if ($this->openString($ending, $stringValue, $nestingPair[0], $nestingPair[1], false) &&
 
814
  $this->end() &&
815
  $this->count > $end
816
  ) {
@@ -834,7 +961,8 @@ class Parser
834
 
835
  // property shortcut
836
  // captures most properties before having to parse a selector
837
- if ($this->keyword($name, false) &&
 
838
  $this->literal(': ', 2) &&
839
  $this->valueList($value) &&
840
  $this->end()
@@ -848,14 +976,13 @@ class Parser
848
  $this->seek($s);
849
 
850
  // variable assigns
851
- if ($this->variable($name) &&
 
852
  $this->matchChar(':') &&
853
  $this->valueList($value) &&
854
  $this->end()
855
  ) {
856
- if ($this->cssOnly) {
857
- $this->throwParseError("SCSS syntax not allowed in CSS file");
858
- }
859
 
860
  // check for '!flag'
861
  $assignmentFlags = $this->stripAssignmentFlags($value);
@@ -866,18 +993,12 @@ class Parser
866
 
867
  $this->seek($s);
868
 
869
- // misc
870
- if ($this->literal('-->', 3)) {
871
- return true;
872
- }
873
-
874
  // opening css block
875
- if ($this->selectors($selectors) && $this->matchChar('{', false)) {
876
- if ($this->cssOnly) {
877
- if (! empty($this->env->parent)) {
878
- $this->throwParseError("SCSS syntax not allowed in CSS file");
879
- }
880
- }
881
 
882
  $this->pushBlock($selectors, $s);
883
 
@@ -892,12 +1013,15 @@ class Parser
892
  $this->seek($s);
893
 
894
  // property assign, or nested assign
895
- if ($this->propertyName($name) && $this->matchChar(':')) {
 
 
 
896
  $foundSomething = false;
897
 
898
  if ($this->valueList($value)) {
899
  if (empty($this->env->parent)) {
900
- $this->throwParseError('expected "{"');
901
  }
902
 
903
  $this->append([Type::T_ASSIGN, $name, $value], $s);
@@ -905,11 +1029,10 @@ class Parser
905
  }
906
 
907
  if ($this->matchChar('{', false)) {
908
- if ($this->cssOnly) {
909
- $this->throwParseError("SCSS syntax not allowed in CSS file");
910
- }
911
 
912
- $propBlock = $this->pushSpecialBlock(Type::T_NESTED_PROPERTY, $s);
 
913
  $propBlock->prefix = $name;
914
  $propBlock->hasValue = $foundSomething;
915
 
@@ -930,17 +1053,20 @@ class Parser
930
  $block = $this->popBlock();
931
 
932
  if (! isset($block->type) || $block->type !== Type::T_IF) {
 
 
933
  if ($this->env->parent) {
934
  $this->append(null); // collect comments before next statement if needed
935
  }
936
  }
937
 
938
- if (isset($block->type) && $block->type === Type::T_INCLUDE) {
939
  $include = $block->child;
 
940
  unset($block->child);
941
  $include[3] = $block;
942
  $this->append($include, $s);
943
- } elseif (empty($block->dontAppend)) {
944
  $type = isset($block->type) ? $block->type : Type::T_BLOCK;
945
  $this->append([$type, $block], $s);
946
  }
@@ -948,6 +1074,7 @@ class Parser
948
  // collect comments just after the block closing if needed
949
  if ($this->eatWhiteDefault) {
950
  $this->whitespace();
 
951
 
952
  if ($this->env->comments) {
953
  $this->append(null);
@@ -958,9 +1085,7 @@ class Parser
958
  }
959
 
960
  // extra stuff
961
- if ($this->matchChar(';') ||
962
- $this->literal('<!--', 4)
963
- ) {
964
  return true;
965
  }
966
 
@@ -970,21 +1095,35 @@ class Parser
970
  /**
971
  * Push block onto parse tree
972
  *
973
- * @param array $selectors
974
- * @param integer $pos
975
  *
976
- * @return \ScssPhp\ScssPhp\Block
977
  */
978
  protected function pushBlock($selectors, $pos = 0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
979
  {
980
  list($line, $column) = $this->getSourcePosition($pos);
981
 
982
- $b = new Block;
983
  $b->sourceName = $this->sourceName;
984
  $b->sourceLine = $line;
985
  $b->sourceColumn = $column;
986
  $b->sourceIndex = $this->sourceIndex;
987
- $b->selectors = $selectors;
988
  $b->comments = [];
989
  $b->parent = $this->env;
990
 
@@ -1004,22 +1143,23 @@ class Parser
1004
  // collect comments at the beginning of a block if needed
1005
  if ($this->eatWhiteDefault) {
1006
  $this->whitespace();
 
1007
 
1008
  if ($this->env->comments) {
1009
  $this->append(null);
1010
  }
1011
  }
1012
-
1013
- return $b;
1014
  }
1015
 
1016
  /**
1017
  * Push special (named) block onto parse tree
1018
  *
 
 
1019
  * @param string $type
1020
- * @param integer $pos
1021
  *
1022
- * @return \ScssPhp\ScssPhp\Block
1023
  */
1024
  protected function pushSpecialBlock($type, $pos)
1025
  {
@@ -1032,12 +1172,13 @@ class Parser
1032
  /**
1033
  * Pop scope and return last block
1034
  *
1035
- * @return \ScssPhp\ScssPhp\Block
1036
  *
1037
  * @throws \Exception
1038
  */
1039
  protected function popBlock()
1040
  {
 
1041
 
1042
  // collect comments ending just before of a block closing
1043
  if ($this->env->comments) {
@@ -1048,7 +1189,7 @@ class Parser
1048
  $block = $this->env;
1049
 
1050
  if (empty($block->parent)) {
1051
- $this->throwParseError('unexpected }');
1052
  }
1053
 
1054
  if ($block->type == Type::T_AT_ROOT) {
@@ -1066,11 +1207,11 @@ class Parser
1066
  /**
1067
  * Peek input stream
1068
  *
1069
- * @param string $regex
1070
- * @param array $out
1071
- * @param integer $from
1072
  *
1073
- * @return integer
1074
  */
1075
  protected function peek($regex, &$out, $from = null)
1076
  {
@@ -1079,7 +1220,7 @@ class Parser
1079
  }
1080
 
1081
  $r = '/' . $regex . '/' . $this->patternModifiers;
1082
- $result = preg_match($r, $this->buffer, $out, null, $from);
1083
 
1084
  return $result;
1085
  }
@@ -1087,22 +1228,236 @@ class Parser
1087
  /**
1088
  * Seek to position in input stream (or return current position in input stream)
1089
  *
1090
- * @param integer $where
 
 
1091
  */
1092
  protected function seek($where)
1093
  {
1094
  $this->count = $where;
1095
  }
1096
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1097
  /**
1098
  * Match string looking for either ending delim, escape, or string interpolation
1099
  *
1100
  * {@internal This is a workaround for preg_match's 250K string match limit. }}
1101
  *
1102
  * @param array $m Matches (passed by reference)
1103
- * @param string $delim Delimeter
 
 
1104
  *
1105
- * @return boolean True if match; false otherwise
1106
  */
1107
  protected function matchString(&$m, $delim)
1108
  {
@@ -1111,7 +1466,7 @@ class Parser
1111
  $end = \strlen($this->buffer);
1112
 
1113
  // look for either ending delim, escape, or string interpolation
1114
- foreach (['#{', '\\', $delim] as $lookahead) {
1115
  $pos = strpos($this->buffer, $lookahead, $this->count);
1116
 
1117
  if ($pos !== false && $pos < $end) {
@@ -1138,17 +1493,19 @@ class Parser
1138
  /**
1139
  * Try to match something on head of buffer
1140
  *
1141
- * @param string $regex
1142
- * @param array $out
1143
- * @param boolean $eatWhitespace
1144
  *
1145
- * @return boolean
 
 
1146
  */
1147
  protected function match($regex, &$out, $eatWhitespace = null)
1148
  {
1149
  $r = '/' . $regex . '/' . $this->patternModifiers;
1150
 
1151
- if (! preg_match($r, $this->buffer, $out, null, $this->count)) {
1152
  return false;
1153
  }
1154
 
@@ -1168,10 +1525,12 @@ class Parser
1168
  /**
1169
  * Match a single string
1170
  *
1171
- * @param string $char
1172
- * @param boolean $eatWhitespace
 
 
1173
  *
1174
- * @return boolean
1175
  */
1176
  protected function matchChar($char, $eatWhitespace = null)
1177
  {
@@ -1195,11 +1554,13 @@ class Parser
1195
  /**
1196
  * Match literal string
1197
  *
1198
- * @param string $what
1199
- * @param integer $len
1200
- * @param boolean $eatWhitespace
 
 
1201
  *
1202
- * @return boolean
1203
  */
1204
  protected function literal($what, $len, $eatWhitespace = null)
1205
  {
@@ -1223,13 +1584,15 @@ class Parser
1223
  /**
1224
  * Match some whitespace
1225
  *
1226
- * @return boolean
 
 
1227
  */
1228
  protected function whitespace()
1229
  {
1230
  $gotWhite = false;
1231
 
1232
- while (preg_match(static::$whitePattern, $this->buffer, $m, null, $this->count)) {
1233
  if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
1234
  // comment that are kept in the output CSS
1235
  $comment = [];
@@ -1248,7 +1611,7 @@ class Parser
1248
  if ($this->interpolation($out)) {
1249
  // keep right spaces in the following string part
1250
  if ($out[3]) {
1251
- while ($this->buffer[$this->count-1] !== '}') {
1252
  $this->count--;
1253
  }
1254
 
@@ -1257,6 +1620,11 @@ class Parser
1257
 
1258
  $comment[] = [Type::T_COMMENT, substr($this->buffer, $p, $this->count - $p), $out];
1259
  } else {
 
 
 
 
 
1260
  $comment[] = substr($this->buffer, $this->count, 2);
1261
 
1262
  $this->count += 2;
@@ -1270,18 +1638,29 @@ class Parser
1270
 
1271
  if (! $comment) {
1272
  // single part static comment
1273
- $this->appendComment([Type::T_COMMENT, $c]);
1274
  } else {
1275
  $comment[] = $c;
1276
  $staticComment = substr($this->buffer, $startCommentCount, $endCommentCount - $startCommentCount);
1277
- $this->appendComment([Type::T_COMMENT, $staticComment, [Type::T_STRING, '', $comment]]);
1278
  }
1279
 
 
 
 
 
 
 
 
1280
  $this->commentsSeen[$startCommentCount] = true;
1281
  $this->count = $endCommentCount;
1282
  } else {
1283
  // comment that are ignored and not kept in the output css
1284
  $this->count += \strlen($m[0]);
 
 
 
 
1285
  }
1286
 
1287
  $gotWhite = true;
@@ -1294,29 +1673,14 @@ class Parser
1294
  * Append comment to current block
1295
  *
1296
  * @param array $comment
 
 
1297
  */
1298
  protected function appendComment($comment)
1299
  {
1300
- if (! $this->discardComments) {
1301
- if ($comment[0] === Type::T_COMMENT) {
1302
- if (\is_string($comment[1])) {
1303
- $comment[1] = substr(preg_replace(['/^\s+/m', '/^(.)/m'], ['', ' \1'], $comment[1]), 1);
1304
- }
1305
-
1306
- if (isset($comment[2]) and \is_array($comment[2]) and $comment[2][0] === Type::T_STRING) {
1307
- foreach ($comment[2][2] as $k => $v) {
1308
- if (\is_string($v)) {
1309
- $p = strpos($v, "\n");
1310
-
1311
- if ($p !== false) {
1312
- $comment[2][2][$k] = substr($v, 0, $p + 1)
1313
- . preg_replace(['/^\s+/m', '/^(.)/m'], ['', ' \1'], substr($v, $p+1));
1314
- }
1315
- }
1316
- }
1317
- }
1318
- }
1319
 
 
1320
  $this->env->comments[] = $comment;
1321
  }
1322
  }
@@ -1324,12 +1688,18 @@ class Parser
1324
  /**
1325
  * Append statement to current block
1326
  *
1327
- * @param array $statement
1328
- * @param integer $pos
 
 
1329
  */
1330
  protected function append($statement, $pos = null)
1331
  {
 
 
1332
  if (! \is_null($statement)) {
 
 
1333
  if (! \is_null($pos)) {
1334
  list($line, $column) = $this->getSourcePosition($pos);
1335
 
@@ -1356,11 +1726,15 @@ class Parser
1356
  */
1357
  protected function last()
1358
  {
 
 
1359
  $i = \count($this->env->children) - 1;
1360
 
1361
  if (isset($this->env->children[$i])) {
1362
  return $this->env->children[$i];
1363
  }
 
 
1364
  }
1365
 
1366
  /**
@@ -1368,7 +1742,7 @@ class Parser
1368
  *
1369
  * @param array $out
1370
  *
1371
- * @return boolean
1372
  */
1373
  protected function mediaQueryList(&$out)
1374
  {
@@ -1380,14 +1754,16 @@ class Parser
1380
  *
1381
  * @param array $out
1382
  *
1383
- * @return boolean
1384
  */
1385
  protected function mediaQuery(&$out)
1386
  {
1387
  $expressions = null;
1388
  $parts = [];
1389
 
1390
- if (($this->literal('only', 4) && ($only = true) || $this->literal('not', 3) && ($not = true) || true) &&
 
 
1391
  $this->mixedKeyword($mediaType)
1392
  ) {
1393
  $prop = [Type::T_MEDIA_TYPE];
@@ -1432,7 +1808,7 @@ class Parser
1432
  *
1433
  * @param array $out
1434
  *
1435
- * @return boolean
1436
  */
1437
  protected function supportsQuery(&$out)
1438
  {
@@ -1443,7 +1819,8 @@ class Parser
1443
 
1444
  $not = false;
1445
 
1446
- if (($this->literal('not', 3) && ($not = true) || true) &&
 
1447
  $this->matchChar('(') &&
1448
  ($this->expression($property)) &&
1449
  $this->literal(': ', 2) &&
@@ -1462,7 +1839,8 @@ class Parser
1462
  $this->seek($s);
1463
  }
1464
 
1465
- if ($this->matchChar('(') &&
 
1466
  $this->supportsQuery($subQuery) &&
1467
  $this->matchChar(')')
1468
  ) {
@@ -1472,7 +1850,8 @@ class Parser
1472
  $this->seek($s);
1473
  }
1474
 
1475
- if ($this->literal('not', 3) &&
 
1476
  $this->supportsQuery($subQuery)
1477
  ) {
1478
  $parts[] = [Type::T_STRING, '', [[Type::T_KEYWORD, 'not '], $subQuery]];
@@ -1481,7 +1860,8 @@ class Parser
1481
  $this->seek($s);
1482
  }
1483
 
1484
- if ($this->literal('selector(', 9) &&
 
1485
  $this->selector($selector) &&
1486
  $this->matchChar(')')
1487
  ) {
@@ -1518,8 +1898,10 @@ class Parser
1518
  $this->seek($s);
1519
  }
1520
 
1521
- if ($this->literal('and', 3) &&
1522
- $this->genericList($expressions, 'supportsQuery', ' and', false)) {
 
 
1523
  array_unshift($expressions[2], [Type::T_STRING, '', $parts]);
1524
 
1525
  $parts = [$expressions];
@@ -1528,8 +1910,10 @@ class Parser
1528
  $this->seek($s);
1529
  }
1530
 
1531
- if ($this->literal('or', 2) &&
1532
- $this->genericList($expressions, 'supportsQuery', ' or', false)) {
 
 
1533
  array_unshift($expressions[2], [Type::T_STRING, '', $parts]);
1534
 
1535
  $parts = [$expressions];
@@ -1557,16 +1941,18 @@ class Parser
1557
  *
1558
  * @param array $out
1559
  *
1560
- * @return boolean
1561
  */
1562
  protected function mediaExpression(&$out)
1563
  {
1564
  $s = $this->count;
1565
  $value = null;
1566
 
1567
- if ($this->matchChar('(') &&
 
1568
  $this->expression($feature) &&
1569
- ($this->matchChar(':') && $this->expression($value) || true) &&
 
1570
  $this->matchChar(')')
1571
  ) {
1572
  $out = [Type::T_MEDIA_EXPRESSION, $feature];
@@ -1588,16 +1974,23 @@ class Parser
1588
  *
1589
  * @param array $out
1590
  *
1591
- * @return boolean
1592
  */
1593
  protected function argValues(&$out)
1594
  {
 
 
 
1595
  if ($this->genericList($list, 'argValue', ',', false)) {
1596
  $out = $list[2];
1597
 
 
 
1598
  return true;
1599
  }
1600
 
 
 
1601
  return false;
1602
  }
1603
 
@@ -1606,7 +1999,7 @@ class Parser
1606
  *
1607
  * @param array $out
1608
  *
1609
- * @return boolean
1610
  */
1611
  protected function argValue(&$out)
1612
  {
@@ -1620,7 +2013,7 @@ class Parser
1620
  $keyword = null;
1621
  }
1622
 
1623
- if ($this->genericList($value, 'expression')) {
1624
  $out = [$keyword, $value, false];
1625
  $s = $this->count;
1626
 
@@ -1636,13 +2029,62 @@ class Parser
1636
  return false;
1637
  }
1638
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1639
  /**
1640
  * Parse directive value list that considers $vars as keyword
1641
  *
1642
- * @param array $out
1643
- * @param boolean|string $endChar
1644
  *
1645
- * @return boolean
 
 
1646
  */
1647
  protected function directiveValue(&$out, $endChar = false)
1648
  {
@@ -1660,8 +2102,13 @@ class Parser
1660
 
1661
  $this->seek($s);
1662
 
1663
- if ($endChar and $this->openString($endChar, $out)) {
1664
- if ($this->matchChar($endChar, false)) {
 
 
 
 
 
1665
  return true;
1666
  }
1667
  }
@@ -1698,7 +2145,7 @@ class Parser
1698
  *
1699
  * @param array $out
1700
  *
1701
- * @return boolean
1702
  */
1703
  protected function valueList(&$out)
1704
  {
@@ -1710,12 +2157,52 @@ class Parser
1710
  return $res;
1711
  }
1712
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1713
  /**
1714
  * Parse space separated value list
1715
  *
1716
  * @param array $out
1717
  *
1718
- * @return boolean
1719
  */
1720
  protected function spaceList(&$out)
1721
  {
@@ -1725,17 +2212,18 @@ class Parser
1725
  /**
1726
  * Parse generic list
1727
  *
1728
- * @param array $out
1729
- * @param callable $parseItem
1730
- * @param string $delim
1731
- * @param boolean $flatten
1732
  *
1733
- * @return boolean
1734
  */
1735
  protected function genericList(&$out, $parseItem, $delim = '', $flatten = true)
1736
  {
1737
  $s = $this->count;
1738
  $items = [];
 
1739
  $value = null;
1740
 
1741
  while ($this->$parseItem($value)) {
@@ -1748,6 +2236,64 @@ class Parser
1748
  }
1749
 
1750
  $trailing_delim = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1751
  }
1752
  }
1753
 
@@ -1773,11 +2319,13 @@ class Parser
1773
  /**
1774
  * Parse expression
1775
  *
1776
- * @param array $out
1777
- * @param boolean $listOnly
1778
- * @param boolean $lookForExp
1779
  *
1780
- * @return boolean
 
 
1781
  */
1782
  protected function expression(&$out, $listOnly = false, $lookForExp = true)
1783
  {
@@ -1787,7 +2335,7 @@ class Parser
1787
  $allowedTypes = ($listOnly ? [Type::T_LIST] : [Type::T_LIST, Type::T_MAP]);
1788
 
1789
  if ($this->matchChar('(')) {
1790
- if ($this->enclosedExpression($lhs, $s, ")", $allowedTypes)) {
1791
  if ($lookForExp) {
1792
  $out = $this->expHelper($lhs, 0);
1793
  } else {
@@ -1803,7 +2351,7 @@ class Parser
1803
  }
1804
 
1805
  if (\in_array(Type::T_LIST, $allowedTypes) && $this->matchChar('[')) {
1806
- if ($this->enclosedExpression($lhs, $s, "]", [Type::T_LIST])) {
1807
  if ($lookForExp) {
1808
  $out = $this->expHelper($lhs, 0);
1809
  } else {
@@ -1838,24 +2386,26 @@ class Parser
1838
  /**
1839
  * Parse expression specifically checking for lists in parenthesis or brackets
1840
  *
1841
- * @param array $out
1842
- * @param integer $s
1843
- * @param string $closingParen
1844
- * @param array $allowedTypes
 
 
1845
  *
1846
- * @return boolean
1847
  */
1848
- protected function enclosedExpression(&$out, $s, $closingParen = ")", $allowedTypes = [Type::T_LIST, Type::T_MAP])
1849
  {
1850
  if ($this->matchChar($closingParen) && \in_array(Type::T_LIST, $allowedTypes)) {
1851
  $out = [Type::T_LIST, '', []];
1852
 
1853
  switch ($closingParen) {
1854
- case ")":
1855
  $out['enclosing'] = 'parent'; // parenthesis list
1856
  break;
1857
 
1858
- case "]":
1859
  $out['enclosing'] = 'bracket'; // bracketed list
1860
  break;
1861
  }
@@ -1863,8 +2413,10 @@ class Parser
1863
  return true;
1864
  }
1865
 
1866
- if ($this->valueList($out) && $this->matchChar($closingParen) &&
1867
- \in_array($out[0], [Type::T_LIST, Type::T_KEYWORD]) &&
 
 
1868
  \in_array(Type::T_LIST, $allowedTypes)
1869
  ) {
1870
  if ($out[0] !== Type::T_LIST || ! empty($out['enclosing'])) {
@@ -1872,11 +2424,11 @@ class Parser
1872
  }
1873
 
1874
  switch ($closingParen) {
1875
- case ")":
1876
  $out['enclosing'] = 'parent'; // parenthesis list
1877
  break;
1878
 
1879
- case "]":
1880
  $out['enclosing'] = 'bracket'; // bracketed list
1881
  break;
1882
  }
@@ -1896,8 +2448,8 @@ class Parser
1896
  /**
1897
  * Parse left-hand side of subexpression
1898
  *
1899
- * @param array $lhs
1900
- * @param integer $minP
1901
  *
1902
  * @return array
1903
  */
@@ -1928,12 +2480,15 @@ class Parser
1928
  break;
1929
  }
1930
 
1931
- // peek and see if rhs belongs to next operator
1932
- if ($this->peek($operators, $next) && static::$precedence[$next[1]] > static::$precedence[$op]) {
1933
- $rhs = $this->expHelper($rhs, static::$precedence[$next[1]]);
1934
  }
1935
 
 
 
 
1936
  $lhs = [Type::T_EXPRESSION, $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter];
 
1937
  $ss = $this->count;
1938
  $whiteBefore = isset($this->buffer[$this->count - 1]) &&
1939
  ctype_space($this->buffer[$this->count - 1]);
@@ -1949,7 +2504,7 @@ class Parser
1949
  *
1950
  * @param array $out
1951
  *
1952
- * @return boolean
1953
  */
1954
  protected function value(&$out)
1955
  {
@@ -1960,7 +2515,10 @@ class Parser
1960
  $s = $this->count;
1961
  $char = $this->buffer[$this->count];
1962
 
1963
- if ($this->literal('url(', 4) && $this->match('data:([a-z]+)\/([a-z0-9.+-]+);base64,', $m, false)) {
 
 
 
1964
  $len = strspn(
1965
  $this->buffer,
1966
  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwyxz0123456789+/=',
@@ -1979,7 +2537,10 @@ class Parser
1979
 
1980
  $this->seek($s);
1981
 
1982
- if ($this->literal('url(', 4, false) && $this->match('\s*(\/\/[^\s\)]+)\s*', $m)) {
 
 
 
1983
  $content = 'url(' . $m[1];
1984
 
1985
  if ($this->matchChar(')')) {
@@ -1994,7 +2555,10 @@ class Parser
1994
 
1995
  // not
1996
  if ($char === 'n' && $this->literal('not', 3, false)) {
1997
- if ($this->whitespace() && $this->value($inner)) {
 
 
 
1998
  $out = [Type::T_UNARY, 'not', $inner, $this->inParens];
1999
 
2000
  return true;
@@ -2049,7 +2613,10 @@ class Parser
2049
  return true;
2050
  }
2051
 
2052
- if ($this->keyword($inner) && ! $this->func($inner, $out)) {
 
 
 
2053
  $out = [Type::T_UNARY, '-', $inner, $this->inParens];
2054
 
2055
  return true;
@@ -2077,7 +2644,7 @@ class Parser
2077
  $this->count++;
2078
 
2079
  if ($this->keyword($keyword)) {
2080
- $out = [Type::T_KEYWORD, "#" . $keyword];
2081
 
2082
  return true;
2083
  }
@@ -2108,10 +2675,17 @@ class Parser
2108
  }
2109
 
2110
  // unicode range with wildcards
2111
- if ($this->literal('U+', 2) && $this->match('([0-9A-F]+\?*)(-([0-9A-F]+))?', $m, false)) {
2112
- $out = [Type::T_KEYWORD, 'U+' . $m[0]];
 
 
 
 
 
2113
 
2114
- return true;
 
 
2115
  }
2116
 
2117
  if ($this->keyword($keyword, false)) {
@@ -2138,7 +2712,7 @@ class Parser
2138
  *
2139
  * @param array $out
2140
  *
2141
- * @return boolean
2142
  */
2143
  protected function parenValue(&$out)
2144
  {
@@ -2155,7 +2729,10 @@ class Parser
2155
 
2156
  $this->inParens = true;
2157
 
2158
- if ($this->expression($exp) && $this->matchChar(')')) {
 
 
 
2159
  $out = $exp;
2160
  $this->inParens = $inParens;
2161
 
@@ -2174,13 +2751,14 @@ class Parser
2174
  *
2175
  * @param array $out
2176
  *
2177
- * @return boolean
2178
  */
2179
  protected function progid(&$out)
2180
  {
2181
  $s = $this->count;
2182
 
2183
- if ($this->literal('progid:', 7, false) &&
 
2184
  $this->openString('(', $fn) &&
2185
  $this->matchChar('(')
2186
  ) {
@@ -2206,7 +2784,7 @@ class Parser
2206
  * @param string $name
2207
  * @param array $func
2208
  *
2209
- * @return boolean
2210
  */
2211
  protected function func($name, &$func)
2212
  {
@@ -2222,7 +2800,10 @@ class Parser
2222
  if ($name !== 'expression' && ! preg_match('/^(-[a-z]+-)?calc$/', $name)) {
2223
  $ss = $this->count;
2224
 
2225
- if ($this->argValues($args) && $this->matchChar(')')) {
 
 
 
2226
  $func = [Type::T_FUNCTION_CALL, $name, $args];
2227
 
2228
  return true;
@@ -2231,7 +2812,8 @@ class Parser
2231
  $this->seek($ss);
2232
  }
2233
 
2234
- if (($this->openString(')', $str, '(') || true) &&
 
2235
  $this->matchChar(')')
2236
  ) {
2237
  $args = [];
@@ -2256,7 +2838,7 @@ class Parser
2256
  *
2257
  * @param array $out
2258
  *
2259
- * @return boolean
2260
  */
2261
  protected function argumentList(&$out)
2262
  {
@@ -2266,7 +2848,10 @@ class Parser
2266
  $args = [];
2267
 
2268
  while ($this->keyword($var)) {
2269
- if ($this->matchChar('=') && $this->expression($exp)) {
 
 
 
2270
  $args[] = [Type::T_STRING, '', [$var . '=']];
2271
  $arg = $exp;
2272
  } else {
@@ -2298,7 +2883,7 @@ class Parser
2298
  *
2299
  * @param array $out
2300
  *
2301
- * @return boolean
2302
  */
2303
  protected function argumentDef(&$out)
2304
  {
@@ -2312,7 +2897,10 @@ class Parser
2312
 
2313
  $ss = $this->count;
2314
 
2315
- if ($this->matchChar(':') && $this->genericList($defaultVal, 'expression')) {
 
 
 
2316
  $arg[1] = $defaultVal;
2317
  } else {
2318
  $this->seek($ss);
@@ -2324,7 +2912,7 @@ class Parser
2324
  $sss = $this->count;
2325
 
2326
  if (! $this->matchChar(')')) {
2327
- $this->throwParseError('... has to be after the final argument');
2328
  }
2329
 
2330
  $arg[2] = true;
@@ -2357,7 +2945,7 @@ class Parser
2357
  *
2358
  * @param array $out
2359
  *
2360
- * @return boolean
2361
  */
2362
  protected function map(&$out)
2363
  {
@@ -2370,8 +2958,10 @@ class Parser
2370
  $keys = [];
2371
  $values = [];
2372
 
2373
- while ($this->genericList($key, 'expression') && $this->matchChar(':') &&
2374
- $this->genericList($value, 'expression')
 
 
2375
  ) {
2376
  $keys[] = $key;
2377
  $values[] = $value;
@@ -2397,13 +2987,13 @@ class Parser
2397
  *
2398
  * @param array $out
2399
  *
2400
- * @return boolean
2401
  */
2402
  protected function color(&$out)
2403
  {
2404
  $s = $this->count;
2405
 
2406
- if ($this->match('(#([0-9a-f]+))', $m)) {
2407
  if (\in_array(\strlen($m[2]), [3,4,6,8])) {
2408
  $out = [Type::T_KEYWORD, $m[0]];
2409
 
@@ -2423,7 +3013,7 @@ class Parser
2423
  *
2424
  * @param array $unit
2425
  *
2426
- * @return boolean
2427
  */
2428
  protected function unit(&$unit)
2429
  {
@@ -2448,10 +3038,11 @@ class Parser
2448
  * Parse string
2449
  *
2450
  * @param array $out
 
2451
  *
2452
- * @return boolean
2453
  */
2454
- protected function string(&$out)
2455
  {
2456
  $s = $this->count;
2457
 
@@ -2483,21 +3074,27 @@ class Parser
2483
  $this->count += \strlen($m[2]);
2484
  $content[] = '#{'; // ignore it
2485
  }
 
 
 
 
 
 
 
 
 
2486
  } elseif ($m[2] === '\\') {
2487
- if ($this->matchChar('"', false)) {
2488
- $content[] = $m[2] . '"';
2489
- } elseif ($this->matchChar("'", false)) {
2490
- $content[] = $m[2] . "'";
2491
- } elseif ($this->literal("\\", 1, false)) {
2492
- $content[] = $m[2] . "\\";
2493
- } elseif ($this->literal("\r\n", 2, false) ||
2494
  $this->matchChar("\r", false) ||
2495
  $this->matchChar("\n", false) ||
2496
  $this->matchChar("\f", false)
2497
  ) {
2498
  // this is a continuation escaping, to be ignored
 
 
2499
  } else {
2500
- $content[] = $m[2];
2501
  }
2502
  } else {
2503
  $this->count -= \strlen($delim);
@@ -2508,18 +3105,8 @@ class Parser
2508
  $this->eatWhiteDefault = $oldWhite;
2509
 
2510
  if ($this->literal($delim, \strlen($delim))) {
2511
- if ($hasInterpolation) {
2512
  $delim = '"';
2513
-
2514
- foreach ($content as &$string) {
2515
- if ($string === "\\\\") {
2516
- $string = "\\";
2517
- } elseif ($string === "\\'") {
2518
- $string = "'";
2519
- } elseif ($string === '\\"') {
2520
- $string = '"';
2521
- }
2522
- }
2523
  }
2524
 
2525
  $out = [Type::T_STRING, $delim, $content];
@@ -2532,13 +3119,63 @@ class Parser
2532
  return false;
2533
  }
2534
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2535
  /**
2536
  * Parse keyword or interpolation
2537
  *
2538
- * @param array $out
2539
- * @param boolean $restricted
2540
  *
2541
- * @return boolean
2542
  */
2543
  protected function mixedKeyword(&$out, $restricted = false)
2544
  {
@@ -2579,26 +3216,28 @@ class Parser
2579
  /**
2580
  * Parse an unbounded string stopped by $end
2581
  *
2582
- * @param string $end
2583
- * @param array $out
2584
- * @param string $nestingOpen
2585
- * @param string $nestingClose
2586
- * @param boolean $trimEnd
 
2587
  *
2588
- * @return boolean
2589
  */
2590
- protected function openString($end, &$out, $nestingOpen = null, $nestingClose = null, $trimEnd = true)
2591
  {
2592
  $oldWhite = $this->eatWhiteDefault;
2593
  $this->eatWhiteDefault = false;
2594
 
2595
- if ($nestingOpen && ! $nestingClose) {
2596
- $nestingClose = $end;
2597
  }
2598
 
2599
- $patt = '(.*?)([\'"]|#\{|'
 
2600
  . $this->pregQuote($end) . '|'
2601
- . (($nestingClose && $nestingClose !== $end) ? $this->pregQuote($nestingClose) . '|' : '')
2602
  . static::$commentPattern . ')';
2603
 
2604
  $nestingLevel = 0;
@@ -2609,24 +3248,24 @@ class Parser
2609
  if (isset($m[1]) && $m[1] !== '') {
2610
  $content[] = $m[1];
2611
 
2612
- if ($nestingOpen) {
2613
- $nestingLevel += substr_count($m[1], $nestingOpen);
2614
  }
2615
  }
2616
 
2617
  $tok = $m[2];
2618
 
2619
- $this->count-= \strlen($tok);
2620
 
2621
  if ($tok === $end && ! $nestingLevel) {
2622
  break;
2623
  }
2624
 
2625
- if ($tok === $nestingClose) {
2626
  $nestingLevel--;
2627
  }
2628
 
2629
- if (($tok === "'" || $tok === '"') && $this->string($str)) {
2630
  $content[] = $str;
2631
  continue;
2632
  }
@@ -2637,7 +3276,7 @@ class Parser
2637
  }
2638
 
2639
  $content[] = $tok;
2640
- $this->count+= \strlen($tok);
2641
  }
2642
 
2643
  $this->eatWhiteDefault = $oldWhite;
@@ -2647,7 +3286,7 @@ class Parser
2647
  }
2648
 
2649
  // trim the end
2650
- if ($trimEnd && \is_string(end($content))) {
2651
  $content[\count($content) - 1] = rtrim(end($content));
2652
  }
2653
 
@@ -2660,9 +3299,9 @@ class Parser
2660
  * Parser interpolation
2661
  *
2662
  * @param string|array $out
2663
- * @param boolean $lookWhite save information about whitespace before and after
2664
  *
2665
- * @return boolean
2666
  */
2667
  protected function interpolation(&$out, $lookWhite = true)
2668
  {
@@ -2673,13 +3312,20 @@ class Parser
2673
 
2674
  $s = $this->count;
2675
 
2676
- if ($this->literal('#{', 2) && $this->valueList($value) && $this->matchChar('}', false)) {
 
 
 
 
2677
  if ($value === [Type::T_SELF]) {
2678
  $out = $value;
2679
  } else {
2680
  if ($lookWhite) {
2681
  $left = ($s > 0 && preg_match('/\s/', $this->buffer[$s - 1])) ? ' ' : '';
2682
- $right = preg_match('/\s/', $this->buffer[$this->count]) ? ' ': '';
 
 
 
2683
  } else {
2684
  $left = $right = false;
2685
  }
@@ -2710,7 +3356,7 @@ class Parser
2710
  *
2711
  * @param array $out
2712
  *
2713
- * @return boolean
2714
  */
2715
  protected function propertyName(&$out)
2716
  {
@@ -2746,7 +3392,7 @@ class Parser
2746
  }
2747
 
2748
  // match comment hack
2749
- if (preg_match(static::$whitePattern, $this->buffer, $m, null, $this->count)) {
2750
  if (! empty($m[0])) {
2751
  $parts[] = $m[0];
2752
  $this->count += \strlen($m[0]);
@@ -2765,7 +3411,7 @@ class Parser
2765
  *
2766
  * @param array $out
2767
  *
2768
- * @return boolean
2769
  */
2770
  protected function customProperty(&$out)
2771
  {
@@ -2786,6 +3432,11 @@ class Parser
2786
  continue;
2787
  }
2788
 
 
 
 
 
 
2789
  if ($this->variable($var)) {
2790
  $parts[] = $var;
2791
  continue;
@@ -2817,10 +3468,10 @@ class Parser
2817
  /**
2818
  * Parse comma separated selector list
2819
  *
2820
- * @param array $out
2821
- * @param boolean $subSelector
2822
  *
2823
- * @return boolean
2824
  */
2825
  protected function selectors(&$out, $subSelector = false)
2826
  {
@@ -2853,20 +3504,24 @@ class Parser
2853
  /**
2854
  * Parse whitespace separated selector list
2855
  *
2856
- * @param array $out
2857
- * @param boolean $subSelector
2858
  *
2859
- * @return boolean
2860
  */
2861
  protected function selector(&$out, $subSelector = false)
2862
  {
2863
  $selector = [];
2864
 
 
 
 
2865
  for (;;) {
2866
  $s = $this->count;
2867
 
2868
  if ($this->match('[>+~]+', $m, true)) {
2869
- if ($subSelector && \is_string($subSelector) && strpos($subSelector, 'nth-') === 0 &&
 
2870
  $m[0] === '+' && $this->match("(\d+|n\b)", $counter)
2871
  ) {
2872
  $this->seek($s);
@@ -2878,18 +3533,15 @@ class Parser
2878
 
2879
  if ($this->selectorSingle($part, $subSelector)) {
2880
  $selector[] = $part;
2881
- $this->match('\s+', $m);
2882
- continue;
2883
- }
2884
-
2885
- if ($this->match('\/[^\/]+\/', $m, true)) {
2886
- $selector[] = [$m[0]];
2887
  continue;
2888
  }
2889
 
2890
  break;
2891
  }
2892
 
 
 
2893
  if (! $selector) {
2894
  return false;
2895
  }
@@ -2899,6 +3551,56 @@ class Parser
2899
  return true;
2900
  }
2901
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2902
  /**
2903
  * Parse the parts that make up a selector
2904
  *
@@ -2906,10 +3608,10 @@ class Parser
2906
  * div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
2907
  * }}
2908
  *
2909
- * @param array $out
2910
- * @param boolean $subSelector
2911
  *
2912
- * @return boolean
2913
  */
2914
  protected function selectorSingle(&$out, $subSelector = false)
2915
  {
@@ -2945,6 +3647,7 @@ class Parser
2945
  case '&':
2946
  $parts[] = Compiler::$selfSelector;
2947
  $this->count++;
 
2948
  continue 2;
2949
 
2950
  case '.':
@@ -2958,9 +3661,14 @@ class Parser
2958
  continue 2;
2959
  }
2960
 
2961
- if ($char === '\\' && $this->match('\\\\\S', $m)) {
2962
- $parts[] = $m[0];
2963
- continue;
 
 
 
 
 
2964
  }
2965
 
2966
  if ($char === '%') {
@@ -2969,6 +3677,7 @@ class Parser
2969
  if ($this->placeholder($placeholder)) {
2970
  $parts[] = '%';
2971
  $parts[] = $placeholder;
 
2972
  continue;
2973
  }
2974
 
@@ -2978,6 +3687,7 @@ class Parser
2978
  if ($char === '#') {
2979
  if ($this->interpolation($inter)) {
2980
  $parts[] = $inter;
 
2981
  continue;
2982
  }
2983
 
@@ -3005,13 +3715,19 @@ class Parser
3005
 
3006
  $ss = $this->count;
3007
 
3008
- if ($nameParts === ['not'] || $nameParts === ['is'] ||
3009
- $nameParts === ['has'] || $nameParts === ['where'] ||
 
 
 
3010
  $nameParts === ['slotted'] ||
3011
- $nameParts === ['nth-child'] || $nameParts == ['nth-last-child'] ||
3012
- $nameParts === ['nth-of-type'] || $nameParts == ['nth-last-of-type']
 
 
3013
  ) {
3014
- if ($this->matchChar('(', true) &&
 
3015
  ($this->selectors($subs, reset($nameParts)) || true) &&
3016
  $this->matchChar(')')
3017
  ) {
@@ -3037,21 +3753,20 @@ class Parser
3037
  } else {
3038
  $this->seek($ss);
3039
  }
3040
- } else {
3041
- if ($this->matchChar('(') &&
3042
- ($this->openString(')', $str, '(') || true) &&
3043
- $this->matchChar(')')
3044
- ) {
3045
- $parts[] = '(';
3046
-
3047
- if (! empty($str)) {
3048
- $parts[] = $str;
3049
- }
3050
 
3051
- $parts[] = ')';
3052
- } else {
3053
- $this->seek($ss);
3054
  }
 
 
 
 
3055
  }
3056
 
3057
  continue;
@@ -3072,7 +3787,8 @@ class Parser
3072
  $this->seek($s);
3073
 
3074
  // attribute selector
3075
- if ($char === '[' &&
 
3076
  $this->matchChar('[') &&
3077
  ($this->openString(']', $str, '[') || true) &&
3078
  $this->matchChar(']')
@@ -3095,7 +3811,7 @@ class Parser
3095
  continue;
3096
  }
3097
 
3098
- if ($this->restrictedKeyword($name)) {
3099
  $parts[] = $name;
3100
  continue;
3101
  }
@@ -3119,13 +3835,16 @@ class Parser
3119
  *
3120
  * @param array $out
3121
  *
3122
- * @return boolean
3123
  */
3124
  protected function variable(&$out)
3125
  {
3126
  $s = $this->count;
3127
 
3128
- if ($this->matchChar('$', false) && $this->keyword($name)) {
 
 
 
3129
  if ($this->allowVars) {
3130
  $out = [Type::T_VARIABLE, $name];
3131
  } else {
@@ -3143,22 +3862,64 @@ class Parser
3143
  /**
3144
  * Parse a keyword
3145
  *
3146
- * @param string $word
3147
- * @param boolean $eatWhitespace
 
3148
  *
3149
- * @return boolean
3150
  */
3151
- protected function keyword(&$word, $eatWhitespace = null)
3152
  {
3153
- if ($this->match(
 
3154
  $this->utf8
3155
- ? '(([\pL\w\x{00A0}-\x{10FFFF}_\-\*!"\']|[\\\\].)([\pL\w\x{00A0}-\x{10FFFF}\-_"\']|[\\\\].)*)'
3156
- : '(([\w_\-\*!"\']|[\\\\].)([\w\-_"\']|[\\\\].)*)',
3157
  $m,
3158
- $eatWhitespace
3159
- )) {
 
 
3160
  $word = $m[1];
3161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3162
  return true;
3163
  }
3164
 
@@ -3168,16 +3929,17 @@ class Parser
3168
  /**
3169
  * Parse a keyword that should not start with a number
3170
  *
3171
- * @param string $word
3172
- * @param boolean $eatWhitespace
 
3173
  *
3174
- * @return boolean
3175
  */
3176
- protected function restrictedKeyword(&$word, $eatWhitespace = null)
3177
  {
3178
  $s = $this->count;
3179
 
3180
- if ($this->keyword($word, $eatWhitespace) && (\ord($word[0]) > 57 || \ord($word[0]) < 48)) {
3181
  return true;
3182
  }
3183
 
@@ -3191,16 +3953,18 @@ class Parser
3191
  *
3192
  * @param string|array $placeholder
3193
  *
3194
- * @return boolean
3195
  */
3196
  protected function placeholder(&$placeholder)
3197
  {
3198
- if ($this->match(
3199
  $this->utf8
3200
  ? '([\pL\w\-_]+)'
3201
  : '([\w\-_]+)',
3202
  $m
3203
- )) {
 
 
3204
  $placeholder = $m[1];
3205
 
3206
  return true;
@@ -3218,14 +3982,32 @@ class Parser
3218
  *
3219
  * @param array $out
3220
  *
3221
- * @return boolean
3222
  */
3223
  protected function url(&$out)
3224
  {
3225
- if ($this->match('(url\(\s*(["\']?)([^)]+)\2\s*\))', $m)) {
3226
- $out = [Type::T_STRING, '', ['url(' . $m[2] . $m[3] . $m[2] . ')']];
3227
 
3228
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3229
  }
3230
 
3231
  return false;
@@ -3233,12 +4015,13 @@ class Parser
3233
 
3234
  /**
3235
  * Consume an end of statement delimiter
 
3236
  *
3237
- * @return boolean
3238
  */
3239
- protected function end()
3240
  {
3241
- if ($this->matchChar(';')) {
3242
  return true;
3243
  }
3244
 
@@ -3255,7 +4038,7 @@ class Parser
3255
  *
3256
  * @param array $value
3257
  *
3258
- * @return array
3259
  */
3260
  protected function stripAssignmentFlags(&$value)
3261
  {
@@ -3282,7 +4065,7 @@ class Parser
3282
  *
3283
  * @param array $selectors
3284
  *
3285
- * @return string
3286
  */
3287
  protected function stripOptionalFlag(&$selectors)
3288
  {
@@ -3331,6 +4114,8 @@ class Parser
3331
  * Extract line numbers from buffer
3332
  *
3333
  * @param string $buffer
 
 
3334
  */
3335
  private function extractLineNumbers($buffer)
3336
  {
@@ -3352,9 +4137,10 @@ class Parser
3352
  /**
3353
  * Get source line number and column (given character position in the buffer)
3354
  *
3355
- * @param integer $pos
3356
  *
3357
  * @return array
 
3358
  */
3359
  private function getSourcePosition($pos)
3360
  {
@@ -3381,11 +4167,20 @@ class Parser
3381
  }
3382
 
3383
  /**
3384
- * Save internal encoding
 
 
 
 
 
 
 
 
 
3385
  */
3386
  private function saveEncoding()
3387
  {
3388
- if (\extension_loaded('mbstring')) {
3389
  $this->encoding = mb_internal_encoding();
3390
 
3391
  mb_internal_encoding('iso-8859-1');
@@ -3394,6 +4189,8 @@ class Parser
3394
 
3395
  /**
3396
  * Restore internal encoding
 
 
3397
  */
3398
  private function restoreEncoding()
3399
  {
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
12
 
13
  namespace ScssPhp\ScssPhp;
14
 
15
+ use ScssPhp\ScssPhp\Block\AtRootBlock;
16
+ use ScssPhp\ScssPhp\Block\CallableBlock;
17
+ use ScssPhp\ScssPhp\Block\ContentBlock;
18
+ use ScssPhp\ScssPhp\Block\DirectiveBlock;
19
+ use ScssPhp\ScssPhp\Block\EachBlock;
20
+ use ScssPhp\ScssPhp\Block\ElseBlock;
21
+ use ScssPhp\ScssPhp\Block\ElseifBlock;
22
+ use ScssPhp\ScssPhp\Block\ForBlock;
23
+ use ScssPhp\ScssPhp\Block\IfBlock;
24
+ use ScssPhp\ScssPhp\Block\MediaBlock;
25
+ use ScssPhp\ScssPhp\Block\NestedPropertyBlock;
26
+ use ScssPhp\ScssPhp\Block\WhileBlock;
27
  use ScssPhp\ScssPhp\Exception\ParserException;
28
+ use ScssPhp\ScssPhp\Logger\LoggerInterface;
29
+ use ScssPhp\ScssPhp\Logger\QuietLogger;
30
+ use ScssPhp\ScssPhp\Node\Number;
31
 
32
  /**
33
  * Parser
34
  *
35
  * @author Leaf Corcoran <leafot@gmail.com>
36
+ *
37
+ * @internal
38
  */
39
  class Parser
40
  {
43
  const SOURCE_COLUMN = -3;
44
 
45
  /**
46
+ * @var array<string, int>
47
  */
48
  protected static $precedence = [
49
  '=' => 0,
51
  'and' => 2,
52
  '==' => 3,
53
  '!=' => 3,
 
54
  '<=' => 4,
55
  '>=' => 4,
56
  '<' => 4,
62
  '%' => 6,
63
  ];
64
 
65
+ /**
66
+ * @var string
67
+ */
68
  protected static $commentPattern;
69
+ /**
70
+ * @var string
71
+ */
72
  protected static $operatorPattern;
73
+ /**
74
+ * @var string
75
+ */
76
  protected static $whitePattern;
77
 
78
+ /**
79
+ * @var Cache|null
80
+ */
81
  protected $cache;
82
 
83
  private $sourceName;
84
  private $sourceIndex;
85
+ /**
86
+ * @var array<int, int>
87
+ */
88
  private $sourcePositions;
89
+ /**
90
+ * The current offset in the buffer
91
+ *
92
+ * @var int
93
+ */
94
  private $count;
95
+ /**
96
+ * @var Block|null
97
+ */
98
  private $env;
99
+ /**
100
+ * @var bool
101
+ */
102
  private $inParens;
103
+ /**
104
+ * @var bool
105
+ */
106
  private $eatWhiteDefault;
107
+ /**
108
+ * @var bool
109
+ */
110
  private $discardComments;
111
  private $allowVars;
112
+ /**
113
+ * @var string
114
+ */
115
  private $buffer;
116
  private $utf8;
117
+ /**
118
+ * @var string|null
119
+ */
120
  private $encoding;
121
  private $patternModifiers;
122
  private $commentsSeen;
123
 
124
  private $cssOnly;
125
 
126
+ /**
127
+ * @var LoggerInterface
128
+ */
129
+ private $logger;
130
+
131
  /**
132
  * Constructor
133
  *
134
  * @api
135
  *
136
+ * @param string|null $sourceName
137
+ * @param int $sourceIndex
138
+ * @param string|null $encoding
139
+ * @param Cache|null $cache
140
+ * @param bool $cssOnly
141
+ * @param LoggerInterface|null $logger
142
  */
143
+ public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8', Cache $cache = null, $cssOnly = false, LoggerInterface $logger = null)
144
  {
145
  $this->sourceName = $sourceName ?: '(stdin)';
146
  $this->sourceIndex = $sourceIndex;
 
147
  $this->utf8 = ! $encoding || strtolower($encoding) === 'utf-8';
148
  $this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais';
149
  $this->commentsSeen = [];
 
150
  $this->allowVars = true;
151
  $this->cssOnly = $cssOnly;
152
+ $this->logger = $logger ?: new QuietLogger();
153
 
154
  if (empty(static::$operatorPattern)) {
155
+ static::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=?|and|or)';
156
 
157
  $commentSingle = '\/\/';
158
  $commentMultiLeft = '\/\*';
164
  : '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisS';
165
  }
166
 
167
+ $this->cache = $cache;
 
 
168
  }
169
 
170
  /**
186
  *
187
  * @param string $msg
188
  *
189
+ * @phpstan-return never-return
190
+ *
191
+ * @throws ParserException
192
+ *
193
+ * @deprecated use "parseError" and throw the exception in the caller instead.
194
  */
195
  public function throwParseError($msg = 'parse error')
196
+ {
197
+ @trigger_error(
198
+ 'The method "throwParseError" is deprecated. Use "parseError" and throw the exception in the caller instead',
199
+ E_USER_DEPRECATED
200
+ );
201
+
202
+ throw $this->parseError($msg);
203
+ }
204
+
205
+ /**
206
+ * Creates a parser error
207
+ *
208
+ * @api
209
+ *
210
+ * @param string $msg
211
+ *
212
+ * @return ParserException
213
+ */
214
+ public function parseError($msg = 'parse error')
215
  {
216
  list($line, $column) = $this->getSourcePosition($this->count);
217
 
219
  ? "line: $line, column: $column"
220
  : "$this->sourceName on line $line, at column $column";
221
 
222
+ if ($this->peek('(.*?)(\n|$)', $m, $this->count)) {
223
  $this->restoreEncoding();
224
 
225
+ $e = new ParserException("$msg: failed at `$m[1]` $loc");
226
+ $e->setSourcePosition([$this->sourceName, $line, $column]);
227
+
228
+ return $e;
229
  }
230
 
231
  $this->restoreEncoding();
232
 
233
+ $e = new ParserException("$msg: $loc");
234
+ $e->setSourcePosition([$this->sourceName, $line, $column]);
235
+
236
+ return $e;
237
  }
238
 
239
  /**
243
  *
244
  * @param string $buffer
245
  *
246
+ * @return Block
247
  */
248
  public function parse($buffer)
249
  {
250
  if ($this->cache) {
251
+ $cacheKey = $this->sourceName . ':' . md5($buffer);
252
  $parseOptions = [
 
253
  'utf8' => $this->utf8,
254
  ];
255
+ $v = $this->cache->getCache('parse', $cacheKey, $parseOptions);
256
 
257
  if (! \is_null($v)) {
258
  return $v;
283
  }
284
 
285
  if ($this->count !== \strlen($this->buffer)) {
286
+ throw $this->parseError();
287
  }
288
 
289
  if (! empty($this->env->parent)) {
290
+ throw $this->parseError('unclosed block');
 
 
 
 
291
  }
292
 
293
  $this->restoreEncoding();
294
+ assert($this->env !== null);
295
 
296
  if ($this->cache) {
297
+ $this->cache->setCache('parse', $cacheKey, $this->env, $parseOptions);
298
  }
299
 
300
  return $this->env;
308
  * @param string $buffer
309
  * @param string|array $out
310
  *
311
+ * @return bool
312
  */
313
  public function parseValue($buffer, &$out)
314
  {
319
  $this->buffer = (string) $buffer;
320
 
321
  $this->saveEncoding();
322
+ $this->extractLineNumbers($this->buffer);
323
 
324
  $list = $this->valueList($out);
325
 
335
  *
336
  * @param string $buffer
337
  * @param string|array $out
338
+ * @param bool $shouldValidate
339
  *
340
+ * @return bool
341
  */
342
+ public function parseSelector($buffer, &$out, $shouldValidate = true)
343
  {
344
  $this->count = 0;
345
  $this->env = null;
348
  $this->buffer = (string) $buffer;
349
 
350
  $this->saveEncoding();
351
+ $this->extractLineNumbers($this->buffer);
352
+
353
+ // discard space/comments at the start
354
+ $this->discardComments = true;
355
+ $this->whitespace();
356
+ $this->discardComments = false;
357
 
358
  $selector = $this->selectors($out);
359
 
360
  $this->restoreEncoding();
361
 
362
+ if ($shouldValidate && $this->count !== strlen($buffer)) {
363
+ throw $this->parseError("`" . substr($buffer, $this->count) . "` is not a valid Selector in `$buffer`");
364
+ }
365
+
366
  return $selector;
367
  }
368
 
371
  *
372
  * @api
373
  *
374
+ * @param string $buffer
375
+ * @param array $out
376
  *
377
+ * @return bool
378
  */
379
  public function parseMediaQueryList($buffer, &$out)
380
  {
385
  $this->buffer = (string) $buffer;
386
 
387
  $this->saveEncoding();
388
+ $this->extractLineNumbers($this->buffer);
389
 
390
  $isMediaQuery = $this->mediaQueryList($out);
391
 
431
  * position into $s. Then if a chain fails, use $this->seek($s) to
432
  * go back where we started.
433
  *
434
+ * @return bool
435
  */
436
  protected function parseChunk()
437
  {
439
 
440
  // the directives
441
  if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') {
442
+ if (
443
+ $this->literal('@at-root', 8) &&
444
  ($this->selectors($selector) || true) &&
445
  ($this->map($with) || true) &&
446
+ (($this->matchChar('(') &&
447
+ $this->interpolation($with) &&
448
+ $this->matchChar(')')) || true) &&
449
  $this->matchChar('{', false)
450
  ) {
451
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
452
 
453
+ $atRoot = new AtRootBlock();
454
+ $this->registerPushedBlock($atRoot, $s);
455
  $atRoot->selector = $selector;
456
  $atRoot->with = $with;
457
 
460
 
461
  $this->seek($s);
462
 
463
+ if (
464
+ $this->literal('@media', 6) &&
465
+ $this->mediaQueryList($mediaQueryList) &&
466
+ $this->matchChar('{', false)
467
+ ) {
468
+ $media = new MediaBlock();
469
+ $this->registerPushedBlock($media, $s);
470
  $media->queryList = $mediaQueryList[2];
471
 
472
  return true;
474
 
475
  $this->seek($s);
476
 
477
+ if (
478
+ $this->literal('@mixin', 6) &&
479
  $this->keyword($mixinName) &&
480
  ($this->argumentDef($args) || true) &&
481
  $this->matchChar('{', false)
482
  ) {
483
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
484
 
485
+ $mixin = new CallableBlock(Type::T_MIXIN);
486
+ $this->registerPushedBlock($mixin, $s);
487
  $mixin->name = $mixinName;
488
  $mixin->args = $args;
489
 
492
 
493
  $this->seek($s);
494
 
495
+ if (
496
+ ($this->literal('@include', 8) &&
497
+ $this->keyword($mixinName) &&
498
+ ($this->matchChar('(') &&
499
  ($this->argValues($argValues) || true) &&
500
  $this->matchChar(')') || true) &&
501
+ ($this->end()) ||
502
+ ($this->literal('using', 5) &&
503
+ $this->argumentDef($argUsing) &&
504
+ ($this->end() || $this->matchChar('{') && $hasBlock = true)) ||
505
+ $this->matchChar('{') && $hasBlock = true)
506
  ) {
507
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
508
 
509
  $child = [
510
  Type::T_INCLUDE,
515
  ];
516
 
517
  if (! empty($hasBlock)) {
518
+ $include = new ContentBlock();
519
+ $this->registerPushedBlock($include, $s);
520
  $include->child = $child;
521
  } else {
522
  $this->append($child, $s);
527
 
528
  $this->seek($s);
529
 
530
+ if (
531
+ $this->literal('@scssphp-import-once', 20) &&
532
  $this->valueList($importPath) &&
533
  $this->end()
534
  ) {
535
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
536
+
537
+ list($line, $column) = $this->getSourcePosition($s);
538
+ $file = $this->sourceName;
539
+ $this->logger->warn("The \"@scssphp-import-once\" directive is deprecated and will be removed in ScssPhp 2.0, in \"$file\", line $line, column $column.", true);
540
 
541
  $this->append([Type::T_SCSSPHP_IMPORT_ONCE, $importPath], $s);
542
 
545
 
546
  $this->seek($s);
547
 
548
+ if (
549
+ $this->literal('@import', 7) &&
550
  $this->valueList($importPath) &&
551
+ $importPath[0] !== Type::T_FUNCTION_CALL &&
552
  $this->end()
553
  ) {
554
+ if ($this->cssOnly) {
555
+ $this->assertPlainCssValid([Type::T_IMPORT, $importPath], $s);
556
+ $this->append([Type::T_COMMENT, rtrim(substr($this->buffer, $s, $this->count - $s))]);
557
+ return true;
558
+ }
559
+
560
  $this->append([Type::T_IMPORT, $importPath], $s);
561
 
562
  return true;
564
 
565
  $this->seek($s);
566
 
567
+ if (
568
+ $this->literal('@import', 7) &&
569
  $this->url($importPath) &&
570
  $this->end()
571
  ) {
572
  if ($this->cssOnly) {
573
+ $this->assertPlainCssValid([Type::T_IMPORT, $importPath], $s);
574
+ $this->append([Type::T_COMMENT, rtrim(substr($this->buffer, $s, $this->count - $s))]);
575
+ return true;
576
  }
577
 
578
  $this->append([Type::T_IMPORT, $importPath], $s);
582
 
583
  $this->seek($s);
584
 
585
+ if (
586
+ $this->literal('@extend', 7) &&
587
  $this->selectors($selectors) &&
588
  $this->end()
589
  ) {
590
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
591
 
592
  // check for '!flag'
593
  $optional = $this->stripOptionalFlag($selectors);
598
 
599
  $this->seek($s);
600
 
601
+ if (
602
+ $this->literal('@function', 9) &&
603
  $this->keyword($fnName) &&
604
  $this->argumentDef($args) &&
605
  $this->matchChar('{', false)
606
  ) {
607
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
608
 
609
+ $func = new CallableBlock(Type::T_FUNCTION);
610
+ $this->registerPushedBlock($func, $s);
611
  $func->name = $fnName;
612
  $func->args = $args;
613
 
616
 
617
  $this->seek($s);
618
 
619
+ if (
620
+ $this->literal('@return', 7) &&
621
+ ($this->valueList($retVal) || true) &&
622
+ $this->end()
623
+ ) {
624
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
 
626
  $this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s);
627
 
630
 
631
  $this->seek($s);
632
 
633
+ if (
634
+ $this->literal('@each', 5) &&
635
  $this->genericList($varNames, 'variable', ',', false) &&
636
  $this->literal('in', 2) &&
637
  $this->valueList($list) &&
638
  $this->matchChar('{', false)
639
  ) {
640
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
641
 
642
+ $each = new EachBlock();
643
+ $this->registerPushedBlock($each, $s);
644
 
645
  foreach ($varNames[2] as $varName) {
646
  $each->vars[] = $varName[1];
653
 
654
  $this->seek($s);
655
 
656
+ if (
657
+ $this->literal('@while', 6) &&
658
  $this->expression($cond) &&
659
  $this->matchChar('{', false)
660
  ) {
661
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
662
+
663
+ while (
664
+ $cond[0] === Type::T_LIST &&
665
+ ! empty($cond['enclosing']) &&
666
+ $cond['enclosing'] === 'parent' &&
667
+ \count($cond[2]) == 1
668
+ ) {
669
+ $cond = reset($cond[2]);
670
  }
671
 
672
+ $while = new WhileBlock();
673
+ $this->registerPushedBlock($while, $s);
674
  $while->cond = $cond;
675
 
676
  return true;
678
 
679
  $this->seek($s);
680
 
681
+ if (
682
+ $this->literal('@for', 4) &&
683
  $this->variable($varName) &&
684
  $this->literal('from', 4) &&
685
  $this->expression($start) &&
688
  $this->expression($end) &&
689
  $this->matchChar('{', false)
690
  ) {
691
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
692
 
693
+ $for = new ForBlock();
694
+ $this->registerPushedBlock($for, $s);
695
  $for->var = $varName[1];
696
  $for->start = $start;
697
  $for->end = $end;
702
 
703
  $this->seek($s);
704
 
705
+ if (
706
+ $this->literal('@if', 3) &&
707
+ $this->functionCallArgumentsList($cond, false, '{', false)
708
+ ) {
709
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
710
 
711
+ $if = new IfBlock();
712
+ $this->registerPushedBlock($if, $s);
713
 
714
+ while (
715
+ $cond[0] === Type::T_LIST &&
716
+ ! empty($cond['enclosing']) &&
717
+ $cond['enclosing'] === 'parent' &&
718
+ \count($cond[2]) == 1
719
+ ) {
720
  $cond = reset($cond[2]);
721
  }
722
 
728
 
729
  $this->seek($s);
730
 
731
+ if (
732
+ $this->literal('@debug', 6) &&
733
+ $this->functionCallArgumentsList($value, false)
734
  ) {
735
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
736
 
737
  $this->append([Type::T_DEBUG, $value], $s);
738
 
741
 
742
  $this->seek($s);
743
 
744
+ if (
745
+ $this->literal('@warn', 5) &&
746
+ $this->functionCallArgumentsList($value, false)
747
  ) {
748
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
749
 
750
  $this->append([Type::T_WARN, $value], $s);
751
 
754
 
755
  $this->seek($s);
756
 
757
+ if (
758
+ $this->literal('@error', 6) &&
759
+ $this->functionCallArgumentsList($value, false)
760
  ) {
761
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
762
 
763
  $this->append([Type::T_ERROR, $value], $s);
764
 
767
 
768
  $this->seek($s);
769
 
770
+ if (
771
+ $this->literal('@content', 8) &&
772
  ($this->end() ||
773
  $this->matchChar('(') &&
774
  $this->argValues($argContent) &&
775
  $this->matchChar(')') &&
776
  $this->end())
777
  ) {
778
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
779
 
780
  $this->append([Type::T_MIXIN_CONTENT, isset($argContent) ? $argContent : null], $s);
781
 
788
 
789
  if (isset($last) && $last[0] === Type::T_IF) {
790
  list(, $if) = $last;
791
+ assert($if instanceof IfBlock);
792
 
793
  if ($this->literal('@else', 5)) {
794
  if ($this->matchChar('{', false)) {
795
+ $else = new ElseBlock();
796
+ } elseif (
797
+ $this->literal('if', 2) &&
798
+ $this->functionCallArgumentsList($cond, false, '{', false)
799
+ ) {
800
+ $else = new ElseifBlock();
801
  $else->cond = $cond;
802
  }
803
 
804
  if (isset($else)) {
805
+ $this->registerPushedBlock($else, $s);
806
  $if->cases[] = $else;
807
 
808
  return true;
813
  }
814
 
815
  // only retain the first @charset directive encountered
816
+ if (
817
+ $this->literal('@charset', 8) &&
818
  $this->valueList($charset) &&
819
  $this->end()
820
  ) {
 
 
 
 
 
 
 
 
 
 
 
 
821
  return true;
822
  }
823
 
824
  $this->seek($s);
825
 
826
+ if (
827
+ $this->literal('@supports', 9) &&
828
+ ($t1 = $this->supportsQuery($supportQuery)) &&
829
+ ($t2 = $this->matchChar('{', false))
830
  ) {
831
+ $directive = new DirectiveBlock();
832
+ $this->registerPushedBlock($directive, $s);
833
  $directive->name = 'supports';
834
  $directive->value = $supportQuery;
835
 
839
  $this->seek($s);
840
 
841
  // doesn't match built in directive, do generic one
842
+ if (
843
+ $this->matchChar('@', false) &&
844
+ $this->mixedKeyword($dirName) &&
845
  $this->directiveValue($dirValue, '{')
846
  ) {
847
+ if (count($dirName) === 1 && is_string(reset($dirName))) {
848
+ $dirName = reset($dirName);
849
+ } else {
850
+ $dirName = [Type::T_STRING, '', $dirName];
851
+ }
852
  if ($dirName === 'media') {
853
+ $directive = new MediaBlock();
854
  } else {
855
+ $directive = new DirectiveBlock();
856
  $directive->name = $dirName;
857
  }
858
+ $this->registerPushedBlock($directive, $s);
859
 
860
  if (isset($dirValue)) {
861
+ ! $this->cssOnly || ($dirValue = $this->assertPlainCssValid($dirValue));
862
  $directive->value = $dirValue;
863
  }
864
 
868
  $this->seek($s);
869
 
870
  // maybe it's a generic blockless directive
871
+ if (
872
+ $this->matchChar('@', false) &&
873
+ $this->mixedKeyword($dirName) &&
874
+ ! $this->isKnownGenericDirective($dirName) &&
875
+ ($this->end(false) || ($this->directiveValue($dirValue, '') && $this->end(false)))
876
  ) {
877
+ if (\count($dirName) === 1 && \is_string(\reset($dirName))) {
878
+ $dirName = \reset($dirName);
879
+ } else {
880
+ $dirName = [Type::T_STRING, '', $dirName];
881
+ }
882
+ if (
883
+ ! empty($this->env->parent) &&
884
+ $this->env->type &&
885
+ ! \in_array($this->env->type, [Type::T_DIRECTIVE, Type::T_MEDIA])
886
+ ) {
887
+ $plain = \trim(\substr($this->buffer, $s, $this->count - $s));
888
+ throw $this->parseError(
889
+ "Unknown directive `{$plain}` not allowed in `" . $this->env->type . "` block"
890
+ );
891
+ }
892
+ // blockless directives with a blank line after keeps their blank lines after
893
+ // sass-spec compliance purpose
894
+ $s = $this->count;
895
+ $hasBlankLine = false;
896
+ if ($this->match('\s*?\n\s*\n', $out, false)) {
897
+ $hasBlankLine = true;
898
+ $this->seek($s);
899
+ }
900
+ $isNotRoot = ! empty($this->env->parent);
901
+ $this->append([Type::T_DIRECTIVE, [$dirName, $dirValue, $hasBlankLine, $isNotRoot]], $s);
902
+ $this->whitespace();
903
 
904
  return true;
905
  }
909
  return false;
910
  }
911
 
912
+ $inCssSelector = null;
913
+ if ($this->cssOnly) {
914
+ $inCssSelector = (! empty($this->env->parent) &&
915
+ ! in_array($this->env->type, [Type::T_DIRECTIVE, Type::T_MEDIA]));
916
+ }
917
  // custom properties : right part is static
918
+ if (($this->customProperty($name) ) && $this->matchChar(':', false)) {
 
 
919
  $start = $this->count;
920
 
921
  // but can be complex and finish with ; or }
922
  foreach ([';','}'] as $ending) {
923
+ if (
924
+ $this->openString($ending, $stringValue, '(', ')', false) &&
925
  $this->end()
926
  ) {
927
  $end = $this->count;
936
  if ($p && $p < $end) {
937
  $this->seek($start);
938
 
939
+ if (
940
+ $this->openString($ending, $stringValue, $nestingPair[0], $nestingPair[1], false) &&
941
  $this->end() &&
942
  $this->count > $end
943
  ) {
961
 
962
  // property shortcut
963
  // captures most properties before having to parse a selector
964
+ if (
965
+ $this->keyword($name, false) &&
966
  $this->literal(': ', 2) &&
967
  $this->valueList($value) &&
968
  $this->end()
976
  $this->seek($s);
977
 
978
  // variable assigns
979
+ if (
980
+ $this->variable($name) &&
981
  $this->matchChar(':') &&
982
  $this->valueList($value) &&
983
  $this->end()
984
  ) {
985
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
986
 
987
  // check for '!flag'
988
  $assignmentFlags = $this->stripAssignmentFlags($value);
993
 
994
  $this->seek($s);
995
 
 
 
 
 
 
996
  // opening css block
997
+ if (
998
+ $this->selectors($selectors) &&
999
+ $this->matchChar('{', false)
1000
+ ) {
1001
+ ! $this->cssOnly || ! $inCssSelector || $this->assertPlainCssValid(false);
 
1002
 
1003
  $this->pushBlock($selectors, $s);
1004
 
1013
  $this->seek($s);
1014
 
1015
  // property assign, or nested assign
1016
+ if (
1017
+ $this->propertyName($name) &&
1018
+ $this->matchChar(':')
1019
+ ) {
1020
  $foundSomething = false;
1021
 
1022
  if ($this->valueList($value)) {
1023
  if (empty($this->env->parent)) {
1024
+ throw $this->parseError('expected "{"');
1025
  }
1026
 
1027
  $this->append([Type::T_ASSIGN, $name, $value], $s);
1029
  }
1030
 
1031
  if ($this->matchChar('{', false)) {
1032
+ ! $this->cssOnly || $this->assertPlainCssValid(false);
 
 
1033
 
1034
+ $propBlock = new NestedPropertyBlock();
1035
+ $this->registerPushedBlock($propBlock, $s);
1036
  $propBlock->prefix = $name;
1037
  $propBlock->hasValue = $foundSomething;
1038
 
1053
  $block = $this->popBlock();
1054
 
1055
  if (! isset($block->type) || $block->type !== Type::T_IF) {
1056
+ assert($this->env !== null);
1057
+
1058
  if ($this->env->parent) {
1059
  $this->append(null); // collect comments before next statement if needed
1060
  }
1061
  }
1062
 
1063
+ if ($block instanceof ContentBlock) {
1064
  $include = $block->child;
1065
+ assert(\is_array($include));
1066
  unset($block->child);
1067
  $include[3] = $block;
1068
  $this->append($include, $s);
1069
+ } elseif (!$block instanceof ElseBlock && !$block instanceof ElseifBlock) {
1070
  $type = isset($block->type) ? $block->type : Type::T_BLOCK;
1071
  $this->append([$type, $block], $s);
1072
  }
1074
  // collect comments just after the block closing if needed
1075
  if ($this->eatWhiteDefault) {
1076
  $this->whitespace();
1077
+ assert($this->env !== null);
1078
 
1079
  if ($this->env->comments) {
1080
  $this->append(null);
1085
  }
1086
 
1087
  // extra stuff
1088
+ if ($this->matchChar(';')) {
 
 
1089
  return true;
1090
  }
1091
 
1095
  /**
1096
  * Push block onto parse tree
1097
  *
1098
+ * @param array|null $selectors
1099
+ * @param int $pos
1100
  *
1101
+ * @return Block
1102
  */
1103
  protected function pushBlock($selectors, $pos = 0)
1104
+ {
1105
+ $b = new Block();
1106
+ $b->selectors = $selectors;
1107
+
1108
+ $this->registerPushedBlock($b, $pos);
1109
+
1110
+ return $b;
1111
+ }
1112
+
1113
+ /**
1114
+ * @param Block $b
1115
+ * @param int $pos
1116
+ *
1117
+ * @return void
1118
+ */
1119
+ private function registerPushedBlock(Block $b, $pos)
1120
  {
1121
  list($line, $column) = $this->getSourcePosition($pos);
1122
 
 
1123
  $b->sourceName = $this->sourceName;
1124
  $b->sourceLine = $line;
1125
  $b->sourceColumn = $column;
1126
  $b->sourceIndex = $this->sourceIndex;
 
1127
  $b->comments = [];
1128
  $b->parent = $this->env;
1129
 
1143
  // collect comments at the beginning of a block if needed
1144
  if ($this->eatWhiteDefault) {
1145
  $this->whitespace();
1146
+ assert($this->env !== null);
1147
 
1148
  if ($this->env->comments) {
1149
  $this->append(null);
1150
  }
1151
  }
 
 
1152
  }
1153
 
1154
  /**
1155
  * Push special (named) block onto parse tree
1156
  *
1157
+ * @deprecated
1158
+ *
1159
  * @param string $type
1160
+ * @param int $pos
1161
  *
1162
+ * @return Block
1163
  */
1164
  protected function pushSpecialBlock($type, $pos)
1165
  {
1172
  /**
1173
  * Pop scope and return last block
1174
  *
1175
+ * @return Block
1176
  *
1177
  * @throws \Exception
1178
  */
1179
  protected function popBlock()
1180
  {
1181
+ assert($this->env !== null);
1182
 
1183
  // collect comments ending just before of a block closing
1184
  if ($this->env->comments) {
1189
  $block = $this->env;
1190
 
1191
  if (empty($block->parent)) {
1192
+ throw $this->parseError('unexpected }');
1193
  }
1194
 
1195
  if ($block->type == Type::T_AT_ROOT) {
1207
  /**
1208
  * Peek input stream
1209
  *
1210
+ * @param string $regex
1211
+ * @param array $out
1212
+ * @param int $from
1213
  *
1214
+ * @return int
1215
  */
1216
  protected function peek($regex, &$out, $from = null)
1217
  {
1220
  }
1221
 
1222
  $r = '/' . $regex . '/' . $this->patternModifiers;
1223
+ $result = preg_match($r, $this->buffer, $out, 0, $from);
1224
 
1225
  return $result;
1226
  }
1228
  /**
1229
  * Seek to position in input stream (or return current position in input stream)
1230
  *
1231
+ * @param int $where
1232
+ *
1233
+ * @return void
1234
  */
1235
  protected function seek($where)
1236
  {
1237
  $this->count = $where;
1238
  }
1239
 
1240
+ /**
1241
+ * Assert a parsed part is plain CSS Valid
1242
+ *
1243
+ * @param array|false $parsed
1244
+ * @param int $startPos
1245
+ *
1246
+ * @return array
1247
+ *
1248
+ * @throws ParserException
1249
+ */
1250
+ protected function assertPlainCssValid($parsed, $startPos = null)
1251
+ {
1252
+ $type = '';
1253
+ if ($parsed) {
1254
+ $type = $parsed[0];
1255
+ $parsed = $this->isPlainCssValidElement($parsed);
1256
+ }
1257
+ if (! $parsed) {
1258
+ if (! \is_null($startPos)) {
1259
+ $plain = rtrim(substr($this->buffer, $startPos, $this->count - $startPos));
1260
+ $message = "Error : `{$plain}` isn't allowed in plain CSS";
1261
+ } else {
1262
+ $message = 'Error: SCSS syntax not allowed in CSS file';
1263
+ }
1264
+ if ($type) {
1265
+ $message .= " ($type)";
1266
+ }
1267
+ throw $this->parseError($message);
1268
+ }
1269
+
1270
+ return $parsed;
1271
+ }
1272
+
1273
+ /**
1274
+ * Check a parsed element is plain CSS Valid
1275
+ *
1276
+ * @param array $parsed
1277
+ * @param bool $allowExpression
1278
+ *
1279
+ * @return array|false
1280
+ */
1281
+ protected function isPlainCssValidElement($parsed, $allowExpression = false)
1282
+ {
1283
+ // keep string as is
1284
+ if (is_string($parsed)) {
1285
+ return $parsed;
1286
+ }
1287
+
1288
+ if (
1289
+ \in_array($parsed[0], [Type::T_FUNCTION, Type::T_FUNCTION_CALL]) &&
1290
+ !\in_array($parsed[1], [
1291
+ 'alpha',
1292
+ 'attr',
1293
+ 'calc',
1294
+ 'cubic-bezier',
1295
+ 'env',
1296
+ 'grayscale',
1297
+ 'hsl',
1298
+ 'hsla',
1299
+ 'hwb',
1300
+ 'invert',
1301
+ 'linear-gradient',
1302
+ 'min',
1303
+ 'max',
1304
+ 'radial-gradient',
1305
+ 'repeating-linear-gradient',
1306
+ 'repeating-radial-gradient',
1307
+ 'rgb',
1308
+ 'rgba',
1309
+ 'rotate',
1310
+ 'saturate',
1311
+ 'var',
1312
+ ]) &&
1313
+ Compiler::isNativeFunction($parsed[1])
1314
+ ) {
1315
+ return false;
1316
+ }
1317
+
1318
+ switch ($parsed[0]) {
1319
+ case Type::T_BLOCK:
1320
+ case Type::T_KEYWORD:
1321
+ case Type::T_NULL:
1322
+ case Type::T_NUMBER:
1323
+ case Type::T_MEDIA:
1324
+ return $parsed;
1325
+
1326
+ case Type::T_COMMENT:
1327
+ if (isset($parsed[2])) {
1328
+ return false;
1329
+ }
1330
+ return $parsed;
1331
+
1332
+ case Type::T_DIRECTIVE:
1333
+ if (\is_array($parsed[1])) {
1334
+ $parsed[1][1] = $this->isPlainCssValidElement($parsed[1][1]);
1335
+ if (! $parsed[1][1]) {
1336
+ return false;
1337
+ }
1338
+ }
1339
+
1340
+ return $parsed;
1341
+
1342
+ case Type::T_IMPORT:
1343
+ if ($parsed[1][0] === Type::T_LIST) {
1344
+ return false;
1345
+ }
1346
+ $parsed[1] = $this->isPlainCssValidElement($parsed[1]);
1347
+ if ($parsed[1] === false) {
1348
+ return false;
1349
+ }
1350
+ return $parsed;
1351
+
1352
+ case Type::T_STRING:
1353
+ foreach ($parsed[2] as $k => $substr) {
1354
+ if (\is_array($substr)) {
1355
+ $parsed[2][$k] = $this->isPlainCssValidElement($substr);
1356
+ if (! $parsed[2][$k]) {
1357
+ return false;
1358
+ }
1359
+ }
1360
+ }
1361
+ return $parsed;
1362
+
1363
+ case Type::T_LIST:
1364
+ if (!empty($parsed['enclosing'])) {
1365
+ return false;
1366
+ }
1367
+ foreach ($parsed[2] as $k => $listElement) {
1368
+ $parsed[2][$k] = $this->isPlainCssValidElement($listElement);
1369
+ if (! $parsed[2][$k]) {
1370
+ return false;
1371
+ }
1372
+ }
1373
+ return $parsed;
1374
+
1375
+ case Type::T_ASSIGN:
1376
+ foreach ([1, 2, 3] as $k) {
1377
+ if (! empty($parsed[$k])) {
1378
+ $parsed[$k] = $this->isPlainCssValidElement($parsed[$k]);
1379
+ if (! $parsed[$k]) {
1380
+ return false;
1381
+ }
1382
+ }
1383
+ }
1384
+ return $parsed;
1385
+
1386
+ case Type::T_EXPRESSION:
1387
+ list( ,$op, $lhs, $rhs, $inParens, $whiteBefore, $whiteAfter) = $parsed;
1388
+ if (! $allowExpression && ! \in_array($op, ['and', 'or', '/'])) {
1389
+ return false;
1390
+ }
1391
+ $lhs = $this->isPlainCssValidElement($lhs, true);
1392
+ if (! $lhs) {
1393
+ return false;
1394
+ }
1395
+ $rhs = $this->isPlainCssValidElement($rhs, true);
1396
+ if (! $rhs) {
1397
+ return false;
1398
+ }
1399
+
1400
+ return [
1401
+ Type::T_STRING,
1402
+ '', [
1403
+ $this->inParens ? '(' : '',
1404
+ $lhs,
1405
+ ($whiteBefore ? ' ' : '') . $op . ($whiteAfter ? ' ' : ''),
1406
+ $rhs,
1407
+ $this->inParens ? ')' : ''
1408
+ ]
1409
+ ];
1410
+
1411
+ case Type::T_CUSTOM_PROPERTY:
1412
+ case Type::T_UNARY:
1413
+ $parsed[2] = $this->isPlainCssValidElement($parsed[2]);
1414
+ if (! $parsed[2]) {
1415
+ return false;
1416
+ }
1417
+ return $parsed;
1418
+
1419
+ case Type::T_FUNCTION:
1420
+ $argsList = $parsed[2];
1421
+ foreach ($argsList[2] as $argElement) {
1422
+ if (! $this->isPlainCssValidElement($argElement)) {
1423
+ return false;
1424
+ }
1425
+ }
1426
+ return $parsed;
1427
+
1428
+ case Type::T_FUNCTION_CALL:
1429
+ $parsed[0] = Type::T_FUNCTION;
1430
+ $argsList = [Type::T_LIST, ',', []];
1431
+ foreach ($parsed[2] as $arg) {
1432
+ if ($arg[0] || ! empty($arg[2])) {
1433
+ // no named arguments possible in a css function call
1434
+ // nor ... argument
1435
+ return false;
1436
+ }
1437
+ $arg = $this->isPlainCssValidElement($arg[1], $parsed[1] === 'calc');
1438
+ if (! $arg) {
1439
+ return false;
1440
+ }
1441
+ $argsList[2][] = $arg;
1442
+ }
1443
+ $parsed[2] = $argsList;
1444
+ return $parsed;
1445
+ }
1446
+
1447
+ return false;
1448
+ }
1449
+
1450
  /**
1451
  * Match string looking for either ending delim, escape, or string interpolation
1452
  *
1453
  * {@internal This is a workaround for preg_match's 250K string match limit. }}
1454
  *
1455
  * @param array $m Matches (passed by reference)
1456
+ * @param string $delim Delimiter
1457
+ *
1458
+ * @return bool True if match; false otherwise
1459
  *
1460
+ * @phpstan-impure
1461
  */
1462
  protected function matchString(&$m, $delim)
1463
  {
1466
  $end = \strlen($this->buffer);
1467
 
1468
  // look for either ending delim, escape, or string interpolation
1469
+ foreach (['#{', '\\', "\r", $delim] as $lookahead) {
1470
  $pos = strpos($this->buffer, $lookahead, $this->count);
1471
 
1472
  if ($pos !== false && $pos < $end) {
1493
  /**
1494
  * Try to match something on head of buffer
1495
  *
1496
+ * @param string $regex
1497
+ * @param array $out
1498
+ * @param bool $eatWhitespace
1499
  *
1500
+ * @return bool
1501
+ *
1502
+ * @phpstan-impure
1503
  */
1504
  protected function match($regex, &$out, $eatWhitespace = null)
1505
  {
1506
  $r = '/' . $regex . '/' . $this->patternModifiers;
1507
 
1508
+ if (! preg_match($r, $this->buffer, $out, 0, $this->count)) {
1509
  return false;
1510
  }
1511
 
1525
  /**
1526
  * Match a single string
1527
  *
1528
+ * @param string $char
1529
+ * @param bool $eatWhitespace
1530
+ *
1531
+ * @return bool
1532
  *
1533
+ * @phpstan-impure
1534
  */
1535
  protected function matchChar($char, $eatWhitespace = null)
1536
  {
1554
  /**
1555
  * Match literal string
1556
  *
1557
+ * @param string $what
1558
+ * @param int $len
1559
+ * @param bool $eatWhitespace
1560
+ *
1561
+ * @return bool
1562
  *
1563
+ * @phpstan-impure
1564
  */
1565
  protected function literal($what, $len, $eatWhitespace = null)
1566
  {
1584
  /**
1585
  * Match some whitespace
1586
  *
1587
+ * @return bool
1588
+ *
1589
+ * @phpstan-impure
1590
  */
1591
  protected function whitespace()
1592
  {
1593
  $gotWhite = false;
1594
 
1595
+ while (preg_match(static::$whitePattern, $this->buffer, $m, 0, $this->count)) {
1596
  if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
1597
  // comment that are kept in the output CSS
1598
  $comment = [];
1611
  if ($this->interpolation($out)) {
1612
  // keep right spaces in the following string part
1613
  if ($out[3]) {
1614
+ while ($this->buffer[$this->count - 1] !== '}') {
1615
  $this->count--;
1616
  }
1617
 
1620
 
1621
  $comment[] = [Type::T_COMMENT, substr($this->buffer, $p, $this->count - $p), $out];
1622
  } else {
1623
+ list($line, $column) = $this->getSourcePosition($this->count);
1624
+ $file = $this->sourceName;
1625
+ if (!$this->discardComments) {
1626
+ $this->logger->warn("Unterminated interpolations in multiline comments are deprecated and will be removed in ScssPhp 2.0, in \"$file\", line $line, column $column.", true);
1627
+ }
1628
  $comment[] = substr($this->buffer, $this->count, 2);
1629
 
1630
  $this->count += 2;
1638
 
1639
  if (! $comment) {
1640
  // single part static comment
1641
+ $commentStatement = [Type::T_COMMENT, $c];
1642
  } else {
1643
  $comment[] = $c;
1644
  $staticComment = substr($this->buffer, $startCommentCount, $endCommentCount - $startCommentCount);
1645
+ $commentStatement = [Type::T_COMMENT, $staticComment, [Type::T_STRING, '', $comment]];
1646
  }
1647
 
1648
+ list($line, $column) = $this->getSourcePosition($startCommentCount);
1649
+ $commentStatement[self::SOURCE_LINE] = $line;
1650
+ $commentStatement[self::SOURCE_COLUMN] = $column;
1651
+ $commentStatement[self::SOURCE_INDEX] = $this->sourceIndex;
1652
+
1653
+ $this->appendComment($commentStatement);
1654
+
1655
  $this->commentsSeen[$startCommentCount] = true;
1656
  $this->count = $endCommentCount;
1657
  } else {
1658
  // comment that are ignored and not kept in the output css
1659
  $this->count += \strlen($m[0]);
1660
+ // silent comments are not allowed in plain CSS files
1661
+ ! $this->cssOnly
1662
+ || ! \strlen(trim($m[0]))
1663
+ || $this->assertPlainCssValid(false, $this->count - \strlen($m[0]));
1664
  }
1665
 
1666
  $gotWhite = true;
1673
  * Append comment to current block
1674
  *
1675
  * @param array $comment
1676
+ *
1677
+ * @return void
1678
  */
1679
  protected function appendComment($comment)
1680
  {
1681
+ assert($this->env !== null);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1682
 
1683
+ if (! $this->discardComments) {
1684
  $this->env->comments[] = $comment;
1685
  }
1686
  }
1688
  /**
1689
  * Append statement to current block
1690
  *
1691
+ * @param array|null $statement
1692
+ * @param int $pos
1693
+ *
1694
+ * @return void
1695
  */
1696
  protected function append($statement, $pos = null)
1697
  {
1698
+ assert($this->env !== null);
1699
+
1700
  if (! \is_null($statement)) {
1701
+ ! $this->cssOnly || ($statement = $this->assertPlainCssValid($statement, $pos));
1702
+
1703
  if (! \is_null($pos)) {
1704
  list($line, $column) = $this->getSourcePosition($pos);
1705
 
1726
  */
1727
  protected function last()
1728
  {
1729
+ assert($this->env !== null);
1730
+
1731
  $i = \count($this->env->children) - 1;
1732
 
1733
  if (isset($this->env->children[$i])) {
1734
  return $this->env->children[$i];
1735
  }
1736
+
1737
+ return null;
1738
  }
1739
 
1740
  /**
1742
  *
1743
  * @param array $out
1744
  *
1745
+ * @return bool
1746
  */
1747
  protected function mediaQueryList(&$out)
1748
  {
1754
  *
1755
  * @param array $out
1756
  *
1757
+ * @return bool
1758
  */
1759
  protected function mediaQuery(&$out)
1760
  {
1761
  $expressions = null;
1762
  $parts = [];
1763
 
1764
+ if (
1765
+ ($this->literal('only', 4) && ($only = true) ||
1766
+ $this->literal('not', 3) && ($not = true) || true) &&
1767
  $this->mixedKeyword($mediaType)
1768
  ) {
1769
  $prop = [Type::T_MEDIA_TYPE];
1808
  *
1809
  * @param array $out
1810
  *
1811
+ * @return bool
1812
  */
1813
  protected function supportsQuery(&$out)
1814
  {
1819
 
1820
  $not = false;
1821
 
1822
+ if (
1823
+ ($this->literal('not', 3) && ($not = true) || true) &&
1824
  $this->matchChar('(') &&
1825
  ($this->expression($property)) &&
1826
  $this->literal(': ', 2) &&
1839
  $this->seek($s);
1840
  }
1841
 
1842
+ if (
1843
+ $this->matchChar('(') &&
1844
  $this->supportsQuery($subQuery) &&
1845
  $this->matchChar(')')
1846
  ) {
1850
  $this->seek($s);
1851
  }
1852
 
1853
+ if (
1854
+ $this->literal('not', 3) &&
1855
  $this->supportsQuery($subQuery)
1856
  ) {
1857
  $parts[] = [Type::T_STRING, '', [[Type::T_KEYWORD, 'not '], $subQuery]];
1860
  $this->seek($s);
1861
  }
1862
 
1863
+ if (
1864
+ $this->literal('selector(', 9) &&
1865
  $this->selector($selector) &&
1866
  $this->matchChar(')')
1867
  ) {
1898
  $this->seek($s);
1899
  }
1900
 
1901
+ if (
1902
+ $this->literal('and', 3) &&
1903
+ $this->genericList($expressions, 'supportsQuery', ' and', false)
1904
+ ) {
1905
  array_unshift($expressions[2], [Type::T_STRING, '', $parts]);
1906
 
1907
  $parts = [$expressions];
1910
  $this->seek($s);
1911
  }
1912
 
1913
+ if (
1914
+ $this->literal('or', 2) &&
1915
+ $this->genericList($expressions, 'supportsQuery', ' or', false)
1916
+ ) {
1917
  array_unshift($expressions[2], [Type::T_STRING, '', $parts]);
1918
 
1919
  $parts = [$expressions];
1941
  *
1942
  * @param array $out
1943
  *
1944
+ * @return bool
1945
  */
1946
  protected function mediaExpression(&$out)
1947
  {
1948
  $s = $this->count;
1949
  $value = null;
1950
 
1951
+ if (
1952
+ $this->matchChar('(') &&
1953
  $this->expression($feature) &&
1954
+ ($this->matchChar(':') &&
1955
+ $this->expression($value) || true) &&
1956
  $this->matchChar(')')
1957
  ) {
1958
  $out = [Type::T_MEDIA_EXPRESSION, $feature];
1974
  *
1975
  * @param array $out
1976
  *
1977
+ * @return bool
1978
  */
1979
  protected function argValues(&$out)
1980
  {
1981
+ $discardComments = $this->discardComments;
1982
+ $this->discardComments = true;
1983
+
1984
  if ($this->genericList($list, 'argValue', ',', false)) {
1985
  $out = $list[2];
1986
 
1987
+ $this->discardComments = $discardComments;
1988
+
1989
  return true;
1990
  }
1991
 
1992
+ $this->discardComments = $discardComments;
1993
+
1994
  return false;
1995
  }
1996
 
1999
  *
2000
  * @param array $out
2001
  *
2002
+ * @return bool
2003
  */
2004
  protected function argValue(&$out)
2005
  {
2013
  $keyword = null;
2014
  }
2015
 
2016
+ if ($this->genericList($value, 'expression', '', true)) {
2017
  $out = [$keyword, $value, false];
2018
  $s = $this->count;
2019
 
2029
  return false;
2030
  }
2031
 
2032
+ /**
2033
+ * Check if a generic directive is known to be able to allow almost any syntax or not
2034
+ * @param mixed $directiveName
2035
+ * @return bool
2036
+ */
2037
+ protected function isKnownGenericDirective($directiveName)
2038
+ {
2039
+ if (\is_array($directiveName) && \is_string(reset($directiveName))) {
2040
+ $directiveName = reset($directiveName);
2041
+ }
2042
+ if (! \is_string($directiveName)) {
2043
+ return false;
2044
+ }
2045
+ if (
2046
+ \in_array($directiveName, [
2047
+ 'at-root',
2048
+ 'media',
2049
+ 'mixin',
2050
+ 'include',
2051
+ 'scssphp-import-once',
2052
+ 'import',
2053
+ 'extend',
2054
+ 'function',
2055
+ 'break',
2056
+ 'continue',
2057
+ 'return',
2058
+ 'each',
2059
+ 'while',
2060
+ 'for',
2061
+ 'if',
2062
+ 'debug',
2063
+ 'warn',
2064
+ 'error',
2065
+ 'content',
2066
+ 'else',
2067
+ 'charset',
2068
+ 'supports',
2069
+ // Todo
2070
+ 'use',
2071
+ 'forward',
2072
+ ])
2073
+ ) {
2074
+ return true;
2075
+ }
2076
+ return false;
2077
+ }
2078
+
2079
  /**
2080
  * Parse directive value list that considers $vars as keyword
2081
  *
2082
+ * @param array $out
2083
+ * @param string|false $endChar
2084
  *
2085
+ * @return bool
2086
+ *
2087
+ * @phpstan-impure
2088
  */
2089
  protected function directiveValue(&$out, $endChar = false)
2090
  {
2102
 
2103
  $this->seek($s);
2104
 
2105
+ if (\is_string($endChar) && $this->openString($endChar ? $endChar : ';', $out, null, null, true, ";}{")) {
2106
+ if ($endChar && $this->matchChar($endChar, false)) {
2107
+ return true;
2108
+ }
2109
+ $ss = $this->count;
2110
+ if (!$endChar && $this->end()) {
2111
+ $this->seek($ss);
2112
  return true;
2113
  }
2114
  }
2145
  *
2146
  * @param array $out
2147
  *
2148
+ * @return bool
2149
  */
2150
  protected function valueList(&$out)
2151
  {
2157
  return $res;
2158
  }
2159
 
2160
+ /**
2161
+ * Parse a function call, where externals () are part of the call
2162
+ * and not of the value list
2163
+ *
2164
+ * @param array $out
2165
+ * @param bool $mandatoryEnclos
2166
+ * @param null|string $charAfter
2167
+ * @param null|bool $eatWhiteSp
2168
+ *
2169
+ * @return bool
2170
+ */
2171
+ protected function functionCallArgumentsList(&$out, $mandatoryEnclos = true, $charAfter = null, $eatWhiteSp = null)
2172
+ {
2173
+ $s = $this->count;
2174
+
2175
+ if (
2176
+ $this->matchChar('(') &&
2177
+ $this->valueList($out) &&
2178
+ $this->matchChar(')') &&
2179
+ ($charAfter ? $this->matchChar($charAfter, $eatWhiteSp) : $this->end())
2180
+ ) {
2181
+ return true;
2182
+ }
2183
+
2184
+ if (! $mandatoryEnclos) {
2185
+ $this->seek($s);
2186
+
2187
+ if (
2188
+ $this->valueList($out) &&
2189
+ ($charAfter ? $this->matchChar($charAfter, $eatWhiteSp) : $this->end())
2190
+ ) {
2191
+ return true;
2192
+ }
2193
+ }
2194
+
2195
+ $this->seek($s);
2196
+
2197
+ return false;
2198
+ }
2199
+
2200
  /**
2201
  * Parse space separated value list
2202
  *
2203
  * @param array $out
2204
  *
2205
+ * @return bool
2206
  */
2207
  protected function spaceList(&$out)
2208
  {
2212
  /**
2213
  * Parse generic list
2214
  *
2215
+ * @param array $out
2216
+ * @param string $parseItem The name of the method used to parse items
2217
+ * @param string $delim
2218
+ * @param bool $flatten
2219
  *
2220
+ * @return bool
2221
  */
2222
  protected function genericList(&$out, $parseItem, $delim = '', $flatten = true)
2223
  {
2224
  $s = $this->count;
2225
  $items = [];
2226
+ /** @var array|Number|null $value */
2227
  $value = null;
2228
 
2229
  while ($this->$parseItem($value)) {
2236
  }
2237
 
2238
  $trailing_delim = true;
2239
+ } else {
2240
+ assert(\is_array($value) || $value instanceof Number);
2241
+ // if no delim watch that a keyword didn't eat the single/double quote
2242
+ // from the following starting string
2243
+ if ($value[0] === Type::T_KEYWORD) {
2244
+ assert(\is_array($value));
2245
+ /** @var string $word */
2246
+ $word = $value[1];
2247
+
2248
+ $last_char = substr($word, -1);
2249
+
2250
+ if (
2251
+ strlen($word) > 1 &&
2252
+ in_array($last_char, [ "'", '"']) &&
2253
+ substr($word, -2, 1) !== '\\'
2254
+ ) {
2255
+ // if there is a non escaped opening quote in the keyword, this seems unlikely a mistake
2256
+ $word = str_replace('\\' . $last_char, '\\\\', $word);
2257
+ if (strpos($word, $last_char) < strlen($word) - 1) {
2258
+ continue;
2259
+ }
2260
+
2261
+ $currentCount = $this->count;
2262
+
2263
+ // let's try to rewind to previous char and try a parse
2264
+ $this->count--;
2265
+ // in case the keyword also eat spaces
2266
+ while (substr($this->buffer, $this->count, 1) !== $last_char) {
2267
+ $this->count--;
2268
+ }
2269
+
2270
+ /** @var array|Number|null $nextValue */
2271
+ $nextValue = null;
2272
+ if ($this->$parseItem($nextValue)) {
2273
+ assert(\is_array($nextValue) || $nextValue instanceof Number);
2274
+ if ($nextValue[0] === Type::T_KEYWORD && $nextValue[1] === $last_char) {
2275
+ // bad try, forget it
2276
+ $this->seek($currentCount);
2277
+ continue;
2278
+ }
2279
+ if ($nextValue[0] !== Type::T_STRING) {
2280
+ // bad try, forget it
2281
+ $this->seek($currentCount);
2282
+ continue;
2283
+ }
2284
+
2285
+ // OK it was a good idea
2286
+ $value[1] = substr($value[1], 0, -1);
2287
+ array_pop($items);
2288
+ $items[] = $value;
2289
+ $items[] = $nextValue;
2290
+ } else {
2291
+ // bad try, forget it
2292
+ $this->seek($currentCount);
2293
+ continue;
2294
+ }
2295
+ }
2296
+ }
2297
  }
2298
  }
2299
 
2319
  /**
2320
  * Parse expression
2321
  *
2322
+ * @param array $out
2323
+ * @param bool $listOnly
2324
+ * @param bool $lookForExp
2325
  *
2326
+ * @return bool
2327
+ *
2328
+ * @phpstan-impure
2329
  */
2330
  protected function expression(&$out, $listOnly = false, $lookForExp = true)
2331
  {
2335
  $allowedTypes = ($listOnly ? [Type::T_LIST] : [Type::T_LIST, Type::T_MAP]);
2336
 
2337
  if ($this->matchChar('(')) {
2338
+ if ($this->enclosedExpression($lhs, $s, ')', $allowedTypes)) {
2339
  if ($lookForExp) {
2340
  $out = $this->expHelper($lhs, 0);
2341
  } else {
2351
  }
2352
 
2353
  if (\in_array(Type::T_LIST, $allowedTypes) && $this->matchChar('[')) {
2354
+ if ($this->enclosedExpression($lhs, $s, ']', [Type::T_LIST])) {
2355
  if ($lookForExp) {
2356
  $out = $this->expHelper($lhs, 0);
2357
  } else {
2386
  /**
2387
  * Parse expression specifically checking for lists in parenthesis or brackets
2388
  *
2389
+ * @param array $out
2390
+ * @param int $s
2391
+ * @param string $closingParen
2392
+ * @param string[] $allowedTypes
2393
+ *
2394
+ * @return bool
2395
  *
2396
+ * @phpstan-param array<Type::*> $allowedTypes
2397
  */
2398
+ protected function enclosedExpression(&$out, $s, $closingParen = ')', $allowedTypes = [Type::T_LIST, Type::T_MAP])
2399
  {
2400
  if ($this->matchChar($closingParen) && \in_array(Type::T_LIST, $allowedTypes)) {
2401
  $out = [Type::T_LIST, '', []];
2402
 
2403
  switch ($closingParen) {
2404
+ case ')':
2405
  $out['enclosing'] = 'parent'; // parenthesis list
2406
  break;
2407
 
2408
+ case ']':
2409
  $out['enclosing'] = 'bracket'; // bracketed list
2410
  break;
2411
  }
2413
  return true;
2414
  }
2415
 
2416
+ if (
2417
+ $this->valueList($out) &&
2418
+ $this->matchChar($closingParen) && ! ($closingParen === ')' &&
2419
+ \in_array($out[0], [Type::T_EXPRESSION, Type::T_UNARY])) &&
2420
  \in_array(Type::T_LIST, $allowedTypes)
2421
  ) {
2422
  if ($out[0] !== Type::T_LIST || ! empty($out['enclosing'])) {
2424
  }
2425
 
2426
  switch ($closingParen) {
2427
+ case ')':
2428
  $out['enclosing'] = 'parent'; // parenthesis list
2429
  break;
2430
 
2431
+ case ']':
2432
  $out['enclosing'] = 'bracket'; // bracketed list
2433
  break;
2434
  }
2448
  /**
2449
  * Parse left-hand side of subexpression
2450
  *
2451
+ * @param array $lhs
2452
+ * @param int $minP
2453
  *
2454
  * @return array
2455
  */
2480
  break;
2481
  }
2482
 
2483
+ if ($op === '-' && ! $whiteAfter && $rhs[0] === Type::T_KEYWORD) {
2484
+ break;
 
2485
  }
2486
 
2487
+ // consume higher-precedence operators on the right-hand side
2488
+ $rhs = $this->expHelper($rhs, static::$precedence[$op] + 1);
2489
+
2490
  $lhs = [Type::T_EXPRESSION, $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter];
2491
+
2492
  $ss = $this->count;
2493
  $whiteBefore = isset($this->buffer[$this->count - 1]) &&
2494
  ctype_space($this->buffer[$this->count - 1]);
2504
  *
2505
  * @param array $out
2506
  *
2507
+ * @return bool
2508
  */
2509
  protected function value(&$out)
2510
  {
2515
  $s = $this->count;
2516
  $char = $this->buffer[$this->count];
2517
 
2518
+ if (
2519
+ $this->literal('url(', 4) &&
2520
+ $this->match('data:([a-z]+)\/([a-z0-9.+-]+);base64,', $m, false)
2521
+ ) {
2522
  $len = strspn(
2523
  $this->buffer,
2524
  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwyxz0123456789+/=',
2537
 
2538
  $this->seek($s);
2539
 
2540
+ if (
2541
+ $this->literal('url(', 4, false) &&
2542
+ $this->match('\s*(\/\/[^\s\)]+)\s*', $m)
2543
+ ) {
2544
  $content = 'url(' . $m[1];
2545
 
2546
  if ($this->matchChar(')')) {
2555
 
2556
  // not
2557
  if ($char === 'n' && $this->literal('not', 3, false)) {
2558
+ if (
2559
+ $this->whitespace() &&
2560
+ $this->value($inner)
2561
+ ) {
2562
  $out = [Type::T_UNARY, 'not', $inner, $this->inParens];
2563
 
2564
  return true;
2613
  return true;
2614
  }
2615
 
2616
+ if (
2617
+ $this->keyword($inner) &&
2618
+ ! $this->func($inner, $out)
2619
+ ) {
2620
  $out = [Type::T_UNARY, '-', $inner, $this->inParens];
2621
 
2622
  return true;
2644
  $this->count++;
2645
 
2646
  if ($this->keyword($keyword)) {
2647
+ $out = [Type::T_KEYWORD, '#' . $keyword];
2648
 
2649
  return true;
2650
  }
2675
  }
2676
 
2677
  // unicode range with wildcards
2678
+ if (
2679
+ $this->literal('U+', 2) &&
2680
+ $this->match('\?+|([0-9A-F]+(\?+|(-[0-9A-F]+))?)', $m, false)
2681
+ ) {
2682
+ $unicode = explode('-', $m[0]);
2683
+ if (strlen(reset($unicode)) <= 6 && strlen(end($unicode)) <= 6) {
2684
+ $out = [Type::T_KEYWORD, 'U+' . $m[0]];
2685
 
2686
+ return true;
2687
+ }
2688
+ $this->count -= strlen($m[0]) + 2;
2689
  }
2690
 
2691
  if ($this->keyword($keyword, false)) {
2712
  *
2713
  * @param array $out
2714
  *
2715
+ * @return bool
2716
  */
2717
  protected function parenValue(&$out)
2718
  {
2729
 
2730
  $this->inParens = true;
2731
 
2732
+ if (
2733
+ $this->expression($exp) &&
2734
+ $this->matchChar(')')
2735
+ ) {
2736
  $out = $exp;
2737
  $this->inParens = $inParens;
2738
 
2751
  *
2752
  * @param array $out
2753
  *
2754
+ * @return bool
2755
  */
2756
  protected function progid(&$out)
2757
  {
2758
  $s = $this->count;
2759
 
2760
+ if (
2761
+ $this->literal('progid:', 7, false) &&
2762
  $this->openString('(', $fn) &&
2763
  $this->matchChar('(')
2764
  ) {
2784
  * @param string $name
2785
  * @param array $func
2786
  *
2787
+ * @return bool
2788
  */
2789
  protected function func($name, &$func)
2790
  {
2800
  if ($name !== 'expression' && ! preg_match('/^(-[a-z]+-)?calc$/', $name)) {
2801
  $ss = $this->count;
2802
 
2803
+ if (
2804
+ $this->argValues($args) &&
2805
+ $this->matchChar(')')
2806
+ ) {
2807
  $func = [Type::T_FUNCTION_CALL, $name, $args];
2808
 
2809
  return true;
2812
  $this->seek($ss);
2813
  }
2814
 
2815
+ if (
2816
+ ($this->openString(')', $str, '(') || true) &&
2817
  $this->matchChar(')')
2818
  ) {
2819
  $args = [];
2838
  *
2839
  * @param array $out
2840
  *
2841
+ * @return bool
2842
  */
2843
  protected function argumentList(&$out)
2844
  {
2848
  $args = [];
2849
 
2850
  while ($this->keyword($var)) {
2851
+ if (
2852
+ $this->matchChar('=') &&
2853
+ $this->expression($exp)
2854
+ ) {
2855
  $args[] = [Type::T_STRING, '', [$var . '=']];
2856
  $arg = $exp;
2857
  } else {
2883
  *
2884
  * @param array $out
2885
  *
2886
+ * @return bool
2887
  */
2888
  protected function argumentDef(&$out)
2889
  {
2897
 
2898
  $ss = $this->count;
2899
 
2900
+ if (
2901
+ $this->matchChar(':') &&
2902
+ $this->genericList($defaultVal, 'expression', '', true)
2903
+ ) {
2904
  $arg[1] = $defaultVal;
2905
  } else {
2906
  $this->seek($ss);
2912
  $sss = $this->count;
2913
 
2914
  if (! $this->matchChar(')')) {
2915
+ throw $this->parseError('... has to be after the final argument');
2916
  }
2917
 
2918
  $arg[2] = true;
2945
  *
2946
  * @param array $out
2947
  *
2948
+ * @return bool
2949
  */
2950
  protected function map(&$out)
2951
  {
2958
  $keys = [];
2959
  $values = [];
2960
 
2961
+ while (
2962
+ $this->genericList($key, 'expression', '', true) &&
2963
+ $this->matchChar(':') &&
2964
+ $this->genericList($value, 'expression', '', true)
2965
  ) {
2966
  $keys[] = $key;
2967
  $values[] = $value;
2987
  *
2988
  * @param array $out
2989
  *
2990
+ * @return bool
2991
  */
2992
  protected function color(&$out)
2993
  {
2994
  $s = $this->count;
2995
 
2996
+ if ($this->match('(#([0-9a-f]+)\b)', $m)) {
2997
  if (\in_array(\strlen($m[2]), [3,4,6,8])) {
2998
  $out = [Type::T_KEYWORD, $m[0]];
2999
 
3013
  *
3014
  * @param array $unit
3015
  *
3016
+ * @return bool
3017
  */
3018
  protected function unit(&$unit)
3019
  {
3038
  * Parse string
3039
  *
3040
  * @param array $out
3041
+ * @param bool $keepDelimWithInterpolation
3042
  *
3043
+ * @return bool
3044
  */
3045
+ protected function string(&$out, $keepDelimWithInterpolation = false)
3046
  {
3047
  $s = $this->count;
3048
 
3074
  $this->count += \strlen($m[2]);
3075
  $content[] = '#{'; // ignore it
3076
  }
3077
+ } elseif ($m[2] === "\r") {
3078
+ $content[] = chr(10);
3079
+ // TODO : warning
3080
+ # DEPRECATION WARNING on line x, column y of zzz:
3081
+ # Unescaped multiline strings are deprecated and will be removed in a future version of Sass.
3082
+ # To include a newline in a string, use "\a" or "\a " as in CSS.
3083
+ if ($this->matchChar("\n", false)) {
3084
+ $content[] = ' ';
3085
+ }
3086
  } elseif ($m[2] === '\\') {
3087
+ if (
3088
+ $this->literal("\r\n", 2, false) ||
 
 
 
 
 
3089
  $this->matchChar("\r", false) ||
3090
  $this->matchChar("\n", false) ||
3091
  $this->matchChar("\f", false)
3092
  ) {
3093
  // this is a continuation escaping, to be ignored
3094
+ } elseif ($this->matchEscapeCharacter($c)) {
3095
+ $content[] = $c;
3096
  } else {
3097
+ throw $this->parseError('Unterminated escape sequence');
3098
  }
3099
  } else {
3100
  $this->count -= \strlen($delim);
3105
  $this->eatWhiteDefault = $oldWhite;
3106
 
3107
  if ($this->literal($delim, \strlen($delim))) {
3108
+ if ($hasInterpolation && ! $keepDelimWithInterpolation) {
3109
  $delim = '"';
 
 
 
 
 
 
 
 
 
 
3110
  }
3111
 
3112
  $out = [Type::T_STRING, $delim, $content];
3119
  return false;
3120
  }
3121
 
3122
+ /**
3123
+ * @param string $out
3124
+ * @param bool $inKeywords
3125
+ *
3126
+ * @return bool
3127
+ */
3128
+ protected function matchEscapeCharacter(&$out, $inKeywords = false)
3129
+ {
3130
+ $s = $this->count;
3131
+ if ($this->match('[a-f0-9]', $m, false)) {
3132
+ $hex = $m[0];
3133
+
3134
+ for ($i = 5; $i--;) {
3135
+ if ($this->match('[a-f0-9]', $m, false)) {
3136
+ $hex .= $m[0];
3137
+ } else {
3138
+ break;
3139
+ }
3140
+ }
3141
+
3142
+ // CSS allows Unicode escape sequences to be followed by a delimiter space
3143
+ // (necessary in some cases for shorter sequences to disambiguate their end)
3144
+ $this->matchChar(' ', false);
3145
+
3146
+ $value = hexdec($hex);
3147
+
3148
+ if (!$inKeywords && ($value == 0 || ($value >= 0xD800 && $value <= 0xDFFF) || $value >= 0x10FFFF)) {
3149
+ $out = "\xEF\xBF\xBD"; // "\u{FFFD}" but with a syntax supported on PHP 5
3150
+ } elseif ($value < 0x20) {
3151
+ $out = Util::mbChr($value);
3152
+ } else {
3153
+ $out = Util::mbChr($value);
3154
+ }
3155
+
3156
+ return true;
3157
+ }
3158
+
3159
+ if ($this->match('.', $m, false)) {
3160
+ if ($inKeywords && in_array($m[0], ["'",'"','@','&',' ','\\',':','/','%'])) {
3161
+ $this->seek($s);
3162
+ return false;
3163
+ }
3164
+ $out = $m[0];
3165
+
3166
+ return true;
3167
+ }
3168
+
3169
+ return false;
3170
+ }
3171
+
3172
  /**
3173
  * Parse keyword or interpolation
3174
  *
3175
+ * @param array $out
3176
+ * @param bool $restricted
3177
  *
3178
+ * @return bool
3179
  */
3180
  protected function mixedKeyword(&$out, $restricted = false)
3181
  {
3216
  /**
3217
  * Parse an unbounded string stopped by $end
3218
  *
3219
+ * @param string $end
3220
+ * @param array $out
3221
+ * @param string $nestOpen
3222
+ * @param string $nestClose
3223
+ * @param bool $rtrim
3224
+ * @param string $disallow
3225
  *
3226
+ * @return bool
3227
  */
3228
+ protected function openString($end, &$out, $nestOpen = null, $nestClose = null, $rtrim = true, $disallow = null)
3229
  {
3230
  $oldWhite = $this->eatWhiteDefault;
3231
  $this->eatWhiteDefault = false;
3232
 
3233
+ if ($nestOpen && ! $nestClose) {
3234
+ $nestClose = $end;
3235
  }
3236
 
3237
+ $patt = ($disallow ? '[^' . $this->pregQuote($disallow) . ']' : '.');
3238
+ $patt = '(' . $patt . '*?)([\'"]|#\{|'
3239
  . $this->pregQuote($end) . '|'
3240
+ . (($nestClose && $nestClose !== $end) ? $this->pregQuote($nestClose) . '|' : '')
3241
  . static::$commentPattern . ')';
3242
 
3243
  $nestingLevel = 0;
3248
  if (isset($m[1]) && $m[1] !== '') {
3249
  $content[] = $m[1];
3250
 
3251
+ if ($nestOpen) {
3252
+ $nestingLevel += substr_count($m[1], $nestOpen);
3253
  }
3254
  }
3255
 
3256
  $tok = $m[2];
3257
 
3258
+ $this->count -= \strlen($tok);
3259
 
3260
  if ($tok === $end && ! $nestingLevel) {
3261
  break;
3262
  }
3263
 
3264
+ if ($tok === $nestClose) {
3265
  $nestingLevel--;
3266
  }
3267
 
3268
+ if (($tok === "'" || $tok === '"') && $this->string($str, true)) {
3269
  $content[] = $str;
3270
  continue;
3271
  }
3276
  }
3277
 
3278
  $content[] = $tok;
3279
+ $this->count += \strlen($tok);
3280
  }
3281
 
3282
  $this->eatWhiteDefault = $oldWhite;
3286
  }
3287
 
3288
  // trim the end
3289
+ if ($rtrim && \is_string(end($content))) {
3290
  $content[\count($content) - 1] = rtrim(end($content));
3291
  }
3292
 
3299
  * Parser interpolation
3300
  *
3301
  * @param string|array $out
3302
+ * @param bool $lookWhite save information about whitespace before and after
3303
  *
3304
+ * @return bool
3305
  */
3306
  protected function interpolation(&$out, $lookWhite = true)
3307
  {
3312
 
3313
  $s = $this->count;
3314
 
3315
+ if (
3316
+ $this->literal('#{', 2) &&
3317
+ $this->valueList($value) &&
3318
+ $this->matchChar('}', false)
3319
+ ) {
3320
  if ($value === [Type::T_SELF]) {
3321
  $out = $value;
3322
  } else {
3323
  if ($lookWhite) {
3324
  $left = ($s > 0 && preg_match('/\s/', $this->buffer[$s - 1])) ? ' ' : '';
3325
+ $right = (
3326
+ ! empty($this->buffer[$this->count]) &&
3327
+ preg_match('/\s/', $this->buffer[$this->count])
3328
+ ) ? ' ' : '';
3329
  } else {
3330
  $left = $right = false;
3331
  }
3356
  *
3357
  * @param array $out
3358
  *
3359
+ * @return bool
3360
  */
3361
  protected function propertyName(&$out)
3362
  {
3392
  }
3393
 
3394
  // match comment hack
3395
+ if (preg_match(static::$whitePattern, $this->buffer, $m, 0, $this->count)) {
3396
  if (! empty($m[0])) {
3397
  $parts[] = $m[0];
3398
  $this->count += \strlen($m[0]);
3411
  *
3412
  * @param array $out
3413
  *
3414
+ * @return bool
3415
  */
3416
  protected function customProperty(&$out)
3417
  {
3432
  continue;
3433
  }
3434
 
3435
+ if ($this->matchChar('&', false)) {
3436
+ $parts[] = [Type::T_SELF];
3437
+ continue;
3438
+ }
3439
+
3440
  if ($this->variable($var)) {
3441
  $parts[] = $var;
3442
  continue;
3468
  /**
3469
  * Parse comma separated selector list
3470
  *
3471
+ * @param array $out
3472
+ * @param string|bool $subSelector
3473
  *
3474
+ * @return bool
3475
  */
3476
  protected function selectors(&$out, $subSelector = false)
3477
  {
3504
  /**
3505
  * Parse whitespace separated selector list
3506
  *
3507
+ * @param array $out
3508
+ * @param string|bool $subSelector
3509
  *
3510
+ * @return bool
3511
  */
3512
  protected function selector(&$out, $subSelector = false)
3513
  {
3514
  $selector = [];
3515
 
3516
+ $discardComments = $this->discardComments;
3517
+ $this->discardComments = true;
3518
+
3519
  for (;;) {
3520
  $s = $this->count;
3521
 
3522
  if ($this->match('[>+~]+', $m, true)) {
3523
+ if (
3524
+ $subSelector && \is_string($subSelector) && strpos($subSelector, 'nth-') === 0 &&
3525
  $m[0] === '+' && $this->match("(\d+|n\b)", $counter)
3526
  ) {
3527
  $this->seek($s);
3533
 
3534
  if ($this->selectorSingle($part, $subSelector)) {
3535
  $selector[] = $part;
3536
+ $this->whitespace();
 
 
 
 
 
3537
  continue;
3538
  }
3539
 
3540
  break;
3541
  }
3542
 
3543
+ $this->discardComments = $discardComments;
3544
+
3545
  if (! $selector) {
3546
  return false;
3547
  }
3551
  return true;
3552
  }
3553
 
3554
+ /**
3555
+ * parsing escaped chars in selectors:
3556
+ * - escaped single chars are kept escaped in the selector but in a normalized form
3557
+ * (if not in 0-9a-f range as this would be ambigous)
3558
+ * - other escaped sequences (multibyte chars or 0-9a-f) are kept in their initial escaped form,
3559
+ * normalized to lowercase
3560
+ *
3561
+ * TODO: this is a fallback solution. Ideally escaped chars in selectors should be encoded as the genuine chars,
3562
+ * and escaping added when printing in the Compiler, where/if it's mandatory
3563
+ * - but this require a better formal selector representation instead of the array we have now
3564
+ *
3565
+ * @param string $out
3566
+ * @param bool $keepEscapedNumber
3567
+ *
3568
+ * @return bool
3569
+ */
3570
+ protected function matchEscapeCharacterInSelector(&$out, $keepEscapedNumber = false)
3571
+ {
3572
+ $s_escape = $this->count;
3573
+ if ($this->match('\\\\', $m)) {
3574
+ $out = '\\' . $m[0];
3575
+ return true;
3576
+ }
3577
+
3578
+ if ($this->matchEscapeCharacter($escapedout, true)) {
3579
+ if (strlen($escapedout) === 1) {
3580
+ if (!preg_match(",\w,", $escapedout)) {
3581
+ $out = '\\' . $escapedout;
3582
+ return true;
3583
+ } elseif (! $keepEscapedNumber || ! \is_numeric($escapedout)) {
3584
+ $out = $escapedout;
3585
+ return true;
3586
+ }
3587
+ }
3588
+ $escape_sequence = rtrim(substr($this->buffer, $s_escape, $this->count - $s_escape));
3589
+ if (strlen($escape_sequence) < 6) {
3590
+ $escape_sequence .= ' ';
3591
+ }
3592
+ $out = '\\' . strtolower($escape_sequence);
3593
+ return true;
3594
+ }
3595
+ if ($this->match('\\S', $m)) {
3596
+ $out = '\\' . $m[0];
3597
+ return true;
3598
+ }
3599
+
3600
+
3601
+ return false;
3602
+ }
3603
+
3604
  /**
3605
  * Parse the parts that make up a selector
3606
  *
3608
  * div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
3609
  * }}
3610
  *
3611
+ * @param array $out
3612
+ * @param string|bool $subSelector
3613
  *
3614
+ * @return bool
3615
  */
3616
  protected function selectorSingle(&$out, $subSelector = false)
3617
  {
3647
  case '&':
3648
  $parts[] = Compiler::$selfSelector;
3649
  $this->count++;
3650
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
3651
  continue 2;
3652
 
3653
  case '.':
3661
  continue 2;
3662
  }
3663
 
3664
+ // handling of escaping in selectors : get the escaped char
3665
+ if ($char === '\\') {
3666
+ $this->count++;
3667
+ if ($this->matchEscapeCharacterInSelector($escaped, true)) {
3668
+ $parts[] = $escaped;
3669
+ continue;
3670
+ }
3671
+ $this->count--;
3672
  }
3673
 
3674
  if ($char === '%') {
3677
  if ($this->placeholder($placeholder)) {
3678
  $parts[] = '%';
3679
  $parts[] = $placeholder;
3680
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
3681
  continue;
3682
  }
3683
 
3687
  if ($char === '#') {
3688
  if ($this->interpolation($inter)) {
3689
  $parts[] = $inter;
3690
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
3691
  continue;
3692
  }
3693
 
3715
 
3716
  $ss = $this->count;
3717
 
3718
+ if (
3719
+ $nameParts === ['not'] ||
3720
+ $nameParts === ['is'] ||
3721
+ $nameParts === ['has'] ||
3722
+ $nameParts === ['where'] ||
3723
  $nameParts === ['slotted'] ||
3724
+ $nameParts === ['nth-child'] ||
3725
+ $nameParts === ['nth-last-child'] ||
3726
+ $nameParts === ['nth-of-type'] ||
3727
+ $nameParts === ['nth-last-of-type']
3728
  ) {
3729
+ if (
3730
+ $this->matchChar('(', true) &&
3731
  ($this->selectors($subs, reset($nameParts)) || true) &&
3732
  $this->matchChar(')')
3733
  ) {
3753
  } else {
3754
  $this->seek($ss);
3755
  }
3756
+ } elseif (
3757
+ $this->matchChar('(', true) &&
3758
+ ($this->openString(')', $str, '(') || true) &&
3759
+ $this->matchChar(')')
3760
+ ) {
3761
+ $parts[] = '(';
 
 
 
 
3762
 
3763
+ if (! empty($str)) {
3764
+ $parts[] = $str;
 
3765
  }
3766
+
3767
+ $parts[] = ')';
3768
+ } else {
3769
+ $this->seek($ss);
3770
  }
3771
 
3772
  continue;
3787
  $this->seek($s);
3788
 
3789
  // attribute selector
3790
+ if (
3791
+ $char === '[' &&
3792
  $this->matchChar('[') &&
3793
  ($this->openString(']', $str, '[') || true) &&
3794
  $this->matchChar(']')
3811
  continue;
3812
  }
3813
 
3814
+ if ($this->restrictedKeyword($name, false, true)) {
3815
  $parts[] = $name;
3816
  continue;
3817
  }
3835
  *
3836
  * @param array $out
3837
  *
3838
+ * @return bool
3839
  */
3840
  protected function variable(&$out)
3841
  {
3842
  $s = $this->count;
3843
 
3844
+ if (
3845
+ $this->matchChar('$', false) &&
3846
+ $this->keyword($name)
3847
+ ) {
3848
  if ($this->allowVars) {
3849
  $out = [Type::T_VARIABLE, $name];
3850
  } else {
3862
  /**
3863
  * Parse a keyword
3864
  *
3865
+ * @param string $word
3866
+ * @param bool $eatWhitespace
3867
+ * @param bool $inSelector
3868
  *
3869
+ * @return bool
3870
  */
3871
+ protected function keyword(&$word, $eatWhitespace = null, $inSelector = false)
3872
  {
3873
+ $s = $this->count;
3874
+ $match = $this->match(
3875
  $this->utf8
3876
+ ? '(([\pL\w\x{00A0}-\x{10FFFF}_\-\*!"\']|\\\\[a-f0-9]{6} ?|\\\\[a-f0-9]{1,5}(?![a-f0-9]) ?|[\\\\].)([\pL\w\x{00A0}-\x{10FFFF}\-_"\']|\\\\[a-f0-9]{6} ?|\\\\[a-f0-9]{1,5}(?![a-f0-9]) ?|[\\\\].)*)'
3877
+ : '(([\w_\-\*!"\']|\\\\[a-f0-9]{6} ?|\\\\[a-f0-9]{1,5}(?![a-f0-9]) ?|[\\\\].)([\w\-_"\']|\\\\[a-f0-9]{6} ?|\\\\[a-f0-9]{1,5}(?![a-f0-9]) ?|[\\\\].)*)',
3878
  $m,
3879
+ false
3880
+ );
3881
+
3882
+ if ($match) {
3883
  $word = $m[1];
3884
 
3885
+ // handling of escaping in keyword : get the escaped char
3886
+ if (strpos($word, '\\') !== false) {
3887
+ $send = $this->count;
3888
+ $escapedWord = [];
3889
+ $this->seek($s);
3890
+ $previousEscape = false;
3891
+ while ($this->count < $send) {
3892
+ $char = $this->buffer[$this->count];
3893
+ $this->count++;
3894
+ if (
3895
+ $this->count < $send
3896
+ && $char === '\\'
3897
+ && !$previousEscape
3898
+ && (
3899
+ $inSelector ?
3900
+ $this->matchEscapeCharacterInSelector($out)
3901
+ :
3902
+ $this->matchEscapeCharacter($out, true)
3903
+ )
3904
+ ) {
3905
+ $escapedWord[] = $out;
3906
+ } else {
3907
+ if ($previousEscape) {
3908
+ $previousEscape = false;
3909
+ } elseif ($char === '\\') {
3910
+ $previousEscape = true;
3911
+ }
3912
+ $escapedWord[] = $char;
3913
+ }
3914
+ }
3915
+
3916
+ $word = implode('', $escapedWord);
3917
+ }
3918
+
3919
+ if (is_null($eatWhitespace) ? $this->eatWhiteDefault : $eatWhitespace) {
3920
+ $this->whitespace();
3921
+ }
3922
+
3923
  return true;
3924
  }
3925
 
3929
  /**
3930
  * Parse a keyword that should not start with a number
3931
  *
3932
+ * @param string $word
3933
+ * @param bool $eatWhitespace
3934
+ * @param bool $inSelector
3935
  *
3936
+ * @return bool
3937
  */
3938
+ protected function restrictedKeyword(&$word, $eatWhitespace = null, $inSelector = false)
3939
  {
3940
  $s = $this->count;
3941
 
3942
+ if ($this->keyword($word, $eatWhitespace, $inSelector) && (\ord($word[0]) > 57 || \ord($word[0]) < 48)) {
3943
  return true;
3944
  }
3945
 
3953
  *
3954
  * @param string|array $placeholder
3955
  *
3956
+ * @return bool
3957
  */
3958
  protected function placeholder(&$placeholder)
3959
  {
3960
+ $match = $this->match(
3961
  $this->utf8
3962
  ? '([\pL\w\-_]+)'
3963
  : '([\w\-_]+)',
3964
  $m
3965
+ );
3966
+
3967
+ if ($match) {
3968
  $placeholder = $m[1];
3969
 
3970
  return true;
3982
  *
3983
  * @param array $out
3984
  *
3985
+ * @return bool
3986
  */
3987
  protected function url(&$out)
3988
  {
3989
+ if ($this->literal('url(', 4)) {
3990
+ $s = $this->count;
3991
 
3992
+ if (
3993
+ ($this->string($out) || $this->spaceList($out)) &&
3994
+ $this->matchChar(')')
3995
+ ) {
3996
+ $out = [Type::T_STRING, '', ['url(', $out, ')']];
3997
+
3998
+ return true;
3999
+ }
4000
+
4001
+ $this->seek($s);
4002
+
4003
+ if (
4004
+ $this->openString(')', $out) &&
4005
+ $this->matchChar(')')
4006
+ ) {
4007
+ $out = [Type::T_STRING, '', ['url(', $out, ')']];
4008
+
4009
+ return true;
4010
+ }
4011
  }
4012
 
4013
  return false;
4015
 
4016
  /**
4017
  * Consume an end of statement delimiter
4018
+ * @param bool $eatWhitespace
4019
  *
4020
+ * @return bool
4021
  */
4022
+ protected function end($eatWhitespace = null)
4023
  {
4024
+ if ($this->matchChar(';', $eatWhitespace)) {
4025
  return true;
4026
  }
4027
 
4038
  *
4039
  * @param array $value
4040
  *
4041
+ * @return string[]
4042
  */
4043
  protected function stripAssignmentFlags(&$value)
4044
  {
4065
  *
4066
  * @param array $selectors
4067
  *
4068
+ * @return bool
4069
  */
4070
  protected function stripOptionalFlag(&$selectors)
4071
  {
4114
  * Extract line numbers from buffer
4115
  *
4116
  * @param string $buffer
4117
+ *
4118
+ * @return void
4119
  */
4120
  private function extractLineNumbers($buffer)
4121
  {
4137
  /**
4138
  * Get source line number and column (given character position in the buffer)
4139
  *
4140
+ * @param int $pos
4141
  *
4142
  * @return array
4143
+ * @phpstan-return array{int, int}
4144
  */
4145
  private function getSourcePosition($pos)
4146
  {
4167
  }
4168
 
4169
  /**
4170
+ * Save internal encoding of mbstring
4171
+ *
4172
+ * When mbstring.func_overload is used to replace the standard PHP string functions,
4173
+ * this method configures the internal encoding to a single-byte one so that the
4174
+ * behavior matches the normal behavior of PHP string functions while using the parser.
4175
+ * The existing internal encoding is saved and will be restored when calling {@see restoreEncoding}.
4176
+ *
4177
+ * If mbstring.func_overload is not used (or does not override string functions), this method is a no-op.
4178
+ *
4179
+ * @return void
4180
  */
4181
  private function saveEncoding()
4182
  {
4183
+ if (\PHP_VERSION_ID < 80000 && \extension_loaded('mbstring') && (2 & (int) ini_get('mbstring.func_overload')) > 0) {
4184
  $this->encoding = mb_internal_encoding();
4185
 
4186
  mb_internal_encoding('iso-8859-1');
4189
 
4190
  /**
4191
  * Restore internal encoding
4192
+ *
4193
+ * @return void
4194
  */
4195
  private function restoreEncoding()
4196
  {
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/SourceMap/Base64.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,11 +16,13 @@ namespace ScssPhp\ScssPhp\SourceMap;
15
  * Base 64 Encode/Decode
16
  *
17
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
18
  */
19
  class Base64
20
  {
21
  /**
22
- * @var array
23
  */
24
  private static $encodingMap = [
25
  0 => 'A',
@@ -89,7 +92,7 @@ class Base64
89
  ];
90
 
91
  /**
92
- * @var array
93
  */
94
  private static $decodingMap = [
95
  'A' => 0,
@@ -161,7 +164,7 @@ class Base64
161
  /**
162
  * Convert to base64
163
  *
164
- * @param integer $value
165
  *
166
  * @return string
167
  */
@@ -175,7 +178,7 @@ class Base64
175
  *
176
  * @param string $value
177
  *
178
- * @return integer
179
  */
180
  public static function decode($value)
181
  {
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
16
  * Base 64 Encode/Decode
17
  *
18
  * @author Anthon Pang <anthon.pang@gmail.com>
19
+ *
20
+ * @internal
21
  */
22
  class Base64
23
  {
24
  /**
25
+ * @var array<int, string>
26
  */
27
  private static $encodingMap = [
28
  0 => 'A',
92
  ];
93
 
94
  /**
95
+ * @var array<string|int, int>
96
  */
97
  private static $decodingMap = [
98
  'A' => 0,
164
  /**
165
  * Convert to base64
166
  *
167
+ * @param int $value
168
  *
169
  * @return string
170
  */
178
  *
179
  * @param string $value
180
  *
181
+ * @return int
182
  */
183
  public static function decode($value)
184
  {
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/SourceMap/Base64VLQ.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -11,8 +12,6 @@
11
 
12
  namespace ScssPhp\ScssPhp\SourceMap;
13
 
14
- use ScssPhp\ScssPhp\SourceMap\Base64;
15
-
16
  /**
17
  * Base 64 VLQ
18
  *
@@ -35,6 +34,8 @@ use ScssPhp\ScssPhp\SourceMap\Base64;
35
  *
36
  * @author John Lenz <johnlenz@google.com>
37
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
38
  */
39
  class Base64VLQ
40
  {
@@ -50,7 +51,7 @@ class Base64VLQ
50
  /**
51
  * Returns the VLQ encoded value.
52
  *
53
- * @param integer $value
54
  *
55
  * @return string
56
  */
@@ -61,7 +62,9 @@ class Base64VLQ
61
 
62
  do {
63
  $digit = $vlq & self::VLQ_BASE_MASK;
64
- $vlq >>= self::VLQ_BASE_SHIFT;
 
 
65
 
66
  if ($vlq > 0) {
67
  $digit |= self::VLQ_CONTINUATION_BIT;
@@ -77,9 +80,9 @@ class Base64VLQ
77
  * Decodes VLQValue.
78
  *
79
  * @param string $str
80
- * @param integer $index
81
  *
82
- * @return integer
83
  */
84
  public static function decode($str, &$index)
85
  {
@@ -104,9 +107,9 @@ class Base64VLQ
104
  * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
105
  * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
106
  *
107
- * @param integer $value
108
  *
109
- * @return integer
110
  */
111
  private static function toVLQSigned($value)
112
  {
@@ -123,14 +126,16 @@ class Base64VLQ
123
  * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
124
  * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
125
  *
126
- * @param integer $value
127
  *
128
- * @return integer
129
  */
130
  private static function fromVLQSigned($value)
131
  {
132
  $negate = ($value & 1) === 1;
133
- $value = ($value >> 1) & ~(1<<(8 * PHP_INT_SIZE - 1)); // unsigned right shift
 
 
134
 
135
  if (! $negate) {
136
  return $value;
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
12
 
13
  namespace ScssPhp\ScssPhp\SourceMap;
14
 
 
 
15
  /**
16
  * Base 64 VLQ
17
  *
34
  *
35
  * @author John Lenz <johnlenz@google.com>
36
  * @author Anthon Pang <anthon.pang@gmail.com>
37
+ *
38
+ * @internal
39
  */
40
  class Base64VLQ
41
  {
51
  /**
52
  * Returns the VLQ encoded value.
53
  *
54
+ * @param int $value
55
  *
56
  * @return string
57
  */
62
 
63
  do {
64
  $digit = $vlq & self::VLQ_BASE_MASK;
65
+
66
+ //$vlq >>>= self::VLQ_BASE_SHIFT; // unsigned right shift
67
+ $vlq = (($vlq >> 1) & PHP_INT_MAX) >> (self::VLQ_BASE_SHIFT - 1);
68
 
69
  if ($vlq > 0) {
70
  $digit |= self::VLQ_CONTINUATION_BIT;
80
  * Decodes VLQValue.
81
  *
82
  * @param string $str
83
+ * @param int $index
84
  *
85
+ * @return int
86
  */
87
  public static function decode($str, &$index)
88
  {
107
  * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
108
  * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
109
  *
110
+ * @param int $value
111
  *
112
+ * @return int
113
  */
114
  private static function toVLQSigned($value)
115
  {
126
  * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
127
  * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
128
  *
129
+ * @param int $value
130
  *
131
+ * @return int
132
  */
133
  private static function fromVLQSigned($value)
134
  {
135
  $negate = ($value & 1) === 1;
136
+
137
+ //$value >>>= 1; // unsigned right shift
138
+ $value = ($value >> 1) & PHP_INT_MAX;
139
 
140
  if (! $negate) {
141
  return $value;
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/SourceMap/SourceMapGenerator.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -20,6 +21,8 @@ use ScssPhp\ScssPhp\Exception\CompilerException;
20
  *
21
  * @author Josh Schmidt <oyejorge@gmail.com>
22
  * @author Nicolas FRANÇOIS <nicolas.francois@frog-labs.com>
 
 
23
  */
24
  class SourceMapGenerator
25
  {
@@ -32,6 +35,7 @@ class SourceMapGenerator
32
  * Array of default options
33
  *
34
  * @var array
 
35
  */
36
  protected $defaultOptions = [
37
  // an optional source root, useful for relocating source files
@@ -69,6 +73,7 @@ class SourceMapGenerator
69
  * Array of mappings
70
  *
71
  * @var array
 
72
  */
73
  protected $mappings = [];
74
 
@@ -82,30 +87,40 @@ class SourceMapGenerator
82
  /**
83
  * File to content map
84
  *
85
- * @var array
86
  */
87
  protected $sources = [];
 
 
 
 
88
  protected $sourceKeys = [];
89
 
90
  /**
91
  * @var array
 
92
  */
93
  private $options;
94
 
 
 
 
95
  public function __construct(array $options = [])
96
  {
97
- $this->options = array_merge($this->defaultOptions, $options);
98
  $this->encoder = new Base64VLQ();
99
  }
100
 
101
  /**
102
  * Adds a mapping
103
  *
104
- * @param integer $generatedLine The line number in generated file
105
- * @param integer $generatedColumn The column number in generated file
106
- * @param integer $originalLine The line number in original file
107
- * @param integer $originalColumn The column number in original file
108
- * @param string $sourceFile The original source file
 
 
109
  */
110
  public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $sourceFile)
111
  {
@@ -125,13 +140,15 @@ class SourceMapGenerator
125
  *
126
  * @param string $content The content to write
127
  *
128
- * @return string
129
  *
130
  * @throws \ScssPhp\ScssPhp\Exception\CompilerException If the file could not be saved
 
131
  */
132
  public function saveMap($content)
133
  {
134
  $file = $this->options['sourceMapWriteTo'];
 
135
  $dir = \dirname($file);
136
 
137
  // directory does not exist
@@ -153,14 +170,16 @@ class SourceMapGenerator
153
  /**
154
  * Generates the JSON source map
155
  *
 
 
156
  * @return string
157
  *
158
  * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
159
  */
160
- public function generateJson()
161
  {
162
  $sourceMap = [];
163
- $mappings = $this->generateMappings();
164
 
165
  // File version (always the first entry in the object) and must be a positive integer.
166
  $sourceMap['version'] = self::VERSION;
@@ -183,7 +202,7 @@ class SourceMapGenerator
183
  // A list of original sources used by the 'mappings' entry.
184
  $sourceMap['sources'] = [];
185
 
186
- foreach ($this->sources as $sourceUri => $sourceFilename) {
187
  $sourceMap['sources'][] = $this->normalizeFilename($sourceFilename);
188
  }
189
 
@@ -205,13 +224,21 @@ class SourceMapGenerator
205
  unset($sourceMap['sourceRoot']);
206
  }
207
 
208
- return json_encode($sourceMap, JSON_UNESCAPED_SLASHES);
 
 
 
 
 
 
 
 
209
  }
210
 
211
  /**
212
  * Returns the sources contents
213
  *
214
- * @return array|null
215
  */
216
  protected function getSourcesContent()
217
  {
@@ -231,14 +258,21 @@ class SourceMapGenerator
231
  /**
232
  * Generates the mappings string
233
  *
 
 
234
  * @return string
235
  */
236
- public function generateMappings()
237
  {
238
  if (! \count($this->mappings)) {
239
  return '';
240
  }
241
 
 
 
 
 
 
242
  $this->sourceKeys = array_flip(array_keys($this->sources));
243
 
244
  // group mappings by generated line number.
@@ -253,6 +287,12 @@ class SourceMapGenerator
253
  $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
254
 
255
  foreach ($groupedMap as $lineNumber => $lineMap) {
 
 
 
 
 
 
256
  while (++$lastGeneratedLine < $lineNumber) {
257
  $groupedMapEncoded[] = ';';
258
  }
@@ -261,8 +301,10 @@ class SourceMapGenerator
261
  $lastGeneratedColumn = 0;
262
 
263
  foreach ($lineMap as $m) {
264
- $mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn);
265
- $lastGeneratedColumn = $m['generated_column'];
 
 
266
 
267
  // find the index
268
  if ($m['source_file']) {
@@ -293,7 +335,7 @@ class SourceMapGenerator
293
  *
294
  * @param string $filename
295
  *
296
- * @return integer|false
297
  */
298
  protected function findFileIndex($filename)
299
  {
@@ -329,8 +371,8 @@ class SourceMapGenerator
329
  /**
330
  * Fix windows paths
331
  *
332
- * @param string $path
333
- * @param boolean $addEndSlash
334
  *
335
  * @return string
336
  */
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
21
  *
22
  * @author Josh Schmidt <oyejorge@gmail.com>
23
  * @author Nicolas FRANÇOIS <nicolas.francois@frog-labs.com>
24
+ *
25
+ * @internal
26
  */
27
  class SourceMapGenerator
28
  {
35
  * Array of default options
36
  *
37
  * @var array
38
+ * @phpstan-var array{sourceRoot: string, sourceMapFilename: string|null, sourceMapURL: string|null, sourceMapWriteTo: string|null, outputSourceFiles: bool, sourceMapRootpath: string, sourceMapBasepath: string}
39
  */
40
  protected $defaultOptions = [
41
  // an optional source root, useful for relocating source files
73
  * Array of mappings
74
  *
75
  * @var array
76
+ * @phpstan-var list<array{generated_line: int, generated_column: int, original_line: int, original_column: int, source_file: string}>
77
  */
78
  protected $mappings = [];
79
 
87
  /**
88
  * File to content map
89
  *
90
+ * @var array<string, string>
91
  */
92
  protected $sources = [];
93
+
94
+ /**
95
+ * @var array<string, int>
96
+ */
97
  protected $sourceKeys = [];
98
 
99
  /**
100
  * @var array
101
+ * @phpstan-var array{sourceRoot: string, sourceMapFilename: string|null, sourceMapURL: string|null, sourceMapWriteTo: string|null, outputSourceFiles: bool, sourceMapRootpath: string, sourceMapBasepath: string}
102
  */
103
  private $options;
104
 
105
+ /**
106
+ * @phpstan-param array{sourceRoot?: string, sourceMapFilename?: string|null, sourceMapURL?: string|null, sourceMapWriteTo?: string|null, outputSourceFiles?: bool, sourceMapRootpath?: string, sourceMapBasepath?: string} $options
107
+ */
108
  public function __construct(array $options = [])
109
  {
110
+ $this->options = array_replace($this->defaultOptions, $options);
111
  $this->encoder = new Base64VLQ();
112
  }
113
 
114
  /**
115
  * Adds a mapping
116
  *
117
+ * @param int $generatedLine The line number in generated file
118
+ * @param int $generatedColumn The column number in generated file
119
+ * @param int $originalLine The line number in original file
120
+ * @param int $originalColumn The column number in original file
121
+ * @param string $sourceFile The original source file
122
+ *
123
+ * @return void
124
  */
125
  public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $sourceFile)
126
  {
140
  *
141
  * @param string $content The content to write
142
  *
143
+ * @return string|null
144
  *
145
  * @throws \ScssPhp\ScssPhp\Exception\CompilerException If the file could not be saved
146
+ * @deprecated
147
  */
148
  public function saveMap($content)
149
  {
150
  $file = $this->options['sourceMapWriteTo'];
151
+ assert($file !== null);
152
  $dir = \dirname($file);
153
 
154
  // directory does not exist
170
  /**
171
  * Generates the JSON source map
172
  *
173
+ * @param string $prefix A prefix added in the output file, which needs to shift mappings
174
+ *
175
  * @return string
176
  *
177
  * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
178
  */
179
+ public function generateJson($prefix = '')
180
  {
181
  $sourceMap = [];
182
+ $mappings = $this->generateMappings($prefix);
183
 
184
  // File version (always the first entry in the object) and must be a positive integer.
185
  $sourceMap['version'] = self::VERSION;
202
  // A list of original sources used by the 'mappings' entry.
203
  $sourceMap['sources'] = [];
204
 
205
+ foreach ($this->sources as $sourceFilename) {
206
  $sourceMap['sources'][] = $this->normalizeFilename($sourceFilename);
207
  }
208
 
224
  unset($sourceMap['sourceRoot']);
225
  }
226
 
227
+ $jsonSourceMap = json_encode($sourceMap, JSON_UNESCAPED_SLASHES);
228
+
229
+ if (json_last_error() !== JSON_ERROR_NONE) {
230
+ throw new \RuntimeException(json_last_error_msg());
231
+ }
232
+
233
+ assert($jsonSourceMap !== false);
234
+
235
+ return $jsonSourceMap;
236
  }
237
 
238
  /**
239
  * Returns the sources contents
240
  *
241
+ * @return string[]|null
242
  */
243
  protected function getSourcesContent()
244
  {
258
  /**
259
  * Generates the mappings string
260
  *
261
+ * @param string $prefix A prefix added in the output file, which needs to shift mappings
262
+ *
263
  * @return string
264
  */
265
+ public function generateMappings($prefix = '')
266
  {
267
  if (! \count($this->mappings)) {
268
  return '';
269
  }
270
 
271
+ $prefixLines = substr_count($prefix, "\n");
272
+ $lastPrefixNewLine = strrpos($prefix, "\n");
273
+ $lastPrefixLineStart = false === $lastPrefixNewLine ? 0 : $lastPrefixNewLine + 1;
274
+ $prefixColumn = strlen($prefix) - $lastPrefixLineStart;
275
+
276
  $this->sourceKeys = array_flip(array_keys($this->sources));
277
 
278
  // group mappings by generated line number.
287
  $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
288
 
289
  foreach ($groupedMap as $lineNumber => $lineMap) {
290
+ if ($lineNumber > 1) {
291
+ // The prefix only impacts the column for the first line of the original output
292
+ $prefixColumn = 0;
293
+ }
294
+ $lineNumber += $prefixLines;
295
+
296
  while (++$lastGeneratedLine < $lineNumber) {
297
  $groupedMapEncoded[] = ';';
298
  }
301
  $lastGeneratedColumn = 0;
302
 
303
  foreach ($lineMap as $m) {
304
+ $generatedColumn = $m['generated_column'] + $prefixColumn;
305
+
306
+ $mapEncoded = $this->encoder->encode($generatedColumn - $lastGeneratedColumn);
307
+ $lastGeneratedColumn = $generatedColumn;
308
 
309
  // find the index
310
  if ($m['source_file']) {
335
  *
336
  * @param string $filename
337
  *
338
+ * @return int|false
339
  */
340
  protected function findFileIndex($filename)
341
  {
371
  /**
372
  * Fix windows paths
373
  *
374
+ * @param string $path
375
+ * @param bool $addEndSlash
376
  *
377
  * @return string
378
  */
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Type.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -18,53 +19,190 @@ namespace ScssPhp\ScssPhp;
18
  */
19
  class Type
20
  {
 
 
 
21
  const T_ASSIGN = 'assign';
 
 
 
22
  const T_AT_ROOT = 'at-root';
 
 
 
23
  const T_BLOCK = 'block';
 
 
 
 
24
  const T_BREAK = 'break';
 
 
 
25
  const T_CHARSET = 'charset';
26
  const T_COLOR = 'color';
 
 
 
27
  const T_COMMENT = 'comment';
 
 
 
 
28
  const T_CONTINUE = 'continue';
 
 
 
 
29
  const T_CONTROL = 'control';
 
 
 
30
  const T_CUSTOM_PROPERTY = 'custom';
 
 
 
31
  const T_DEBUG = 'debug';
 
 
 
32
  const T_DIRECTIVE = 'directive';
 
 
 
33
  const T_EACH = 'each';
 
 
 
34
  const T_ELSE = 'else';
 
 
 
35
  const T_ELSEIF = 'elseif';
 
 
 
36
  const T_ERROR = 'error';
 
 
 
37
  const T_EXPRESSION = 'exp';
 
 
 
38
  const T_EXTEND = 'extend';
 
 
 
39
  const T_FOR = 'for';
40
  const T_FUNCTION = 'function';
 
 
 
 
 
 
 
41
  const T_FUNCTION_CALL = 'fncall';
 
 
 
42
  const T_HSL = 'hsl';
 
 
 
 
 
 
 
43
  const T_IF = 'if';
 
 
 
44
  const T_IMPORT = 'import';
 
 
 
45
  const T_INCLUDE = 'include';
 
 
 
46
  const T_INTERPOLATE = 'interpolate';
 
 
 
47
  const T_INTERPOLATED = 'interpolated';
 
 
 
48
  const T_KEYWORD = 'keyword';
49
  const T_LIST = 'list';
50
  const T_MAP = 'map';
 
 
 
51
  const T_MEDIA = 'media';
 
 
 
52
  const T_MEDIA_EXPRESSION = 'mediaExp';
 
 
 
53
  const T_MEDIA_TYPE = 'mediaType';
 
 
 
54
  const T_MEDIA_VALUE = 'mediaValue';
 
 
 
55
  const T_MIXIN = 'mixin';
 
 
 
56
  const T_MIXIN_CONTENT = 'mixin_content';
 
 
 
57
  const T_NESTED_PROPERTY = 'nestedprop';
 
 
 
58
  const T_NOT = 'not';
59
  const T_NULL = 'null';
60
  const T_NUMBER = 'number';
 
 
 
61
  const T_RETURN = 'return';
 
 
 
62
  const T_ROOT = 'root';
 
 
 
63
  const T_SCSSPHP_IMPORT_ONCE = 'scssphp-import-once';
 
 
 
64
  const T_SELF = 'self';
65
  const T_STRING = 'string';
 
 
 
66
  const T_UNARY = 'unary';
 
 
 
67
  const T_VARIABLE = 'var';
 
 
 
68
  const T_WARN = 'warn';
 
 
 
69
  const T_WHILE = 'while';
70
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
19
  */
20
  class Type
21
  {
22
+ /**
23
+ * @internal
24
+ */
25
  const T_ASSIGN = 'assign';
26
+ /**
27
+ * @internal
28
+ */
29
  const T_AT_ROOT = 'at-root';
30
+ /**
31
+ * @internal
32
+ */
33
  const T_BLOCK = 'block';
34
+ /**
35
+ * @deprecated
36
+ * @internal
37
+ */
38
  const T_BREAK = 'break';
39
+ /**
40
+ * @internal
41
+ */
42
  const T_CHARSET = 'charset';
43
  const T_COLOR = 'color';
44
+ /**
45
+ * @internal
46
+ */
47
  const T_COMMENT = 'comment';
48
+ /**
49
+ * @deprecated
50
+ * @internal
51
+ */
52
  const T_CONTINUE = 'continue';
53
+ /**
54
+ * @deprecated
55
+ * @internal
56
+ */
57
  const T_CONTROL = 'control';
58
+ /**
59
+ * @internal
60
+ */
61
  const T_CUSTOM_PROPERTY = 'custom';
62
+ /**
63
+ * @internal
64
+ */
65
  const T_DEBUG = 'debug';
66
+ /**
67
+ * @internal
68
+ */
69
  const T_DIRECTIVE = 'directive';
70
+ /**
71
+ * @internal
72
+ */
73
  const T_EACH = 'each';
74
+ /**
75
+ * @internal
76
+ */
77
  const T_ELSE = 'else';
78
+ /**
79
+ * @internal
80
+ */
81
  const T_ELSEIF = 'elseif';
82
+ /**
83
+ * @internal
84
+ */
85
  const T_ERROR = 'error';
86
+ /**
87
+ * @internal
88
+ */
89
  const T_EXPRESSION = 'exp';
90
+ /**
91
+ * @internal
92
+ */
93
  const T_EXTEND = 'extend';
94
+ /**
95
+ * @internal
96
+ */
97
  const T_FOR = 'for';
98
  const T_FUNCTION = 'function';
99
+ /**
100
+ * @internal
101
+ */
102
+ const T_FUNCTION_REFERENCE = 'function-reference';
103
+ /**
104
+ * @internal
105
+ */
106
  const T_FUNCTION_CALL = 'fncall';
107
+ /**
108
+ * @internal
109
+ */
110
  const T_HSL = 'hsl';
111
+ /**
112
+ * @internal
113
+ */
114
+ const T_HWB = 'hwb';
115
+ /**
116
+ * @internal
117
+ */
118
  const T_IF = 'if';
119
+ /**
120
+ * @internal
121
+ */
122
  const T_IMPORT = 'import';
123
+ /**
124
+ * @internal
125
+ */
126
  const T_INCLUDE = 'include';
127
+ /**
128
+ * @internal
129
+ */
130
  const T_INTERPOLATE = 'interpolate';
131
+ /**
132
+ * @internal
133
+ */
134
  const T_INTERPOLATED = 'interpolated';
135
+ /**
136
+ * @internal
137
+ */
138
  const T_KEYWORD = 'keyword';
139
  const T_LIST = 'list';
140
  const T_MAP = 'map';
141
+ /**
142
+ * @internal
143
+ */
144
  const T_MEDIA = 'media';
145
+ /**
146
+ * @internal
147
+ */
148
  const T_MEDIA_EXPRESSION = 'mediaExp';
149
+ /**
150
+ * @internal
151
+ */
152
  const T_MEDIA_TYPE = 'mediaType';
153
+ /**
154
+ * @internal
155
+ */
156
  const T_MEDIA_VALUE = 'mediaValue';
157
+ /**
158
+ * @internal
159
+ */
160
  const T_MIXIN = 'mixin';
161
+ /**
162
+ * @internal
163
+ */
164
  const T_MIXIN_CONTENT = 'mixin_content';
165
+ /**
166
+ * @internal
167
+ */
168
  const T_NESTED_PROPERTY = 'nestedprop';
169
+ /**
170
+ * @internal
171
+ */
172
  const T_NOT = 'not';
173
  const T_NULL = 'null';
174
  const T_NUMBER = 'number';
175
+ /**
176
+ * @internal
177
+ */
178
  const T_RETURN = 'return';
179
+ /**
180
+ * @internal
181
+ */
182
  const T_ROOT = 'root';
183
+ /**
184
+ * @internal
185
+ */
186
  const T_SCSSPHP_IMPORT_ONCE = 'scssphp-import-once';
187
+ /**
188
+ * @internal
189
+ */
190
  const T_SELF = 'self';
191
  const T_STRING = 'string';
192
+ /**
193
+ * @internal
194
+ */
195
  const T_UNARY = 'unary';
196
+ /**
197
+ * @internal
198
+ */
199
  const T_VARIABLE = 'var';
200
+ /**
201
+ * @internal
202
+ */
203
  const T_WARN = 'warn';
204
+ /**
205
+ * @internal
206
+ */
207
  const T_WHILE = 'while';
208
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Util.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -13,11 +14,14 @@ namespace ScssPhp\ScssPhp;
13
 
14
  use ScssPhp\ScssPhp\Base\Range;
15
  use ScssPhp\ScssPhp\Exception\RangeException;
 
16
 
17
  /**
18
- * Utilty functions
19
  *
20
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
21
  */
22
  class Util
23
  {
@@ -25,10 +29,10 @@ class Util
25
  * Asserts that `value` falls within `range` (inclusive), leaving
26
  * room for slight floating-point errors.
27
  *
28
- * @param string $name The name of the value. Used in the error message.
29
- * @param \ScssPhp\ScssPhp\Base\Range $range Range of values.
30
- * @param array $value The value to check.
31
- * @param string $unit The unit of the value. Used in error reporting.
32
  *
33
  * @return mixed `value` adjusted to fall within range, if it was outside by a floating-point margin.
34
  *
@@ -39,6 +43,10 @@ class Util
39
  $val = $value[1];
40
  $grace = new Range(-0.00001, 0.00001);
41
 
 
 
 
 
42
  if ($range->includes($val)) {
43
  return $val;
44
  }
@@ -67,4 +75,110 @@ class Util
67
 
68
  return strtr(rawurlencode($string), $revert);
69
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
14
 
15
  use ScssPhp\ScssPhp\Base\Range;
16
  use ScssPhp\ScssPhp\Exception\RangeException;
17
+ use ScssPhp\ScssPhp\Node\Number;
18
 
19
  /**
20
+ * Utility functions
21
  *
22
  * @author Anthon Pang <anthon.pang@gmail.com>
23
+ *
24
+ * @internal
25
  */
26
  class Util
27
  {
29
  * Asserts that `value` falls within `range` (inclusive), leaving
30
  * room for slight floating-point errors.
31
  *
32
+ * @param string $name The name of the value. Used in the error message.
33
+ * @param Range $range Range of values.
34
+ * @param array|Number $value The value to check.
35
+ * @param string $unit The unit of the value. Used in error reporting.
36
  *
37
  * @return mixed `value` adjusted to fall within range, if it was outside by a floating-point margin.
38
  *
43
  $val = $value[1];
44
  $grace = new Range(-0.00001, 0.00001);
45
 
46
+ if (! \is_numeric($val)) {
47
+ throw new RangeException("$name {$val} is not a number.");
48
+ }
49
+
50
  if ($range->includes($val)) {
51
  return $val;
52
  }
75
 
76
  return strtr(rawurlencode($string), $revert);
77
  }
78
+
79
+ /**
80
+ * mb_chr() wrapper
81
+ *
82
+ * @param int $code
83
+ *
84
+ * @return string
85
+ */
86
+ public static function mbChr($code)
87
+ {
88
+ // Use the native implementation if available, but not on PHP 7.2 as mb_chr(0) is buggy there
89
+ if (\PHP_VERSION_ID > 70300 && \function_exists('mb_chr')) {
90
+ return mb_chr($code, 'UTF-8');
91
+ }
92
+
93
+ if (0x80 > $code %= 0x200000) {
94
+ $s = \chr($code);
95
+ } elseif (0x800 > $code) {
96
+ $s = \chr(0xC0 | $code >> 6) . \chr(0x80 | $code & 0x3F);
97
+ } elseif (0x10000 > $code) {
98
+ $s = \chr(0xE0 | $code >> 12) . \chr(0x80 | $code >> 6 & 0x3F) . \chr(0x80 | $code & 0x3F);
99
+ } else {
100
+ $s = \chr(0xF0 | $code >> 18) . \chr(0x80 | $code >> 12 & 0x3F)
101
+ . \chr(0x80 | $code >> 6 & 0x3F) . \chr(0x80 | $code & 0x3F);
102
+ }
103
+
104
+ return $s;
105
+ }
106
+
107
+ /**
108
+ * mb_strlen() wrapper
109
+ *
110
+ * @param string $string
111
+ * @return int
112
+ */
113
+ public static function mbStrlen($string)
114
+ {
115
+ // Use the native implementation if available.
116
+ if (\function_exists('mb_strlen')) {
117
+ return mb_strlen($string, 'UTF-8');
118
+ }
119
+
120
+ if (\function_exists('iconv_strlen')) {
121
+ return (int) @iconv_strlen($string, 'UTF-8');
122
+ }
123
+
124
+ throw new \LogicException('Either mbstring (recommended) or iconv is necessary to use Scssphp.');
125
+ }
126
+
127
+ /**
128
+ * mb_substr() wrapper
129
+ * @param string $string
130
+ * @param int $start
131
+ * @param null|int $length
132
+ * @return string
133
+ */
134
+ public static function mbSubstr($string, $start, $length = null)
135
+ {
136
+ // Use the native implementation if available.
137
+ if (\function_exists('mb_substr')) {
138
+ return mb_substr($string, $start, $length, 'UTF-8');
139
+ }
140
+
141
+ if (\function_exists('iconv_substr')) {
142
+ if ($start < 0) {
143
+ $start = static::mbStrlen($string) + $start;
144
+ if ($start < 0) {
145
+ $start = 0;
146
+ }
147
+ }
148
+
149
+ if (null === $length) {
150
+ $length = 2147483647;
151
+ } elseif ($length < 0) {
152
+ $length = static::mbStrlen($string) + $length - $start;
153
+ if ($length < 0) {
154
+ return '';
155
+ }
156
+ }
157
+
158
+ return (string)iconv_substr($string, $start, $length, 'UTF-8');
159
+ }
160
+
161
+ throw new \LogicException('Either mbstring (recommended) or iconv is necessary to use Scssphp.');
162
+ }
163
+
164
+ /**
165
+ * mb_strpos wrapper
166
+ * @param string $haystack
167
+ * @param string $needle
168
+ * @param int $offset
169
+ *
170
+ * @return int|false
171
+ */
172
+ public static function mbStrpos($haystack, $needle, $offset = 0)
173
+ {
174
+ if (\function_exists('mb_strpos')) {
175
+ return mb_strpos($haystack, $needle, $offset, 'UTF-8');
176
+ }
177
+
178
+ if (\function_exists('iconv_strpos')) {
179
+ return iconv_strpos($haystack, $needle, $offset, 'UTF-8');
180
+ }
181
+
182
+ throw new \LogicException('Either mbstring (recommended) or iconv is necessary to use Scssphp.');
183
+ }
184
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Util/Path.php ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ScssPhp\ScssPhp\Util;
4
+
5
+ /**
6
+ * @internal
7
+ */
8
+ class Path
9
+ {
10
+ /**
11
+ * @param string $path
12
+ *
13
+ * @return bool
14
+ */
15
+ public static function isAbsolute($path)
16
+ {
17
+ if ($path === '') {
18
+ return false;
19
+ }
20
+
21
+ if ($path[0] === '/') {
22
+ return true;
23
+ }
24
+
25
+ if (\DIRECTORY_SEPARATOR !== '\\') {
26
+ return false;
27
+ }
28
+
29
+ if ($path[0] === '\\') {
30
+ return true;
31
+ }
32
+
33
+ if (\strlen($path) < 3) {
34
+ return false;
35
+ }
36
+
37
+ if ($path[1] !== ':') {
38
+ return false;
39
+ }
40
+
41
+ if ($path[2] !== '/' && $path[2] !== '\\') {
42
+ return false;
43
+ }
44
+
45
+ if (!preg_match('/^[A-Za-z]$/', $path[0])) {
46
+ return false;
47
+ }
48
+
49
+ return true;
50
+ }
51
+
52
+ /**
53
+ * @param string $part1
54
+ * @param string $part2
55
+ *
56
+ * @return string
57
+ */
58
+ public static function join($part1, $part2)
59
+ {
60
+ if ($part1 === '' || self::isAbsolute($part2)) {
61
+ return $part2;
62
+ }
63
+
64
+ if ($part2 === '') {
65
+ return $part1;
66
+ }
67
+
68
+ $last = $part1[\strlen($part1) - 1];
69
+ $separator = \DIRECTORY_SEPARATOR;
70
+
71
+ if ($last === '/' || $last === \DIRECTORY_SEPARATOR) {
72
+ $separator = '';
73
+ }
74
+
75
+ return $part1 . $separator . $part2;
76
+ }
77
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/ValueConverter.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp;
14
+
15
+ use ScssPhp\ScssPhp\Node\Number;
16
+
17
+ final class ValueConverter
18
+ {
19
+ // Prevent instantiating it
20
+ private function __construct()
21
+ {
22
+ }
23
+
24
+ /**
25
+ * Parses a value from a Scss source string.
26
+ *
27
+ * The returned value is guaranteed to be supported by the
28
+ * Compiler methods for registering custom variables. No other
29
+ * guarantee about it is provided. It should be considered
30
+ * opaque values by the caller.
31
+ *
32
+ * @param string $source
33
+ *
34
+ * @return mixed
35
+ */
36
+ public static function parseValue($source)
37
+ {
38
+ $parser = new Parser(__CLASS__);
39
+
40
+ if (!$parser->parseValue($source, $value)) {
41
+ throw new \InvalidArgumentException(sprintf('Invalid value source "%s".', $source));
42
+ }
43
+
44
+ return $value;
45
+ }
46
+
47
+ /**
48
+ * Converts a PHP value to a Sass value
49
+ *
50
+ * The returned value is guaranteed to be supported by the
51
+ * Compiler methods for registering custom variables. No other
52
+ * guarantee about it is provided. It should be considered
53
+ * opaque values by the caller.
54
+ *
55
+ * @param mixed $value
56
+ *
57
+ * @return mixed
58
+ */
59
+ public static function fromPhp($value)
60
+ {
61
+ if ($value instanceof Number) {
62
+ return $value;
63
+ }
64
+
65
+ if (is_array($value) && isset($value[0]) && \in_array($value[0], [Type::T_NULL, Type::T_COLOR, Type::T_KEYWORD, Type::T_LIST, Type::T_MAP, Type::T_STRING])) {
66
+ return $value;
67
+ }
68
+
69
+ if ($value === null) {
70
+ return Compiler::$null;
71
+ }
72
+
73
+ if ($value === true) {
74
+ return Compiler::$true;
75
+ }
76
+
77
+ if ($value === false) {
78
+ return Compiler::$false;
79
+ }
80
+
81
+ if ($value === '') {
82
+ return Compiler::$emptyString;
83
+ }
84
+
85
+ if (\is_int($value) || \is_float($value)) {
86
+ return new Number($value, '');
87
+ }
88
+
89
+ if (\is_string($value)) {
90
+ return [Type::T_STRING, '"', [$value]];
91
+ }
92
+
93
+ throw new \InvalidArgumentException(sprintf('Cannot convert the value of type "%s" to a Sass value.', gettype($value)));
94
+ }
95
+ }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Version.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -18,5 +19,5 @@ namespace ScssPhp\ScssPhp;
18
  */
19
  class Version
20
  {
21
- const VERSION = '1.1.0';
22
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
19
  */
20
  class Version
21
  {
22
+ const VERSION = '1.10.2';
23
  }
v4.0.0/libs/scssphp/vendor/scssphp/scssphp/src/Warn.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * SCSSPHP
5
+ *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
+ *
8
+ * @license http://opensource.org/licenses/MIT MIT
9
+ *
10
+ * @link http://scssphp.github.io/scssphp
11
+ */
12
+
13
+ namespace ScssPhp\ScssPhp;
14
+
15
+ final class Warn
16
+ {
17
+ /**
18
+ * @var callable|null
19
+ * @phpstan-var (callable(string, bool): void)|null
20
+ */
21
+ private static $callback;
22
+
23
+ /**
24
+ * Prints a warning message associated with the current `@import` or function call.
25
+ *
26
+ * This may only be called within a custom function or importer callback.
27
+ *
28
+ * @param string $message
29
+ *
30
+ * @return void
31
+ */
32
+ public static function warning($message)
33
+ {
34
+ self::reportWarning($message, false);
35
+ }
36
+
37
+ /**
38
+ * Prints a deprecation warning message associated with the current `@import` or function call.
39
+ *
40
+ * This may only be called within a custom function or importer callback.
41
+ *
42
+ * @param string $message
43
+ *
44
+ * @return void
45
+ */
46
+ public static function deprecation($message)
47
+ {
48
+ self::reportWarning($message, true);
49
+ }
50
+
51
+ /**
52
+ * @param callable|null $callback
53
+ *
54
+ * @return callable|null The previous warn callback
55
+ *
56
+ * @phpstan-param (callable(string, bool): void)|null $callback
57
+ *
58
+ * @phpstan-return (callable(string, bool): void)|null
59
+ *
60
+ * @internal
61
+ */
62
+ public static function setCallback(callable $callback = null)
63
+ {
64
+ $previousCallback = self::$callback;
65
+ self::$callback = $callback;
66
+
67
+ return $previousCallback;
68
+ }
69
+
70
+ /**
71
+ * @param string $message
72
+ * @param bool $deprecation
73
+ *
74
+ * @return void
75
+ */
76
+ private static function reportWarning($message, $deprecation)
77
+ {
78
+ if (self::$callback === null) {
79
+ throw new \BadMethodCallException('The warning Reporter may only be called within a custom function or importer callback.');
80
+ }
81
+
82
+ \call_user_func(self::$callback, $message, $deprecation);
83
+ }
84
+ }
v4.0.0/templates/rmp-editor.php CHANGED
@@ -47,7 +47,11 @@ $theme_manager = Theme_Manager::get_instance();
47
  $editor = Editor::get_instance();
48
  $menu_id = get_the_ID();
49
  $options = $option_manager->get_options( $menu_id );
50
-
 
 
 
 
51
  ?>
52
  <!DOCTYPE html>
53
  <html <?php language_attributes(); ?>>
@@ -71,9 +75,9 @@ $options = $option_manager->get_options( $menu_id );
71
  <li id="rmp-tab-item-mobile-menu" class="rmp-tab-item" aria-owns="tab-mobile-menu">
72
  <span class="rmp-tab-item-icon">
73
  <?php
74
- $svg_mobile = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/mobile.svg' );
75
- if ( is_array( $svg_mobile ) && ! is_wp_error( $svg_mobile ) ) {
76
- echo wp_kses( $svg_mobile['body'], rmp_allow_svg_html_tags() );
77
  }
78
  ?>
79
  </span>
@@ -83,9 +87,9 @@ $options = $option_manager->get_options( $menu_id );
83
  <li id="rmp-tab-item-desktop-menu" class="rmp-tab-item" aria-owns="tab-desktop-menu">
84
  <span class="rmp-tab-item-icon">
85
  <?php
86
- $svg_desktop = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/desktop.svg' );
87
- if ( is_array( $svg_desktop ) && ! is_wp_error( $svg_desktop ) ) {
88
- echo wp_kses( $svg_desktop['body'], rmp_allow_svg_html_tags() );
89
  }
90
  ?>
91
  </span>
@@ -98,9 +102,9 @@ $options = $option_manager->get_options( $menu_id );
98
  <li id="rmp-tab-item-dropdowns" class="rmp-tab-item" aria-owns="tab-menu-styling">
99
  <span class="rmp-tab-item-icon">
100
  <?php
101
- $svg_dropdowns = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/dropdowns.svg' );
102
- if ( is_array( $svg_dropdowns ) && ! is_wp_error( $svg_dropdowns ) ) {
103
- echo wp_kses( $svg_dropdowns['body'], rmp_allow_svg_html_tags() );
104
  }
105
  ?>
106
  </span>
@@ -110,9 +114,9 @@ $options = $option_manager->get_options( $menu_id );
110
  <li id="rmp-tab-item-header-bar" class="rmp-tab-item" aria-owns="tab-header-bar">
111
  <span class="rmp-tab-item-icon">
112
  <?php
113
- $svg_header = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/header.svg' );
114
- if ( is_array( $svg_header ) && ! is_wp_error( $svg_header ) ) {
115
- echo wp_kses( $svg_header['body'], rmp_allow_svg_html_tags() );
116
  }
117
  ?>
118
  </span>
@@ -125,9 +129,9 @@ $options = $option_manager->get_options( $menu_id );
125
  <li id="rmp-tab-item-themes" class="rmp-tab-item" aria-owns="tab-themes">
126
  <span class="rmp-tab-item-icon">
127
  <?php
128
- $svg_advanced = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/advanced.svg' );
129
- if ( is_array( $svg_advanced ) && ! is_wp_error( $svg_advanced ) ) {
130
- echo wp_kses( $svg_advanced['body'], rmp_allow_svg_html_tags() );
131
  }
132
  ?>
133
  </span>
@@ -137,9 +141,9 @@ $options = $option_manager->get_options( $menu_id );
137
  <li id="rmp-tab-item-settings" class="rmp-tab-item" aria-owns="tab-settings">
138
  <span class="rmp-tab-item-icon">
139
  <?php
140
- $svg_general = wp_remote_get( RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/general.svg' );
141
- if ( is_array( $svg_general ) && ! is_wp_error( $svg_general ) ) {
142
- echo wp_kses( $svg_general['body'], rmp_allow_svg_html_tags() );
143
  }
144
  ?>
145
  </span>
@@ -416,7 +420,7 @@ $options = $option_manager->get_options( $menu_id );
416
  'item_class' => 'is-child-item',
417
  'aria_owns' => 'tab-container',
418
  'item_header' => array(
419
- 'item_svg_icon' => RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/container.svg',
420
  'item_title' => esc_html__( 'Container', 'responsive-menu' ),
421
  ),
422
  )
@@ -427,7 +431,7 @@ $options = $option_manager->get_options( $menu_id );
427
  'item_class' => 'is-child-item',
428
  'aria_owns' => 'tab-toggle-button',
429
  'item_header' => array(
430
- 'item_svg_icon' => RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/toggle.svg',
431
  'item_title' => esc_html__( 'Toggle button', 'responsive-menu' ),
432
  ),
433
  )
@@ -444,7 +448,7 @@ $options = $option_manager->get_options( $menu_id );
444
  'item_class' => 'is-child-item rmp-tab-item-general-settings',
445
  'aria_owns' => 'tab-general-settings',
446
  'item_header' => array(
447
- 'item_svg_icon' => RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/general.svg',
448
  'item_title' => esc_html__( 'General Settings', 'responsive-menu' ),
449
  ),
450
  )
@@ -455,7 +459,7 @@ $options = $option_manager->get_options( $menu_id );
455
  'item_class' => 'is-child-item rmp-tab-item-advanced-settings',
456
  'aria_owns' => 'tab-advanced-settings',
457
  'item_header' => array(
458
- 'item_svg_icon' => RMP_PLUGIN_URL_V4 . '/assets/admin/icons/svg/advanced.svg',
459
  'item_title' => esc_html__( 'Advanced Settings', 'responsive-menu' ),
460
  ),
461
  )
47
  $editor = Editor::get_instance();
48
  $menu_id = get_the_ID();
49
  $options = $option_manager->get_options( $menu_id );
50
+ global $wp_filesystem;
51
+ if ( empty( $wp_filesystem ) ) {
52
+ require_once ABSPATH . 'wp-admin/includes/file.php';
53
+ }
54
+ WP_Filesystem();
55
  ?>
56
  <!DOCTYPE html>
57
  <html <?php language_attributes(); ?>>
75
  <li id="rmp-tab-item-mobile-menu" class="rmp-tab-item" aria-owns="tab-mobile-menu">
76
  <span class="rmp-tab-item-icon">
77
  <?php
78
+ $svg_mobile = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/mobile.svg' );
79
+ if ( $svg_mobile ) {
80
+ echo wp_kses( $svg_mobile, rmp_allow_svg_html_tags() );
81
  }
82
  ?>
83
  </span>
87
  <li id="rmp-tab-item-desktop-menu" class="rmp-tab-item" aria-owns="tab-desktop-menu">
88
  <span class="rmp-tab-item-icon">
89
  <?php
90
+ $svg_desktop = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/desktop.svg' );
91
+ if ( $svg_desktop ) {
92
+ echo wp_kses( $svg_desktop, rmp_allow_svg_html_tags() );
93
  }
94
  ?>
95
  </span>
102
  <li id="rmp-tab-item-dropdowns" class="rmp-tab-item" aria-owns="tab-menu-styling">
103
  <span class="rmp-tab-item-icon">
104
  <?php
105
+ $svg_dropdowns = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/dropdowns.svg' );
106
+ if ( $svg_dropdowns ) {
107
+ echo wp_kses( $svg_dropdowns, rmp_allow_svg_html_tags() );
108
  }
109
  ?>
110
  </span>
114
  <li id="rmp-tab-item-header-bar" class="rmp-tab-item" aria-owns="tab-header-bar">
115
  <span class="rmp-tab-item-icon">
116
  <?php
117
+ $svg_header = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/header.svg' );
118
+ if ( $svg_header ) {
119
+ echo wp_kses( $svg_header, rmp_allow_svg_html_tags() );
120
  }
121
  ?>
122
  </span>
129
  <li id="rmp-tab-item-themes" class="rmp-tab-item" aria-owns="tab-themes">
130
  <span class="rmp-tab-item-icon">
131
  <?php
132
+ $svg_advanced = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/advanced.svg' );
133
+ if ( $svg_advanced ) {
134
+ echo wp_kses( $svg_advanced, rmp_allow_svg_html_tags() );
135
  }
136
  ?>
137
  </span>
141
  <li id="rmp-tab-item-settings" class="rmp-tab-item" aria-owns="tab-settings">
142
  <span class="rmp-tab-item-icon">
143
  <?php
144
+ $svg_general = $wp_filesystem->get_contents( RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/general.svg' );
145
+ if ( $svg_general ) {
146
+ echo wp_kses( $svg_general, rmp_allow_svg_html_tags() );
147
  }
148
  ?>
149
  </span>
420
  'item_class' => 'is-child-item',
421
  'aria_owns' => 'tab-container',
422
  'item_header' => array(
423
+ 'item_svg_icon' => RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/container.svg',
424
  'item_title' => esc_html__( 'Container', 'responsive-menu' ),
425
  ),
426
  )
431
  'item_class' => 'is-child-item',
432
  'aria_owns' => 'tab-toggle-button',
433
  'item_header' => array(
434
+ 'item_svg_icon' => RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/toggle.svg',
435
  'item_title' => esc_html__( 'Toggle button', 'responsive-menu' ),
436
  ),
437
  )
448
  'item_class' => 'is-child-item rmp-tab-item-general-settings',
449
  'aria_owns' => 'tab-general-settings',
450
  'item_header' => array(
451
+ 'item_svg_icon' => RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/general.svg',
452
  'item_title' => esc_html__( 'General Settings', 'responsive-menu' ),
453
  ),
454
  )
459
  'item_class' => 'is-child-item rmp-tab-item-advanced-settings',
460
  'aria_owns' => 'tab-advanced-settings',
461
  'item_header' => array(
462
+ 'item_svg_icon' => RMP_PLUGIN_PATH_V4 . '/assets/admin/icons/svg/advanced.svg',
463
  'item_title' => esc_html__( 'Advanced Settings', 'responsive-menu' ),
464
  ),
465
  )