WordPress Button Plugin MaxButtons - Version 8.8

Version Description

  • Updated SCSSPHP library to 1.8.1
  • Updated several deprecated JQuery calls
Download this release

Release Info

Developer basszje
Plugin Icon 128x128 WordPress Button Plugin MaxButtons
Version 8.8
Comparing to
See all releases

Code changes from version 8.7 to 8.8

Files changed (60) hide show
  1. assets/integrations/cache/cache.php +1 -1
  2. assets/libraries/alpha-color/alpha-color-214.js +0 -500
  3. assets/libraries/alpha-color/alpha-color-picker.js +6 -6
  4. assets/libraries/scssphp/README.md +34 -9
  5. assets/libraries/scssphp/composer.json +69 -12
  6. assets/libraries/scssphp/phpcs.xml.dist +12 -0
  7. assets/libraries/scssphp/scss.inc.php +18 -28
  8. assets/libraries/scssphp/src/Base/Range.php +10 -0
  9. assets/libraries/scssphp/src/Block.php +5 -2
  10. assets/libraries/scssphp/src/Cache.php +47 -14
  11. assets/libraries/scssphp/src/Colors.php +17 -11
  12. assets/libraries/scssphp/src/CompilationResult.php +69 -0
  13. assets/libraries/scssphp/src/Compiler.php +3725 -1482
  14. assets/libraries/scssphp/src/Compiler/CachedResult.php +77 -0
  15. assets/libraries/scssphp/src/Compiler/Environment.php +5 -2
  16. assets/libraries/scssphp/src/Exception/CompilerException.php +4 -1
  17. assets/libraries/scssphp/src/Exception/ParserException.php +30 -1
  18. assets/libraries/scssphp/src/Exception/RangeException.php +4 -1
  19. assets/libraries/scssphp/src/Exception/SassException.php +7 -0
  20. assets/libraries/scssphp/src/Exception/SassScriptException.php +32 -0
  21. assets/libraries/scssphp/src/Exception/ServerException.php +6 -1
  22. assets/libraries/scssphp/src/Formatter.php +48 -15
  23. assets/libraries/scssphp/src/Formatter/Compact.php +7 -0
  24. assets/libraries/scssphp/src/Formatter/Compressed.php +5 -1
  25. assets/libraries/scssphp/src/Formatter/Crunched.php +9 -1
  26. assets/libraries/scssphp/src/Formatter/Debug.php +7 -1
  27. assets/libraries/scssphp/src/Formatter/Expanded.php +4 -2
  28. assets/libraries/scssphp/src/Formatter/Nested.php +10 -3
  29. assets/libraries/scssphp/src/Formatter/OutputBlock.php +10 -7
  30. assets/libraries/scssphp/src/Logger/LoggerInterface.php +48 -0
  31. assets/libraries/scssphp/src/Logger/QuietLogger.php +27 -0
  32. assets/libraries/scssphp/src/Logger/StreamLogger.php +60 -0
  33. assets/libraries/scssphp/src/Node.php +5 -2
  34. assets/libraries/scssphp/src/Node/Number.php +560 -146
  35. assets/libraries/scssphp/src/OutputStyle.php +9 -0
  36. assets/libraries/scssphp/src/Parser.php +1072 -348
  37. assets/libraries/scssphp/src/SourceMap/Base64.php +5 -2
  38. assets/libraries/scssphp/src/SourceMap/Base64VLQ.php +3 -2
  39. assets/libraries/scssphp/src/SourceMap/SourceMapGenerator.php +40 -7
  40. assets/libraries/scssphp/src/Type.php +6 -0
  41. assets/libraries/scssphp/src/Util.php +119 -5
  42. assets/libraries/scssphp/src/Util/Path.php +77 -0
  43. assets/libraries/scssphp/src/ValueConverter.php +95 -0
  44. assets/libraries/scssphp/src/Version.php +2 -1
  45. assets/libraries/scssphp/src/Warn.php +84 -0
  46. classes/button.php +5 -3
  47. classes/controllers/listController.php +0 -1
  48. classes/installation.php +1 -2
  49. classes/max-utils.php +0 -15
  50. classes/maxCSSParser.php +6 -3
  51. includes/maxbuttons-button.php +0 -1
  52. includes/maxbuttons-list-trash.php +0 -186
  53. includes/maxbuttons-list.php +0 -17
  54. includes/social-share.php +1 -0
  55. js/live-preview.js +2 -2
  56. js/maxbuttons-admin.js +20 -4
  57. js/min/live-preview.js +1 -1
  58. js/min/maxbuttons-admin.js +1 -1
  59. maxbuttons.php +3 -3
  60. readme.txt +6 -1
assets/integrations/cache/cache.php CHANGED
@@ -4,7 +4,7 @@ defined('ABSPATH') or die('No direct access permitted');
4
 
5
  class MBCache
6
  {
7
- protected $has_supercache = false; // supercache seems to replace quite fine, without our help. @todo Test if this is needed
8
  protected $has_w3tc = false;
9
  protected $has_wpengine = false;
10
  protected $has_fastestcache = false;
4
 
5
  class MBCache
6
  {
7
+ protected $has_supercache = false; // supercache seems to replace quite fine, without our help.
8
  protected $has_w3tc = false;
9
  protected $has_wpengine = false;
10
  protected $has_fastestcache = false;
assets/libraries/alpha-color/alpha-color-214.js DELETED
@@ -1,500 +0,0 @@
1
- /**!
2
- * wp-color-picker-alpha
3
- *
4
- * Overwrite Automattic Iris for enabled Alpha Channel in wpColorPicker
5
- * Only run in input and is defined data alpha in true
6
- *
7
- * Version: 2.1.4
8
- * https://github.com/kallookoo/wp-color-picker-alpha
9
- * Licensed under the GPLv2 license or later.
10
- */
11
- ( function( $ ) {
12
- // Prevent double-init.
13
- if ( $.wp.wpColorPicker.prototype._hasAlpha ) {
14
- return;
15
- }
16
-
17
- // Variable for some backgrounds ( grid )
18
- var image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAAHnlligAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHJJREFUeNpi+P///4EDBxiAGMgCCCAGFB5AADGCRBgYDh48CCRZIJS9vT2QBAggFBkmBiSAogxFBiCAoHogAKIKAlBUYTELAiAmEtABEECk20G6BOmuIl0CIMBQ/IEMkO0myiSSraaaBhZcbkUOs0HuBwDplz5uFJ3Z4gAAAABJRU5ErkJggg==',
19
- // html stuff for wpColorPicker copy of the original color-picker.js
20
- _after = '<div class="wp-picker-holder" />',
21
- _wrap = '<div class="wp-picker-container" />',
22
- _button = '<input type="button" class="button button-small" />',
23
- _deprecated = ( wpColorPickerL10n.current !== undefined );
24
- // Prevent CSS issues in < WordPress 4.9
25
-
26
- // Declare some global variables when is deprecated or not
27
- if ( _deprecated ) {
28
- var _before = '<a tabindex="0" class="wp-color-result" />';
29
- } else {
30
- var _before = '<button type="button" class="button wp-color-result" aria-expanded="false"></button>',
31
- _wrappingLabel = '<label></label>',
32
- _wrappingLabelText = '<span class="screen-reader-text"></span>';
33
- }
34
- /**
35
- * Overwrite Color
36
- * for enable support rbga
37
- */
38
- Color.fn.toString = function() {
39
- if ( this._alpha < 1 )
40
- return this.toCSS( 'rgba', this._alpha ).replace( /\s+/g, '' );
41
-
42
- var hex = parseInt( this._color, 10 ).toString( 16 );
43
-
44
- if ( this.error )
45
- return '';
46
-
47
- if ( hex.length < 6 )
48
- hex = ( '00000' + hex ).substr( -6 );
49
-
50
- return '#' + hex;
51
- };
52
-
53
- /**
54
- * Overwrite wpColorPicker
55
- */
56
- $.widget( 'wp.wpColorPicker', $.wp.wpColorPicker, {
57
- _hasAlpha: true,
58
- /**
59
- * @summary Creates the color picker.
60
- *
61
- * Creates the color picker, sets default values, css classes and wraps it all in HTML.
62
- *
63
- * @since 3.5.0
64
- *
65
- * @access private
66
- *
67
- * @returns {void}
68
- */
69
- _create: function() {
70
- // Return early if Iris support is missing.
71
- if ( ! $.support.iris ) {
72
- return;
73
- }
74
-
75
- var self = this,
76
- el = self.element;
77
-
78
- // Override default options with options bound to the element.
79
- $.extend( self.options, el.data() );
80
-
81
- // Create a color picker which only allows adjustments to the hue.
82
- if ( self.options.type === 'hue' ) {
83
- return self._createHueOnly();
84
- }
85
-
86
- // Bind the close event.
87
- self.close = $.proxy( self.close, self );
88
-
89
- self.initialValue = el.val();
90
-
91
- // Add a CSS class to the input field.
92
- el.addClass( 'wp-color-picker' );
93
-
94
- if ( _deprecated ) {
95
- el.hide().wrap( _wrap );
96
- self.wrap = el.parent();
97
- self.toggler = $( _before )
98
- .insertBefore( el )
99
- .css( { backgroundColor : self.initialValue } )
100
- .attr( 'title', wpColorPickerL10n.pick )
101
- .attr( 'data-current', wpColorPickerL10n.current );
102
- self.pickerContainer = $( _after ).insertAfter( el );
103
- self.button = $( _button ).addClass('hidden');
104
- } else {
105
- /*
106
- * Check if there's already a wrapping label, e.g. in the Customizer.
107
- * If there's no label, add a default one to match the Customizer template.
108
- */
109
- if ( ! el.parent( 'label' ).length ) {
110
- // Wrap the input field in the default label.
111
- el.wrap( _wrappingLabel );
112
- // Insert the default label text.
113
- self.wrappingLabelText = $( _wrappingLabelText )
114
- .insertBefore( el )
115
- .text( wpColorPickerL10n.defaultLabel );
116
- }
117
-
118
- /*
119
- * At this point, either it's the standalone version or the Customizer
120
- * one, we have a wrapping label to use as hook in the DOM, let's store it.
121
- */
122
- self.wrappingLabel = el.parent();
123
-
124
- // Wrap the label in the main wrapper.
125
- self.wrappingLabel.wrap( _wrap );
126
- // Store a reference to the main wrapper.
127
- self.wrap = self.wrappingLabel.parent();
128
- // Set up the toggle button and insert it before the wrapping label.
129
- self.toggler = $( _before )
130
- .insertBefore( self.wrappingLabel )
131
- .css( { backgroundColor: self.initialValue } );
132
- // Set the toggle button span element text.
133
- // self.toggler.find( '.wp-color-result-text' ).text( wpColorPickerL10n.pick );
134
- // Set up the Iris container and insert it after the wrapping label.
135
- self.pickerContainer = $( _after ).insertAfter( self.wrappingLabel );
136
- // Store a reference to the Clear/Default button.
137
- self.button = $( _button );
138
- }
139
-
140
- // Set up the Clear/Default button.
141
- if ( self.options.defaultColor ) {
142
- self.button.addClass( 'wp-picker-default' ).val( wpColorPickerL10n.defaultString );
143
- if ( ! _deprecated ) {
144
- self.button.attr( 'aria-label', wpColorPickerL10n.defaultAriaLabel );
145
- }
146
- } else {
147
- self.button.addClass( 'wp-picker-clear' ).val( wpColorPickerL10n.clear );
148
- if ( ! _deprecated ) {
149
- self.button.attr( 'aria-label', wpColorPickerL10n.clearAriaLabel );
150
- }
151
- }
152
-
153
- if ( _deprecated ) {
154
- el.wrap( '<span class="wp-picker-input-wrap" />' ).after( self.button );
155
- } else {
156
- // Wrap the wrapping label in its wrapper and append the Clear/Default button.
157
- self.wrappingLabel
158
- .wrap( '<span class="wp-picker-input-wrap hidden" />' )
159
- .after( self.button );
160
-
161
- /*
162
- * The input wrapper now contains the label+input+Clear/Default button.
163
- * Store a reference to the input wrapper: we'll use this to toggle
164
- * the controls visibility.
165
- */
166
- self.inputWrapper = el.closest( '.wp-picker-input-wrap' );
167
- }
168
-
169
- el.iris( {
170
- target: self.pickerContainer,
171
- hide: self.options.hide,
172
- width: self.options.width,
173
- mode: self.options.mode,
174
- palettes: self.options.palettes,
175
- /**
176
- * @summary Handles the onChange event if one has been defined in the options.
177
- *
178
- * Handles the onChange event if one has been defined in the options and additionally
179
- * sets the background color for the toggler element.
180
- *
181
- * @since 3.5.0
182
- *
183
- * @param {Event} event The event that's being called.
184
- * @param {HTMLElement} ui The HTMLElement containing the color picker.
185
- *
186
- * @returns {void}
187
- */
188
- change: function( event, ui ) {
189
- if ( self.options.alpha ) {
190
- self.toggler.css( { 'background-image' : 'url(' + image + ')' } );
191
- if ( _deprecated ) {
192
- self.toggler.html( '<span class="color-alpha" />' );
193
- } else {
194
- self.toggler.css( {
195
- 'position' : 'relative'
196
- } );
197
- if ( self.toggler.find('span.color-alpha').length == 0 ) {
198
- self.toggler.append('<span class="color-alpha" />');
199
- }
200
- }
201
-
202
- self.toggler.find( 'span.color-alpha' ).css( {
203
- 'width' : '30px',
204
- 'height' : '100%',
205
- 'position' : 'absolute',
206
- 'top' : 0,
207
- 'left' : 0,
208
- 'border-top-left-radius' : '2px',
209
- 'border-bottom-left-radius' : '2px',
210
- 'background' : ui.color.toString()
211
- } );
212
- } else {
213
- self.toggler.css( { backgroundColor : ui.color.toString() } );
214
- }
215
-
216
- if ( $.isFunction( self.options.change ) ) {
217
- self.options.change.call( this, event, ui );
218
- }
219
- }
220
- } );
221
-
222
- el.val( self.initialValue );
223
- self._addListeners();
224
-
225
- // Force the color picker to always be closed on initial load.
226
- if ( ! self.options.hide ) {
227
- self.toggler.click();
228
- }
229
- },
230
- /**
231
- * @summary Binds event listeners to the color picker.
232
- *
233
- * @since 3.5.0
234
- *
235
- * @access private
236
- *
237
- * @returns {void}
238
- */
239
- _addListeners: function() {
240
- var self = this;
241
-
242
- /**
243
- * @summary Prevent any clicks inside this widget from leaking to the top and closing it.
244
- *
245
- * @since 3.5.0
246
- *
247
- * @param {Event} event The event that's being called.
248
- *
249
- * @returs {void}
250
- */
251
- self.wrap.on( 'click.wpcolorpicker', function( event ) {
252
- event.stopPropagation();
253
- });
254
-
255
- /**
256
- * @summary Open or close the color picker depending on the class.
257
- *
258
- * @since 3.5
259
- */
260
- self.toggler.click( function(){
261
- if ( self.toggler.hasClass( 'wp-picker-open' ) ) {
262
- self.close();
263
- } else {
264
- self.open();
265
- }
266
- });
267
-
268
- /**
269
- * @summary Checks if value is empty when changing the color in the color picker.
270
- *
271
- * Checks if value is empty when changing the color in the color picker.
272
- * If so, the background color is cleared.
273
- *
274
- * @since 3.5.0
275
- *
276
- * @param {Event} event The event that's being called.
277
- *
278
- * @returns {void}
279
- */
280
- self.element.on( 'change', function( event ) {
281
- // Empty or Error = clear
282
- if ( $( this ).val() === '' || self.element.hasClass( 'iris-error' ) ) {
283
- if ( self.options.alpha ) {
284
- if ( _deprecated ) {
285
- self.toggler.removeAttr( 'style' );
286
- }
287
- self.toggler.find( 'span.color-alpha' ).css( 'backgroundColor', '' );
288
- } else {
289
- self.toggler.css( 'backgroundColor', '' );
290
- }
291
-
292
- // fire clear callback if we have one
293
- if ( $.isFunction( self.options.clear ) )
294
- self.options.clear.call( this, event );
295
- }
296
- } );
297
-
298
- /**
299
- * @summary Enables the user to clear or revert the color in the color picker.
300
- *
301
- * Enables the user to either clear the color in the color picker or revert back to the default color.
302
- *
303
- * @since 3.5.0
304
- *
305
- * @param {Event} event The event that's being called.
306
- *
307
- * @returns {void}
308
- */
309
- self.button.on( 'click', function( event ) {
310
- if ( $( this ).hasClass( 'wp-picker-clear' ) ) {
311
- self.element.val( '' );
312
- if ( self.options.alpha ) {
313
- if ( _deprecated ) {
314
- self.toggler.removeAttr( 'style' );
315
- }
316
- self.toggler.find( 'span.color-alpha' ).css( 'backgroundColor', '' );
317
- } else {
318
- self.toggler.css( 'backgroundColor', '' );
319
- }
320
-
321
- if ( $.isFunction( self.options.clear ) )
322
- self.options.clear.call( this, event );
323
-
324
- self.element.trigger( 'change' );
325
- } else if ( $( this ).hasClass( 'wp-picker-default' ) ) {
326
- self.element.val( self.options.defaultColor ).change();
327
- }
328
- });
329
- },
330
- });
331
-
332
- /**
333
- * Overwrite iris
334
- */
335
- $.widget( 'a8c.iris', $.a8c.iris, {
336
- _create: function() {
337
- this._super();
338
-
339
-
340
- // Global option for check is mode rbga is enabled
341
- this.options.alpha = true; //this.element.data( 'alpha' ) || false;
342
-
343
- // Is not input disabled
344
- if ( ! this.element.is( ':input' ) )
345
- this.options.alpha = false;
346
-
347
- if ( typeof this.options.alpha !== 'undefined' && this.options.alpha ) {
348
- var self = this,
349
- el = self.element,
350
- _html = '<div class="iris-strip iris-slider iris-alpha-slider"><div class="iris-slider-offset iris-slider-offset-alpha"></div></div>',
351
- aContainer = $( _html ).appendTo( self.picker.find( '.iris-picker-inner' ) ),
352
- aSlider = aContainer.find( '.iris-slider-offset-alpha' ),
353
- controls = {
354
- aContainer : aContainer,
355
- aSlider : aSlider
356
- };
357
-
358
- if ( typeof el.data( 'custom-width' ) !== 'undefined' ) {
359
- self.options.customWidth = parseInt( el.data( 'custom-width' ) ) || 0;
360
- } else {
361
- self.options.customWidth = 100;
362
- }
363
-
364
- // Set default width for input reset
365
- self.options.defaultWidth = el.width();
366
-
367
- // Update width for input
368
- if ( self._color._alpha < 1 || self._color.toString().indexOf('rgb') != -1 )
369
- el.width( parseInt( self.options.defaultWidth + self.options.customWidth ) );
370
-
371
- // Push new controls
372
- $.each( controls, function( k, v ) {
373
- self.controls[k] = v;
374
- } );
375
-
376
- // Change size strip and add margin for sliders
377
- self.controls.square.css( { 'margin-right': '0' } );
378
- var emptyWidth = ( self.picker.width() - self.controls.square.width() - 20 ),
379
- stripsMargin = ( emptyWidth / 6 ),
380
- stripsWidth = ( ( emptyWidth / 2 ) - stripsMargin );
381
-
382
- $.each( [ 'aContainer', 'strip' ], function( k, v ) {
383
- self.controls[v].width( stripsWidth ).css( { 'margin-left' : stripsMargin + 'px' } );
384
- } );
385
-
386
- // Add new slider
387
- self._initControls();
388
-
389
- // For updated widget
390
- // self._change();
391
- }
392
- },
393
- _initControls: function() {
394
- this._super();
395
-
396
- if ( this.options.alpha ) {
397
- var self = this,
398
- controls = self.controls;
399
-
400
- controls.aSlider.slider({
401
- orientation : 'vertical',
402
- min : 0,
403
- max : 100,
404
- step : 1,
405
- value : parseInt( self._color._alpha * 100 ),
406
- slide : function( event, ui ) {
407
- // Update alpha value
408
- self._color._alpha = parseFloat( ui.value / 100 );
409
- self._change.apply( self, arguments );
410
- }
411
- });
412
- }
413
- },
414
- _change: function() {
415
- this._super();
416
-
417
- var self = this,
418
- el = self.element;
419
-
420
- if ( this.options.alpha ) {
421
- var controls = self.controls,
422
- alpha = parseInt( self._color._alpha * 100 ),
423
- color = self._color.toRgb(),
424
- gradient = [
425
- 'rgb(' + color.r + ',' + color.g + ',' + color.b + ') 0%',
426
- 'rgba(' + color.r + ',' + color.g + ',' + color.b + ', 0) 100%'
427
- ],
428
- defaultWidth = self.options.defaultWidth,
429
- customWidth = self.options.customWidth,
430
- target = self.picker.closest( '.wp-picker-container' ).find( '.wp-color-result' );
431
-
432
- // Generate background slider alpha, only for CSS3 old browser fuck!! :)
433
- controls.aContainer.css( { 'background' : 'linear-gradient(to bottom, ' + gradient.join( ', ' ) + '), url(' + image + ')' } );
434
-
435
- if ( target.hasClass( 'wp-picker-open' ) ) {
436
- // Update alpha value
437
- controls.aSlider.slider( 'value', alpha );
438
-
439
- /**
440
- * Disabled change opacity in default slider Saturation ( only is alpha enabled )
441
- * and change input width for view all value
442
- */
443
- if ( self._color._alpha < 1 ) {
444
- controls.strip.attr( 'style', controls.strip.attr( 'style' ).replace( /rgba\(([0-9]+,)(\s+)?([0-9]+,)(\s+)?([0-9]+)(,(\s+)?[0-9\.]+)\)/g, 'rgb($1$3$5)' ) );
445
- el.width( parseInt( defaultWidth + customWidth ) );
446
- } else {
447
- el.width( defaultWidth );
448
- }
449
- }
450
- }
451
-
452
- var reset = el.data( 'reset-alpha' ) || false;
453
-
454
- if ( reset ) {
455
- self.picker.find( '.iris-palette-container' ).on( 'click.palette', '.iris-palette', function() {
456
- self._color._alpha = 1;
457
- self.active = 'external';
458
- self._change();
459
- } );
460
- }
461
- el.trigger( 'change' );
462
- },
463
- _addInputListeners: function( input ) {
464
- var self = this,
465
- debounceTimeout = 100,
466
- callback = function( event ) {
467
- var color = new Color( input.val() ),
468
- val = input.val();
469
-
470
- input.removeClass( 'iris-error' );
471
- // we gave a bad color
472
- if ( color.error ) {
473
- // don't error on an empty input
474
- if ( val !== '' )
475
- input.addClass( 'iris-error' );
476
- } else {
477
- if ( color.toString() !== self._color.toString() ) {
478
- // let's not do this on keyup for hex shortcodes
479
- if ( ! ( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) )
480
- self._setOption( 'color', color.toString() );
481
- }
482
- }
483
- };
484
-
485
- input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) );
486
-
487
- // If we initialized hidden, show on first focus. The rest is up to you.
488
- if ( self.options.hide ) {
489
- input.on( 'focus', function() {
490
- self.show();
491
- } );
492
- }
493
- }
494
- } );
495
- }( jQuery ) );
496
-
497
- // Auto Call plugin is class is color-picker
498
- /*jQuery( document ).ready( function( $ ) {
499
- $( '.color-picker' ).wpColorPicker();
500
- } ); */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/libraries/alpha-color/alpha-color-picker.js CHANGED
@@ -542,7 +542,7 @@
542
  self.colorAlpha.css( { 'background-color': ui.color.to_s( self.alphaOptions.alphaColorType ) } );
543
 
544
  // fire change callback if we have one
545
- if ( $.isFunction( self.options.change ) ) {
546
  self.options.change.call( this, event, ui );
547
  }
548
  }
@@ -567,7 +567,7 @@
567
  *
568
  * @since 3.0.0
569
  */
570
- self.toggler.click( function() {
571
  if ( self.toggler.hasClass( 'wp-picker-open' ) ) {
572
  self.close();
573
  } else {
@@ -585,7 +585,7 @@
585
  *
586
  * @return {void}
587
  */
588
- el.change( function( event ) {
589
  var val = $( this ).val();
590
 
591
  if ( el.hasClass( 'iris-error' ) || val === '' || val.match( /^(#|(rgb|hsl)a?)$/ ) ) {
@@ -596,7 +596,7 @@
596
  self.colorAlpha.css( 'background-color', '' );
597
 
598
  // fire clear callback if we have one
599
- if ( $.isFunction( self.options.clear ) ) {
600
  self.options.clear.call( this, event );
601
  }
602
  }
@@ -611,7 +611,7 @@
611
  *
612
  * @return {void}
613
  */
614
- self.button.click( function( event ) {
615
  if ( $( this ).hasClass( 'wp-picker-default' ) ) {
616
  el.val( self.options.defaultColor ).change();
617
  } else if ( $( this ).hasClass( 'wp-picker-clear' ) ) {
@@ -623,7 +623,7 @@
623
  self.colorAlpha.css( 'background-color', '' );
624
 
625
  // fire clear callback if we have one
626
- if ( $.isFunction( self.options.clear ) ) {
627
  self.options.clear.call( this, event );
628
  }
629
 
542
  self.colorAlpha.css( { 'background-color': ui.color.to_s( self.alphaOptions.alphaColorType ) } );
543
 
544
  // fire change callback if we have one
545
+ if ( typeof self.options.change === 'function') {
546
  self.options.change.call( this, event, ui );
547
  }
548
  }
567
  *
568
  * @since 3.0.0
569
  */
570
+ self.toggler.on('click', function() {
571
  if ( self.toggler.hasClass( 'wp-picker-open' ) ) {
572
  self.close();
573
  } else {
585
  *
586
  * @return {void}
587
  */
588
+ el.on('change', function( event ) {
589
  var val = $( this ).val();
590
 
591
  if ( el.hasClass( 'iris-error' ) || val === '' || val.match( /^(#|(rgb|hsl)a?)$/ ) ) {
596
  self.colorAlpha.css( 'background-color', '' );
597
 
598
  // fire clear callback if we have one
599
+ if ( typeof self.options.clear === 'function' ) {
600
  self.options.clear.call( this, event );
601
  }
602
  }
611
  *
612
  * @return {void}
613
  */
614
+ self.button.on('click', function( event ) {
615
  if ( $( this ).hasClass( 'wp-picker-default' ) ) {
616
  el.val( self.options.defaultColor ).change();
617
  } else if ( $( this ).hasClass( 'wp-picker-clear' ) ) {
623
  self.colorAlpha.css( 'background-color', '' );
624
 
625
  // fire clear callback if we have one
626
+ if ( typeof self.options.clear === 'function' ) {
627
  self.options.clear.call( this, event );
628
  }
629
 
assets/libraries/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.
assets/libraries/scssphp/composer.json CHANGED
@@ -30,22 +30,79 @@
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.0",
46
  "zurb/foundation": "~6.5"
47
  },
48
+ "repositories": [
49
+ {
50
+ "type": "package",
51
+ "package": {
52
+ "name": "sass/sass-spec",
53
+ "version": "2021.09.15",
54
+ "source": {
55
+ "type": "git",
56
+ "url": "https://github.com/sass/sass-spec.git",
57
+ "reference": "eb2d7a0865c1faf0b55a39ff962b24aca9b4c955"
58
+ },
59
+ "dist": {
60
+ "type": "zip",
61
+ "url": "https://api.github.com/repos/sass/sass-spec/zipball/eb2d7a0865c1faf0b55a39ff962b24aca9b4c955",
62
+ "reference": "eb2d7a0865c1faf0b55a39ff962b24aca9b4c955",
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.0",
90
+ "source": {
91
+ "type": "git",
92
+ "url": "https://github.com/twbs/bootstrap.git",
93
+ "reference": "6ffb0b48e455430f8a5359ed689ad64c1143fac2"
94
+ },
95
+ "dist": {
96
+ "type": "zip",
97
+ "url": "https://api.github.com/repos/twbs/bootstrap/zipball/6ffb0b48e455430f8a5359ed689ad64c1143fac2",
98
+ "reference": "6ffb0b48e455430f8a5359ed689ad64c1143fac2",
99
+ "shasum": ""
100
+ }
101
+ }
102
+ }
103
+ ],
104
  "bin": ["bin/pscss"],
105
+ "config": {
106
+ "sort-packages": true
 
 
 
 
 
 
 
107
  }
108
  }
assets/libraries/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>
assets/libraries/scssphp/scss.inc.php CHANGED
@@ -1,31 +1,21 @@
1
  <?php
2
 
3
- if (! class_exists('ScssPhp\ScssPhp\Version', false)) {
4
- include_once __DIR__ . '/src/Base/Range.php';
5
- include_once __DIR__ . '/src/Block.php';
6
- include_once __DIR__ . '/src/Cache.php';
7
- include_once __DIR__ . '/src/Colors.php';
8
- include_once __DIR__ . '/src/Compiler.php';
9
- include_once __DIR__ . '/src/Compiler/Environment.php';
10
- include_once __DIR__ . '/src/Exception/CompilerException.php';
11
- include_once __DIR__ . '/src/Exception/ParserException.php';
12
- include_once __DIR__ . '/src/Exception/RangeException.php';
13
- include_once __DIR__ . '/src/Exception/ServerException.php';
14
- include_once __DIR__ . '/src/Formatter.php';
15
- include_once __DIR__ . '/src/Formatter/Compact.php';
16
- include_once __DIR__ . '/src/Formatter/Compressed.php';
17
- include_once __DIR__ . '/src/Formatter/Crunched.php';
18
- include_once __DIR__ . '/src/Formatter/Debug.php';
19
- include_once __DIR__ . '/src/Formatter/Expanded.php';
20
- include_once __DIR__ . '/src/Formatter/Nested.php';
21
- include_once __DIR__ . '/src/Formatter/OutputBlock.php';
22
- include_once __DIR__ . '/src/Node.php';
23
- include_once __DIR__ . '/src/Node/Number.php';
24
- include_once __DIR__ . '/src/Parser.php';
25
- include_once __DIR__ . '/src/SourceMap/Base64.php';
26
- include_once __DIR__ . '/src/SourceMap/Base64VLQ.php';
27
- include_once __DIR__ . '/src/SourceMap/SourceMapGenerator.php';
28
- include_once __DIR__ . '/src/Type.php';
29
- include_once __DIR__ . '/src/Util.php';
30
- include_once __DIR__ . '/src/Version.php';
31
  }
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
  }
assets/libraries/scssphp/src/Base/Range.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,10 +16,19 @@ 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
  /**
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
  /**
assets/libraries/scssphp/src/Block.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,6 +16,8 @@ namespace ScssPhp\ScssPhp;
15
  * Block
16
  *
17
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
18
  */
19
  class Block
20
  {
@@ -49,7 +52,7 @@ class Block
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
  {
52
  public $sourceColumn;
53
 
54
  /**
55
+ * @var array|null
56
  */
57
  public $selectors;
58
 
67
  public $children;
68
 
69
  /**
70
+ * @var \ScssPhp\ScssPhp\Block|null
71
  */
72
  public $selfParent;
73
  }
assets/libraries/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
  {
assets/libraries/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
  {
@@ -204,7 +207,7 @@ class Colors
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
  {
207
  * @param integer $r
208
  * @param integer $g
209
  * @param integer $b
210
+ * @param integer|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
  }
assets/libraries/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
+ }
assets/libraries/scssphp/src/Compiler.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -12,17 +13,20 @@
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 +59,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 +95,7 @@ class Compiler
71
  const SOURCE_MAP_FILE = 2;
72
 
73
  /**
74
- * @var array
75
  */
76
  protected static $operatorNames = [
77
  '+' => 'add',
@@ -87,11 +111,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 +124,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, '', []];
@@ -113,79 +138,227 @@ class Compiler
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
  {
@@ -197,53 +370,96 @@ class Compiler
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 +470,149 @@ 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 +625,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);
@@ -365,6 +662,8 @@ class Compiler
365
  * @param array $target
366
  * @param array $origin
367
  * @param array|null $block
 
 
368
  */
369
  protected function pushExtends($target, $origin, $block)
370
  {
@@ -383,14 +682,14 @@ class Compiler
383
  /**
384
  * Make output block
385
  *
386
- * @param string $type
387
- * @param array $selectors
388
  *
389
  * @return \ScssPhp\ScssPhp\Formatter\OutputBlock
390
  */
391
  protected function makeOutputBlock($type, $selectors = null)
392
  {
393
- $out = new OutputBlock;
394
  $out->type = $type;
395
  $out->lines = [];
396
  $out->children = [];
@@ -415,6 +714,8 @@ class Compiler
415
  * Compile root
416
  *
417
  * @param \ScssPhp\ScssPhp\Block $rootBlock
 
 
418
  */
419
  protected function compileRoot(Block $rootBlock)
420
  {
@@ -427,6 +728,8 @@ class Compiler
427
 
428
  /**
429
  * Report missing selectors
 
 
430
  */
431
  protected function missingSelectors()
432
  {
@@ -446,7 +749,7 @@ class Compiler
446
  $origin = $this->collapseSelectors($origin);
447
 
448
  $this->sourceLine = $block[Parser::SOURCE_LINE];
449
- $this->throwError("\"$origin\" failed to @extend \"$target\". The selector \"$target\" was not found.");
450
  }
451
  }
452
 
@@ -455,6 +758,8 @@ class Compiler
455
  *
456
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
457
  * @param string $parentKey
 
 
458
  */
459
  protected function flattenSelectors(OutputBlock $block, $parentKey = null)
460
  {
@@ -510,7 +815,7 @@ class Compiler
510
  }
511
 
512
  /**
513
- * Glue parts of :not( or :nth-child( ... that are in general splitted in selectors parts
514
  *
515
  * @param array $parts
516
  *
@@ -527,10 +832,11 @@ class Compiler
527
  } else {
528
  // a selector part finishing with a ) is the last part of a :not( or :nth-child(
529
  // and need to be joined to this
530
- if (\count($new) && \is_string($new[\count($new) - 1]) &&
 
531
  \strlen($part) && substr($part, -1) === ')' && strpos($part, '(') === false
532
  ) {
533
- while (\count($new)>1 && substr($new[\count($new) - 1], -1) !== '(') {
534
  $part = array_pop($new) . $part;
535
  }
536
  $new[\count($new) - 1] .= $part;
@@ -550,6 +856,8 @@ class Compiler
550
  * @param array $out
551
  * @param integer $from
552
  * @param boolean $initial
 
 
553
  */
554
  protected function matchExtends($selector, &$out, $from = 0, $initial = true)
555
  {
@@ -594,7 +902,7 @@ class Compiler
594
  }
595
  }
596
 
597
- if (\count($nonBreakableBefore) and $k == \count($new)) {
598
  $k--;
599
  }
600
 
@@ -684,8 +992,9 @@ class Compiler
684
  */
685
  protected function isPseudoSelector($part, &$matches)
686
  {
687
- if (strpos($part, ":") === 0
688
- && preg_match(",^::?([\w-]+)\((.+)\)$,", $part, $matches)
 
689
  ) {
690
  return true;
691
  }
@@ -702,6 +1011,8 @@ class Compiler
702
  *
703
  * @param array $out
704
  * @param array $extended
 
 
705
  */
706
  protected function pushOrMergeExtentedSelector(&$out, $extended)
707
  {
@@ -709,7 +1020,8 @@ class Compiler
709
  $single = reset($extended);
710
  $part = reset($single);
711
 
712
- if ($this->isPseudoSelector($part, $matchesExtended) &&
 
713
  \in_array($matchesExtended[1], [ 'slotted' ])
714
  ) {
715
  $prev = end($out);
@@ -719,11 +1031,12 @@ class Compiler
719
  $single = reset($prev);
720
  $part = reset($single);
721
 
722
- if ($this->isPseudoSelector($part, $matchesPrev) &&
 
723
  $matchesPrev[1] === $matchesExtended[1]
724
  ) {
725
  $extended = explode($matchesExtended[1] . '(', $matchesExtended[0], 2);
726
- $extended[1] = $matchesPrev[2] . ", " . $extended[1];
727
  $extended = implode($matchesExtended[1] . '(', $extended);
728
  $extended = [ [ $extended ]];
729
  array_pop($out);
@@ -783,14 +1096,15 @@ class Compiler
783
  }
784
  }
785
 
786
- if ($initial &&
 
787
  $this->isPseudoSelector($part, $matches) &&
788
  ! \in_array($matches[1], [ 'not' ])
789
  ) {
790
  $buffer = $matches[2];
791
  $parser = $this->parserFactory(__METHOD__);
792
 
793
- if ($parser->parseSelector($buffer, $subSelectors)) {
794
  foreach ($subSelectors as $ksub => $subSelector) {
795
  $subExtended = [];
796
  $this->matchExtends($subSelector, $subExtended, 0, false);
@@ -805,7 +1119,7 @@ class Compiler
805
 
806
  $subSelectorsExtended = implode(', ', $subSelectorsExtended);
807
  $singleExtended = $single;
808
- $singleExtended[$k] = str_replace("(".$buffer.")", "($subSelectorsExtended)", $part);
809
  $outOrigin[] = [ $singleExtended ];
810
  $found = true;
811
  }
@@ -830,14 +1144,15 @@ class Compiler
830
 
831
  foreach ($origin as $j => $new) {
832
  // prevent infinite loop when target extends itself
833
- if ($this->isSelfExtend($single, $origin) and !$initial) {
834
  return false;
835
  }
836
 
837
  $replacement = end($new);
838
 
839
  // Extending a decorated tag with another tag is not possible.
840
- if ($extendingDecoratedTag && $replacement[0] != $extendingDecoratedTag &&
 
841
  preg_match('/^[a-z0-9]+$/i', $replacement[0])
842
  ) {
843
  unset($origin[$j]);
@@ -908,12 +1223,13 @@ class Compiler
908
  $wasTag = false;
909
  $pseudo = [];
910
 
911
- while (\count($other) && strpos(end($other), ':')===0) {
912
  array_unshift($pseudo, array_pop($other));
913
  }
914
 
915
  foreach ([array_reverse($base), array_reverse($other)] as $single) {
916
  $rang = count($single);
 
917
  foreach ($single as $part) {
918
  if (preg_match('/^[\[:]/', $part)) {
919
  $out[] = $part;
@@ -921,7 +1237,7 @@ class Compiler
921
  } elseif (preg_match('/^[\.#]/', $part)) {
922
  array_unshift($out, $part);
923
  $wasTag = false;
924
- } elseif (preg_match('/^[^_-]/', $part) and $rang==1) {
925
  $tag[] = $part;
926
  $wasTag = true;
927
  } elseif ($wasTag) {
@@ -948,6 +1264,8 @@ class Compiler
948
  * Compile media
949
  *
950
  * @param \ScssPhp\ScssPhp\Block $media
 
 
951
  */
952
  protected function compileMedia(Block $media)
953
  {
@@ -955,7 +1273,7 @@ class Compiler
955
 
956
  $mediaQueries = $this->compileMediaQuery($this->multiplyMedia($this->env));
957
 
958
- if (! empty($mediaQueries) && $mediaQueries) {
959
  $previousScope = $this->scope;
960
  $parentScope = $this->mediaParent($this->scope);
961
 
@@ -972,7 +1290,8 @@ class Compiler
972
  foreach ($media->children as $child) {
973
  $type = $child[0];
974
 
975
- if ($type !== Type::T_BLOCK &&
 
976
  $type !== Type::T_MEDIA &&
977
  $type !== Type::T_DIRECTIVE &&
978
  $type !== Type::T_IMPORT
@@ -983,7 +1302,7 @@ class Compiler
983
  }
984
 
985
  if ($needsWrap) {
986
- $wrapped = new Block;
987
  $wrapped->sourceName = $media->sourceName;
988
  $wrapped->sourceIndex = $media->sourceIndex;
989
  $wrapped->sourceLine = $media->sourceLine;
@@ -994,30 +1313,6 @@ class Compiler
994
  $wrapped->children = $media->children;
995
 
996
  $media->children = [[Type::T_BLOCK, $wrapped]];
997
-
998
- if (isset($this->lineNumberStyle)) {
999
- $annotation = $this->makeOutputBlock(Type::T_COMMENT);
1000
- $annotation->depth = 0;
1001
-
1002
- $file = $this->sourceNames[$media->sourceIndex];
1003
- $line = $media->sourceLine;
1004
-
1005
- switch ($this->lineNumberStyle) {
1006
- case static::LINE_COMMENTS:
1007
- $annotation->lines[] = '/* line ' . $line
1008
- . ($file ? ', ' . $file : '')
1009
- . ' */';
1010
- break;
1011
-
1012
- case static::DEBUG_INFO:
1013
- $annotation->lines[] = '@media -sass-debug-info{'
1014
- . ($file ? 'filename{font-family:"' . $file . '"}' : '')
1015
- . 'line{font-family:' . $line . '}}';
1016
- break;
1017
- }
1018
-
1019
- $this->scope->children[] = $annotation;
1020
- }
1021
  }
1022
 
1023
  $this->compileChildrenNoReturn($media->children, $this->scope);
@@ -1051,20 +1346,33 @@ class Compiler
1051
  /**
1052
  * Compile directive
1053
  *
1054
- * @param \ScssPhp\ScssPhp\Block|array $block
1055
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
 
 
1056
  */
1057
  protected function compileDirective($directive, OutputBlock $out)
1058
  {
1059
  if (\is_array($directive)) {
1060
- $s = '@' . $directive[0];
 
1061
 
1062
  if (! empty($directive[1])) {
1063
  $s .= ' ' . $this->compileValue($directive[1]);
1064
  }
 
 
 
 
 
1065
 
1066
- $this->appendRootDirective($s . ';', $out);
 
 
 
 
1067
  } else {
 
1068
  $s = '@' . $directive->name;
1069
 
1070
  if (! empty($directive->value)) {
@@ -1079,10 +1387,28 @@ class Compiler
1079
  }
1080
  }
1081
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1082
  /**
1083
  * Compile at-root
1084
  *
1085
  * @param \ScssPhp\ScssPhp\Block $block
 
 
1086
  */
1087
  protected function compileAtRoot(Block $block)
1088
  {
@@ -1092,7 +1418,7 @@ class Compiler
1092
 
1093
  // wrap inline selector
1094
  if ($block->selector) {
1095
- $wrapped = new Block;
1096
  $wrapped->sourceName = $block->sourceName;
1097
  $wrapped->sourceIndex = $block->sourceIndex;
1098
  $wrapped->sourceLine = $block->sourceLine;
@@ -1108,8 +1434,11 @@ class Compiler
1108
  }
1109
 
1110
  $selfParent = $block->selfParent;
 
1111
 
1112
- if (! $block->selfParent->selectors && isset($block->parent) && $block->parent &&
 
 
1113
  isset($block->parent->selectors) && $block->parent->selectors
1114
  ) {
1115
  $selfParent = $block->parent;
@@ -1137,19 +1466,19 @@ class Compiler
1137
  * @param array $with
1138
  * @param array $without
1139
  *
1140
- * @return mixed
1141
  */
1142
  protected function filterScopeWithWithout($scope, $with, $without)
1143
  {
1144
  $filteredScopes = [];
1145
  $childStash = [];
1146
 
1147
- if ($scope->type === TYPE::T_ROOT) {
1148
  return $scope;
1149
  }
1150
 
1151
  // start from the root
1152
- while ($scope->parent && $scope->parent->type !== TYPE::T_ROOT) {
1153
  array_unshift($childStash, $scope);
1154
  $scope = $scope->parent;
1155
  }
@@ -1210,7 +1539,7 @@ class Compiler
1210
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1211
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $previousScope
1212
  *
1213
- * @return mixed
1214
  */
1215
  protected function completeScope($scope, $previousScope)
1216
  {
@@ -1266,7 +1595,18 @@ class Compiler
1266
  $without = ['rule' => true];
1267
 
1268
  if ($withCondition) {
1269
- if ($this->libMapHasKey([$withCondition, static::$with])) {
 
 
 
 
 
 
 
 
 
 
 
1270
  $without = []; // cancel the default
1271
  $list = $this->coerceList($this->libMapGet([$withCondition, static::$with]));
1272
 
@@ -1277,7 +1617,7 @@ class Compiler
1277
  }
1278
  }
1279
 
1280
- if ($this->libMapHasKey([$withCondition, static::$without])) {
1281
  $without = []; // cancel the default
1282
  $list = $this->coerceList($this->libMapGet([$withCondition, static::$without]));
1283
 
@@ -1295,11 +1635,13 @@ class Compiler
1295
  /**
1296
  * Filter env stack
1297
  *
1298
- * @param array $envs
1299
  * @param array $with
1300
  * @param array $without
1301
  *
1302
- * @return \ScssPhp\ScssPhp\Compiler\Environment
 
 
1303
  */
1304
  protected function filterWithWithout($envs, $with, $without)
1305
  {
@@ -1338,7 +1680,7 @@ class Compiler
1338
 
1339
  if ($block->type === Type::T_DIRECTIVE) {
1340
  if (isset($block->name)) {
1341
- return $this->testWithWithout($block->name, $with, $without);
1342
  } elseif (isset($block->selectors) && preg_match(',@(\w+),ims', json_encode($block->selectors), $m)) {
1343
  return $this->testWithWithout($m[1], $with, $without);
1344
  } else {
@@ -1354,7 +1696,7 @@ class Compiler
1354
  $s = reset($s);
1355
  }
1356
 
1357
- if (\is_object($s) && $s instanceof Node\Number) {
1358
  return $this->testWithWithout('keyframes', $with, $without);
1359
  }
1360
  }
@@ -1377,7 +1719,6 @@ class Compiler
1377
  */
1378
  protected function testWithWithout($what, $with, $without)
1379
  {
1380
-
1381
  // if without, reject only if in the list (or 'all' is in the list)
1382
  if (\count($without)) {
1383
  return (isset($without[$what]) || isset($without['all'])) ? false : true;
@@ -1392,7 +1733,9 @@ class Compiler
1392
  * Compile keyframe block
1393
  *
1394
  * @param \ScssPhp\ScssPhp\Block $block
1395
- * @param array $selectors
 
 
1396
  */
1397
  protected function compileKeyframeBlock(Block $block, $selectors)
1398
  {
@@ -1421,6 +1764,8 @@ class Compiler
1421
  *
1422
  * @param \ScssPhp\ScssPhp\Block $block
1423
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
 
 
1424
  */
1425
  protected function compileNestedPropertiesBlock(Block $block, OutputBlock $out)
1426
  {
@@ -1454,7 +1799,9 @@ class Compiler
1454
  * Compile nested block
1455
  *
1456
  * @param \ScssPhp\ScssPhp\Block $block
1457
- * @param array $selectors
 
 
1458
  */
1459
  protected function compileNestedBlock(Block $block, $selectors)
1460
  {
@@ -1465,7 +1812,7 @@ class Compiler
1465
 
1466
  // wrap assign children in a block
1467
  // except for @font-face
1468
- if ($block->type !== Type::T_DIRECTIVE || $block->name !== "font-face") {
1469
  // need wrapping?
1470
  $needWrapping = false;
1471
 
@@ -1477,7 +1824,7 @@ class Compiler
1477
  }
1478
 
1479
  if ($needWrapping) {
1480
- $wrapped = new Block;
1481
  $wrapped->sourceName = $block->sourceName;
1482
  $wrapped->sourceIndex = $block->sourceIndex;
1483
  $wrapped->sourceLine = $block->sourceLine;
@@ -1516,6 +1863,8 @@ class Compiler
1516
  * @see Compiler::compileChild()
1517
  *
1518
  * @param \ScssPhp\ScssPhp\Block $block
 
 
1519
  */
1520
  protected function compileBlock(Block $block)
1521
  {
@@ -1524,30 +1873,6 @@ class Compiler
1524
 
1525
  $out = $this->makeOutputBlock(null);
1526
 
1527
- if (isset($this->lineNumberStyle) && \count($env->selectors) && \count($block->children)) {
1528
- $annotation = $this->makeOutputBlock(Type::T_COMMENT);
1529
- $annotation->depth = 0;
1530
-
1531
- $file = $this->sourceNames[$block->sourceIndex];
1532
- $line = $block->sourceLine;
1533
-
1534
- switch ($this->lineNumberStyle) {
1535
- case static::LINE_COMMENTS:
1536
- $annotation->lines[] = '/* line ' . $line
1537
- . ($file ? ', ' . $file : '')
1538
- . ' */';
1539
- break;
1540
-
1541
- case static::DEBUG_INFO:
1542
- $annotation->lines[] = '@media -sass-debug-info{'
1543
- . ($file ? 'filename{font-family:"' . $file . '"}' : '')
1544
- . 'line{font-family:' . $line . '}}';
1545
- break;
1546
- }
1547
-
1548
- $this->scope->children[] = $annotation;
1549
- }
1550
-
1551
  $this->scope->children[] = $out;
1552
 
1553
  if (\count($block->children)) {
@@ -1579,7 +1904,7 @@ class Compiler
1579
  * @param array $value
1580
  * @param boolean $pushEnv
1581
  *
1582
- * @return array|mixed|string
1583
  */
1584
  protected function compileCommentValue($value, $pushEnv = false)
1585
  {
@@ -1590,17 +1915,16 @@ class Compiler
1590
  $this->pushEnv();
1591
  }
1592
 
1593
- $ignoreCallStackMessage = $this->ignoreCallStackMessage;
1594
- $this->ignoreCallStackMessage = true;
1595
-
1596
  try {
1597
  $c = $this->compileValue($value[2]);
1598
- } catch (\Exception $e) {
 
 
 
 
1599
  // ignore error in comment compilation which are only interpolation
1600
  }
1601
 
1602
- $this->ignoreCallStackMessage = $ignoreCallStackMessage;
1603
-
1604
  if ($pushEnv) {
1605
  $this->popEnv();
1606
  }
@@ -1613,6 +1937,8 @@ class Compiler
1613
  * Compile root level comment
1614
  *
1615
  * @param array $block
 
 
1616
  */
1617
  protected function compileComment($block)
1618
  {
@@ -1637,11 +1963,17 @@ class Compiler
1637
 
1638
  // after evaluating interpolates, we might need a second pass
1639
  if ($this->shouldEvaluate) {
1640
- $selectors = $this->revertSelfSelector($selectors);
1641
  $buffer = $this->collapseSelectors($selectors);
1642
  $parser = $this->parserFactory(__METHOD__);
1643
 
1644
- if ($parser->parseSelector($buffer, $newSelectors)) {
 
 
 
 
 
 
1645
  $selectors = array_map([$this, 'evalSelector'], $newSelectors);
1646
  }
1647
  }
@@ -1674,11 +2006,12 @@ class Compiler
1674
  if (\is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) {
1675
  $p = $this->compileValue($p);
1676
 
1677
- // force re-evaluation
1678
- if (strpos($p, '&') !== false || strpos($p, ',') !== false) {
1679
  $this->shouldEvaluate = true;
1680
  }
1681
- } elseif (\is_string($p) && \strlen($p) >= 2 &&
 
1682
  ($first = $p[0]) && ($first === '"' || $first === "'") &&
1683
  substr($p, -1) === $first
1684
  ) {
@@ -1692,14 +2025,44 @@ class Compiler
1692
  /**
1693
  * Collapse selectors
1694
  *
1695
- * @param array $selectors
1696
- * @param boolean $selectorFormat
1697
- * if false return a collapsed string
1698
- * if true return an array description of a structured selector
1699
  *
1700
  * @return string
1701
  */
1702
- protected function collapseSelectors($selectors, $selectorFormat = false)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1703
  {
1704
  $parts = [];
1705
 
@@ -1717,7 +2080,7 @@ class Compiler
1717
  }
1718
  );
1719
 
1720
- if ($selectorFormat && $this->isImmediateRelationshipCombinator($compound)) {
1721
  if (\count($output)) {
1722
  $output[\count($output) - 1] .= ' ' . $compound;
1723
  } else {
@@ -1733,43 +2096,36 @@ class Compiler
1733
  }
1734
  }
1735
 
1736
- if ($selectorFormat) {
1737
- foreach ($output as &$o) {
1738
- $o = [Type::T_STRING, '', [$o]];
1739
- }
1740
-
1741
- $output = [Type::T_LIST, ' ', $output];
1742
- } else {
1743
- $output = implode(' ', $output);
1744
  }
1745
 
1746
- $parts[] = $output;
1747
- }
1748
-
1749
- if ($selectorFormat) {
1750
- $parts = [Type::T_LIST, ',', $parts];
1751
- } else {
1752
- $parts = implode(', ', $parts);
1753
  }
1754
 
1755
- return $parts;
1756
  }
1757
 
1758
  /**
1759
  * Parse down the selector and revert [self] to "&" before a reparsing
1760
  *
1761
- * @param array $selectors
 
1762
  *
1763
  * @return array
1764
  */
1765
- protected function revertSelfSelector($selectors)
1766
  {
1767
  foreach ($selectors as &$part) {
1768
  if (\is_array($part)) {
1769
  if ($part === [Type::T_SELF]) {
1770
- $part = '&';
 
 
 
 
1771
  } else {
1772
- $part = $this->revertSelfSelector($part);
1773
  }
1774
  }
1775
  }
@@ -1789,7 +2145,8 @@ class Compiler
1789
  $joined = [];
1790
 
1791
  foreach ($single as $part) {
1792
- if (empty($joined) ||
 
1793
  ! \is_string($part) ||
1794
  preg_match('/[\[.:#%]/', $part)
1795
  ) {
@@ -1881,6 +2238,11 @@ class Compiler
1881
  return false;
1882
  }
1883
 
 
 
 
 
 
1884
  protected function pushCallStack($name = '')
1885
  {
1886
  $this->callStack[] = [
@@ -1894,12 +2256,15 @@ class Compiler
1894
  if (\count($this->callStack) > 25000) {
1895
  // not displayed but you can var_dump it to deep debug
1896
  $msg = $this->callStackMessage(true, 100);
1897
- $msg = "Infinite calling loop";
1898
 
1899
- $this->throwError($msg);
1900
  }
1901
  }
1902
 
 
 
 
1903
  protected function popCallStack()
1904
  {
1905
  array_pop($this->callStack);
@@ -1912,7 +2277,7 @@ class Compiler
1912
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1913
  * @param string $traceName
1914
  *
1915
- * @return array|null
1916
  */
1917
  protected function compileChildren($stms, OutputBlock $out, $traceName = '')
1918
  {
@@ -1934,13 +2299,15 @@ class Compiler
1934
  }
1935
 
1936
  /**
1937
- * Compile children and throw exception if unexpected @return
1938
  *
1939
  * @param array $stms
1940
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1941
  * @param \ScssPhp\ScssPhp\Block $selfParent
1942
  * @param string $traceName
1943
  *
 
 
1944
  * @throws \Exception
1945
  */
1946
  protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null, $traceName = '')
@@ -1952,7 +2319,7 @@ class Compiler
1952
  $stm[1]->selfParent = $selfParent;
1953
  $ret = $this->compileChild($stm, $out);
1954
  $stm[1]->selfParent = null;
1955
- } elseif ($selfParent && \in_array($stm[0], [TYPE::T_INCLUDE, TYPE::T_EXTEND])) {
1956
  $stm['selfParent'] = $selfParent;
1957
  $ret = $this->compileChild($stm, $out);
1958
  unset($stm['selfParent']);
@@ -1961,10 +2328,7 @@ class Compiler
1961
  }
1962
 
1963
  if (isset($ret)) {
1964
- $this->throwError('@return may only be used within a function');
1965
- $this->popCallStack();
1966
-
1967
- return;
1968
  }
1969
  }
1970
 
@@ -1973,7 +2337,7 @@ class Compiler
1973
 
1974
 
1975
  /**
1976
- * evaluate media query : compile internal value keeping the structure inchanged
1977
  *
1978
  * @param array $queryList
1979
  *
@@ -1994,7 +2358,8 @@ class Compiler
1994
 
1995
  // the parser had no mean to know if media type or expression if it was an interpolation
1996
  // so you need to reparse if the T_MEDIA_TYPE looks like anything else a media type
1997
- if ($q[0] == Type::T_MEDIA_TYPE &&
 
1998
  (strpos($value, '(') !== false ||
1999
  strpos($value, ')') !== false ||
2000
  strpos($value, ':') !== false ||
@@ -2042,14 +2407,14 @@ class Compiler
2042
  *
2043
  * @param array $queryList
2044
  *
2045
- * @return array
2046
  */
2047
  protected function compileMediaQuery($queryList)
2048
  {
2049
  $start = '@media ';
2050
  $default = trim($start);
2051
  $out = [];
2052
- $current = "";
2053
 
2054
  foreach ($queryList as $query) {
2055
  $type = null;
@@ -2088,7 +2453,7 @@ class Compiler
2088
  $out[] = $start . $current;
2089
  }
2090
 
2091
- $current = "";
2092
  $type = null;
2093
  $parts = [];
2094
  }
@@ -2263,7 +2628,7 @@ class Compiler
2263
  }
2264
 
2265
  // t1 == t2, neither m1 nor m2 are "not"
2266
- return [empty($m1)? $m2 : $m1, $t1];
2267
  }
2268
 
2269
  /**
@@ -2280,16 +2645,18 @@ class Compiler
2280
  if ($rawPath[0] === Type::T_STRING) {
2281
  $path = $this->compileStringContent($rawPath);
2282
 
2283
- if ($path = $this->findImport($path)) {
2284
- if (! $once || ! \in_array($path, $this->importedFiles)) {
2285
- $this->importFile($path, $out);
2286
- $this->importedFiles[] = $path;
 
 
2287
  }
2288
 
2289
  return true;
2290
  }
2291
 
2292
- $this->appendRootDirective('@import ' . $this->compileValue($rawPath). ';', $out);
2293
 
2294
  return false;
2295
  }
@@ -2302,7 +2669,7 @@ class Compiler
2302
 
2303
  foreach ($rawPath[2] as $path) {
2304
  if ($path[0] !== Type::T_STRING) {
2305
- $this->appendRootDirective('@import ' . $this->compileValue($rawPath) . ';', $out);
2306
 
2307
  return false;
2308
  }
@@ -2315,19 +2682,68 @@ class Compiler
2315
  return true;
2316
  }
2317
 
2318
- $this->appendRootDirective('@import ' . $this->compileValue($rawPath) . ';', $out);
2319
 
2320
  return false;
2321
  }
2322
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2323
 
2324
  /**
2325
  * Append a root directive like @import or @charset as near as the possible from the source code
2326
  * (keeping before comments, @import and @charset coming before in the source code)
2327
  *
2328
- * @param string $line
2329
- * @param @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2330
- * @param array $allowed
 
 
2331
  */
2332
  protected function appendRootDirective($line, $out, $allowed = [Type::T_COMMENT])
2333
  {
@@ -2375,25 +2791,20 @@ class Compiler
2375
  *
2376
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2377
  * @param string $type
2378
- * @param string|mixed $line
 
 
2379
  */
2380
  protected function appendOutputLine(OutputBlock $out, $type, $line)
2381
  {
2382
  $outWrite = &$out;
2383
 
2384
- if ($type === Type::T_COMMENT) {
2385
- $parent = $out->parent;
2386
-
2387
- if (end($parent->children) !== $out) {
2388
- $outWrite = &$parent->children[\count($parent->children) - 1];
2389
- }
2390
- }
2391
-
2392
  // check if it's a flat output or not
2393
  if (\count($out->children)) {
2394
  $lastChild = &$out->children[\count($out->children) - 1];
2395
 
2396
- if ($lastChild->depth === $out->depth &&
 
2397
  \is_null($lastChild->selectors) &&
2398
  ! \count($lastChild->children)
2399
  ) {
@@ -2417,7 +2828,7 @@ class Compiler
2417
  * @param array $child
2418
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2419
  *
2420
- * @return array
2421
  */
2422
  protected function compileChild($child, OutputBlock $out)
2423
  {
@@ -2431,12 +2842,13 @@ class Compiler
2431
  $this->sourceColumn = $child[1]->sourceColumn;
2432
  } elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) {
2433
  $this->sourceLine = $out->sourceLine;
2434
- $this->sourceIndex = array_search($out->sourceName, $this->sourceNames);
2435
  $this->sourceColumn = $out->sourceColumn;
2436
 
2437
- if ($this->sourceIndex === false) {
2438
- $this->sourceIndex = null;
2439
  }
 
2440
  }
2441
 
2442
  switch ($child[0]) {
@@ -2469,10 +2881,6 @@ class Compiler
2469
  break;
2470
 
2471
  case Type::T_CHARSET:
2472
- if (! $this->charsetSeen) {
2473
- $this->charsetSeen = true;
2474
- $this->appendRootDirective('@charset ' . $this->compileValue($child[1]) . ';', $out);
2475
- }
2476
  break;
2477
 
2478
  case Type::T_CUSTOM_PROPERTY:
@@ -2545,7 +2953,7 @@ class Compiler
2545
  break;
2546
  }
2547
 
2548
- if ($compiledName === 'font' and $value[0] === Type::T_LIST && $value[1]==',') {
2549
  // this is the case if more than one font is given: example: "font: 400 1em/1.3 arial,helvetica"
2550
  // we need to handle the first list element
2551
  $shorthandValue=&$value[2][0];
@@ -2561,7 +2969,7 @@ class Compiler
2561
  $divider = $this->reduce($divider, true);
2562
  }
2563
 
2564
- if (\intval($divider->dimension) and ! \count($divider->units)) {
2565
  $revert = false;
2566
  }
2567
  }
@@ -2574,9 +2982,10 @@ class Compiler
2574
  if ($item[0] === Type::T_EXPRESSION && $item[1] === '/') {
2575
  if ($maxShorthandDividers > 0) {
2576
  $revert = true;
 
2577
  // if the list of values is too long, this has to be a shorthand,
2578
  // otherwise it could be a real division
2579
- if (\is_null($maxListElements) or \count($shorthandValue[2]) <= $maxListElements) {
2580
  if ($shorthandDividerNeedsUnit) {
2581
  $divider = $item[3];
2582
 
@@ -2584,7 +2993,7 @@ class Compiler
2584
  $divider = $this->reduce($divider, true);
2585
  }
2586
 
2587
- if (\intval($divider->dimension) and ! \count($divider->units)) {
2588
  $revert = false;
2589
  }
2590
  }
@@ -2642,17 +3051,42 @@ class Compiler
2642
 
2643
  case Type::T_EXTEND:
2644
  foreach ($child[1] as $sel) {
 
 
 
 
 
 
2645
  $results = $this->evalSelectors([$sel]);
2646
 
2647
  foreach ($results as $result) {
 
 
 
 
2648
  // only use the first one
2649
- $result = current($result);
2650
  $selectors = $out->selectors;
2651
 
2652
  if (! $selectors && isset($child['selfParent'])) {
2653
  $selectors = $this->multiplySelectors($this->env, $child['selfParent']);
2654
  }
2655
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2656
  $this->pushExtends($result, $selectors, $child);
2657
  }
2658
  }
@@ -2666,7 +3100,8 @@ class Compiler
2666
  }
2667
 
2668
  foreach ($if->cases as $case) {
2669
- if ($case->type === Type::T_ELSE ||
 
2670
  $case->type === Type::T_ELSEIF && $this->isTruthy($this->reduce($case->cond))
2671
  ) {
2672
  return $this->compileChildren($case->children, $out);
@@ -2695,17 +3130,11 @@ class Compiler
2695
  $ret = $this->compileChildren($each->children, $out);
2696
 
2697
  if ($ret) {
2698
- if ($ret[0] !== Type::T_CONTROL) {
2699
- $store = $this->env->store;
2700
- $this->popEnv();
2701
- $this->backPropagateEnv($store, $each->vars);
2702
-
2703
- return $ret;
2704
- }
2705
 
2706
- if ($ret[1]) {
2707
- break;
2708
- }
2709
  }
2710
  }
2711
  $store = $this->env->store;
@@ -2721,13 +3150,7 @@ class Compiler
2721
  $ret = $this->compileChildren($while->children, $out);
2722
 
2723
  if ($ret) {
2724
- if ($ret[0] !== Type::T_CONTROL) {
2725
- return $ret;
2726
- }
2727
-
2728
- if ($ret[1]) {
2729
- break;
2730
- }
2731
  }
2732
  }
2733
  break;
@@ -2735,46 +3158,39 @@ class Compiler
2735
  case Type::T_FOR:
2736
  list(, $for) = $child;
2737
 
2738
- $start = $this->reduce($for->start, true);
2739
- $end = $this->reduce($for->end, true);
2740
 
2741
- if (! ($start[2] == $end[2] || $end->unitless())) {
2742
- $this->throwError('Incompatible units: "%s" and "%s".', $start->unitStr(), $end->unitStr());
2743
 
2744
- break;
2745
- }
2746
 
2747
- $unit = $start[2];
2748
- $start = $start[1];
2749
- $end = $end[1];
2750
 
2751
  $d = $start < $end ? 1 : -1;
2752
 
2753
  $this->pushEnv();
2754
 
2755
  for (;;) {
2756
- if ((! $for->until && $start - $d == $end) ||
 
2757
  ($for->until && $start == $end)
2758
  ) {
2759
  break;
2760
  }
2761
 
2762
- $this->set($for->var, new Node\Number($start, $unit));
2763
  $start += $d;
2764
 
2765
  $ret = $this->compileChildren($for->children, $out);
2766
 
2767
  if ($ret) {
2768
- if ($ret[0] !== Type::T_CONTROL) {
2769
- $store = $this->env->store;
2770
- $this->popEnv();
2771
- $this->backPropagateEnv($store, [$for->var]);
2772
- return $ret;
2773
- }
2774
 
2775
- if ($ret[1]) {
2776
- break;
2777
- }
2778
  }
2779
  }
2780
 
@@ -2784,12 +3200,6 @@ class Compiler
2784
 
2785
  break;
2786
 
2787
- case Type::T_BREAK:
2788
- return [Type::T_CONTROL, true];
2789
-
2790
- case Type::T_CONTINUE:
2791
- return [Type::T_CONTROL, false];
2792
-
2793
  case Type::T_RETURN:
2794
  return $this->reduce($child[1], true);
2795
 
@@ -2804,8 +3214,7 @@ class Compiler
2804
  $mixin = $this->get(static::$namespaces['mixin'] . $name, false);
2805
 
2806
  if (! $mixin) {
2807
- $this->throwError("Undefined mixin $name");
2808
- break;
2809
  }
2810
 
2811
  $callingScope = $this->getStoreEnv();
@@ -2862,10 +3271,10 @@ class Compiler
2862
  if (! empty($mixin->parentEnv)) {
2863
  $this->env->declarationScopeParent = $mixin->parentEnv;
2864
  } else {
2865
- $this->throwError("@mixin $name() without parentEnv");
2866
  }
2867
 
2868
- $this->compileChildrenNoReturn($mixin->children, $out, $selfParent, $this->env->marker . " " . $name);
2869
 
2870
  $this->popEnv();
2871
  break;
@@ -2905,39 +3314,34 @@ class Compiler
2905
  case Type::T_DEBUG:
2906
  list(, $value) = $child;
2907
 
2908
- $fname = $this->sourceNames[$this->sourceIndex];
2909
  $line = $this->sourceLine;
2910
- $value = $this->compileValue($this->reduce($value, true));
2911
 
2912
- fwrite($this->stderr, "File $fname on line $line DEBUG: $value\n");
2913
  break;
2914
 
2915
  case Type::T_WARN:
2916
  list(, $value) = $child;
2917
 
2918
- $fname = $this->sourceNames[$this->sourceIndex];
2919
  $line = $this->sourceLine;
2920
- $value = $this->compileValue($this->reduce($value, true));
2921
 
2922
- fwrite($this->stderr, "File $fname on line $line WARN: $value\n");
2923
  break;
2924
 
2925
  case Type::T_ERROR:
2926
  list(, $value) = $child;
2927
 
2928
- $fname = $this->sourceNames[$this->sourceIndex];
2929
  $line = $this->sourceLine;
2930
  $value = $this->compileValue($this->reduce($value, true));
2931
 
2932
- $this->throwError("File $fname on line $line ERROR: $value\n");
2933
- break;
2934
-
2935
- case Type::T_CONTROL:
2936
- $this->throwError('@break/@continue not permitted in this scope');
2937
- break;
2938
 
2939
  default:
2940
- $this->throwError("unknown child type: $child[0]");
2941
  }
2942
  }
2943
 
@@ -2945,14 +3349,21 @@ class Compiler
2945
  * Reduce expression to string
2946
  *
2947
  * @param array $exp
 
2948
  *
2949
  * @return array
2950
  */
2951
- protected function expToString($exp)
2952
  {
2953
- list(, $op, $left, $right, /* $inParens */, $whiteLeft, $whiteRight) = $exp;
 
 
2954
 
2955
- $content = [$this->reduce($left)];
 
 
 
 
2956
 
2957
  if ($whiteLeft) {
2958
  $content[] = ' ';
@@ -2966,17 +3377,21 @@ class Compiler
2966
 
2967
  $content[] = $this->reduce($right);
2968
 
 
 
 
 
2969
  return [Type::T_STRING, '', $content];
2970
  }
2971
 
2972
  /**
2973
  * Is truthy?
2974
  *
2975
- * @param array $value
2976
  *
2977
  * @return boolean
2978
  */
2979
- protected function isTruthy($value)
2980
  {
2981
  return $value !== static::$false && $value !== static::$null;
2982
  }
@@ -3020,15 +3435,15 @@ class Compiler
3020
  /**
3021
  * Reduce value
3022
  *
3023
- * @param array $value
3024
  * @param boolean $inExp
3025
  *
3026
- * @return null|string|array|\ScssPhp\ScssPhp\Node\Number
3027
  */
3028
  protected function reduce($value, $inExp = false)
3029
  {
3030
- if (\is_null($value)) {
3031
- return null;
3032
  }
3033
 
3034
  switch ($value[0]) {
@@ -3045,8 +3460,9 @@ class Compiler
3045
  }
3046
 
3047
  // special case: looks like css shorthand
3048
- if ($opName == 'div' && ! $inParens && ! $inExp && isset($right[2]) &&
3049
- (($right[0] !== Type::T_NUMBER && $right[2] != '') ||
 
3050
  ($right[0] === Type::T_NUMBER && ! $right->unitless()))
3051
  ) {
3052
  return $this->expToString($value);
@@ -3067,7 +3483,8 @@ class Compiler
3067
  // 3. op[op name]
3068
  $fn = "op${ucOpName}${ucLType}${ucRType}";
3069
 
3070
- if (\is_callable([$this, $fn]) ||
 
3071
  (($fn = "op${ucLType}${ucRType}") &&
3072
  \is_callable([$this, $fn]) &&
3073
  $passOp = true) ||
@@ -3075,52 +3492,6 @@ class Compiler
3075
  \is_callable([$this, $fn]) &&
3076
  $genOp = true)
3077
  ) {
3078
- $coerceUnit = false;
3079
-
3080
- if (! isset($genOp) &&
3081
- $left[0] === Type::T_NUMBER && $right[0] === Type::T_NUMBER
3082
- ) {
3083
- $coerceUnit = true;
3084
-
3085
- switch ($opName) {
3086
- case 'mul':
3087
- $targetUnit = $left[2];
3088
-
3089
- foreach ($right[2] as $unit => $exp) {
3090
- $targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) + $exp;
3091
- }
3092
- break;
3093
-
3094
- case 'div':
3095
- $targetUnit = $left[2];
3096
-
3097
- foreach ($right[2] as $unit => $exp) {
3098
- $targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) - $exp;
3099
- }
3100
- break;
3101
-
3102
- case 'mod':
3103
- $targetUnit = $left[2];
3104
- break;
3105
-
3106
- default:
3107
- $targetUnit = $left->unitless() ? $right[2] : $left[2];
3108
- }
3109
-
3110
- $baseUnitLeft = $left->isNormalizable();
3111
- $baseUnitRight = $right->isNormalizable();
3112
-
3113
- if ($baseUnitLeft && $baseUnitRight && $baseUnitLeft === $baseUnitRight) {
3114
- $left = $left->normalize();
3115
- $right = $right->normalize();
3116
- }
3117
- else {
3118
- if ($coerceUnit) {
3119
- $left = new Node\Number($left[1], []);
3120
- }
3121
- }
3122
- }
3123
-
3124
  $shouldEval = $inParens || $inExp;
3125
 
3126
  if (isset($passOp)) {
@@ -3130,10 +3501,6 @@ class Compiler
3130
  }
3131
 
3132
  if (isset($out)) {
3133
- if ($coerceUnit && $out[0] === Type::T_NUMBER) {
3134
- $out = $out->coerce($targetUnit);
3135
- }
3136
-
3137
  return $out;
3138
  }
3139
  }
@@ -3146,13 +3513,13 @@ class Compiler
3146
  $inExp = $inExp || $this->shouldEval($exp);
3147
  $exp = $this->reduce($exp);
3148
 
3149
- if ($exp[0] === Type::T_NUMBER) {
3150
  switch ($op) {
3151
  case '+':
3152
- return new Node\Number($exp[1], $exp[2]);
3153
 
3154
  case '-':
3155
- return new Node\Number(-$exp[1], $exp[2]);
3156
  }
3157
  }
3158
 
@@ -3177,6 +3544,14 @@ class Compiler
3177
  foreach ($value[2] as &$item) {
3178
  $item = $this->reduce($item);
3179
  }
 
 
 
 
 
 
 
 
3180
 
3181
  return $value;
3182
 
@@ -3193,7 +3568,7 @@ class Compiler
3193
 
3194
  case Type::T_STRING:
3195
  foreach ($value[2] as &$item) {
3196
- if (\is_array($item) || $item instanceof \ArrayAccess) {
3197
  $item = $this->reduce($item);
3198
  }
3199
  }
@@ -3204,7 +3579,7 @@ class Compiler
3204
  $value[1] = $this->reduce($value[1]);
3205
 
3206
  if ($inExp) {
3207
- return $value[1];
3208
  }
3209
 
3210
  return $value;
@@ -3213,8 +3588,9 @@ class Compiler
3213
  return $this->fncall($value[1], $value[2]);
3214
 
3215
  case Type::T_SELF:
3216
- $selfSelector = $this->multiplySelectors($this->env,!empty($this->env->block->selfParent) ? $this->env->block->selfParent : null);
3217
- $selfSelector = $this->collapseSelectors($selfSelector, true);
 
3218
 
3219
  return $selfSelector;
3220
 
@@ -3226,161 +3602,421 @@ class Compiler
3226
  /**
3227
  * Function caller
3228
  *
3229
- * @param string $name
3230
- * @param array $argValues
3231
  *
3232
- * @return array|null
3233
  */
3234
- protected function fncall($name, $argValues)
3235
  {
3236
- // SCSS @function
3237
- if ($this->callScssFunction($name, $argValues, $returnValue)) {
3238
- return $returnValue;
3239
- }
3240
 
3241
- // native PHP functions
3242
- if ($this->callNativeFunction($name, $argValues, $returnValue)) {
3243
- return $returnValue;
 
3244
  }
3245
 
3246
- // for CSS functions, simply flatten the arguments into a list
3247
- $listArgs = [];
 
 
3248
 
3249
- foreach ((array) $argValues as $arg) {
3250
- if (empty($arg[0])) {
3251
- $listArgs[] = $this->reduce($arg[1]);
 
3252
  }
 
 
3253
  }
3254
 
3255
- return [Type::T_FUNCTION, $name, [Type::T_LIST, ',', $listArgs]];
3256
- }
 
3257
 
3258
- /**
3259
- * Normalize name
3260
- *
3261
- * @param string $name
3262
- *
3263
- * @return string
3264
- */
3265
- protected function normalizeName($name)
3266
- {
3267
- return str_replace('-', '_', $name);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3268
  }
3269
 
3270
  /**
3271
- * Normalize value
 
 
3272
  *
3273
- * @param array $value
3274
- *
3275
- * @return array
3276
  */
3277
- public function normalizeValue($value)
3278
  {
3279
- $value = $this->coerceForExpression($this->reduce($value));
 
 
3280
 
3281
- switch ($value[0]) {
3282
- case Type::T_LIST:
3283
- $value = $this->extractInterpolation($value);
3284
 
3285
- if ($value[0] !== Type::T_LIST) {
3286
- return [Type::T_KEYWORD, $this->compileValue($value)];
 
3287
  }
3288
-
3289
- foreach ($value[2] as $key => $item) {
3290
- $value[2][$key] = $this->normalizeValue($item);
 
 
 
 
3291
  }
 
3292
 
3293
- if (! empty($value['enclosing'])) {
3294
- unset($value['enclosing']);
 
 
 
 
 
 
 
 
 
 
 
 
 
3295
  }
3296
 
3297
- return $value;
3298
 
3299
  case Type::T_STRING:
3300
- return [$value[0], '"', [$this->compileStringContent($value)]];
 
 
 
 
3301
 
3302
- case Type::T_NUMBER:
3303
- return $value->normalize();
 
 
 
 
 
 
 
 
 
 
 
 
 
3304
 
3305
- case Type::T_INTERPOLATE:
3306
- return [Type::T_KEYWORD, $this->compileValue($value)];
 
 
 
 
 
 
 
 
3307
 
 
 
3308
  default:
3309
- return $value;
3310
  }
3311
  }
3312
 
3313
- /**
3314
- * Add numbers
3315
- *
3316
- * @param array $left
3317
- * @param array $right
3318
- *
3319
- * @return \ScssPhp\ScssPhp\Node\Number
3320
- */
3321
- protected function opAddNumberNumber($left, $right)
3322
- {
3323
- return new Node\Number($left[1] + $right[1], $left[2]);
3324
- }
3325
 
3326
  /**
3327
- * Multiply numbers
3328
  *
3329
- * @param array $left
3330
- * @param array $right
3331
  *
3332
- * @return \ScssPhp\ScssPhp\Node\Number
3333
  */
3334
- protected function opMulNumberNumber($left, $right)
3335
  {
3336
- return new Node\Number($left[1] * $right[1], $left[2]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3337
  }
3338
 
3339
  /**
3340
- * Subtract numbers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3341
  *
3342
- * @param array $left
3343
- * @param array $right
3344
  *
3345
- * @return \ScssPhp\ScssPhp\Node\Number
3346
  */
3347
- protected function opSubNumberNumber($left, $right)
3348
  {
3349
- return new Node\Number($left[1] - $right[1], $left[2]);
3350
  }
3351
 
3352
  /**
3353
- * Divide numbers
3354
  *
3355
- * @param array $left
3356
- * @param array $right
 
3357
  *
3358
- * @return array|\ScssPhp\ScssPhp\Node\Number
3359
  */
3360
- protected function opDivNumberNumber($left, $right)
3361
  {
3362
- if ($right[1] == 0) {
3363
- return ($left[1] == 0) ? static::$NaN : static::$Infinity;
 
 
3364
  }
3365
 
3366
- return new Node\Number($left[1] / $right[1], $left[2]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3367
  }
3368
 
3369
  /**
3370
- * Mod numbers
3371
  *
3372
- * @param array $left
3373
- * @param array $right
3374
  *
3375
- * @return \ScssPhp\ScssPhp\Node\Number
3376
  */
3377
- protected function opModNumberNumber($left, $right)
3378
  {
3379
- if ($right[1] == 0) {
3380
- return static::$NaN;
3381
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3382
 
3383
- return new Node\Number($left[1] % $right[1], $left[2]);
 
 
 
 
 
 
 
 
 
 
3384
  }
3385
 
3386
  /**
@@ -3419,11 +4055,11 @@ class Compiler
3419
  /**
3420
  * Boolean and
3421
  *
3422
- * @param array $left
3423
- * @param array $right
3424
  * @param boolean $shouldEval
3425
  *
3426
- * @return array|null
3427
  */
3428
  protected function opAnd($left, $right, $shouldEval)
3429
  {
@@ -3447,11 +4083,11 @@ class Compiler
3447
  /**
3448
  * Boolean or
3449
  *
3450
- * @param array $left
3451
- * @param array $right
3452
  * @param boolean $shouldEval
3453
  *
3454
- * @return array|null
3455
  */
3456
  protected function opOr($left, $right, $shouldEval)
3457
  {
@@ -3483,6 +4119,15 @@ class Compiler
3483
  */
3484
  protected function opColorColor($op, $left, $right)
3485
  {
 
 
 
 
 
 
 
 
 
3486
  $out = [Type::T_COLOR];
3487
 
3488
  foreach ([1, 2, 3] as $i) {
@@ -3503,13 +4148,16 @@ class Compiler
3503
  break;
3504
 
3505
  case '%':
 
 
 
 
3506
  $out[] = $lval % $rval;
3507
  break;
3508
 
3509
  case '/':
3510
  if ($rval == 0) {
3511
- $this->throwError("color: Can't divide by zero");
3512
- break 2;
3513
  }
3514
 
3515
  $out[] = (int) ($lval / $rval);
@@ -3522,8 +4170,7 @@ class Compiler
3522
  return $this->opNeq($left, $right);
3523
 
3524
  default:
3525
- $this->throwError("color: unknown op $op");
3526
- break 2;
3527
  }
3528
  }
3529
 
@@ -3541,13 +4188,21 @@ class Compiler
3541
  *
3542
  * @param string $op
3543
  * @param array $left
3544
- * @param array $right
3545
  *
3546
  * @return array
3547
  */
3548
- protected function opColorNumber($op, $left, $right)
3549
  {
3550
- $value = $right[1];
 
 
 
 
 
 
 
 
3551
 
3552
  return $this->opColorColor(
3553
  $op,
@@ -3560,14 +4215,22 @@ class Compiler
3560
  * Compare number and color
3561
  *
3562
  * @param string $op
3563
- * @param array $left
3564
  * @param array $right
3565
  *
3566
  * @return array
3567
  */
3568
- protected function opNumberColor($op, $left, $right)
3569
  {
3570
- $value = $left[1];
 
 
 
 
 
 
 
 
3571
 
3572
  return $this->opColorColor(
3573
  $op,
@@ -3579,8 +4242,8 @@ class Compiler
3579
  /**
3580
  * Compare number1 == number2
3581
  *
3582
- * @param array $left
3583
- * @param array $right
3584
  *
3585
  * @return array
3586
  */
@@ -3600,8 +4263,8 @@ class Compiler
3600
  /**
3601
  * Compare number1 != number2
3602
  *
3603
- * @param array $left
3604
- * @param array $right
3605
  *
3606
  * @return array
3607
  */
@@ -3619,70 +4282,81 @@ class Compiler
3619
  }
3620
 
3621
  /**
3622
- * Compare number1 >= number2
3623
  *
3624
- * @param array $left
3625
- * @param array $right
3626
  *
3627
  * @return array
3628
  */
3629
- protected function opGteNumberNumber($left, $right)
3630
  {
3631
- return $this->toBool($left[1] >= $right[1]);
3632
  }
3633
 
3634
  /**
3635
- * Compare number1 > number2
3636
  *
3637
- * @param array $left
3638
- * @param array $right
3639
  *
3640
  * @return array
3641
  */
3642
- protected function opGtNumberNumber($left, $right)
3643
  {
3644
- return $this->toBool($left[1] > $right[1]);
3645
  }
3646
 
3647
  /**
3648
- * Compare number1 <= number2
3649
  *
3650
- * @param array $left
3651
- * @param array $right
3652
  *
3653
  * @return array
3654
  */
3655
- protected function opLteNumberNumber($left, $right)
3656
  {
3657
- return $this->toBool($left[1] <= $right[1]);
3658
  }
3659
 
3660
  /**
3661
- * Compare number1 < number2
3662
  *
3663
- * @param array $left
3664
- * @param array $right
3665
  *
3666
  * @return array
3667
  */
3668
- protected function opLtNumberNumber($left, $right)
3669
  {
3670
- return $this->toBool($left[1] < $right[1]);
3671
  }
3672
 
3673
  /**
3674
- * Three-way comparison, aka spaceship operator
3675
  *
3676
- * @param array $left
3677
- * @param array $right
3678
  *
3679
- * @return \ScssPhp\ScssPhp\Node\Number
3680
  */
3681
- protected function opCmpNumberNumber($left, $right)
3682
  {
3683
- $n = $left[1] - $right[1];
 
3684
 
3685
- return new Node\Number($n ? $n / abs($n) : 0, '');
 
 
 
 
 
 
 
 
 
 
3686
  }
3687
 
3688
  /**
@@ -3690,7 +4364,7 @@ class Compiler
3690
  *
3691
  * @api
3692
  *
3693
- * @param mixed $thing
3694
  *
3695
  * @return array
3696
  */
@@ -3699,6 +4373,53 @@ class Compiler
3699
  return $thing ? static::$true : static::$false;
3700
  }
3701
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3702
  /**
3703
  * Compiles a primitive value into a CSS property value.
3704
  *
@@ -3712,17 +4433,22 @@ class Compiler
3712
  *
3713
  * @api
3714
  *
3715
- * @param array $value
 
3716
  *
3717
- * @return string|array
3718
  */
3719
- public function compileValue($value)
3720
  {
3721
  $value = $this->reduce($value);
3722
 
 
 
 
 
3723
  switch ($value[0]) {
3724
  case Type::T_KEYWORD:
3725
- return $value[1];
3726
 
3727
  case Type::T_COLOR:
3728
  // [1] - red component (either number for a %)
@@ -3746,7 +4472,7 @@ class Compiler
3746
  }
3747
 
3748
  if (is_numeric($alpha)) {
3749
- $a = new Node\Number($alpha, '');
3750
  } else {
3751
  $a = $alpha;
3752
  }
@@ -3774,61 +4500,106 @@ class Compiler
3774
 
3775
  return $h;
3776
 
3777
- case Type::T_NUMBER:
3778
- return $value->output($this);
3779
-
3780
  case Type::T_STRING:
3781
- return $value[1] . $this->compileStringContent($value) . $value[1];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3782
 
3783
  case Type::T_FUNCTION:
3784
- $args = ! empty($value[2]) ? $this->compileValue($value[2]) : '';
3785
 
3786
  return "$value[1]($args)";
3787
 
 
 
 
 
 
3788
  case Type::T_LIST:
3789
  $value = $this->extractInterpolation($value);
3790
 
3791
  if ($value[0] !== Type::T_LIST) {
3792
- return $this->compileValue($value);
3793
  }
3794
 
3795
  list(, $delim, $items) = $value;
3796
- $pre = $post = "";
3797
 
3798
  if (! empty($value['enclosing'])) {
3799
  switch ($value['enclosing']) {
3800
  case 'parent':
3801
- //$pre = "(";
3802
- //$post = ")";
3803
  break;
3804
  case 'forced_parent':
3805
- $pre = "(";
3806
- $post = ")";
3807
  break;
3808
  case 'bracket':
3809
  case 'forced_bracket':
3810
- $pre = "[";
3811
- $post = "]";
3812
  break;
3813
  }
3814
  }
3815
 
3816
  $prefix_value = '';
 
3817
  if ($delim !== ' ') {
3818
  $prefix_value = ' ';
3819
  }
3820
 
3821
  $filtered = [];
3822
 
 
3823
  foreach ($items as $item) {
 
 
 
 
 
 
 
 
 
 
 
 
3824
  if ($item[0] === Type::T_NULL) {
3825
  continue;
3826
  }
 
 
 
 
 
3827
 
3828
- $compiled = $this->compileValue($item);
3829
  if ($prefix_value && \strlen($compiled)) {
3830
  $compiled = $prefix_value . $compiled;
3831
  }
 
3832
  $filtered[] = $compiled;
3833
  }
3834
 
@@ -3840,7 +4611,7 @@ class Compiler
3840
  $filtered = [];
3841
 
3842
  for ($i = 0, $s = \count($keys); $i < $s; $i++) {
3843
- $filtered[$this->compileValue($keys[$i])] = $this->compileValue($values[$i]);
3844
  }
3845
 
3846
  array_walk($filtered, function (&$value, $key) {
@@ -3860,8 +4631,9 @@ class Compiler
3860
  $delim .= ' ';
3861
  }
3862
 
3863
- $left = \count($left[2]) > 0 ?
3864
- $this->compileValue($left) . $delim . $whiteLeft: '';
 
3865
 
3866
  $delim = $right[1];
3867
 
@@ -3870,14 +4642,18 @@ class Compiler
3870
  }
3871
 
3872
  $right = \count($right[2]) > 0 ?
3873
- $whiteRight . $delim . $this->compileValue($right) : '';
3874
 
3875
- return $left . $this->compileValue($interpolate) . $right;
3876
 
3877
  case Type::T_INTERPOLATE:
3878
  // strip quotes if it's a string
3879
  $reduced = $this->reduce($value[1]);
3880
 
 
 
 
 
3881
  switch ($reduced[0]) {
3882
  case Type::T_LIST:
3883
  $reduced = $this->extractInterpolation($reduced);
@@ -3899,14 +4675,12 @@ class Compiler
3899
  continue;
3900
  }
3901
 
3902
- $temp = $this->compileValue([Type::T_KEYWORD, $item]);
3903
-
3904
- if ($temp[0] === Type::T_STRING) {
3905
- $filtered[] = $this->compileStringContent($temp);
3906
- } elseif ($temp[0] === Type::T_KEYWORD) {
3907
- $filtered[] = $temp[1];
3908
  } else {
3909
- $filtered[] = $this->compileValue($temp);
3910
  }
3911
  }
3912
 
@@ -3914,14 +4688,14 @@ class Compiler
3914
  break;
3915
 
3916
  case Type::T_STRING:
3917
- $reduced = [Type::T_KEYWORD, $this->compileStringContent($reduced)];
3918
  break;
3919
 
3920
  case Type::T_NULL:
3921
  $reduced = [Type::T_KEYWORD, ''];
3922
  }
3923
 
3924
- return $this->compileValue($reduced);
3925
 
3926
  case Type::T_NULL:
3927
  return 'null';
@@ -3930,7 +4704,29 @@ class Compiler
3930
  return $this->compileCommentValue($value);
3931
 
3932
  default:
3933
- $this->throwError("unknown value type: ".json_encode($value));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3934
  }
3935
  }
3936
 
@@ -3940,29 +4736,53 @@ class Compiler
3940
  * @param array $list
3941
  *
3942
  * @return string
 
 
3943
  */
3944
  protected function flattenList($list)
3945
  {
 
 
3946
  return $this->compileValue($list);
3947
  }
3948
 
3949
  /**
3950
- * Compile string content
3951
  *
3952
- * @param array $string
 
 
 
3953
  *
3954
  * @return string
3955
  */
3956
- protected function compileStringContent($string)
3957
  {
3958
- $parts = [];
3959
-
3960
- foreach ($string[2] as $part) {
3961
- if (\is_array($part) || $part instanceof \ArrayAccess) {
3962
- $parts[] = $this->compileValue($part);
3963
- } else {
3964
- $parts[] = $part;
3965
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3966
  }
3967
 
3968
  return implode($parts);
@@ -4023,8 +4843,8 @@ class Compiler
4023
  $prevSelectors = $selectors;
4024
  $selectors = [];
4025
 
4026
- foreach ($prevSelectors as $selector) {
4027
- foreach ($parentSelectors as $parent) {
4028
  if ($selfParentSelectors) {
4029
  foreach ($selfParentSelectors as $selfParent) {
4030
  // if no '&' in the selector, each call will give same result, only add once
@@ -4045,7 +4865,7 @@ class Compiler
4045
  $selectors = array_values($selectors);
4046
 
4047
  // case we are just starting a at-root : nothing to multiply but parentSelectors
4048
- if (!$selectors and $selfParentSelectors) {
4049
  $selectors = $selfParentSelectors;
4050
  }
4051
 
@@ -4124,7 +4944,8 @@ class Compiler
4124
  */
4125
  protected function multiplyMedia(Environment $env = null, $childQueries = null)
4126
  {
4127
- if (! isset($env) ||
 
4128
  ! empty($env->block->type) && $env->block->type !== Type::T_MEDIA
4129
  ) {
4130
  return $childQueries;
@@ -4170,9 +4991,11 @@ class Compiler
4170
  /**
4171
  * Convert env linked list to stack
4172
  *
4173
- * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4174
  *
4175
- * @return array
 
 
4176
  */
4177
  protected function compactEnv(Environment $env)
4178
  {
@@ -4186,9 +5009,11 @@ class Compiler
4186
  /**
4187
  * Convert env stack to singly linked list
4188
  *
4189
- * @param array $envs
4190
  *
4191
- * @return \ScssPhp\ScssPhp\Compiler\Environment
 
 
4192
  */
4193
  protected function extractEnv($envs)
4194
  {
@@ -4209,7 +5034,7 @@ class Compiler
4209
  */
4210
  protected function pushEnv(Block $block = null)
4211
  {
4212
- $env = new Environment;
4213
  $env->parent = $this->env;
4214
  $env->parentStore = $this->storeEnv;
4215
  $env->store = [];
@@ -4224,6 +5049,8 @@ class Compiler
4224
 
4225
  /**
4226
  * Pop environment
 
 
4227
  */
4228
  protected function popEnv()
4229
  {
@@ -4234,8 +5061,10 @@ class Compiler
4234
  /**
4235
  * Propagate vars from a just poped Env (used in @each and @for)
4236
  *
4237
- * @param array $store
4238
- * @param null|array $excludedVars
 
 
4239
  */
4240
  protected function backPropagateEnv($store, $excludedVars = null)
4241
  {
@@ -4264,6 +5093,8 @@ class Compiler
4264
  * @param boolean $shadow
4265
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4266
  * @param mixed $valueUnreduced
 
 
4267
  */
4268
  protected function set($name, $value, $shadow = false, Environment $env = null, $valueUnreduced = null)
4269
  {
@@ -4287,6 +5118,8 @@ class Compiler
4287
  * @param mixed $value
4288
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4289
  * @param mixed $valueUnreduced
 
 
4290
  */
4291
  protected function setExisting($name, $value, Environment $env, $valueUnreduced = null)
4292
  {
@@ -4345,6 +5178,8 @@ class Compiler
4345
  * @param mixed $value
4346
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4347
  * @param mixed $valueUnreduced
 
 
4348
  */
4349
  protected function setRaw($name, $value, Environment $env, $valueUnreduced = null)
4350
  {
@@ -4358,7 +5193,7 @@ class Compiler
4358
  /**
4359
  * Get variable
4360
  *
4361
- * @api
4362
  *
4363
  * @param string $name
4364
  * @param boolean $shouldThrow
@@ -4417,7 +5252,7 @@ class Compiler
4417
  }
4418
 
4419
  if ($shouldThrow) {
4420
- $this->throwError("Undefined variable \$$name" . ($maxDepth <= 0 ? " (infinite recursion)" : ""));
4421
  }
4422
 
4423
  // found nothing
@@ -4441,6 +5276,8 @@ class Compiler
4441
  * Inject variables
4442
  *
4443
  * @param array $args
 
 
4444
  */
4445
  protected function injectVariables(array $args)
4446
  {
@@ -4455,7 +5292,7 @@ class Compiler
4455
  $name = substr($name, 1);
4456
  }
4457
 
4458
- if (! $parser->parseValue($strValue, $value)) {
4459
  $value = $this->coerceValue($strValue);
4460
  }
4461
 
@@ -4463,16 +5300,59 @@ class Compiler
4463
  }
4464
  }
4465
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4466
  /**
4467
  * Set variables
4468
  *
4469
  * @api
4470
  *
4471
  * @param array $variables
 
 
 
 
4472
  */
4473
  public function setVariables(array $variables)
4474
  {
4475
- $this->registeredVars = array_merge($this->registeredVars, $variables);
 
 
4476
  }
4477
 
4478
  /**
@@ -4481,6 +5361,8 @@ class Compiler
4481
  * @api
4482
  *
4483
  * @param string $name
 
 
4484
  */
4485
  public function unsetVariable($name)
4486
  {
@@ -4502,13 +5384,15 @@ class Compiler
4502
  /**
4503
  * Adds to list of parsed files
4504
  *
4505
- * @api
4506
  *
4507
- * @param string $path
 
 
4508
  */
4509
  public function addParsedFile($path)
4510
  {
4511
- if (isset($path) && is_file($path)) {
4512
  $this->parsedFiles[realpath($path)] = filemtime($path);
4513
  }
4514
  }
@@ -4516,12 +5400,12 @@ class Compiler
4516
  /**
4517
  * Returns list of parsed files
4518
  *
4519
- * @api
4520
- *
4521
- * @return array
4522
  */
4523
  public function getParsedFiles()
4524
  {
 
4525
  return $this->parsedFiles;
4526
  }
4527
 
@@ -4531,6 +5415,8 @@ class Compiler
4531
  * @api
4532
  *
4533
  * @param string|callable $path
 
 
4534
  */
4535
  public function addImportPath($path)
4536
  {
@@ -4544,11 +5430,24 @@ class Compiler
4544
  *
4545
  * @api
4546
  *
4547
- * @param string|array $path
 
 
4548
  */
4549
  public function setImportPaths($path)
4550
  {
4551
- $this->importPaths = (array) $path;
 
 
 
 
 
 
 
 
 
 
 
4552
  }
4553
 
4554
  /**
@@ -4557,10 +5456,42 @@ class Compiler
4557
  * @api
4558
  *
4559
  * @param integer $numberPrecision
 
 
 
 
4560
  */
4561
  public function setNumberPrecision($numberPrecision)
4562
  {
4563
- Node\Number::$precision = $numberPrecision;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4564
  }
4565
 
4566
  /**
@@ -4569,9 +5500,18 @@ class Compiler
4569
  * @api
4570
  *
4571
  * @param string $formatterName
 
 
 
 
4572
  */
4573
  public function setFormatter($formatterName)
4574
  {
 
 
 
 
 
4575
  $this->formatter = $formatterName;
4576
  }
4577
 
@@ -4581,10 +5521,34 @@ class Compiler
4581
  * @api
4582
  *
4583
  * @param string $lineNumberStyle
 
 
 
 
4584
  */
4585
  public function setLineNumberStyle($lineNumberStyle)
4586
  {
4587
- $this->lineNumberStyle = $lineNumberStyle;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4588
  }
4589
 
4590
  /**
@@ -4593,6 +5557,10 @@ class Compiler
4593
  * @api
4594
  *
4595
  * @param integer $sourceMap
 
 
 
 
4596
  */
4597
  public function setSourceMap($sourceMap)
4598
  {
@@ -4605,6 +5573,10 @@ class Compiler
4605
  * @api
4606
  *
4607
  * @param array $sourceMapOptions
 
 
 
 
4608
  */
4609
  public function setSourceMapOptions($sourceMapOptions)
4610
  {
@@ -4616,13 +5588,23 @@ class Compiler
4616
  *
4617
  * @api
4618
  *
4619
- * @param string $name
4620
- * @param callable $func
4621
- * @param array $prototype
 
 
4622
  */
4623
- public function registerFunction($name, $func, $prototype = null)
4624
  {
4625
- $this->userFunctions[$this->normalizeName($name)] = [$func, $prototype];
 
 
 
 
 
 
 
 
4626
  }
4627
 
4628
  /**
@@ -4631,6 +5613,8 @@ class Compiler
4631
  * @api
4632
  *
4633
  * @param string $name
 
 
4634
  */
4635
  public function unregisterFunction($name)
4636
  {
@@ -4643,9 +5627,15 @@ class Compiler
4643
  * @api
4644
  *
4645
  * @param string $name
 
 
 
 
4646
  */
4647
  public function addFeature($name)
4648
  {
 
 
4649
  $this->registeredFeatures[$name] = true;
4650
  }
4651
 
@@ -4654,13 +5644,24 @@ class Compiler
4654
  *
4655
  * @param string $path
4656
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
 
 
4657
  */
4658
  protected function importFile($path, OutputBlock $out)
4659
  {
4660
- $this->pushCallStack('import '.$path);
4661
  // see if tree is cached
4662
  $realPath = realpath($path);
4663
 
 
 
 
 
 
 
 
 
 
4664
  if (isset($this->importCache[$realPath])) {
4665
  $this->handleImportLoop($realPath);
4666
 
@@ -4673,62 +5674,109 @@ class Compiler
4673
  $this->importCache[$realPath] = $tree;
4674
  }
4675
 
4676
- $pi = pathinfo($path);
 
4677
 
4678
- array_unshift($this->importPaths, $pi['dirname']);
4679
  $this->compileChildrenNoReturn($tree->children, $out);
4680
- array_shift($this->importPaths);
4681
  $this->popCallStack();
4682
  }
4683
 
4684
  /**
4685
- * Return the file path for an import url if it exists
4686
  *
4687
- * @api
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4688
  *
4689
  * @param string $url
4690
  *
4691
- * @return string|null
4692
  */
4693
- public function findImport($url)
4694
  {
4695
- $urls = [];
 
4696
 
4697
- $hasExtension = preg_match('/[.]s?css$/', $url);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4698
 
4699
- // for "normal" scss imports (ignore vanilla css and external requests)
4700
- if (! preg_match('~\.css$|^https?://|^//~', $url)) {
4701
- $isPartial = (strpos(basename($url), '_') === 0);
4702
 
4703
- // try both normal and the _partial filename
4704
- $urls = [$url . ($hasExtension ? '' : '.scss')];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4705
 
4706
- if (! $isPartial) {
4707
- $urls[] = preg_replace('~[^/]+$~', '_\0', $url) . ($hasExtension ? '' : '.scss');
 
4708
  }
 
 
 
 
 
4709
 
4710
- if (! $hasExtension) {
4711
- $urls[] = "$url/index.scss";
4712
- $urls[] = "$url/_index.scss";
4713
- // allow to find a plain css file, *if* no scss or partial scss is found
4714
- $urls[] .= $url . ".css";
4715
  }
4716
  }
4717
 
4718
  foreach ($this->importPaths as $dir) {
4719
  if (\is_string($dir)) {
4720
- // check urls for normal import paths
4721
- foreach ($urls as $full) {
4722
- $separator = (
4723
- ! empty($dir) &&
4724
- substr($dir, -1) !== '/' &&
4725
- substr($full, 0, 1) !== '/'
4726
- ) ? '/' : '';
4727
- $full = $dir . $separator . $full;
4728
-
4729
- if (is_file($file = $full)) {
4730
- return $file;
4731
- }
4732
  }
4733
  } elseif (\is_callable($dir)) {
4734
  // check custom callback for import path
@@ -4740,233 +5788,455 @@ class Compiler
4740
  }
4741
  }
4742
 
4743
- if ($urls) {
4744
- if (! $hasExtension or preg_match('/[.]scss$/', $url)) {
4745
- $this->throwError("`$url` file not found for @import");
 
 
 
 
4746
  }
4747
  }
4748
 
4749
- return null;
4750
  }
4751
 
4752
  /**
4753
- * Set encoding
4754
- *
4755
- * @api
4756
  *
4757
- * @param string $encoding
4758
  */
4759
- public function setEncoding($encoding)
4760
  {
4761
- $this->encoding = $encoding;
4762
- }
4763
 
4764
- /**
4765
- * Ignore errors?
4766
- *
4767
- * @api
4768
- *
4769
- * @param boolean $ignoreErrors
4770
- *
4771
- * @return \ScssPhp\ScssPhp\Compiler
4772
- */
4773
- public function setIgnoreErrors($ignoreErrors)
4774
- {
4775
- $this->ignoreErrors = $ignoreErrors;
4776
 
4777
- return $this;
 
 
 
 
 
 
 
 
 
 
4778
  }
4779
 
4780
  /**
4781
- * Throw error (exception)
4782
- *
4783
- * @api
4784
- *
4785
- * @param string $msg Message with optional sprintf()-style vararg parameters
4786
  *
4787
- * @throws \ScssPhp\ScssPhp\Exception\CompilerException
4788
  */
4789
- public function throwError($msg)
4790
  {
4791
- if ($this->ignoreErrors) {
4792
- return;
4793
  }
4794
 
4795
- if (\func_num_args() > 1) {
4796
- $msg = \call_user_func_array('sprintf', \func_get_args());
4797
  }
4798
 
4799
- if (! $this->ignoreCallStackMessage) {
4800
- $line = $this->sourceLine;
4801
- $column = $this->sourceColumn;
4802
 
4803
- $loc = isset($this->sourceNames[$this->sourceIndex])
4804
- ? $this->sourceNames[$this->sourceIndex] . " on line $line, at column $column"
4805
- : "line: $line, column: $column";
4806
 
4807
- $msg = "$msg: $loc";
 
4808
 
4809
- $callStackMsg = $this->callStackMessage();
 
 
 
 
 
 
 
 
 
 
4810
 
4811
- if ($callStackMsg) {
4812
- $msg .= "\nCall Stack:\n" . $callStackMsg;
4813
- }
4814
  }
4815
 
4816
- throw new CompilerException($msg);
4817
  }
4818
 
4819
  /**
4820
- * Beautify call stack for output
4821
- *
4822
- * @param boolean $all
4823
- * @param null $limit
4824
  *
4825
- * @return string
4826
  */
4827
- protected function callStackMessage($all = false, $limit = null)
4828
  {
4829
- $callStackMsg = [];
4830
- $ncall = 0;
4831
 
4832
- if ($this->callStack) {
4833
- foreach (array_reverse($this->callStack) as $call) {
4834
- if ($all || (isset($call['n']) && $call['n'])) {
4835
- $msg = "#" . $ncall++ . " " . $call['n'] . " ";
4836
- $msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]])
4837
- ? $this->sourceNames[$call[Parser::SOURCE_INDEX]]
4838
- : '(unknown file)');
4839
- $msg .= " on line " . $call[Parser::SOURCE_LINE];
4840
 
4841
- $callStackMsg[] = $msg;
 
 
4842
 
4843
- if (! \is_null($limit) && $ncall > $limit) {
4844
- break;
4845
- }
4846
- }
4847
- }
4848
  }
4849
 
4850
- return implode("\n", $callStackMsg);
4851
  }
4852
 
4853
  /**
4854
- * Handle import loop
4855
- *
4856
- * @param string $name
4857
  *
4858
- * @throws \Exception
4859
  */
4860
- protected function handleImportLoop($name)
4861
  {
4862
- for ($env = $this->env; $env; $env = $env->parent) {
4863
- if (! $env->block) {
4864
- continue;
4865
- }
4866
-
4867
- $file = $this->sourceNames[$env->block->sourceIndex];
4868
-
4869
- if (realpath($file) === $name) {
4870
- $this->throwError('An @import loop has been found: %s imports %s', $file, basename($file));
4871
- break;
4872
- }
4873
  }
 
 
4874
  }
4875
 
4876
  /**
4877
- * Call SCSS @function
4878
- *
4879
- * @param string $name
4880
- * @param array $argValues
4881
- * @param array $returnValue
4882
  *
4883
- * @return boolean Returns true if returnValue is set; otherwise, false
4884
  */
4885
- protected function callScssFunction($name, $argValues, &$returnValue)
4886
  {
4887
- $func = $this->get(static::$namespaces['function'] . $name, false);
4888
-
4889
- if (! $func) {
4890
- return false;
4891
  }
4892
 
4893
- $this->pushEnv();
 
4894
 
4895
- // set the args
4896
- if (isset($func->args)) {
4897
- $this->applyArguments($func->args, $argValues);
4898
  }
4899
 
4900
- // throw away lines and children
4901
- $tmp = new OutputBlock;
4902
- $tmp->lines = [];
4903
- $tmp->children = [];
4904
 
4905
- $this->env->marker = 'function';
 
4906
 
4907
- if (! empty($func->parentEnv)) {
4908
- $this->env->declarationScopeParent = $func->parentEnv;
 
 
 
 
 
 
 
 
 
 
 
 
 
4909
  } else {
4910
- $this->throwError("@function $name() without parentEnv");
4911
  }
4912
 
4913
- $ret = $this->compileChildren($func->children, $tmp, $this->env->marker . " " . $name);
 
4914
 
4915
- $this->popEnv();
 
 
 
 
 
 
 
 
 
 
 
 
 
4916
 
4917
- $returnValue = ! isset($ret) ? static::$defaultValue : $ret;
 
4918
 
4919
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4920
  }
4921
 
4922
  /**
4923
- * Call built-in and registered (PHP) functions
4924
  *
4925
- * @param string $name
4926
- * @param array $args
4927
- * @param array $returnValue
 
 
4928
  *
4929
- * @return boolean Returns true if returnValue is set; otherwise, false
4930
  */
4931
- protected function callNativeFunction($name, $args, &$returnValue)
4932
  {
4933
- // try a lib function
4934
- $name = $this->normalizeName($name);
4935
- $libName = null;
 
4936
 
4937
- if (isset($this->userFunctions[$name])) {
4938
- // see if we can find a user function
4939
- list($f, $prototype) = $this->userFunctions[$name];
4940
- } elseif (($f = $this->getBuiltinFunction($name)) && \is_callable($f)) {
4941
- $libName = $f[1];
4942
- $prototype = isset(static::$$libName) ? static::$$libName : null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4943
  } else {
4944
- return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4945
  }
4946
 
4947
- @list($sorted, $kwargs) = $this->sortNativeFunctionArgs($libName, $prototype, $args);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4948
 
4949
- if ($name !== 'if' && $name !== 'call') {
4950
- $inExp = true;
 
4951
 
4952
- if ($name === 'join') {
4953
- $inExp = false;
4954
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4955
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4956
  foreach ($sorted as &$val) {
4957
- $val = $this->reduce($val, $inExp);
 
 
4958
  }
4959
  }
4960
 
4961
- $returnValue = \call_user_func($f, $sorted, $kwargs);
4962
 
4963
  if (! isset($returnValue)) {
4964
- return false;
4965
  }
4966
 
4967
- $returnValue = $this->coerceValue($returnValue);
 
 
4968
 
4969
- return true;
 
 
4970
  }
4971
 
4972
  /**
@@ -4978,6 +6248,22 @@ class Compiler
4978
  */
4979
  protected function getBuiltinFunction($name)
4980
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4981
  $libName = 'lib' . preg_replace_callback(
4982
  '/_(.)/',
4983
  function ($m) {
@@ -4985,18 +6271,31 @@ class Compiler
4985
  },
4986
  ucfirst($name)
4987
  );
 
 
4988
 
4989
- return [$this, $libName];
 
 
 
 
 
 
 
 
 
 
 
4990
  }
4991
 
4992
  /**
4993
  * Sorts keyword arguments
4994
  *
4995
  * @param string $functionName
4996
- * @param array $prototypes
4997
  * @param array $args
4998
  *
4999
- * @return array
5000
  */
5001
  protected function sortNativeFunctionArgs($functionName, $prototypes, $args)
5002
  {
@@ -5006,16 +6305,18 @@ class Compiler
5006
  $keyArgs = [];
5007
  $posArgs = [];
5008
 
 
 
 
 
5009
  // separate positional and keyword arguments
5010
  foreach ($args as $arg) {
5011
  list($key, $value) = $arg;
5012
 
5013
- $key = $key[1];
5014
-
5015
- if (empty($key)) {
5016
  $posArgs[] = empty($arg[2]) ? $value : $arg;
5017
  } else {
5018
- $keyArgs[$key] = $value;
5019
  }
5020
  }
5021
 
@@ -5027,123 +6328,417 @@ class Compiler
5027
  // notation 100 127 255 / 0 is in fact a simple list of 4 values
5028
  foreach ($args as $k => $arg) {
5029
  if ($arg[1][0] === Type::T_LIST && \count($arg[1][2]) === 3) {
5030
- $last = end($arg[1][2]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5031
 
5032
- if ($last[0] === Type::T_EXPRESSION && $last[1] === '/') {
5033
- array_pop($arg[1][2]);
5034
- $arg[1][2][] = $last[2];
5035
- $arg[1][2][] = $last[3];
5036
- $args[$k] = $arg;
5037
- }
5038
  }
 
 
 
 
5039
  }
5040
  }
5041
 
5042
- $finalArgs = [];
5043
-
5044
- if (! \is_array(reset($prototypes))) {
5045
- $prototypes = [$prototypes];
5046
  }
5047
 
5048
- $keyArgs = [];
 
 
 
 
 
 
 
 
 
 
 
5049
 
5050
- // trying each prototypes
5051
- $prototypeHasMatch = false;
5052
- $exceptionMessage = '';
 
5053
 
5054
- foreach ($prototypes as $prototype) {
5055
- $argDef = [];
 
 
 
 
 
 
 
 
 
 
5056
 
5057
- foreach ($prototype as $i => $p) {
5058
- $default = null;
5059
- $p = explode(':', $p, 2);
5060
- $name = array_shift($p);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5061
 
5062
- if (\count($p)) {
5063
- $p = trim(reset($p));
 
 
 
 
5064
 
5065
- if ($p === 'null') {
5066
- // differentiate this null from the static::$null
5067
- $default = [Type::T_KEYWORD, 'null'];
5068
- } else {
5069
- if (\is_null($parser)) {
5070
- $parser = $this->parserFactory(__METHOD__);
5071
- }
5072
 
5073
- $parser->parseValue($p, $default);
5074
- }
 
 
 
5075
  }
5076
 
5077
- $isVariable = false;
 
 
 
 
 
5078
 
5079
- if (substr($name, -3) === '...') {
5080
- $isVariable = true;
5081
- $name = substr($name, 0, -3);
5082
- }
 
5083
 
5084
- $argDef[] = [$name, $default, $isVariable];
5085
- }
5086
 
5087
- $ignoreCallStackMessage = $this->ignoreCallStackMessage;
5088
- $this->ignoreCallStackMessage = true;
 
5089
 
5090
- try {
5091
- $vars = $this->applyArguments($argDef, $args, false, false);
5092
 
5093
- // ensure all args are populated
5094
- foreach ($prototype as $i => $p) {
5095
- $name = explode(':', $p)[0];
5096
 
5097
- if (! isset($finalArgs[$i])) {
5098
- $finalArgs[$i] = null;
 
 
5099
  }
5100
- }
5101
-
5102
- // apply positional args
5103
- foreach (array_values($vars) as $i => $val) {
5104
- $finalArgs[$i] = $val;
5105
- }
5106
 
5107
- $keyArgs = array_merge($keyArgs, $vars);
5108
- $prototypeHasMatch = true;
5109
 
5110
- // overwrite positional args with keyword args
5111
- foreach ($prototype as $i => $p) {
5112
- $name = explode(':', $p)[0];
5113
 
5114
- if (isset($keyArgs[$name])) {
5115
- $finalArgs[$i] = $keyArgs[$name];
5116
- }
 
 
 
 
5117
 
5118
- // special null value as default: translate to real null here
5119
- if ($finalArgs[$i] === [Type::T_KEYWORD, 'null']) {
5120
- $finalArgs[$i] = null;
5121
  }
 
 
5122
  }
5123
- // should we break if this prototype seems fulfilled?
5124
- } catch (CompilerException $e) {
5125
- $exceptionMessage = $e->getMessage();
 
5126
  }
5127
- $this->ignoreCallStackMessage = $ignoreCallStackMessage;
5128
  }
5129
 
5130
- if ($exceptionMessage && ! $prototypeHasMatch) {
5131
- $this->throwError($exceptionMessage);
 
 
 
 
 
 
 
 
 
 
 
5132
  }
5133
 
5134
- return [$finalArgs, $keyArgs];
5135
  }
5136
 
5137
  /**
5138
  * Apply argument values per definition
5139
  *
5140
- * @param array $argDef
5141
- * @param array $argValues
5142
  * @param boolean $storeInEnv
5143
  * @param boolean $reduce
5144
  * only used if $storeInEnv = false
5145
  *
5146
- * @return array
 
 
5147
  *
5148
  * @throws \Exception
5149
  */
@@ -5151,151 +6746,58 @@ class Compiler
5151
  {
5152
  $output = [];
5153
 
5154
- if (\is_array($argValues) && \count($argValues) && end($argValues) === static::$null) {
5155
- array_pop($argValues);
5156
  }
5157
 
5158
  if ($storeInEnv) {
5159
  $storeEnv = $this->getStoreEnv();
5160
 
5161
- $env = new Environment;
5162
  $env->store = $storeEnv->store;
5163
  }
5164
 
5165
- $hasVariable = false;
5166
- $args = [];
5167
 
5168
  foreach ($argDef as $i => $arg) {
5169
- list($name, $default, $isVariable) = $argDef[$i];
 
5170
 
5171
- $args[$name] = [$i, $name, $default, $isVariable];
5172
- $hasVariable |= $isVariable;
 
 
 
 
5173
  }
5174
 
5175
- $splatSeparator = null;
5176
- $keywordArgs = [];
5177
- $deferredKeywordArgs = [];
5178
- $remaining = [];
5179
- $hasKeywordArgument = false;
5180
-
5181
- // assign the keyword args
5182
- foreach ((array) $argValues as $arg) {
5183
- if (! empty($arg[0])) {
5184
- $hasKeywordArgument = true;
5185
-
5186
- $name = $arg[0][1];
5187
- if (! isset($args[$name])) {
5188
- foreach (array_keys($args) as $an) {
5189
- if (str_replace("_", "-", $an) === str_replace("_", "-", $name)) {
5190
- $name = $an;
5191
- break;
5192
- }
5193
- }
5194
- }
5195
-
5196
- if (! isset($args[$name]) || $args[$name][3]) {
5197
- if ($hasVariable) {
5198
- $deferredKeywordArgs[$name] = $arg[1];
5199
- } else {
5200
- $this->throwError("Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
5201
- break;
5202
- }
5203
- } elseif ($args[$name][0] < \count($remaining)) {
5204
- $this->throwError("The argument $%s was passed both by position and by name.", $arg[0][1]);
5205
- break;
5206
- } else {
5207
- $keywordArgs[$name] = $arg[1];
5208
- }
5209
- } elseif ($arg[2] === true) {
5210
- $val = $this->reduce($arg[1], true);
5211
 
5212
- if ($val[0] === Type::T_LIST) {
5213
- foreach ($val[2] as $name => $item) {
5214
- if (! is_numeric($name)) {
5215
- if (! isset($args[$name])) {
5216
- foreach (array_keys($args) as $an) {
5217
- if (str_replace("_", "-", $an) === str_replace("_", "-", $name)) {
5218
- $name = $an;
5219
- break;
5220
- }
5221
- }
5222
- }
5223
 
5224
- if ($hasVariable) {
5225
- $deferredKeywordArgs[$name] = $item;
5226
- } else {
5227
- $keywordArgs[$name] = $item;
5228
- }
5229
- } else {
5230
- if (\is_null($splatSeparator)) {
5231
- $splatSeparator = $val[1];
5232
- }
5233
 
5234
- $remaining[] = $item;
5235
- }
5236
- }
5237
- } elseif ($val[0] === Type::T_MAP) {
5238
- foreach ($val[1] as $i => $name) {
5239
- $name = $this->compileStringContent($this->coerceString($name));
5240
- $item = $val[2][$i];
5241
 
5242
- if (! is_numeric($name)) {
5243
- if (! isset($args[$name])) {
5244
- foreach (array_keys($args) as $an) {
5245
- if (str_replace("_", "-", $an) === str_replace("_", "-", $name)) {
5246
- $name = $an;
5247
- break;
5248
- }
5249
- }
5250
- }
5251
 
5252
- if ($hasVariable) {
5253
- $deferredKeywordArgs[$name] = $item;
5254
- } else {
5255
- $keywordArgs[$name] = $item;
5256
- }
5257
- } else {
5258
- if (\is_null($splatSeparator)) {
5259
- $splatSeparator = $val[1];
5260
- }
5261
 
5262
- $remaining[] = $item;
5263
- }
5264
- }
5265
- } else {
5266
- $remaining[] = $val;
5267
- }
5268
- } elseif ($hasKeywordArgument) {
5269
- $this->throwError('Positional arguments must come before keyword arguments.');
5270
- break;
5271
  } else {
5272
- $remaining[] = $arg[1];
5273
  }
5274
  }
5275
 
5276
- foreach ($args as $arg) {
5277
- list($i, $name, $default, $isVariable) = $arg;
5278
-
5279
- if ($isVariable) {
5280
- $val = [Type::T_LIST, \is_null($splatSeparator) ? ',' : $splatSeparator , [], $isVariable];
5281
-
5282
- for ($count = \count($remaining); $i < $count; $i++) {
5283
- $val[2][] = $remaining[$i];
5284
- }
5285
-
5286
- foreach ($deferredKeywordArgs as $itemName => $item) {
5287
- $val[2][$itemName] = $item;
5288
- }
5289
- } elseif (isset($remaining[$i])) {
5290
- $val = $remaining[$i];
5291
- } elseif (isset($keywordArgs[$name])) {
5292
- $val = $keywordArgs[$name];
5293
- } elseif (! empty($default)) {
5294
- continue;
5295
- } else {
5296
- $this->throwError("Missing argument $name");
5297
- break;
5298
- }
5299
 
5300
  if ($storeInEnv) {
5301
  $this->set($name, $this->reduce($val, true), true, $env);
@@ -5308,12 +6810,13 @@ class Compiler
5308
  $storeEnv->store = $env->store;
5309
  }
5310
 
5311
- foreach ($args as $arg) {
5312
- list($i, $name, $default, $isVariable) = $arg;
5313
 
5314
- if ($isVariable || isset($remaining[$i]) || isset($keywordArgs[$name]) || empty($default)) {
5315
  continue;
5316
  }
 
5317
 
5318
  if ($storeInEnv) {
5319
  $this->set($name, $this->reduce($default, true), true);
@@ -5325,16 +6828,77 @@ class Compiler
5325
  return $output;
5326
  }
5327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5328
  /**
5329
  * Coerce a php value into a scss one
5330
  *
5331
  * @param mixed $value
5332
  *
5333
- * @return array|\ScssPhp\ScssPhp\Node\Number
5334
  */
5335
  protected function coerceValue($value)
5336
  {
5337
- if (\is_array($value) || $value instanceof \ArrayAccess) {
5338
  return $value;
5339
  }
5340
 
@@ -5347,7 +6911,7 @@ class Compiler
5347
  }
5348
 
5349
  if (is_numeric($value)) {
5350
- return new Node\Number($value, '');
5351
  }
5352
 
5353
  if ($value === '') {
@@ -5367,9 +6931,9 @@ class Compiler
5367
  /**
5368
  * Coerce something to map
5369
  *
5370
- * @param array $item
5371
  *
5372
- * @return array
5373
  */
5374
  protected function coerceMap($item)
5375
  {
@@ -5377,28 +6941,32 @@ class Compiler
5377
  return $item;
5378
  }
5379
 
5380
- if ($item[0] === static::$emptyList[0] &&
5381
- $item[1] === static::$emptyList[1] &&
5382
- $item[2] === static::$emptyList[2]
5383
  ) {
5384
  return static::$emptyMap;
5385
  }
5386
 
5387
- return [Type::T_MAP, [$item], [static::$null]];
5388
  }
5389
 
5390
  /**
5391
  * Coerce something to list
5392
  *
5393
- * @param array $item
5394
- * @param string $delim
5395
- * @param boolean $removeTrailingNull
5396
  *
5397
  * @return array
5398
  */
5399
  protected function coerceList($item, $delim = ',', $removeTrailingNull = false)
5400
  {
5401
- if (isset($item) && $item[0] === Type::T_LIST) {
 
 
 
 
5402
  // remove trailing null from the list
5403
  if ($removeTrailingNull && end($item[2]) === static::$null) {
5404
  array_pop($item[2]);
@@ -5407,7 +6975,7 @@ class Compiler
5407
  return $item;
5408
  }
5409
 
5410
- if (isset($item) && $item[0] === Type::T_MAP) {
5411
  $keys = $item[1];
5412
  $values = $item[2];
5413
  $list = [];
@@ -5438,15 +7006,15 @@ class Compiler
5438
  return [Type::T_LIST, ',', $list];
5439
  }
5440
 
5441
- return [Type::T_LIST, $delim, ! isset($item) ? []: [$item]];
5442
  }
5443
 
5444
  /**
5445
  * Coerce color for expression
5446
  *
5447
- * @param array $value
5448
  *
5449
- * @return array|null
5450
  */
5451
  protected function coerceForExpression($value)
5452
  {
@@ -5460,12 +7028,17 @@ class Compiler
5460
  /**
5461
  * Coerce value to color
5462
  *
5463
- * @param array $value
 
5464
  *
5465
  * @return array|null
5466
  */
5467
  protected function coerceColor($value, $inRGBFunction = false)
5468
  {
 
 
 
 
5469
  switch ($value[0]) {
5470
  case Type::T_COLOR:
5471
  for ($i = 1; $i <= 3; $i++) {
@@ -5551,7 +7124,7 @@ class Compiler
5551
  if ($color[3] === 255) {
5552
  $color[3] = 1; // fully opaque
5553
  } else {
5554
- $color[3] = round($color[3] / 255, 3);
5555
  }
5556
  }
5557
 
@@ -5574,8 +7147,8 @@ class Compiler
5574
  }
5575
 
5576
  /**
5577
- * @param integer|\ScssPhp\ScssPhp\Node\Number $value
5578
- * @param boolean $isAlpha
5579
  *
5580
  * @return integer|mixed
5581
  */
@@ -5593,36 +7166,27 @@ class Compiler
5593
  * @param integer|float $min
5594
  * @param integer|float $max
5595
  * @param boolean $isInt
5596
- * @param boolean $clamp
5597
- * @param boolean $modulo
5598
  *
5599
  * @return integer|mixed
5600
  */
5601
- protected function compileColorPartValue($value, $min, $max, $isInt = true, $clamp = true, $modulo = false)
5602
  {
5603
  if (! is_numeric($value)) {
5604
  if (\is_array($value)) {
5605
  $reduced = $this->reduce($value);
5606
 
5607
- if (\is_object($reduced) && $value->type === Type::T_NUMBER) {
5608
  $value = $reduced;
5609
  }
5610
  }
5611
 
5612
- if (\is_object($value) && $value->type === Type::T_NUMBER) {
5613
- $num = $value->dimension;
5614
-
5615
- if (\count($value->units)) {
5616
- $unit = array_keys($value->units);
5617
- $unit = reset($unit);
5618
-
5619
- switch ($unit) {
5620
- case '%':
5621
- $num *= $max / 100;
5622
- break;
5623
- default:
5624
- break;
5625
- }
5626
  }
5627
 
5628
  $value = $num;
@@ -5636,18 +7200,7 @@ class Compiler
5636
  $value = round($value);
5637
  }
5638
 
5639
- if ($clamp) {
5640
- $value = min($max, max($min, $value));
5641
- }
5642
-
5643
- if ($modulo) {
5644
- $value = $value % $max;
5645
-
5646
- // still negative?
5647
- while ($value < $min) {
5648
- $value += $max;
5649
- }
5650
- }
5651
 
5652
  return $value;
5653
  }
@@ -5658,9 +7211,9 @@ class Compiler
5658
  /**
5659
  * Coerce value to string
5660
  *
5661
- * @param array $value
5662
  *
5663
- * @return array|null
5664
  */
5665
  protected function coerceString($value)
5666
  {
@@ -5671,21 +7224,57 @@ class Compiler
5671
  return [Type::T_STRING, '', [$this->compileValue($value)]];
5672
  }
5673
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5674
  /**
5675
  * Coerce value to a percentage
5676
  *
5677
- * @param array $value
5678
  *
5679
  * @return integer|float
 
 
5680
  */
5681
  protected function coercePercent($value)
5682
  {
5683
- if ($value[0] === Type::T_NUMBER) {
5684
- if (! empty($value[2]['%'])) {
5685
- return $value[1] / 100;
 
 
5686
  }
5687
 
5688
- return $value[1];
5689
  }
5690
 
5691
  return 0;
@@ -5696,18 +7285,21 @@ class Compiler
5696
  *
5697
  * @api
5698
  *
5699
- * @param array $value
 
5700
  *
5701
  * @return array
5702
  *
5703
- * @throws \Exception
5704
  */
5705
- public function assertMap($value)
5706
  {
5707
  $value = $this->coerceMap($value);
5708
 
5709
  if ($value[0] !== Type::T_MAP) {
5710
- $this->throwError('expecting map, %s received', $value[0]);
 
 
5711
  }
5712
 
5713
  return $value;
@@ -5718,61 +7310,128 @@ class Compiler
5718
  *
5719
  * @api
5720
  *
5721
- * @param array $value
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5722
  *
5723
  * @return array
5724
  *
5725
- * @throws \Exception
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5726
  */
5727
- public function assertList($value)
5728
  {
5729
- if ($value[0] !== Type::T_LIST) {
5730
- $this->throwError('expecting list, %s received', $value[0]);
 
5731
  }
5732
 
5733
  return $value;
5734
  }
5735
 
5736
  /**
5737
- * Assert value is a color
5738
  *
5739
  * @api
5740
  *
5741
- * @param array $value
 
5742
  *
5743
- * @return array
5744
  *
5745
- * @throws \Exception
5746
  */
5747
- public function assertColor($value)
5748
  {
5749
- if ($color = $this->coerceColor($value)) {
5750
- return $color;
 
5751
  }
5752
 
5753
- $this->throwError('expecting color, %s received', $value[0]);
5754
  }
5755
 
5756
  /**
5757
- * Assert value is a number
5758
- *
5759
- * @api
5760
- *
5761
- * @param array $value
5762
- *
5763
- * @return integer|float
5764
  *
5765
- * @throws \Exception
 
5766
  */
5767
- public function assertNumber($value)
5768
  {
5769
- if ($value[0] !== Type::T_NUMBER) {
5770
- $this->throwError('expecting number, %s received', $value[0]);
 
 
 
5771
  }
5772
-
5773
- return $value[1];
5774
  }
5775
 
 
5776
  /**
5777
  * Make sure a color's components don't go out of bounds
5778
  *
@@ -5790,6 +7449,10 @@ class Compiler
5790
  if ($c[$i] > 255) {
5791
  $c[$i] = 255;
5792
  }
 
 
 
 
5793
  }
5794
 
5795
  return $c;
@@ -5798,7 +7461,7 @@ class Compiler
5798
  /**
5799
  * Convert RGB to HSL
5800
  *
5801
- * @api
5802
  *
5803
  * @param integer $red
5804
  * @param integer $green
@@ -5832,7 +7495,7 @@ class Compiler
5832
  }
5833
  }
5834
 
5835
- return [Type::T_HSL, fmod($h, 360), $s * 100, $l / 5.1];
5836
  }
5837
 
5838
  /**
@@ -5861,7 +7524,7 @@ class Compiler
5861
  }
5862
 
5863
  if ($h * 3 < 2) {
5864
- return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
5865
  }
5866
 
5867
  return $m1;
@@ -5870,11 +7533,11 @@ class Compiler
5870
  /**
5871
  * Convert HSL to RGB
5872
  *
5873
- * @api
5874
  *
5875
- * @param integer $hue H from 0 to 360
5876
- * @param integer $saturation S from 0 to 100
5877
- * @param integer $lightness L from 0 to 100
5878
  *
5879
  * @return array
5880
  */
@@ -5891,35 +7554,133 @@ class Compiler
5891
  $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
5892
  $m1 = $l * 2 - $m2;
5893
 
5894
- $r = $this->hueToRGB($m1, $m2, $h + 1/3) * 255;
5895
  $g = $this->hueToRGB($m1, $m2, $h) * 255;
5896
- $b = $this->hueToRGB($m1, $m2, $h - 1/3) * 255;
5897
 
5898
  $out = [Type::T_COLOR, $r, $g, $b];
5899
 
5900
  return $out;
5901
  }
5902
 
5903
- // Built in functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5904
 
5905
- protected static $libCall = ['name', 'args...'];
5906
- protected function libCall($args, $kwargs)
 
 
 
 
 
 
 
 
 
 
 
 
 
5907
  {
5908
- $name = $this->compileStringContent($this->coerceString($this->reduce(array_shift($args), true)));
5909
- $callArgs = [];
5910
 
5911
- // $kwargs['args'] is [Type::T_LIST, ',', [..]]
5912
- foreach ($kwargs['args'][2] as $varname => $arg) {
5913
- if (is_numeric($varname)) {
5914
- $varname = null;
5915
- } else {
5916
- $varname = [ 'var', $varname];
 
 
 
 
 
 
5917
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5918
 
5919
- $callArgs[] = [$varname, $arg, false];
 
5920
  }
5921
 
5922
- return $this->reduce([Type::T_FUNCTION_CALL, $name, $callArgs]);
5923
  }
5924
 
5925
  protected static $libIf = ['condition', 'if-true', 'if-false:'];
@@ -5939,7 +7700,8 @@ class Compiler
5939
  {
5940
  list($list, $value) = $args;
5941
 
5942
- if ($list[0] === Type::T_MAP ||
 
5943
  $list[0] === Type::T_STRING ||
5944
  $list[0] === Type::T_KEYWORD ||
5945
  $list[0] === Type::T_INTERPOLATE
@@ -5951,15 +7713,32 @@ class Compiler
5951
  return static::$null;
5952
  }
5953
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5954
  $values = [];
5955
 
 
5956
  foreach ($list[2] as $item) {
5957
  $values[] = $this->normalizeValue($item);
5958
  }
5959
 
5960
  $key = array_search($this->normalizeValue($value), $values);
5961
 
5962
- return false === $key ? static::$null : $key + 1;
5963
  }
5964
 
5965
  protected static $libRgb = [
@@ -5981,7 +7760,7 @@ class Compiler
5981
  $color = [Type::T_COLOR, $args[0], $args[1], $args[2]];
5982
 
5983
  if (! $color = $this->coerceColor($color)) {
5984
- $color = [Type::T_STRING, '', [$funcName .'(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
5985
  }
5986
 
5987
  return $color;
@@ -5997,7 +7776,7 @@ class Compiler
5997
  [$funcName . '(', $color[1], ', ', $color[2], ', ', $color[3], ', ', $alpha, ')']];
5998
  }
5999
  } else {
6000
- $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
6001
  }
6002
  break;
6003
 
@@ -6026,32 +7805,133 @@ class Compiler
6026
  return $this->libRgb($args, $kwargs, 'rgba');
6027
  }
6028
 
6029
- // helper function for adjust_color, change_color, and scale_color
6030
- protected function alterColor($args, $fn)
 
 
 
 
 
 
 
 
 
 
6031
  {
6032
- $color = $this->assertColor($args[0]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6033
 
6034
- foreach ([1 => 1, 2 => 2, 3 => 3, 7 => 4] as $iarg => $irgba) {
6035
- if (isset($args[$iarg])) {
6036
- $val = $this->assertNumber($args[$iarg]);
6037
 
6038
- if (! isset($color[$irgba])) {
6039
- $color[$irgba] = (($irgba < 4) ? 0 : 1);
 
 
6040
  }
 
 
 
 
 
6041
 
6042
- $color[$irgba] = \call_user_func($fn, $color[$irgba], $val, $iarg);
 
6043
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6044
  }
6045
 
6046
- if (! empty($args[4]) || ! empty($args[5]) || ! empty($args[6])) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6047
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6048
 
6049
- foreach ([4 => 1, 5 => 2, 6 => 3] as $iarg => $ihsl) {
6050
- if (! empty($args[$iarg])) {
6051
- $val = $this->assertNumber($args[$iarg]);
6052
- $hsl[$ihsl] = \call_user_func($fn, $hsl[$ihsl], $val, $iarg);
6053
- }
6054
  }
 
 
6055
 
6056
  $rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
6057
 
@@ -6062,58 +7942,54 @@ class Compiler
6062
  $color = $rgb;
6063
  }
6064
 
 
 
 
 
 
6065
  return $color;
6066
  }
6067
 
6068
- protected static $libAdjustColor = [
6069
- 'color', 'red:null', 'green:null', 'blue:null',
6070
- 'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
6071
- ];
6072
  protected function libAdjustColor($args)
6073
  {
6074
- return $this->alterColor($args, function ($base, $alter, $i) {
6075
- return $base + $alter;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6076
  });
6077
  }
6078
 
6079
- protected static $libChangeColor = [
6080
- 'color', 'red:null', 'green:null', 'blue:null',
6081
- 'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
6082
- ];
6083
  protected function libChangeColor($args)
6084
  {
6085
- return $this->alterColor($args, function ($base, $alter, $i) {
 
 
 
 
6086
  return $alter;
6087
  });
6088
  }
6089
 
6090
- protected static $libScaleColor = [
6091
- 'color', 'red:null', 'green:null', 'blue:null',
6092
- 'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
6093
- ];
6094
  protected function libScaleColor($args)
6095
  {
6096
- return $this->alterColor($args, function ($base, $scale, $i) {
6097
- // 1, 2, 3 - rgb
6098
- // 4, 5, 6 - hsl
6099
- // 7 - a
6100
- switch ($i) {
6101
- case 1:
6102
- case 2:
6103
- case 3:
6104
- $max = 255;
6105
- break;
6106
-
6107
- case 4:
6108
- $max = 360;
6109
- break;
6110
-
6111
- case 7:
6112
- $max = 1;
6113
- break;
6114
-
6115
- default:
6116
- $max = 100;
6117
  }
6118
 
6119
  $scale = $scale / 100;
@@ -6130,6 +8006,11 @@ class Compiler
6130
  protected function libIeHexStr($args)
6131
  {
6132
  $color = $this->coerceColor($args[0]);
 
 
 
 
 
6133
  $color[4] = isset($color[4]) ? round(255 * $color[4]) : 255;
6134
 
6135
  return [Type::T_STRING, '', [sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3])]];
@@ -6140,7 +8021,11 @@ class Compiler
6140
  {
6141
  $color = $this->coerceColor($args[0]);
6142
 
6143
- return $color[1];
 
 
 
 
6144
  }
6145
 
6146
  protected static $libGreen = ['color'];
@@ -6148,7 +8033,11 @@ class Compiler
6148
  {
6149
  $color = $this->coerceColor($args[0]);
6150
 
6151
- return $color[2];
 
 
 
 
6152
  }
6153
 
6154
  protected static $libBlue = ['color'];
@@ -6156,14 +8045,18 @@ class Compiler
6156
  {
6157
  $color = $this->coerceColor($args[0]);
6158
 
6159
- return $color[3];
 
 
 
 
6160
  }
6161
 
6162
  protected static $libAlpha = ['color'];
6163
  protected function libAlpha($args)
6164
  {
6165
  if ($color = $this->coerceColor($args[0])) {
6166
- return isset($color[4]) ? $color[4] : 1;
6167
  }
6168
 
6169
  // this might be the IE function, so return value unchanged
@@ -6175,7 +8068,7 @@ class Compiler
6175
  {
6176
  $value = $args[0];
6177
 
6178
- if ($value[0] === Type::T_NUMBER) {
6179
  return null;
6180
  }
6181
 
@@ -6183,76 +8076,263 @@ class Compiler
6183
  }
6184
 
6185
  // mix two colors
6186
- protected static $libMix = ['color-1', 'color-2', 'weight:0.5'];
 
 
 
6187
  protected function libMix($args)
6188
  {
6189
  list($first, $second, $weight) = $args;
6190
 
6191
- $first = $this->assertColor($first);
6192
- $second = $this->assertColor($second);
6193
-
6194
- if (! isset($weight)) {
6195
- $weight = 0.5;
6196
- } else {
6197
- $weight = $this->coercePercent($weight);
6198
- }
6199
 
6200
  $firstAlpha = isset($first[4]) ? $first[4] : 1;
6201
  $secondAlpha = isset($second[4]) ? $second[4] : 1;
6202
 
6203
- $w = $weight * 2 - 1;
6204
- $a = $firstAlpha - $secondAlpha;
6205
 
6206
- $w1 = (($w * $a === -1 ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2.0;
6207
- $w2 = 1.0 - $w1;
 
6208
 
6209
  $new = [Type::T_COLOR,
6210
- $w1 * $first[1] + $w2 * $second[1],
6211
- $w1 * $first[2] + $w2 * $second[2],
6212
- $w1 * $first[3] + $w2 * $second[3],
6213
  ];
6214
 
6215
  if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
6216
- $new[] = $firstAlpha * $weight + $secondAlpha * (1 - $weight);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6217
  }
6218
 
6219
- return $this->fixColor($new);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6220
  }
6221
 
6222
- protected static $libHsl =[
 
 
6223
  ['channels'],
6224
- ['hue', 'saturation', 'lightness'],
6225
- ['hue', 'saturation', 'lightness', 'alpha'] ];
6226
- protected function libHsl($args, $kwargs, $funcName = 'hsl')
6227
  {
 
 
6228
  if (\count($args) == 1) {
6229
- if ($args[0][0] !== Type::T_LIST || \count($args[0][2]) < 3 || \count($args[0][2]) > 4) {
6230
- return [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
 
 
 
 
 
 
 
 
6231
  }
6232
 
6233
  $args = $args[0][2];
 
 
 
 
 
 
 
 
6234
  }
6235
 
6236
- $hue = $this->compileColorPartValue($args[0], 0, 360, false, false, true);
6237
- $saturation = $this->compileColorPartValue($args[1], 0, 100, false);
6238
- $lightness = $this->compileColorPartValue($args[2], 0, 100, false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6239
 
 
 
 
6240
  $alpha = null;
6241
 
6242
  if (\count($args) === 4) {
6243
- $alpha = $this->compileColorPartValue($args[3], 0, 100, false);
6244
 
6245
- if (! is_numeric($hue) || ! is_numeric($saturation) || ! is_numeric($lightness) || ! is_numeric($alpha)) {
6246
- return [Type::T_STRING, '',
6247
- [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']];
6248
- }
6249
- } else {
6250
- if (! is_numeric($hue) || ! is_numeric($saturation) || ! is_numeric($lightness)) {
6251
- return [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
6252
  }
6253
  }
6254
 
6255
- $color = $this->toRGB($hue, $saturation, $lightness);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6256
 
6257
  if (! \is_null($alpha)) {
6258
  $color[4] = $alpha;
@@ -6261,45 +8341,35 @@ class Compiler
6261
  return $color;
6262
  }
6263
 
6264
- protected static $libHsla = [
6265
- ['channels'],
6266
- ['hue', 'saturation', 'lightness', 'alpha:1'] ];
6267
- protected function libHsla($args, $kwargs)
6268
- {
6269
- return $this->libHsl($args, $kwargs, 'hsla');
6270
- }
6271
 
6272
- protected static $libHue = ['color'];
6273
- protected function libHue($args)
6274
- {
6275
  $color = $this->assertColor($args[0]);
6276
- $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6277
 
6278
- return new Node\Number($hsl[1], 'deg');
6279
  }
6280
 
6281
- protected static $libSaturation = ['color'];
6282
- protected function libSaturation($args)
6283
- {
6284
- $color = $this->assertColor($args[0]);
6285
- $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6286
-
6287
- return new Node\Number($hsl[2], '%');
6288
- }
6289
 
6290
- protected static $libLightness = ['color'];
6291
- protected function libLightness($args)
6292
- {
6293
  $color = $this->assertColor($args[0]);
6294
- $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6295
 
6296
- return new Node\Number($hsl[3], '%');
6297
  }
 
6298
 
6299
  protected function adjustHsl($color, $idx, $amount)
6300
  {
6301
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6302
  $hsl[$idx] += $amount;
 
 
 
 
 
 
6303
  $out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
6304
 
6305
  if (isset($color[4])) {
@@ -6312,8 +8382,8 @@ class Compiler
6312
  protected static $libAdjustHue = ['color', 'degrees'];
6313
  protected function libAdjustHue($args)
6314
  {
6315
- $color = $this->assertColor($args[0]);
6316
- $degrees = $this->assertNumber($args[1]);
6317
 
6318
  return $this->adjustHsl($color, 1, $degrees);
6319
  }
@@ -6321,7 +8391,7 @@ class Compiler
6321
  protected static $libLighten = ['color', 'amount'];
6322
  protected function libLighten($args)
6323
  {
6324
- $color = $this->assertColor($args[0]);
6325
  $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
6326
 
6327
  return $this->adjustHsl($color, 3, $amount);
@@ -6330,34 +8400,36 @@ class Compiler
6330
  protected static $libDarken = ['color', 'amount'];
6331
  protected function libDarken($args)
6332
  {
6333
- $color = $this->assertColor($args[0]);
6334
  $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
6335
 
6336
  return $this->adjustHsl($color, 3, -$amount);
6337
  }
6338
 
6339
- protected static $libSaturate = [['color', 'amount'], ['number']];
6340
  protected function libSaturate($args)
6341
  {
6342
  $value = $args[0];
6343
 
6344
- if ($value[0] === Type::T_NUMBER) {
 
 
6345
  return null;
6346
  }
6347
 
6348
- $color = $this->assertColor($value);
6349
- $amount = 100 * $this->coercePercent($args[1]);
6350
 
6351
- return $this->adjustHsl($color, 2, $amount);
6352
  }
6353
 
6354
  protected static $libDesaturate = ['color', 'amount'];
6355
  protected function libDesaturate($args)
6356
  {
6357
- $color = $this->assertColor($args[0]);
6358
- $amount = 100 * $this->coercePercent($args[1]);
6359
 
6360
- return $this->adjustHsl($color, 2, -$amount);
6361
  }
6362
 
6363
  protected static $libGrayscale = ['color'];
@@ -6365,55 +8437,51 @@ class Compiler
6365
  {
6366
  $value = $args[0];
6367
 
6368
- if ($value[0] === Type::T_NUMBER) {
6369
  return null;
6370
  }
6371
 
6372
- return $this->adjustHsl($this->assertColor($value), 2, -100);
6373
  }
6374
 
6375
  protected static $libComplement = ['color'];
6376
  protected function libComplement($args)
6377
  {
6378
- return $this->adjustHsl($this->assertColor($args[0]), 1, 180);
6379
  }
6380
 
6381
- protected static $libInvert = ['color', 'weight:1'];
6382
  protected function libInvert($args)
6383
  {
6384
- list($value, $weight) = $args;
6385
 
6386
- if (! isset($weight)) {
6387
- $weight = 1;
6388
- } else {
6389
- $weight = $this->coercePercent($weight);
6390
- }
 
6391
 
6392
- if ($value[0] === Type::T_NUMBER) {
6393
  return null;
6394
  }
6395
 
6396
- $color = $this->assertColor($value);
6397
  $inverted = $color;
6398
  $inverted[1] = 255 - $inverted[1];
6399
  $inverted[2] = 255 - $inverted[2];
6400
  $inverted[3] = 255 - $inverted[3];
6401
 
6402
- if ($weight < 1) {
6403
- return $this->libMix([$inverted, $color, [Type::T_NUMBER, $weight]]);
6404
- }
6405
-
6406
- return $inverted;
6407
  }
6408
 
6409
  // increases opacity by amount
6410
  protected static $libOpacify = ['color', 'amount'];
6411
  protected function libOpacify($args)
6412
  {
6413
- $color = $this->assertColor($args[0]);
6414
- $amount = $this->coercePercent($args[1]);
6415
 
6416
- $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
6417
  $color[4] = min(1, max(0, $color[4]));
6418
 
6419
  return $color;
@@ -6429,10 +8497,10 @@ class Compiler
6429
  protected static $libTransparentize = ['color', 'amount'];
6430
  protected function libTransparentize($args)
6431
  {
6432
- $color = $this->assertColor($args[0]);
6433
- $amount = $this->coercePercent($args[1]);
6434
 
6435
- $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
6436
  $color[4] = min(1, max(0, $color[4]));
6437
 
6438
  return $color;
@@ -6447,144 +8515,121 @@ class Compiler
6447
  protected static $libUnquote = ['string'];
6448
  protected function libUnquote($args)
6449
  {
6450
- $str = $args[0];
 
 
 
 
 
 
 
 
6451
 
6452
- if ($str[0] === Type::T_STRING) {
6453
- $str[1] = '';
 
6454
  }
6455
 
 
 
6456
  return $str;
6457
  }
6458
 
6459
  protected static $libQuote = ['string'];
6460
  protected function libQuote($args)
6461
  {
6462
- $value = $args[0];
6463
 
6464
- if ($value[0] === Type::T_STRING && ! empty($value[1])) {
6465
- return $value;
6466
- }
6467
 
6468
- return [Type::T_STRING, '"', [$value]];
6469
  }
6470
 
6471
  protected static $libPercentage = ['number'];
6472
  protected function libPercentage($args)
6473
  {
6474
- return new Node\Number($this->coercePercent($args[0]) * 100, '%');
 
 
 
6475
  }
6476
 
6477
  protected static $libRound = ['number'];
6478
  protected function libRound($args)
6479
  {
6480
- $num = $args[0];
6481
 
6482
- return new Node\Number(round($num[1]), $num[2]);
6483
  }
6484
 
6485
  protected static $libFloor = ['number'];
6486
  protected function libFloor($args)
6487
  {
6488
- $num = $args[0];
6489
 
6490
- return new Node\Number(floor($num[1]), $num[2]);
6491
  }
6492
 
6493
  protected static $libCeil = ['number'];
6494
  protected function libCeil($args)
6495
  {
6496
- $num = $args[0];
6497
 
6498
- return new Node\Number(ceil($num[1]), $num[2]);
6499
  }
6500
 
6501
  protected static $libAbs = ['number'];
6502
  protected function libAbs($args)
6503
  {
6504
- $num = $args[0];
6505
 
6506
- return new Node\Number(abs($num[1]), $num[2]);
6507
  }
6508
 
 
6509
  protected function libMin($args)
6510
  {
6511
- $numbers = $this->getNormalizedNumbers($args);
6512
- $minOriginal = null;
6513
- $minNormalized = null;
 
6514
 
6515
- foreach ($numbers as $key => $pair) {
6516
- list($original, $normalized) = $pair;
6517
 
6518
- if (\is_null($normalized) or \is_null($minNormalized)) {
6519
- if (\is_null($minOriginal) || $original[1] <= $minOriginal[1]) {
6520
- $minOriginal = $original;
6521
- $minNormalized = $normalized;
6522
- }
6523
- } elseif ($normalized[1] <= $minNormalized[1]) {
6524
- $minOriginal = $original;
6525
- $minNormalized = $normalized;
6526
  }
6527
  }
6528
 
6529
- return $minOriginal;
6530
- }
6531
-
6532
- protected function libMax($args)
6533
- {
6534
- $numbers = $this->getNormalizedNumbers($args);
6535
- $maxOriginal = null;
6536
- $maxNormalized = null;
6537
-
6538
- foreach ($numbers as $key => $pair) {
6539
- list($original, $normalized) = $pair;
6540
-
6541
- if (\is_null($normalized) or \is_null($maxNormalized)) {
6542
- if (\is_null($maxOriginal) || $original[1] >= $maxOriginal[1]) {
6543
- $maxOriginal = $original;
6544
- $maxNormalized = $normalized;
6545
- }
6546
- } elseif ($normalized[1] >= $maxNormalized[1]) {
6547
- $maxOriginal = $original;
6548
- $maxNormalized = $normalized;
6549
- }
6550
  }
6551
 
6552
- return $maxOriginal;
6553
  }
6554
 
6555
- /**
6556
- * Helper to normalize args containing numbers
6557
- *
6558
- * @param array $args
6559
- *
6560
- * @return array
6561
- */
6562
- protected function getNormalizedNumbers($args)
6563
  {
6564
- $unit = null;
6565
- $originalUnit = null;
6566
- $numbers = [];
6567
-
6568
- foreach ($args as $key => $item) {
6569
- if ($item[0] !== Type::T_NUMBER) {
6570
- $this->throwError('%s is not a number', $item[0]);
6571
- break;
6572
- }
6573
 
6574
- $number = $item->normalize();
 
6575
 
6576
- if (empty($unit)) {
6577
- $unit = $number[2];
6578
- $originalUnit = $item->unitStr();
6579
- } elseif ($number[1] && $unit !== $number[2] && ! empty($number[2])) {
6580
- $this->throwError('Incompatible units: "%s" and "%s".', $originalUnit, $item->unitStr());
6581
- break;
6582
  }
 
6583
 
6584
- $numbers[$key] = [$args[$key], empty($number[2]) ? null : $number];
 
6585
  }
6586
 
6587
- return $numbers;
6588
  }
6589
 
6590
  protected static $libLength = ['list'];
@@ -6592,34 +8637,34 @@ class Compiler
6592
  {
6593
  $list = $this->coerceList($args[0], ',', true);
6594
 
6595
- return \count($list[2]);
6596
  }
6597
 
6598
- //protected static $libListSeparator = ['list...'];
6599
  protected function libListSeparator($args)
6600
  {
6601
- if (\count($args) > 1) {
6602
- return 'comma';
6603
  }
6604
 
6605
  $list = $this->coerceList($args[0]);
6606
 
6607
- if (\count($list[2]) <= 1) {
6608
- return 'space';
6609
  }
6610
 
6611
  if ($list[1] === ',') {
6612
- return 'comma';
6613
  }
6614
 
6615
- return 'space';
6616
  }
6617
 
6618
  protected static $libNth = ['list', 'n'];
6619
  protected function libNth($args)
6620
  {
6621
  $list = $this->coerceList($args[0], ',', false);
6622
- $n = $this->assertNumber($args[1]);
6623
 
6624
  if ($n > 0) {
6625
  $n--;
@@ -6634,7 +8679,7 @@ class Compiler
6634
  protected function libSetNth($args)
6635
  {
6636
  $list = $this->coerceList($args[0]);
6637
- $n = $this->assertNumber($args[1]);
6638
 
6639
  if ($n > 0) {
6640
  $n--;
@@ -6643,9 +8688,7 @@ class Compiler
6643
  }
6644
 
6645
  if (! isset($list[2][$n])) {
6646
- $this->throwError('Invalid argument for "n"');
6647
-
6648
- return null;
6649
  }
6650
 
6651
  $list[2][$n] = $args[2];
@@ -6656,7 +8699,7 @@ class Compiler
6656
  protected static $libMapGet = ['map', 'key'];
6657
  protected function libMapGet($args)
6658
  {
6659
- $map = $this->assertMap($args[0]);
6660
  $key = $args[1];
6661
 
6662
  if (! \is_null($key)) {
@@ -6675,7 +8718,7 @@ class Compiler
6675
  protected static $libMapKeys = ['map'];
6676
  protected function libMapKeys($args)
6677
  {
6678
- $map = $this->assertMap($args[0]);
6679
  $keys = $map[1];
6680
 
6681
  return [Type::T_LIST, ',', $keys];
@@ -6684,20 +8727,33 @@ class Compiler
6684
  protected static $libMapValues = ['map'];
6685
  protected function libMapValues($args)
6686
  {
6687
- $map = $this->assertMap($args[0]);
6688
  $values = $map[2];
6689
 
6690
  return [Type::T_LIST, ',', $values];
6691
  }
6692
 
6693
- protected static $libMapRemove = ['map', 'key'];
 
 
 
6694
  protected function libMapRemove($args)
6695
  {
6696
- $map = $this->assertMap($args[0]);
6697
- $key = $this->compileStringContent($this->coerceString($args[1]));
 
 
 
 
 
 
 
 
 
 
6698
 
6699
  for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
6700
- if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
6701
  array_splice($map[1], $i, 1);
6702
  array_splice($map[2], $i, 1);
6703
  }
@@ -6709,8 +8765,19 @@ class Compiler
6709
  protected static $libMapHasKey = ['map', 'key'];
6710
  protected function libMapHasKey($args)
6711
  {
6712
- $map = $this->assertMap($args[0]);
6713
- $key = $this->compileStringContent($this->coerceString($args[1]));
 
 
 
 
 
 
 
 
 
 
 
6714
 
6715
  for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
6716
  if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
@@ -6721,11 +8788,14 @@ class Compiler
6721
  return false;
6722
  }
6723
 
6724
- protected static $libMapMerge = ['map-1', 'map-2'];
 
 
 
6725
  protected function libMapMerge($args)
6726
  {
6727
- $map1 = $this->assertMap($args[0]);
6728
- $map2 = $this->assertMap($args[1]);
6729
 
6730
  foreach ($map2[1] as $i2 => $key2) {
6731
  $key = $this->compileStringContent($this->coerceString($key2));
@@ -6747,12 +8817,18 @@ class Compiler
6747
  protected static $libKeywords = ['args'];
6748
  protected function libKeywords($args)
6749
  {
6750
- $this->assertList($args[0]);
 
 
 
 
 
 
6751
 
6752
  $keys = [];
6753
  $values = [];
6754
 
6755
- foreach ($args[0][2] as $name => $arg) {
6756
  $keys[] = [Type::T_KEYWORD, $name];
6757
  $values[] = $arg;
6758
  }
@@ -6767,12 +8843,19 @@ class Compiler
6767
  $this->coerceList($list, ' ');
6768
 
6769
  if (! empty($list['enclosing']) && $list['enclosing'] === 'bracket') {
6770
- return true;
6771
  }
6772
 
6773
- return false;
6774
  }
6775
 
 
 
 
 
 
 
 
6776
  protected function listSeparatorForJoin($list1, $sep)
6777
  {
6778
  if (! isset($sep)) {
@@ -6854,30 +8937,39 @@ class Compiler
6854
  return $res;
6855
  }
6856
 
 
6857
  protected function libZip($args)
6858
  {
6859
- foreach ($args as $key => $arg) {
6860
- $args[$key] = $this->coerceList($arg);
 
6861
  }
6862
 
6863
  $lists = [];
6864
- $firstList = array_shift($args);
6865
 
6866
- foreach ($firstList[2] as $key => $item) {
6867
- $list = [Type::T_LIST, '', [$item]];
 
 
6868
 
6869
- foreach ($args as $arg) {
6870
- if (isset($arg[2][$key])) {
6871
- $list[2][] = $arg[2][$key];
6872
- } else {
6873
- break 2;
 
6874
  }
 
 
6875
  }
6876
 
6877
- $lists[] = $list;
 
 
6878
  }
6879
 
6880
- return [Type::T_LIST, ',', $lists];
6881
  }
6882
 
6883
  protected static $libTypeOf = ['value'];
@@ -6885,6 +8977,16 @@ class Compiler
6885
  {
6886
  $value = $args[0];
6887
 
 
 
 
 
 
 
 
 
 
 
6888
  switch ($value[0]) {
6889
  case Type::T_KEYWORD:
6890
  if ($value === static::$true || $value === static::$false) {
@@ -6899,8 +9001,11 @@ class Compiler
6899
  case Type::T_FUNCTION:
6900
  return 'string';
6901
 
 
 
 
6902
  case Type::T_LIST:
6903
- if (isset($value[3]) && $value[3]) {
6904
  return 'arglist';
6905
  }
6906
 
@@ -6913,68 +9018,77 @@ class Compiler
6913
  protected static $libUnit = ['number'];
6914
  protected function libUnit($args)
6915
  {
6916
- $num = $args[0];
6917
-
6918
- if ($num[0] === Type::T_NUMBER) {
6919
- return [Type::T_STRING, '"', [$num->unitStr()]];
6920
- }
6921
 
6922
- return '';
6923
  }
6924
 
6925
  protected static $libUnitless = ['number'];
6926
  protected function libUnitless($args)
6927
  {
6928
- $value = $args[0];
6929
 
6930
- return $value[0] === Type::T_NUMBER && $value->unitless();
6931
  }
6932
 
6933
- protected static $libComparable = ['number-1', 'number-2'];
 
 
 
6934
  protected function libComparable($args)
6935
  {
6936
  list($number1, $number2) = $args;
6937
 
6938
- if (! isset($number1[0]) || $number1[0] !== Type::T_NUMBER ||
6939
- ! isset($number2[0]) || $number2[0] !== Type::T_NUMBER
 
6940
  ) {
6941
- $this->throwError('Invalid argument(s) for "comparable"');
6942
-
6943
- return null;
6944
  }
6945
 
6946
- $number1 = $number1->normalize();
6947
- $number2 = $number2->normalize();
6948
-
6949
- return $number1[2] === $number2[2] || $number1->unitless() || $number2->unitless();
6950
  }
6951
 
6952
  protected static $libStrIndex = ['string', 'substring'];
6953
  protected function libStrIndex($args)
6954
  {
6955
- $string = $this->coerceString($args[0]);
6956
  $stringContent = $this->compileStringContent($string);
6957
 
6958
- $substring = $this->coerceString($args[1]);
6959
  $substringContent = $this->compileStringContent($substring);
6960
 
6961
- $result = strpos($stringContent, $substringContent);
 
 
 
 
6962
 
6963
- return $result === false ? static::$null : new Node\Number($result + 1, '');
6964
  }
6965
 
6966
  protected static $libStrInsert = ['string', 'insert', 'index'];
6967
  protected function libStrInsert($args)
6968
  {
6969
- $string = $this->coerceString($args[0]);
6970
  $stringContent = $this->compileStringContent($string);
6971
 
6972
- $insert = $this->coerceString($args[1]);
6973
  $insertContent = $this->compileStringContent($insert);
6974
 
6975
- list(, $index) = $args[2];
 
 
 
 
 
 
6976
 
6977
- $string[2] = [substr_replace($stringContent, $insertContent, $index - 1, 0)];
 
 
 
 
6978
 
6979
  return $string;
6980
  }
@@ -6982,34 +9096,46 @@ class Compiler
6982
  protected static $libStrLength = ['string'];
6983
  protected function libStrLength($args)
6984
  {
6985
- $string = $this->coerceString($args[0]);
6986
  $stringContent = $this->compileStringContent($string);
6987
 
6988
- return new Node\Number(\strlen($stringContent), '');
6989
  }
6990
 
6991
  protected static $libStrSlice = ['string', 'start-at', 'end-at:-1'];
6992
  protected function libStrSlice($args)
6993
  {
6994
- if (isset($args[2]) && ! $args[2][1]) {
6995
- return static::$nullString;
 
 
 
 
 
 
 
 
 
 
6996
  }
6997
 
6998
- $string = $this->coerceString($args[0]);
6999
- $stringContent = $this->compileStringContent($string);
 
7000
 
7001
- $start = (int) $args[1][1];
 
 
 
 
7002
 
7003
- if ($start > 0) {
7004
- $start--;
7005
  }
7006
 
7007
- $end = isset($args[2]) ? (int) $args[2][1] : -1;
7008
- $length = $end < 0 ? $end + 1 : ($end > 0 ? $end - $start : $end);
7009
 
7010
- $string[2] = $length
7011
- ? [substr($stringContent, $start, $length)]
7012
- : [substr($stringContent, $start)];
7013
 
7014
  return $string;
7015
  }
@@ -7017,10 +9143,10 @@ class Compiler
7017
  protected static $libToLowerCase = ['string'];
7018
  protected function libToLowerCase($args)
7019
  {
7020
- $string = $this->coerceString($args[0]);
7021
  $stringContent = $this->compileStringContent($string);
7022
 
7023
- $string[2] = [\function_exists('mb_strtolower') ? mb_strtolower($stringContent) : strtolower($stringContent)];
7024
 
7025
  return $string;
7026
  }
@@ -7028,18 +9154,45 @@ class Compiler
7028
  protected static $libToUpperCase = ['string'];
7029
  protected function libToUpperCase($args)
7030
  {
7031
- $string = $this->coerceString($args[0]);
7032
  $stringContent = $this->compileStringContent($string);
7033
 
7034
- $string[2] = [\function_exists('mb_strtoupper') ? mb_strtoupper($stringContent) : strtoupper($stringContent)];
7035
 
7036
  return $string;
7037
  }
7038
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7039
  protected static $libFeatureExists = ['feature'];
7040
  protected function libFeatureExists($args)
7041
  {
7042
- $string = $this->coerceString($args[0]);
7043
  $name = $this->compileStringContent($string);
7044
 
7045
  return $this->toBool(
@@ -7050,18 +9203,18 @@ class Compiler
7050
  protected static $libFunctionExists = ['name'];
7051
  protected function libFunctionExists($args)
7052
  {
7053
- $string = $this->coerceString($args[0]);
7054
  $name = $this->compileStringContent($string);
7055
 
7056
  // user defined functions
7057
  if ($this->has(static::$namespaces['function'] . $name)) {
7058
- return true;
7059
  }
7060
 
7061
  $name = $this->normalizeName($name);
7062
 
7063
  if (isset($this->userFunctions[$name])) {
7064
- return true;
7065
  }
7066
 
7067
  // built-in functions
@@ -7073,30 +9226,31 @@ class Compiler
7073
  protected static $libGlobalVariableExists = ['name'];
7074
  protected function libGlobalVariableExists($args)
7075
  {
7076
- $string = $this->coerceString($args[0]);
7077
  $name = $this->compileStringContent($string);
7078
 
7079
- return $this->has($name, $this->rootEnv);
7080
  }
7081
 
7082
  protected static $libMixinExists = ['name'];
7083
  protected function libMixinExists($args)
7084
  {
7085
- $string = $this->coerceString($args[0]);
7086
  $name = $this->compileStringContent($string);
7087
 
7088
- return $this->has(static::$namespaces['mixin'] . $name);
7089
  }
7090
 
7091
  protected static $libVariableExists = ['name'];
7092
  protected function libVariableExists($args)
7093
  {
7094
- $string = $this->coerceString($args[0]);
7095
  $name = $this->compileStringContent($string);
7096
 
7097
- return $this->has($name);
7098
  }
7099
 
 
7100
  /**
7101
  * Workaround IE7's content counter bug.
7102
  *
@@ -7106,35 +9260,29 @@ class Compiler
7106
  */
7107
  protected function libCounter($args)
7108
  {
7109
- $list = array_map([$this, 'compileValue'], $args);
7110
 
7111
  return [Type::T_STRING, '', ['counter(' . implode(',', $list) . ')']];
7112
  }
7113
 
7114
- protected static $libRandom = ['limit:1'];
7115
  protected function libRandom($args)
7116
  {
7117
- if (isset($args[0])) {
7118
- $n = $this->assertNumber($args[0]);
7119
 
7120
  if ($n < 1) {
7121
- $this->throwError("\$limit must be greater than or equal to 1");
7122
-
7123
- return null;
7124
- }
7125
-
7126
- if ($n - \intval($n) > 0) {
7127
- $this->throwError("Expected \$limit to be an integer but got $n for `random`");
7128
-
7129
- return null;
7130
  }
7131
 
7132
- return new Node\Number(mt_rand(1, \intval($n)), '');
7133
  }
7134
 
7135
- return new Node\Number(mt_rand(1, mt_getrandmax()), '');
 
7136
  }
7137
 
 
7138
  protected function libUniqueId()
7139
  {
7140
  static $id;
@@ -7150,6 +9298,12 @@ class Compiler
7150
  return [Type::T_STRING, '', ['u' . str_pad(base_convert($id, 10, 36), 8, '0', STR_PAD_LEFT)]];
7151
  }
7152
 
 
 
 
 
 
 
7153
  protected function inspectFormatValue($value, $force_enclosing_display = false)
7154
  {
7155
  if ($value === static::$null) {
@@ -7158,6 +9312,10 @@ class Compiler
7158
 
7159
  $stringValue = [$value];
7160
 
 
 
 
 
7161
  if ($value[0] === Type::T_LIST) {
7162
  if (end($value[2]) === static::$null) {
7163
  array_pop($value[2]);
@@ -7165,12 +9323,13 @@ class Compiler
7165
  $force_enclosing_display = true;
7166
  }
7167
 
7168
- if (! empty($value['enclosing']) &&
 
7169
  ($force_enclosing_display ||
7170
  ($value['enclosing'] === 'bracket') ||
7171
  ! \count($value[2]))
7172
  ) {
7173
- $value['enclosing'] = 'forced_'.$value['enclosing'];
7174
  $force_enclosing_display = true;
7175
  }
7176
 
@@ -7195,11 +9354,13 @@ class Compiler
7195
  /**
7196
  * Preprocess selector args
7197
  *
7198
- * @param array $arg
 
 
7199
  *
7200
- * @return array|boolean
7201
  */
7202
- protected function getSelectorArg($arg)
7203
  {
7204
  static $parser = null;
7205
 
@@ -7207,19 +9368,59 @@ class Compiler
7207
  $parser = $this->parserFactory(__METHOD__);
7208
  }
7209
 
7210
- $arg = $this->libUnquote([$arg]);
 
 
 
 
 
 
 
 
7211
  $arg = $this->compileValue($arg);
7212
 
7213
  $parsedSelector = [];
7214
 
7215
- if ($parser->parseSelector($arg, $parsedSelector)) {
7216
  $selector = $this->evalSelectors($parsedSelector);
7217
  $gluedSelector = $this->glueFunctionSelectors($selector);
7218
 
 
 
 
 
 
 
 
 
 
 
7219
  return $gluedSelector;
7220
  }
7221
 
7222
- return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7223
  }
7224
 
7225
  /**
@@ -7227,11 +9428,11 @@ class Compiler
7227
  *
7228
  * @param array $selectors
7229
  *
7230
- * @return string
7231
  */
7232
  protected function formatOutputSelector($selectors)
7233
  {
7234
- $selectors = $this->collapseSelectors($selectors, true);
7235
 
7236
  return $selectors;
7237
  }
@@ -7241,10 +9442,10 @@ class Compiler
7241
  {
7242
  list($super, $sub) = $args;
7243
 
7244
- $super = $this->getSelectorArg($super);
7245
- $sub = $this->getSelectorArg($sub);
7246
 
7247
- return $this->isSuperSelector($super, $sub);
7248
  }
7249
 
7250
  /**
@@ -7258,12 +9459,30 @@ class Compiler
7258
  protected function isSuperSelector($super, $sub)
7259
  {
7260
  // one and only one selector for each arg
7261
- if (! $super || \count($super) !== 1) {
7262
- $this->throwError("Invalid super selector for isSuperSelector()");
 
 
 
 
 
 
 
 
 
 
 
 
 
7263
  }
7264
 
7265
- if (! $sub || \count($sub) !== 1) {
7266
- $this->throwError("Invalid sub selector for isSuperSelector()");
 
 
 
 
 
7267
  }
7268
 
7269
  $super = reset($super);
@@ -7345,10 +9564,13 @@ class Compiler
7345
  $args = $args[2];
7346
 
7347
  if (\count($args) < 1) {
7348
- $this->throwError("selector-append() needs at least 1 argument");
7349
  }
7350
 
7351
- $selectors = array_map([$this, 'getSelectorArg'], $args);
 
 
 
7352
 
7353
  return $this->formatOutputSelector($this->selectorAppend($selectors));
7354
  }
@@ -7367,14 +9589,14 @@ class Compiler
7367
  $lastSelectors = array_pop($selectors);
7368
 
7369
  if (! $lastSelectors) {
7370
- $this->throwError("Invalid selector list in selector-append()");
7371
  }
7372
 
7373
  while (\count($selectors)) {
7374
  $previousSelectors = array_pop($selectors);
7375
 
7376
  if (! $previousSelectors) {
7377
- $this->throwError("Invalid selector list in selector-append()");
7378
  }
7379
 
7380
  // do the trick, happening $lastSelector to $previousSelector
@@ -7404,17 +9626,20 @@ class Compiler
7404
  return $lastSelectors;
7405
  }
7406
 
7407
- protected static $libSelectorExtend = ['selectors', 'extendee', 'extender'];
 
 
 
7408
  protected function libSelectorExtend($args)
7409
  {
7410
  list($selectors, $extendee, $extender) = $args;
7411
 
7412
- $selectors = $this->getSelectorArg($selectors);
7413
- $extendee = $this->getSelectorArg($extendee);
7414
- $extender = $this->getSelectorArg($extender);
7415
 
7416
  if (! $selectors || ! $extendee || ! $extender) {
7417
- $this->throwError("selector-extend() invalid arguments");
7418
  }
7419
 
7420
  $extended = $this->extendOrReplaceSelectors($selectors, $extendee, $extender);
@@ -7422,17 +9647,20 @@ class Compiler
7422
  return $this->formatOutputSelector($extended);
7423
  }
7424
 
7425
- protected static $libSelectorReplace = ['selectors', 'original', 'replacement'];
 
 
 
7426
  protected function libSelectorReplace($args)
7427
  {
7428
  list($selectors, $original, $replacement) = $args;
7429
 
7430
- $selectors = $this->getSelectorArg($selectors);
7431
- $original = $this->getSelectorArg($original);
7432
- $replacement = $this->getSelectorArg($replacement);
7433
 
7434
  if (! $selectors || ! $original || ! $replacement) {
7435
- $this->throwError("selector-replace() invalid arguments");
7436
  }
7437
 
7438
  $replaced = $this->extendOrReplaceSelectors($selectors, $original, $replacement, true);
@@ -7460,6 +9688,10 @@ class Compiler
7460
  $this->extendsMap = [];
7461
 
7462
  foreach ($extendee as $es) {
 
 
 
 
7463
  // only use the first one
7464
  $this->pushExtends(reset($es), $extender, null);
7465
  }
@@ -7476,7 +9708,7 @@ class Compiler
7476
  $this->matchExtends($selector, $extended);
7477
 
7478
  // if didnt match, keep the original selector if we are in a replace operation
7479
- if ($replace and \count($extended) === $n) {
7480
  $extended[] = $selector;
7481
  }
7482
  }
@@ -7495,10 +9727,14 @@ class Compiler
7495
  $args = $args[2];
7496
 
7497
  if (\count($args) < 1) {
7498
- $this->throwError("selector-nest() needs at least 1 argument");
 
 
 
 
 
7499
  }
7500
 
7501
- $selectorsMap = array_map([$this, 'getSelectorArg'], $args);
7502
  $envs = [];
7503
 
7504
  foreach ($selectorsMap as $selectors) {
@@ -7515,11 +9751,14 @@ class Compiler
7515
  return $this->formatOutputSelector($outputSelectors);
7516
  }
7517
 
7518
- protected static $libSelectorParse = ['selectors'];
 
 
 
7519
  protected function libSelectorParse($args)
7520
  {
7521
  $selectors = reset($args);
7522
- $selectors = $this->getSelectorArg($selectors);
7523
 
7524
  return $this->formatOutputSelector($selectors);
7525
  }
@@ -7529,11 +9768,11 @@ class Compiler
7529
  {
7530
  list($selectors1, $selectors2) = $args;
7531
 
7532
- $selectors1 = $this->getSelectorArg($selectors1);
7533
- $selectors2 = $this->getSelectorArg($selectors2);
7534
 
7535
  if (! $selectors1 || ! $selectors2) {
7536
- $this->throwError("selector-unify() invalid arguments");
7537
  }
7538
 
7539
  // only consider the first compound of each
@@ -7553,7 +9792,7 @@ class Compiler
7553
  * @param array $compound1
7554
  * @param array $compound2
7555
  *
7556
- * @return array|mixed
7557
  */
7558
  protected function unifyCompoundSelectors($compound1, $compound2)
7559
  {
@@ -7669,7 +9908,7 @@ class Compiler
7669
  * @param array $part
7670
  * @param array $compound
7671
  *
7672
- * @return array|boolean
7673
  */
7674
  protected function matchPartInCompound($part, $compound)
7675
  {
@@ -7764,7 +10003,7 @@ class Compiler
7764
  * @param string $tag1
7765
  * @param string $tag2
7766
  *
7767
- * @return array|boolean
7768
  */
7769
  protected function checkCompatibleTags($tag1, $tag2)
7770
  {
@@ -7787,9 +10026,9 @@ class Compiler
7787
  /**
7788
  * Find the html tag name in a selector parts list
7789
  *
7790
- * @param array $parts
7791
  *
7792
- * @return mixed|string
7793
  */
7794
  protected function findTagName($parts)
7795
  {
@@ -7806,7 +10045,7 @@ class Compiler
7806
  protected function libSimpleSelectors($args)
7807
  {
7808
  $selector = reset($args);
7809
- $selector = $this->getSelectorArg($selector);
7810
 
7811
  // remove selectors list layer, keeping the first one
7812
  $selector = reset($selector);
@@ -7826,7 +10065,11 @@ class Compiler
7826
  protected static $libScssphpGlob = ['pattern'];
7827
  protected function libScssphpGlob($args)
7828
  {
7829
- $string = $this->coerceString($args[0]);
 
 
 
 
7830
  $pattern = $this->compileStringContent($string);
7831
  $matches = glob($pattern);
7832
  $listParts = [];
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
13
  namespace ScssPhp\ScssPhp;
14
 
15
  use ScssPhp\ScssPhp\Base\Range;
16
+ use ScssPhp\ScssPhp\Compiler\CachedResult;
 
 
17
  use ScssPhp\ScssPhp\Compiler\Environment;
18
  use ScssPhp\ScssPhp\Exception\CompilerException;
19
+ use ScssPhp\ScssPhp\Exception\ParserException;
20
+ use ScssPhp\ScssPhp\Exception\SassException;
21
+ use ScssPhp\ScssPhp\Exception\SassScriptException;
22
+ use ScssPhp\ScssPhp\Formatter\Compressed;
23
+ use ScssPhp\ScssPhp\Formatter\Expanded;
24
  use ScssPhp\ScssPhp\Formatter\OutputBlock;
25
+ use ScssPhp\ScssPhp\Logger\LoggerInterface;
26
+ use ScssPhp\ScssPhp\Logger\StreamLogger;
27
+ use ScssPhp\ScssPhp\Node\Number;
28
  use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
29
+ use ScssPhp\ScssPhp\Util\Path;
 
 
30
 
31
  /**
32
  * The scss compiler and parser.
59
  * SCSS compiler
60
  *
61
  * @author Leaf Corcoran <leafot@gmail.com>
62
+ *
63
+ * @final Extending the Compiler is deprecated
64
  */
65
  class Compiler
66
  {
67
+ /**
68
+ * @deprecated
69
+ */
70
  const LINE_COMMENTS = 1;
71
+ /**
72
+ * @deprecated
73
+ */
74
  const DEBUG_INFO = 2;
75
 
76
+ /**
77
+ * @deprecated
78
+ */
79
  const WITH_RULE = 1;
80
+ /**
81
+ * @deprecated
82
+ */
83
  const WITH_MEDIA = 2;
84
+ /**
85
+ * @deprecated
86
+ */
87
  const WITH_SUPPORTS = 4;
88
+ /**
89
+ * @deprecated
90
+ */
91
  const WITH_ALL = 7;
92
 
93
  const SOURCE_MAP_NONE = 0;
95
  const SOURCE_MAP_FILE = 2;
96
 
97
  /**
98
+ * @var array<string, string>
99
  */
100
  protected static $operatorNames = [
101
  '+' => 'add',
111
 
112
  '<=' => 'lte',
113
  '>=' => 'gte',
 
114
  ];
115
 
116
  /**
117
+ * @var array<string, string>
118
  */
119
  protected static $namespaces = [
120
  'special' => '%',
124
 
125
  public static $true = [Type::T_KEYWORD, 'true'];
126
  public static $false = [Type::T_KEYWORD, 'false'];
127
+ /** @deprecated */
128
  public static $NaN = [Type::T_KEYWORD, 'NaN'];
129
+ /** @deprecated */
130
  public static $Infinity = [Type::T_KEYWORD, 'Infinity'];
131
  public static $null = [Type::T_NULL];
132
  public static $nullString = [Type::T_STRING, '', []];
138
  public static $with = [Type::T_KEYWORD, 'with'];
139
  public static $without = [Type::T_KEYWORD, 'without'];
140
 
141
+ /**
142
+ * @var array<int, string|callable>
143
+ */
144
+ protected $importPaths = [];
145
+ /**
146
+ * @var array<string, Block>
147
+ */
148
  protected $importCache = [];
149
+
150
+ /**
151
+ * @var string[]
152
+ */
153
  protected $importedFiles = [];
154
+
155
+ /**
156
+ * @var array
157
+ * @phpstan-var array<string, array{0: callable, 1: array|null}>
158
+ */
159
  protected $userFunctions = [];
160
+ /**
161
+ * @var array<string, mixed>
162
+ */
163
  protected $registeredVars = [];
164
+ /**
165
+ * @var array<string, bool>
166
+ */
167
  protected $registeredFeatures = [
168
  'extend-selector-pseudoclass' => false,
169
  'at-error' => true,
170
+ 'units-level-3' => true,
171
  'global-variable-shadowing' => false,
172
  ];
173
 
174
+ /**
175
+ * @var string|null
176
+ */
177
  protected $encoding = null;
178
+ /**
179
+ * @var null
180
+ * @deprecated
181
+ */
182
  protected $lineNumberStyle = null;
183
 
184
+ /**
185
+ * @var int|SourceMapGenerator
186
+ * @phpstan-var self::SOURCE_MAP_*|SourceMapGenerator
187
+ */
188
  protected $sourceMap = self::SOURCE_MAP_NONE;
189
+
190
+ /**
191
+ * @var array
192
+ * @phpstan-var array{sourceRoot?: string, sourceMapFilename?: string|null, sourceMapURL?: string|null, sourceMapWriteTo?: string|null, outputSourceFiles?: bool, sourceMapRootpath?: string, sourceMapBasepath?: string}
193
+ */
194
  protected $sourceMapOptions = [];
195
 
196
+ /**
197
+ * @var bool
198
+ */
199
+ private $charset = true;
200
+
201
  /**
202
  * @var string|\ScssPhp\ScssPhp\Formatter
203
  */
204
+ protected $formatter = Expanded::class;
205
 
206
+ /**
207
+ * @var Environment
208
+ */
209
  protected $rootEnv;
210
+ /**
211
+ * @var OutputBlock|null
212
+ */
213
  protected $rootBlock;
214
 
215
  /**
216
  * @var \ScssPhp\ScssPhp\Compiler\Environment
217
  */
218
  protected $env;
219
+ /**
220
+ * @var OutputBlock|null
221
+ */
222
  protected $scope;
223
+ /**
224
+ * @var Environment|null
225
+ */
226
  protected $storeEnv;
227
+ /**
228
+ * @var bool|null
229
+ *
230
+ * @deprecated
231
+ */
232
  protected $charsetSeen;
233
+ /**
234
+ * @var array<int, string|null>
235
+ */
236
  protected $sourceNames;
237
 
238
+ /**
239
+ * @var Cache|null
240
+ */
241
  protected $cache;
242
 
243
+ /**
244
+ * @var bool
245
+ */
246
+ protected $cacheCheckImportResolutions = false;
247
+
248
+ /**
249
+ * @var int
250
+ */
251
  protected $indentLevel;
252
+ /**
253
+ * @var array[]
254
+ */
255
  protected $extends;
256
+ /**
257
+ * @var array<string, int[]>
258
+ */
259
  protected $extendsMap;
260
+
261
+ /**
262
+ * @var array<string, int>
263
+ */
264
+ protected $parsedFiles = [];
265
+
266
+ /**
267
+ * @var Parser|null
268
+ */
269
  protected $parser;
270
+ /**
271
+ * @var int|null
272
+ */
273
  protected $sourceIndex;
274
+ /**
275
+ * @var int|null
276
+ */
277
  protected $sourceLine;
278
+ /**
279
+ * @var int|null
280
+ */
281
  protected $sourceColumn;
282
+ /**
283
+ * @var bool|null
284
+ */
285
  protected $shouldEvaluate;
286
+ /**
287
+ * @var null
288
+ * @deprecated
289
+ */
290
  protected $ignoreErrors;
291
+ /**
292
+ * @var bool
293
+ */
294
  protected $ignoreCallStackMessage = false;
295
 
296
+ /**
297
+ * @var array[]
298
+ */
299
  protected $callStack = [];
300
 
301
+ /**
302
+ * @var array
303
+ * @phpstan-var list<array{currentDir: string|null, path: string, filePath: string}>
304
+ */
305
+ private $resolvedImports = [];
306
+
307
+ /**
308
+ * The directory of the currently processed file
309
+ *
310
+ * @var string|null
311
+ */
312
+ private $currentDirectory;
313
+
314
+ /**
315
+ * The directory of the input file
316
+ *
317
+ * @var string
318
+ */
319
+ private $rootDirectory;
320
+
321
+ /**
322
+ * @var bool
323
+ */
324
+ private $legacyCwdImportPath = true;
325
+
326
+ /**
327
+ * @var LoggerInterface
328
+ */
329
+ private $logger;
330
+
331
+ /**
332
+ * @var array<string, bool>
333
+ */
334
+ private $warnedChildFunctions = [];
335
+
336
  /**
337
  * Constructor
338
  *
339
  * @param array|null $cacheOptions
340
+ * @phpstan-param array{cacheDir?: string, prefix?: string, forceRefresh?: string, checkImportResolutions?: bool}|null $cacheOptions
341
  */
342
  public function __construct($cacheOptions = null)
343
  {
 
344
  $this->sourceNames = [];
345
 
346
  if ($cacheOptions) {
347
  $this->cache = new Cache($cacheOptions);
348
+ if (!empty($cacheOptions['checkImportResolutions'])) {
349
+ $this->cacheCheckImportResolutions = true;
350
+ }
351
  }
352
 
353
+ $this->logger = new StreamLogger(fopen('php://stderr', 'w'), true);
354
  }
355
 
356
  /**
357
  * Get compiler options
358
  *
359
+ * @return array<string, mixed>
360
+ *
361
+ * @internal
362
  */
363
  public function getCompileOptions()
364
  {
370
  'sourceMap' => serialize($this->sourceMap),
371
  'sourceMapOptions' => $this->sourceMapOptions,
372
  'formatter' => $this->formatter,
373
+ 'legacyImportPath' => $this->legacyCwdImportPath,
374
  ];
375
 
376
  return $options;
377
  }
378
 
379
+ /**
380
+ * Sets an alternative logger.
381
+ *
382
+ * Changing the logger in the middle of the compilation is not
383
+ * supported and will result in an undefined behavior.
384
+ *
385
+ * @param LoggerInterface $logger
386
+ *
387
+ * @return void
388
+ */
389
+ public function setLogger(LoggerInterface $logger)
390
+ {
391
+ $this->logger = $logger;
392
+ }
393
+
394
  /**
395
  * Set an alternative error output stream, for testing purpose only
396
  *
397
  * @param resource $handle
398
+ *
399
+ * @return void
400
+ *
401
+ * @deprecated Use {@see setLogger} instead
402
  */
403
  public function setErrorOuput($handle)
404
  {
405
+ @trigger_error('The method "setErrorOuput" is deprecated. Use "setLogger" instead.', E_USER_DEPRECATED);
406
+
407
+ $this->logger = new StreamLogger($handle);
408
  }
409
 
410
  /**
411
  * Compile scss
412
  *
413
+ * @param string $code
414
+ * @param string|null $path
 
 
415
  *
416
  * @return string
417
+ *
418
+ * @throws SassException when the source fails to compile
419
+ *
420
+ * @deprecated Use {@see compileString} instead.
421
  */
422
  public function compile($code, $path = null)
423
  {
424
+ @trigger_error(sprintf('The "%s" method is deprecated. Use "compileString" instead.', __METHOD__), E_USER_DEPRECATED);
 
 
 
425
 
426
+ $result = $this->compileString($code, $path);
 
 
 
 
 
 
 
427
 
428
+ $sourceMap = $result->getSourceMap();
429
+
430
+ if ($sourceMap !== null) {
431
+ if ($this->sourceMap instanceof SourceMapGenerator) {
432
+ $this->sourceMap->saveMap($sourceMap);
433
+ } elseif ($this->sourceMap === self::SOURCE_MAP_FILE) {
434
+ $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
435
+ $sourceMapGenerator->saveMap($sourceMap);
436
  }
437
  }
438
 
439
+ return $result->getCss();
440
+ }
441
+
442
+ /**
443
+ * Compile scss
444
+ *
445
+ * @param string $source
446
+ * @param string|null $path
447
+ *
448
+ * @return CompilationResult
449
+ *
450
+ * @throws SassException when the source fails to compile
451
+ */
452
+ public function compileString($source, $path = null)
453
+ {
454
+ if ($this->cache) {
455
+ $cacheKey = ($path ? $path : '(stdin)') . ':' . md5($source);
456
+ $compileOptions = $this->getCompileOptions();
457
+ $cachedResult = $this->cache->getCache('compile', $cacheKey, $compileOptions);
458
+
459
+ if ($cachedResult instanceof CachedResult && $this->isFreshCachedResult($cachedResult)) {
460
+ return $cachedResult->getResult();
461
+ }
462
+ }
463
 
464
  $this->indentLevel = -1;
465
  $this->extends = [];
470
  $this->env = null;
471
  $this->scope = null;
472
  $this->storeEnv = null;
 
473
  $this->shouldEvaluate = null;
474
  $this->ignoreCallStackMessage = false;
475
+ $this->parsedFiles = [];
476
+ $this->importedFiles = [];
477
+ $this->resolvedImports = [];
478
 
479
+ if (!\is_null($path) && is_file($path)) {
480
+ $path = realpath($path) ?: $path;
481
+ $this->currentDirectory = dirname($path);
482
+ $this->rootDirectory = $this->currentDirectory;
483
+ } else {
484
+ $this->currentDirectory = null;
485
+ $this->rootDirectory = getcwd();
486
+ }
487
 
488
+ try {
489
+ $this->parser = $this->parserFactory($path);
490
+ $tree = $this->parser->parse($source);
491
+ $this->parser = null;
492
 
493
+ $this->formatter = new $this->formatter();
494
+ $this->rootBlock = null;
495
+ $this->rootEnv = $this->pushEnv($tree);
496
 
497
+ $warnCallback = function ($message, $deprecation) {
498
+ $this->logger->warn($message, $deprecation);
499
+ };
500
+ $previousWarnCallback = Warn::setCallback($warnCallback);
501
 
502
+ try {
503
+ $this->injectVariables($this->registeredVars);
504
+ $this->compileRoot($tree);
505
+ $this->popEnv();
506
+ } finally {
507
+ Warn::setCallback($previousWarnCallback);
508
  }
 
509
 
510
+ $sourceMapGenerator = null;
511
+
512
+ if ($this->sourceMap) {
513
+ if (\is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) {
514
+ $sourceMapGenerator = $this->sourceMap;
515
+ $this->sourceMap = self::SOURCE_MAP_FILE;
516
+ } elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) {
517
+ $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
518
+ }
519
+ }
520
 
521
+ $out = $this->formatter->format($this->scope, $sourceMapGenerator);
 
 
522
 
523
+ $prefix = '';
 
 
 
524
 
525
+ if ($this->charset && strlen($out) !== Util::mbStrlen($out)) {
526
+ $prefix = '@charset "UTF-8";' . "\n";
527
+ $out = $prefix . $out;
528
+ }
529
+
530
+ $sourceMap = null;
531
+
532
+ if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
533
+ $sourceMap = $sourceMapGenerator->generateJson($prefix);
534
+ $sourceMapUrl = null;
535
+
536
+ switch ($this->sourceMap) {
537
+ case self::SOURCE_MAP_INLINE:
538
+ $sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap));
539
+ break;
540
+
541
+ case self::SOURCE_MAP_FILE:
542
+ if (isset($this->sourceMapOptions['sourceMapURL'])) {
543
+ $sourceMapUrl = $this->sourceMapOptions['sourceMapURL'];
544
+ }
545
+ break;
546
+ }
547
+
548
+ if ($sourceMapUrl !== null) {
549
+ $out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl);
550
+ }
551
  }
552
+ } catch (SassScriptException $e) {
553
+ throw new CompilerException($this->addLocationToMessage($e->getMessage()), 0, $e);
554
+ }
555
 
556
+ $includedFiles = [];
557
+
558
+ foreach ($this->resolvedImports as $resolvedImport) {
559
+ $includedFiles[$resolvedImport['filePath']] = $resolvedImport['filePath'];
560
  }
561
 
562
+ $result = new CompilationResult($out, $sourceMap, array_values($includedFiles));
563
+
564
  if ($this->cache && isset($cacheKey) && isset($compileOptions)) {
565
+ $this->cache->setCache('compile', $cacheKey, new CachedResult($result, $this->parsedFiles, $this->resolvedImports), $compileOptions);
566
+ }
 
 
567
 
568
+ // Reset state to free memory
569
+ // TODO in 2.0, reset parsedFiles as well when the getter is removed.
570
+ $this->resolvedImports = [];
571
+ $this->importedFiles = [];
572
+
573
+ return $result;
574
+ }
575
+
576
+ /**
577
+ * @param CachedResult $result
578
+ *
579
+ * @return bool
580
+ */
581
+ private function isFreshCachedResult(CachedResult $result)
582
+ {
583
+ // check if any dependency file changed since the result was compiled
584
+ foreach ($result->getParsedFiles() as $file => $mtime) {
585
+ if (! is_file($file) || filemtime($file) !== $mtime) {
586
+ return false;
587
+ }
588
  }
589
 
590
+ if ($this->cacheCheckImportResolutions) {
591
+ $resolvedImports = [];
592
+
593
+ foreach ($result->getResolvedImports() as $import) {
594
+ $currentDir = $import['currentDir'];
595
+ $path = $import['path'];
596
+ // store the check across all the results in memory to avoid multiple findImport() on the same path
597
+ // with same context.
598
+ // this is happening in a same hit with multiple compilations (especially with big frameworks)
599
+ if (empty($resolvedImports[$currentDir][$path])) {
600
+ $resolvedImports[$currentDir][$path] = $this->findImport($path, $currentDir);
601
+ }
602
+
603
+ if ($resolvedImports[$currentDir][$path] !== $import['filePath']) {
604
+ return false;
605
+ }
606
+ }
607
+ }
608
+
609
+ return true;
610
  }
611
 
612
  /**
613
  * Instantiate parser
614
  *
615
+ * @param string|null $path
616
  *
617
  * @return \ScssPhp\ScssPhp\Parser
618
  */
625
  // Otherwise, the CSS will be rendered as-is. It can even be extended!
626
  $cssOnly = false;
627
 
628
+ if ($path !== null && substr($path, -4) === '.css') {
629
  $cssOnly = true;
630
  }
631
 
632
+ $parser = new Parser($path, \count($this->sourceNames), $this->encoding, $this->cache, $cssOnly, $this->logger);
633
 
634
  $this->sourceNames[] = $path;
635
  $this->addParsedFile($path);
662
  * @param array $target
663
  * @param array $origin
664
  * @param array|null $block
665
+ *
666
+ * @return void
667
  */
668
  protected function pushExtends($target, $origin, $block)
669
  {
682
  /**
683
  * Make output block
684
  *
685
+ * @param string|null $type
686
+ * @param string[]|null $selectors
687
  *
688
  * @return \ScssPhp\ScssPhp\Formatter\OutputBlock
689
  */
690
  protected function makeOutputBlock($type, $selectors = null)
691
  {
692
+ $out = new OutputBlock();
693
  $out->type = $type;
694
  $out->lines = [];
695
  $out->children = [];
714
  * Compile root
715
  *
716
  * @param \ScssPhp\ScssPhp\Block $rootBlock
717
+ *
718
+ * @return void
719
  */
720
  protected function compileRoot(Block $rootBlock)
721
  {
728
 
729
  /**
730
  * Report missing selectors
731
+ *
732
+ * @return void
733
  */
734
  protected function missingSelectors()
735
  {
749
  $origin = $this->collapseSelectors($origin);
750
 
751
  $this->sourceLine = $block[Parser::SOURCE_LINE];
752
+ throw $this->error("\"$origin\" failed to @extend \"$target\". The selector \"$target\" was not found.");
753
  }
754
  }
755
 
758
  *
759
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
760
  * @param string $parentKey
761
+ *
762
+ * @return void
763
  */
764
  protected function flattenSelectors(OutputBlock $block, $parentKey = null)
765
  {
815
  }
816
 
817
  /**
818
+ * Glue parts of :not( or :nth-child( ... that are in general split in selectors parts
819
  *
820
  * @param array $parts
821
  *
832
  } else {
833
  // a selector part finishing with a ) is the last part of a :not( or :nth-child(
834
  // and need to be joined to this
835
+ if (
836
+ \count($new) && \is_string($new[\count($new) - 1]) &&
837
  \strlen($part) && substr($part, -1) === ')' && strpos($part, '(') === false
838
  ) {
839
+ while (\count($new) > 1 && substr($new[\count($new) - 1], -1) !== '(') {
840
  $part = array_pop($new) . $part;
841
  }
842
  $new[\count($new) - 1] .= $part;
856
  * @param array $out
857
  * @param integer $from
858
  * @param boolean $initial
859
+ *
860
+ * @return void
861
  */
862
  protected function matchExtends($selector, &$out, $from = 0, $initial = true)
863
  {
902
  }
903
  }
904
 
905
+ if (\count($nonBreakableBefore) && $k === \count($new)) {
906
  $k--;
907
  }
908
 
992
  */
993
  protected function isPseudoSelector($part, &$matches)
994
  {
995
+ if (
996
+ strpos($part, ':') === 0 &&
997
+ preg_match(",^::?([\w-]+)\((.+)\)$,", $part, $matches)
998
  ) {
999
  return true;
1000
  }
1011
  *
1012
  * @param array $out
1013
  * @param array $extended
1014
+ *
1015
+ * @return void
1016
  */
1017
  protected function pushOrMergeExtentedSelector(&$out, $extended)
1018
  {
1020
  $single = reset($extended);
1021
  $part = reset($single);
1022
 
1023
+ if (
1024
+ $this->isPseudoSelector($part, $matchesExtended) &&
1025
  \in_array($matchesExtended[1], [ 'slotted' ])
1026
  ) {
1027
  $prev = end($out);
1031
  $single = reset($prev);
1032
  $part = reset($single);
1033
 
1034
+ if (
1035
+ $this->isPseudoSelector($part, $matchesPrev) &&
1036
  $matchesPrev[1] === $matchesExtended[1]
1037
  ) {
1038
  $extended = explode($matchesExtended[1] . '(', $matchesExtended[0], 2);
1039
+ $extended[1] = $matchesPrev[2] . ', ' . $extended[1];
1040
  $extended = implode($matchesExtended[1] . '(', $extended);
1041
  $extended = [ [ $extended ]];
1042
  array_pop($out);
1096
  }
1097
  }
1098
 
1099
+ if (
1100
+ $initial &&
1101
  $this->isPseudoSelector($part, $matches) &&
1102
  ! \in_array($matches[1], [ 'not' ])
1103
  ) {
1104
  $buffer = $matches[2];
1105
  $parser = $this->parserFactory(__METHOD__);
1106
 
1107
+ if ($parser->parseSelector($buffer, $subSelectors, false)) {
1108
  foreach ($subSelectors as $ksub => $subSelector) {
1109
  $subExtended = [];
1110
  $this->matchExtends($subSelector, $subExtended, 0, false);
1119
 
1120
  $subSelectorsExtended = implode(', ', $subSelectorsExtended);
1121
  $singleExtended = $single;
1122
+ $singleExtended[$k] = str_replace('(' . $buffer . ')', "($subSelectorsExtended)", $part);
1123
  $outOrigin[] = [ $singleExtended ];
1124
  $found = true;
1125
  }
1144
 
1145
  foreach ($origin as $j => $new) {
1146
  // prevent infinite loop when target extends itself
1147
+ if ($this->isSelfExtend($single, $origin) && ! $initial) {
1148
  return false;
1149
  }
1150
 
1151
  $replacement = end($new);
1152
 
1153
  // Extending a decorated tag with another tag is not possible.
1154
+ if (
1155
+ $extendingDecoratedTag && $replacement[0] != $extendingDecoratedTag &&
1156
  preg_match('/^[a-z0-9]+$/i', $replacement[0])
1157
  ) {
1158
  unset($origin[$j]);
1223
  $wasTag = false;
1224
  $pseudo = [];
1225
 
1226
+ while (\count($other) && strpos(end($other), ':') === 0) {
1227
  array_unshift($pseudo, array_pop($other));
1228
  }
1229
 
1230
  foreach ([array_reverse($base), array_reverse($other)] as $single) {
1231
  $rang = count($single);
1232
+
1233
  foreach ($single as $part) {
1234
  if (preg_match('/^[\[:]/', $part)) {
1235
  $out[] = $part;
1237
  } elseif (preg_match('/^[\.#]/', $part)) {
1238
  array_unshift($out, $part);
1239
  $wasTag = false;
1240
+ } elseif (preg_match('/^[^_-]/', $part) && $rang === 1) {
1241
  $tag[] = $part;
1242
  $wasTag = true;
1243
  } elseif ($wasTag) {
1264
  * Compile media
1265
  *
1266
  * @param \ScssPhp\ScssPhp\Block $media
1267
+ *
1268
+ * @return void
1269
  */
1270
  protected function compileMedia(Block $media)
1271
  {
1273
 
1274
  $mediaQueries = $this->compileMediaQuery($this->multiplyMedia($this->env));
1275
 
1276
+ if (! empty($mediaQueries)) {
1277
  $previousScope = $this->scope;
1278
  $parentScope = $this->mediaParent($this->scope);
1279
 
1290
  foreach ($media->children as $child) {
1291
  $type = $child[0];
1292
 
1293
+ if (
1294
+ $type !== Type::T_BLOCK &&
1295
  $type !== Type::T_MEDIA &&
1296
  $type !== Type::T_DIRECTIVE &&
1297
  $type !== Type::T_IMPORT
1302
  }
1303
 
1304
  if ($needsWrap) {
1305
+ $wrapped = new Block();
1306
  $wrapped->sourceName = $media->sourceName;
1307
  $wrapped->sourceIndex = $media->sourceIndex;
1308
  $wrapped->sourceLine = $media->sourceLine;
1313
  $wrapped->children = $media->children;
1314
 
1315
  $media->children = [[Type::T_BLOCK, $wrapped]];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1316
  }
1317
 
1318
  $this->compileChildrenNoReturn($media->children, $this->scope);
1346
  /**
1347
  * Compile directive
1348
  *
1349
+ * @param \ScssPhp\ScssPhp\Block|array $directive
1350
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1351
+ *
1352
+ * @return void
1353
  */
1354
  protected function compileDirective($directive, OutputBlock $out)
1355
  {
1356
  if (\is_array($directive)) {
1357
+ $directiveName = $this->compileDirectiveName($directive[0]);
1358
+ $s = '@' . $directiveName;
1359
 
1360
  if (! empty($directive[1])) {
1361
  $s .= ' ' . $this->compileValue($directive[1]);
1362
  }
1363
+ // sass-spec compliance on newline after directives, a bit tricky :/
1364
+ $appendNewLine = (! empty($directive[2]) || strpos($s, "\n")) ? "\n" : "";
1365
+ if (\is_array($directive[0]) && empty($directive[1])) {
1366
+ $appendNewLine = "\n";
1367
+ }
1368
 
1369
+ if (empty($directive[3])) {
1370
+ $this->appendRootDirective($s . ';' . $appendNewLine, $out, [Type::T_COMMENT, Type::T_DIRECTIVE]);
1371
+ } else {
1372
+ $this->appendOutputLine($out, Type::T_DIRECTIVE, $s . ';');
1373
+ }
1374
  } else {
1375
+ $directive->name = $this->compileDirectiveName($directive->name);
1376
  $s = '@' . $directive->name;
1377
 
1378
  if (! empty($directive->value)) {
1387
  }
1388
  }
1389
 
1390
+ /**
1391
+ * directive names can include some interpolation
1392
+ *
1393
+ * @param string|array $directiveName
1394
+ * @return string
1395
+ * @throws CompilerException
1396
+ */
1397
+ protected function compileDirectiveName($directiveName)
1398
+ {
1399
+ if (is_string($directiveName)) {
1400
+ return $directiveName;
1401
+ }
1402
+
1403
+ return $this->compileValue($directiveName);
1404
+ }
1405
+
1406
  /**
1407
  * Compile at-root
1408
  *
1409
  * @param \ScssPhp\ScssPhp\Block $block
1410
+ *
1411
+ * @return void
1412
  */
1413
  protected function compileAtRoot(Block $block)
1414
  {
1418
 
1419
  // wrap inline selector
1420
  if ($block->selector) {
1421
+ $wrapped = new Block();
1422
  $wrapped->sourceName = $block->sourceName;
1423
  $wrapped->sourceIndex = $block->sourceIndex;
1424
  $wrapped->sourceLine = $block->sourceLine;
1434
  }
1435
 
1436
  $selfParent = $block->selfParent;
1437
+ assert($selfParent !== null, 'at-root blocks must have a selfParent set.');
1438
 
1439
+ if (
1440
+ ! $selfParent->selectors &&
1441
+ isset($block->parent) && $block->parent &&
1442
  isset($block->parent->selectors) && $block->parent->selectors
1443
  ) {
1444
  $selfParent = $block->parent;
1466
  * @param array $with
1467
  * @param array $without
1468
  *
1469
+ * @return OutputBlock
1470
  */
1471
  protected function filterScopeWithWithout($scope, $with, $without)
1472
  {
1473
  $filteredScopes = [];
1474
  $childStash = [];
1475
 
1476
+ if ($scope->type === Type::T_ROOT) {
1477
  return $scope;
1478
  }
1479
 
1480
  // start from the root
1481
+ while ($scope->parent && $scope->parent->type !== Type::T_ROOT) {
1482
  array_unshift($childStash, $scope);
1483
  $scope = $scope->parent;
1484
  }
1539
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1540
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $previousScope
1541
  *
1542
+ * @return OutputBlock
1543
  */
1544
  protected function completeScope($scope, $previousScope)
1545
  {
1595
  $without = ['rule' => true];
1596
 
1597
  if ($withCondition) {
1598
+ if ($withCondition[0] === Type::T_INTERPOLATE) {
1599
+ $w = $this->compileValue($withCondition);
1600
+
1601
+ $buffer = "($w)";
1602
+ $parser = $this->parserFactory(__METHOD__);
1603
+
1604
+ if ($parser->parseValue($buffer, $reParsedWith)) {
1605
+ $withCondition = $reParsedWith;
1606
+ }
1607
+ }
1608
+
1609
+ if ($this->mapHasKey($withCondition, static::$with)) {
1610
  $without = []; // cancel the default
1611
  $list = $this->coerceList($this->libMapGet([$withCondition, static::$with]));
1612
 
1617
  }
1618
  }
1619
 
1620
+ if ($this->mapHasKey($withCondition, static::$without)) {
1621
  $without = []; // cancel the default
1622
  $list = $this->coerceList($this->libMapGet([$withCondition, static::$without]));
1623
 
1635
  /**
1636
  * Filter env stack
1637
  *
1638
+ * @param Environment[] $envs
1639
  * @param array $with
1640
  * @param array $without
1641
  *
1642
+ * @return Environment
1643
+ *
1644
+ * @phpstan-param non-empty-array<Environment> $envs
1645
  */
1646
  protected function filterWithWithout($envs, $with, $without)
1647
  {
1680
 
1681
  if ($block->type === Type::T_DIRECTIVE) {
1682
  if (isset($block->name)) {
1683
+ return $this->testWithWithout($this->compileDirectiveName($block->name), $with, $without);
1684
  } elseif (isset($block->selectors) && preg_match(',@(\w+),ims', json_encode($block->selectors), $m)) {
1685
  return $this->testWithWithout($m[1], $with, $without);
1686
  } else {
1696
  $s = reset($s);
1697
  }
1698
 
1699
+ if (\is_object($s) && $s instanceof Number) {
1700
  return $this->testWithWithout('keyframes', $with, $without);
1701
  }
1702
  }
1719
  */
1720
  protected function testWithWithout($what, $with, $without)
1721
  {
 
1722
  // if without, reject only if in the list (or 'all' is in the list)
1723
  if (\count($without)) {
1724
  return (isset($without[$what]) || isset($without['all'])) ? false : true;
1733
  * Compile keyframe block
1734
  *
1735
  * @param \ScssPhp\ScssPhp\Block $block
1736
+ * @param string[] $selectors
1737
+ *
1738
+ * @return void
1739
  */
1740
  protected function compileKeyframeBlock(Block $block, $selectors)
1741
  {
1764
  *
1765
  * @param \ScssPhp\ScssPhp\Block $block
1766
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1767
+ *
1768
+ * @return void
1769
  */
1770
  protected function compileNestedPropertiesBlock(Block $block, OutputBlock $out)
1771
  {
1799
  * Compile nested block
1800
  *
1801
  * @param \ScssPhp\ScssPhp\Block $block
1802
+ * @param string[] $selectors
1803
+ *
1804
+ * @return void
1805
  */
1806
  protected function compileNestedBlock(Block $block, $selectors)
1807
  {
1812
 
1813
  // wrap assign children in a block
1814
  // except for @font-face
1815
+ if ($block->type !== Type::T_DIRECTIVE || $this->compileDirectiveName($block->name) !== 'font-face') {
1816
  // need wrapping?
1817
  $needWrapping = false;
1818
 
1824
  }
1825
 
1826
  if ($needWrapping) {
1827
+ $wrapped = new Block();
1828
  $wrapped->sourceName = $block->sourceName;
1829
  $wrapped->sourceIndex = $block->sourceIndex;
1830
  $wrapped->sourceLine = $block->sourceLine;
1863
  * @see Compiler::compileChild()
1864
  *
1865
  * @param \ScssPhp\ScssPhp\Block $block
1866
+ *
1867
+ * @return void
1868
  */
1869
  protected function compileBlock(Block $block)
1870
  {
1873
 
1874
  $out = $this->makeOutputBlock(null);
1875
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1876
  $this->scope->children[] = $out;
1877
 
1878
  if (\count($block->children)) {
1904
  * @param array $value
1905
  * @param boolean $pushEnv
1906
  *
1907
+ * @return string
1908
  */
1909
  protected function compileCommentValue($value, $pushEnv = false)
1910
  {
1915
  $this->pushEnv();
1916
  }
1917
 
 
 
 
1918
  try {
1919
  $c = $this->compileValue($value[2]);
1920
+ } catch (SassScriptException $e) {
1921
+ $this->logger->warn('Ignoring interpolation errors in multiline comments is deprecated and will be removed in ScssPhp 2.0. ' . $this->addLocationToMessage($e->getMessage()), true);
1922
+ // ignore error in comment compilation which are only interpolation
1923
+ } catch (SassException $e) {
1924
+ $this->logger->warn('Ignoring interpolation errors in multiline comments is deprecated and will be removed in ScssPhp 2.0. ' . $e->getMessage(), true);
1925
  // ignore error in comment compilation which are only interpolation
1926
  }
1927
 
 
 
1928
  if ($pushEnv) {
1929
  $this->popEnv();
1930
  }
1937
  * Compile root level comment
1938
  *
1939
  * @param array $block
1940
+ *
1941
+ * @return void
1942
  */
1943
  protected function compileComment($block)
1944
  {
1963
 
1964
  // after evaluating interpolates, we might need a second pass
1965
  if ($this->shouldEvaluate) {
1966
+ $selectors = $this->replaceSelfSelector($selectors, '&');
1967
  $buffer = $this->collapseSelectors($selectors);
1968
  $parser = $this->parserFactory(__METHOD__);
1969
 
1970
+ try {
1971
+ $isValid = $parser->parseSelector($buffer, $newSelectors, true);
1972
+ } catch (ParserException $e) {
1973
+ throw $this->error($e->getMessage());
1974
+ }
1975
+
1976
+ if ($isValid) {
1977
  $selectors = array_map([$this, 'evalSelector'], $newSelectors);
1978
  }
1979
  }
2006
  if (\is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) {
2007
  $p = $this->compileValue($p);
2008
 
2009
+ // force re-evaluation if self char or non standard char
2010
+ if (preg_match(',[^\w-],', $p)) {
2011
  $this->shouldEvaluate = true;
2012
  }
2013
+ } elseif (
2014
+ \is_string($p) && \strlen($p) >= 2 &&
2015
  ($first = $p[0]) && ($first === '"' || $first === "'") &&
2016
  substr($p, -1) === $first
2017
  ) {
2025
  /**
2026
  * Collapse selectors
2027
  *
2028
+ * @param array $selectors
 
 
 
2029
  *
2030
  * @return string
2031
  */
2032
+ protected function collapseSelectors($selectors)
2033
+ {
2034
+ $parts = [];
2035
+
2036
+ foreach ($selectors as $selector) {
2037
+ $output = [];
2038
+
2039
+ foreach ($selector as $node) {
2040
+ $compound = '';
2041
+
2042
+ array_walk_recursive(
2043
+ $node,
2044
+ function ($value, $key) use (&$compound) {
2045
+ $compound .= $value;
2046
+ }
2047
+ );
2048
+
2049
+ $output[] = $compound;
2050
+ }
2051
+
2052
+ $parts[] = implode(' ', $output);
2053
+ }
2054
+
2055
+ return implode(', ', $parts);
2056
+ }
2057
+
2058
+ /**
2059
+ * Collapse selectors
2060
+ *
2061
+ * @param array $selectors
2062
+ *
2063
+ * @return array
2064
+ */
2065
+ private function collapseSelectorsAsList($selectors)
2066
  {
2067
  $parts = [];
2068
 
2080
  }
2081
  );
2082
 
2083
+ if ($this->isImmediateRelationshipCombinator($compound)) {
2084
  if (\count($output)) {
2085
  $output[\count($output) - 1] .= ' ' . $compound;
2086
  } else {
2096
  }
2097
  }
2098
 
2099
+ foreach ($output as &$o) {
2100
+ $o = [Type::T_STRING, '', [$o]];
 
 
 
 
 
 
2101
  }
2102
 
2103
+ $parts[] = [Type::T_LIST, ' ', $output];
 
 
 
 
 
 
2104
  }
2105
 
2106
+ return [Type::T_LIST, ',', $parts];
2107
  }
2108
 
2109
  /**
2110
  * Parse down the selector and revert [self] to "&" before a reparsing
2111
  *
2112
+ * @param array $selectors
2113
+ * @param string|null $replace
2114
  *
2115
  * @return array
2116
  */
2117
+ protected function replaceSelfSelector($selectors, $replace = null)
2118
  {
2119
  foreach ($selectors as &$part) {
2120
  if (\is_array($part)) {
2121
  if ($part === [Type::T_SELF]) {
2122
+ if (\is_null($replace)) {
2123
+ $replace = $this->reduce([Type::T_SELF]);
2124
+ $replace = $this->compileValue($replace);
2125
+ }
2126
+ $part = $replace;
2127
  } else {
2128
+ $part = $this->replaceSelfSelector($part, $replace);
2129
  }
2130
  }
2131
  }
2145
  $joined = [];
2146
 
2147
  foreach ($single as $part) {
2148
+ if (
2149
+ empty($joined) ||
2150
  ! \is_string($part) ||
2151
  preg_match('/[\[.:#%]/', $part)
2152
  ) {
2238
  return false;
2239
  }
2240
 
2241
+ /**
2242
+ * @param string $name
2243
+ *
2244
+ * @return void
2245
+ */
2246
  protected function pushCallStack($name = '')
2247
  {
2248
  $this->callStack[] = [
2256
  if (\count($this->callStack) > 25000) {
2257
  // not displayed but you can var_dump it to deep debug
2258
  $msg = $this->callStackMessage(true, 100);
2259
+ $msg = 'Infinite calling loop';
2260
 
2261
+ throw $this->error($msg);
2262
  }
2263
  }
2264
 
2265
+ /**
2266
+ * @return void
2267
+ */
2268
  protected function popCallStack()
2269
  {
2270
  array_pop($this->callStack);
2277
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2278
  * @param string $traceName
2279
  *
2280
+ * @return array|Number|null
2281
  */
2282
  protected function compileChildren($stms, OutputBlock $out, $traceName = '')
2283
  {
2299
  }
2300
 
2301
  /**
2302
+ * Compile children and throw exception if unexpected `@return`
2303
  *
2304
  * @param array $stms
2305
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2306
  * @param \ScssPhp\ScssPhp\Block $selfParent
2307
  * @param string $traceName
2308
  *
2309
+ * @return void
2310
+ *
2311
  * @throws \Exception
2312
  */
2313
  protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null, $traceName = '')
2319
  $stm[1]->selfParent = $selfParent;
2320
  $ret = $this->compileChild($stm, $out);
2321
  $stm[1]->selfParent = null;
2322
+ } elseif ($selfParent && \in_array($stm[0], [Type::T_INCLUDE, Type::T_EXTEND])) {
2323
  $stm['selfParent'] = $selfParent;
2324
  $ret = $this->compileChild($stm, $out);
2325
  unset($stm['selfParent']);
2328
  }
2329
 
2330
  if (isset($ret)) {
2331
+ throw $this->error('@return may only be used within a function');
 
 
 
2332
  }
2333
  }
2334
 
2337
 
2338
 
2339
  /**
2340
+ * evaluate media query : compile internal value keeping the structure unchanged
2341
  *
2342
  * @param array $queryList
2343
  *
2358
 
2359
  // the parser had no mean to know if media type or expression if it was an interpolation
2360
  // so you need to reparse if the T_MEDIA_TYPE looks like anything else a media type
2361
+ if (
2362
+ $q[0] == Type::T_MEDIA_TYPE &&
2363
  (strpos($value, '(') !== false ||
2364
  strpos($value, ')') !== false ||
2365
  strpos($value, ':') !== false ||
2407
  *
2408
  * @param array $queryList
2409
  *
2410
+ * @return string[]
2411
  */
2412
  protected function compileMediaQuery($queryList)
2413
  {
2414
  $start = '@media ';
2415
  $default = trim($start);
2416
  $out = [];
2417
+ $current = '';
2418
 
2419
  foreach ($queryList as $query) {
2420
  $type = null;
2453
  $out[] = $start . $current;
2454
  }
2455
 
2456
+ $current = '';
2457
  $type = null;
2458
  $parts = [];
2459
  }
2628
  }
2629
 
2630
  // t1 == t2, neither m1 nor m2 are "not"
2631
+ return [empty($m1) ? $m2 : $m1, $t1];
2632
  }
2633
 
2634
  /**
2645
  if ($rawPath[0] === Type::T_STRING) {
2646
  $path = $this->compileStringContent($rawPath);
2647
 
2648
+ if (strpos($path, 'url(') !== 0 && $filePath = $this->findImport($path, $this->currentDirectory)) {
2649
+ $this->registerImport($this->currentDirectory, $path, $filePath);
2650
+
2651
+ if (! $once || ! \in_array($filePath, $this->importedFiles)) {
2652
+ $this->importFile($filePath, $out);
2653
+ $this->importedFiles[] = $filePath;
2654
  }
2655
 
2656
  return true;
2657
  }
2658
 
2659
+ $this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out);
2660
 
2661
  return false;
2662
  }
2669
 
2670
  foreach ($rawPath[2] as $path) {
2671
  if ($path[0] !== Type::T_STRING) {
2672
+ $this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out);
2673
 
2674
  return false;
2675
  }
2682
  return true;
2683
  }
2684
 
2685
+ $this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out);
2686
 
2687
  return false;
2688
  }
2689
 
2690
+ /**
2691
+ * @param array $rawPath
2692
+ * @return string
2693
+ * @throws CompilerException
2694
+ */
2695
+ protected function compileImportPath($rawPath)
2696
+ {
2697
+ $path = $this->compileValue($rawPath);
2698
+
2699
+ // case url() without quotes : suppress \r \n remaining in the path
2700
+ // if this is a real string there can not be CR or LF char
2701
+ if (strpos($path, 'url(') === 0) {
2702
+ $path = str_replace(array("\r", "\n"), array('', ' '), $path);
2703
+ } else {
2704
+ // if this is a file name in a string, spaces should be escaped
2705
+ $path = $this->reduce($rawPath);
2706
+ $path = $this->escapeImportPathString($path);
2707
+ $path = $this->compileValue($path);
2708
+ }
2709
+
2710
+ return $path;
2711
+ }
2712
+
2713
+ /**
2714
+ * @param array $path
2715
+ * @return array
2716
+ * @throws CompilerException
2717
+ */
2718
+ protected function escapeImportPathString($path)
2719
+ {
2720
+ switch ($path[0]) {
2721
+ case Type::T_LIST:
2722
+ foreach ($path[2] as $k => $v) {
2723
+ $path[2][$k] = $this->escapeImportPathString($v);
2724
+ }
2725
+ break;
2726
+ case Type::T_STRING:
2727
+ if ($path[1]) {
2728
+ $path = $this->compileValue($path);
2729
+ $path = str_replace(' ', '\\ ', $path);
2730
+ $path = [Type::T_KEYWORD, $path];
2731
+ }
2732
+ break;
2733
+ }
2734
+
2735
+ return $path;
2736
+ }
2737
 
2738
  /**
2739
  * Append a root directive like @import or @charset as near as the possible from the source code
2740
  * (keeping before comments, @import and @charset coming before in the source code)
2741
  *
2742
+ * @param string $line
2743
+ * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2744
+ * @param array $allowed
2745
+ *
2746
+ * @return void
2747
  */
2748
  protected function appendRootDirective($line, $out, $allowed = [Type::T_COMMENT])
2749
  {
2791
  *
2792
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2793
  * @param string $type
2794
+ * @param string $line
2795
+ *
2796
+ * @return void
2797
  */
2798
  protected function appendOutputLine(OutputBlock $out, $type, $line)
2799
  {
2800
  $outWrite = &$out;
2801
 
 
 
 
 
 
 
 
 
2802
  // check if it's a flat output or not
2803
  if (\count($out->children)) {
2804
  $lastChild = &$out->children[\count($out->children) - 1];
2805
 
2806
+ if (
2807
+ $lastChild->depth === $out->depth &&
2808
  \is_null($lastChild->selectors) &&
2809
  ! \count($lastChild->children)
2810
  ) {
2828
  * @param array $child
2829
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2830
  *
2831
+ * @return array|Number|null
2832
  */
2833
  protected function compileChild($child, OutputBlock $out)
2834
  {
2842
  $this->sourceColumn = $child[1]->sourceColumn;
2843
  } elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) {
2844
  $this->sourceLine = $out->sourceLine;
2845
+ $sourceIndex = array_search($out->sourceName, $this->sourceNames);
2846
  $this->sourceColumn = $out->sourceColumn;
2847
 
2848
+ if ($sourceIndex === false) {
2849
+ $sourceIndex = null;
2850
  }
2851
+ $this->sourceIndex = $sourceIndex;
2852
  }
2853
 
2854
  switch ($child[0]) {
2881
  break;
2882
 
2883
  case Type::T_CHARSET:
 
 
 
 
2884
  break;
2885
 
2886
  case Type::T_CUSTOM_PROPERTY:
2953
  break;
2954
  }
2955
 
2956
+ if ($compiledName === 'font' && $value[0] === Type::T_LIST && $value[1] === ',') {
2957
  // this is the case if more than one font is given: example: "font: 400 1em/1.3 arial,helvetica"
2958
  // we need to handle the first list element
2959
  $shorthandValue=&$value[2][0];
2969
  $divider = $this->reduce($divider, true);
2970
  }
2971
 
2972
+ if ($divider instanceof Number && \intval($divider->getDimension()) && $divider->unitless()) {
2973
  $revert = false;
2974
  }
2975
  }
2982
  if ($item[0] === Type::T_EXPRESSION && $item[1] === '/') {
2983
  if ($maxShorthandDividers > 0) {
2984
  $revert = true;
2985
+
2986
  // if the list of values is too long, this has to be a shorthand,
2987
  // otherwise it could be a real division
2988
+ if (\is_null($maxListElements) || \count($shorthandValue[2]) <= $maxListElements) {
2989
  if ($shorthandDividerNeedsUnit) {
2990
  $divider = $item[3];
2991
 
2993
  $divider = $this->reduce($divider, true);
2994
  }
2995
 
2996
+ if ($divider instanceof Number && \intval($divider->getDimension()) && $divider->unitless()) {
2997
  $revert = false;
2998
  }
2999
  }
3051
 
3052
  case Type::T_EXTEND:
3053
  foreach ($child[1] as $sel) {
3054
+ $replacedSel = $this->replaceSelfSelector($sel);
3055
+
3056
+ if ($replacedSel !== $sel) {
3057
+ throw $this->error('Parent selectors aren\'t allowed here.');
3058
+ }
3059
+
3060
  $results = $this->evalSelectors([$sel]);
3061
 
3062
  foreach ($results as $result) {
3063
+ if (\count($result) !== 1) {
3064
+ throw $this->error('complex selectors may not be extended.');
3065
+ }
3066
+
3067
  // only use the first one
3068
+ $result = $result[0];
3069
  $selectors = $out->selectors;
3070
 
3071
  if (! $selectors && isset($child['selfParent'])) {
3072
  $selectors = $this->multiplySelectors($this->env, $child['selfParent']);
3073
  }
3074
 
3075
+ if (\count($result) > 1) {
3076
+ $replacement = implode(', ', $result);
3077
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
3078
+ $line = $this->sourceLine;
3079
+
3080
+ $message = <<<EOL
3081
+ on line $line of $fname:
3082
+ Compound selectors may no longer be extended.
3083
+ Consider `@extend $replacement` instead.
3084
+ See http://bit.ly/ExtendCompound for details.
3085
+ EOL;
3086
+
3087
+ $this->logger->warn($message);
3088
+ }
3089
+
3090
  $this->pushExtends($result, $selectors, $child);
3091
  }
3092
  }
3100
  }
3101
 
3102
  foreach ($if->cases as $case) {
3103
+ if (
3104
+ $case->type === Type::T_ELSE ||
3105
  $case->type === Type::T_ELSEIF && $this->isTruthy($this->reduce($case->cond))
3106
  ) {
3107
  return $this->compileChildren($case->children, $out);
3130
  $ret = $this->compileChildren($each->children, $out);
3131
 
3132
  if ($ret) {
3133
+ $store = $this->env->store;
3134
+ $this->popEnv();
3135
+ $this->backPropagateEnv($store, $each->vars);
 
 
 
 
3136
 
3137
+ return $ret;
 
 
3138
  }
3139
  }
3140
  $store = $this->env->store;
3150
  $ret = $this->compileChildren($while->children, $out);
3151
 
3152
  if ($ret) {
3153
+ return $ret;
 
 
 
 
 
 
3154
  }
3155
  }
3156
  break;
3158
  case Type::T_FOR:
3159
  list(, $for) = $child;
3160
 
3161
+ $startNumber = $this->assertNumber($this->reduce($for->start, true));
3162
+ $endNumber = $this->assertNumber($this->reduce($for->end, true));
3163
 
3164
+ $start = $this->assertInteger($startNumber);
 
3165
 
3166
+ $numeratorUnits = $startNumber->getNumeratorUnits();
3167
+ $denominatorUnits = $startNumber->getDenominatorUnits();
3168
 
3169
+ $end = $this->assertInteger($endNumber->coerce($numeratorUnits, $denominatorUnits));
 
 
3170
 
3171
  $d = $start < $end ? 1 : -1;
3172
 
3173
  $this->pushEnv();
3174
 
3175
  for (;;) {
3176
+ if (
3177
+ (! $for->until && $start - $d == $end) ||
3178
  ($for->until && $start == $end)
3179
  ) {
3180
  break;
3181
  }
3182
 
3183
+ $this->set($for->var, new Number($start, $numeratorUnits, $denominatorUnits));
3184
  $start += $d;
3185
 
3186
  $ret = $this->compileChildren($for->children, $out);
3187
 
3188
  if ($ret) {
3189
+ $store = $this->env->store;
3190
+ $this->popEnv();
3191
+ $this->backPropagateEnv($store, [$for->var]);
 
 
 
3192
 
3193
+ return $ret;
 
 
3194
  }
3195
  }
3196
 
3200
 
3201
  break;
3202
 
 
 
 
 
 
 
3203
  case Type::T_RETURN:
3204
  return $this->reduce($child[1], true);
3205
 
3214
  $mixin = $this->get(static::$namespaces['mixin'] . $name, false);
3215
 
3216
  if (! $mixin) {
3217
+ throw $this->error("Undefined mixin $name");
 
3218
  }
3219
 
3220
  $callingScope = $this->getStoreEnv();
3271
  if (! empty($mixin->parentEnv)) {
3272
  $this->env->declarationScopeParent = $mixin->parentEnv;
3273
  } else {
3274
+ throw $this->error("@mixin $name() without parentEnv");
3275
  }
3276
 
3277
+ $this->compileChildrenNoReturn($mixin->children, $out, $selfParent, $this->env->marker . ' ' . $name);
3278
 
3279
  $this->popEnv();
3280
  break;
3314
  case Type::T_DEBUG:
3315
  list(, $value) = $child;
3316
 
3317
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
3318
  $line = $this->sourceLine;
3319
+ $value = $this->compileDebugValue($value);
3320
 
3321
+ $this->logger->debug("$fname:$line DEBUG: $value");
3322
  break;
3323
 
3324
  case Type::T_WARN:
3325
  list(, $value) = $child;
3326
 
3327
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
3328
  $line = $this->sourceLine;
3329
+ $value = $this->compileDebugValue($value);
3330
 
3331
+ $this->logger->warn("$value\n on line $line of $fname");
3332
  break;
3333
 
3334
  case Type::T_ERROR:
3335
  list(, $value) = $child;
3336
 
3337
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
3338
  $line = $this->sourceLine;
3339
  $value = $this->compileValue($this->reduce($value, true));
3340
 
3341
+ throw $this->error("File $fname on line $line ERROR: $value\n");
 
 
 
 
 
3342
 
3343
  default:
3344
+ throw $this->error("unknown child type: $child[0]");
3345
  }
3346
  }
3347
 
3349
  * Reduce expression to string
3350
  *
3351
  * @param array $exp
3352
+ * @param bool $keepParens
3353
  *
3354
  * @return array
3355
  */
3356
+ protected function expToString($exp, $keepParens = false)
3357
  {
3358
+ list(, $op, $left, $right, $inParens, $whiteLeft, $whiteRight) = $exp;
3359
+
3360
+ $content = [];
3361
 
3362
+ if ($keepParens && $inParens) {
3363
+ $content[] = '(';
3364
+ }
3365
+
3366
+ $content[] = $this->reduce($left);
3367
 
3368
  if ($whiteLeft) {
3369
  $content[] = ' ';
3377
 
3378
  $content[] = $this->reduce($right);
3379
 
3380
+ if ($keepParens && $inParens) {
3381
+ $content[] = ')';
3382
+ }
3383
+
3384
  return [Type::T_STRING, '', $content];
3385
  }
3386
 
3387
  /**
3388
  * Is truthy?
3389
  *
3390
+ * @param array|Number $value
3391
  *
3392
  * @return boolean
3393
  */
3394
+ public function isTruthy($value)
3395
  {
3396
  return $value !== static::$false && $value !== static::$null;
3397
  }
3435
  /**
3436
  * Reduce value
3437
  *
3438
+ * @param array|Number $value
3439
  * @param boolean $inExp
3440
  *
3441
+ * @return array|Number
3442
  */
3443
  protected function reduce($value, $inExp = false)
3444
  {
3445
+ if ($value instanceof Number) {
3446
+ return $value;
3447
  }
3448
 
3449
  switch ($value[0]) {
3460
  }
3461
 
3462
  // special case: looks like css shorthand
3463
+ if (
3464
+ $opName == 'div' && ! $inParens && ! $inExp &&
3465
+ (($right[0] !== Type::T_NUMBER && isset($right[2]) && $right[2] != '') ||
3466
  ($right[0] === Type::T_NUMBER && ! $right->unitless()))
3467
  ) {
3468
  return $this->expToString($value);
3483
  // 3. op[op name]
3484
  $fn = "op${ucOpName}${ucLType}${ucRType}";
3485
 
3486
+ if (
3487
+ \is_callable([$this, $fn]) ||
3488
  (($fn = "op${ucLType}${ucRType}") &&
3489
  \is_callable([$this, $fn]) &&
3490
  $passOp = true) ||
3492
  \is_callable([$this, $fn]) &&
3493
  $genOp = true)
3494
  ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3495
  $shouldEval = $inParens || $inExp;
3496
 
3497
  if (isset($passOp)) {
3501
  }
3502
 
3503
  if (isset($out)) {
 
 
 
 
3504
  return $out;
3505
  }
3506
  }
3513
  $inExp = $inExp || $this->shouldEval($exp);
3514
  $exp = $this->reduce($exp);
3515
 
3516
+ if ($exp instanceof Number) {
3517
  switch ($op) {
3518
  case '+':
3519
+ return $exp;
3520
 
3521
  case '-':
3522
+ return $exp->unaryMinus();
3523
  }
3524
  }
3525
 
3544
  foreach ($value[2] as &$item) {
3545
  $item = $this->reduce($item);
3546
  }
3547
+ unset($item);
3548
+
3549
+ if (isset($value[3]) && \is_array($value[3])) {
3550
+ foreach ($value[3] as &$item) {
3551
+ $item = $this->reduce($item);
3552
+ }
3553
+ unset($item);
3554
+ }
3555
 
3556
  return $value;
3557
 
3568
 
3569
  case Type::T_STRING:
3570
  foreach ($value[2] as &$item) {
3571
+ if (\is_array($item) || $item instanceof Number) {
3572
  $item = $this->reduce($item);
3573
  }
3574
  }
3579
  $value[1] = $this->reduce($value[1]);
3580
 
3581
  if ($inExp) {
3582
+ return [Type::T_KEYWORD, $this->compileValue($value, false)];
3583
  }
3584
 
3585
  return $value;
3588
  return $this->fncall($value[1], $value[2]);
3589
 
3590
  case Type::T_SELF:
3591
+ $selfParent = ! empty($this->env->block->selfParent) ? $this->env->block->selfParent : null;
3592
+ $selfSelector = $this->multiplySelectors($this->env, $selfParent);
3593
+ $selfSelector = $this->collapseSelectorsAsList($selfSelector);
3594
 
3595
  return $selfSelector;
3596
 
3602
  /**
3603
  * Function caller
3604
  *
3605
+ * @param string|array $functionReference
3606
+ * @param array $argValues
3607
  *
3608
+ * @return array|Number
3609
  */
3610
+ protected function fncall($functionReference, $argValues)
3611
  {
3612
+ // a string means this is a static hard reference coming from the parsing
3613
+ if (is_string($functionReference)) {
3614
+ $name = $functionReference;
 
3615
 
3616
+ $functionReference = $this->getFunctionReference($name);
3617
+ if ($functionReference === static::$null || $functionReference[0] !== Type::T_FUNCTION_REFERENCE) {
3618
+ $functionReference = [Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]];
3619
+ }
3620
  }
3621
 
3622
+ // a function type means we just want a plain css function call
3623
+ if ($functionReference[0] === Type::T_FUNCTION) {
3624
+ // for CSS functions, simply flatten the arguments into a list
3625
+ $listArgs = [];
3626
 
3627
+ foreach ((array) $argValues as $arg) {
3628
+ if (empty($arg[0]) || count($argValues) === 1) {
3629
+ $listArgs[] = $this->reduce($this->stringifyFncallArgs($arg[1]));
3630
+ }
3631
  }
3632
+
3633
+ return [Type::T_FUNCTION, $functionReference[1], [Type::T_LIST, ',', $listArgs]];
3634
  }
3635
 
3636
+ if ($functionReference === static::$null || $functionReference[0] !== Type::T_FUNCTION_REFERENCE) {
3637
+ return static::$defaultValue;
3638
+ }
3639
 
3640
+
3641
+ switch ($functionReference[1]) {
3642
+ // SCSS @function
3643
+ case 'scss':
3644
+ return $this->callScssFunction($functionReference[3], $argValues);
3645
+
3646
+ // native PHP functions
3647
+ case 'user':
3648
+ case 'native':
3649
+ list(,,$name, $fn, $prototype) = $functionReference;
3650
+
3651
+ // special cases of css valid functions min/max
3652
+ $name = strtolower($name);
3653
+ if (\in_array($name, ['min', 'max']) && count($argValues) >= 1) {
3654
+ $cssFunction = $this->cssValidArg(
3655
+ [Type::T_FUNCTION_CALL, $name, $argValues],
3656
+ ['min', 'max', 'calc', 'env', 'var']
3657
+ );
3658
+ if ($cssFunction !== false) {
3659
+ return $cssFunction;
3660
+ }
3661
+ }
3662
+ $returnValue = $this->callNativeFunction($name, $fn, $prototype, $argValues);
3663
+
3664
+ if (! isset($returnValue)) {
3665
+ return $this->fncall([Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]], $argValues);
3666
+ }
3667
+
3668
+ return $returnValue;
3669
+
3670
+ default:
3671
+ return static::$defaultValue;
3672
+ }
3673
  }
3674
 
3675
  /**
3676
+ * @param array|Number $arg
3677
+ * @param string[] $allowed_function
3678
+ * @param bool $inFunction
3679
  *
3680
+ * @return array|Number|false
 
 
3681
  */
3682
+ protected function cssValidArg($arg, $allowed_function = [], $inFunction = false)
3683
  {
3684
+ if ($arg instanceof Number) {
3685
+ return $this->stringifyFncallArgs($arg);
3686
+ }
3687
 
3688
+ switch ($arg[0]) {
3689
+ case Type::T_INTERPOLATE:
3690
+ return [Type::T_KEYWORD, $this->CompileValue($arg)];
3691
 
3692
+ case Type::T_FUNCTION:
3693
+ if (! \in_array($arg[1], $allowed_function)) {
3694
+ return false;
3695
  }
3696
+ if ($arg[2][0] === Type::T_LIST) {
3697
+ foreach ($arg[2][2] as $k => $subarg) {
3698
+ $arg[2][2][$k] = $this->cssValidArg($subarg, $allowed_function, $arg[1]);
3699
+ if ($arg[2][2][$k] === false) {
3700
+ return false;
3701
+ }
3702
+ }
3703
  }
3704
+ return $arg;
3705
 
3706
+ case Type::T_FUNCTION_CALL:
3707
+ if (! \in_array($arg[1], $allowed_function)) {
3708
+ return false;
3709
+ }
3710
+ $cssArgs = [];
3711
+ foreach ($arg[2] as $argValue) {
3712
+ if ($argValue === static::$null) {
3713
+ return false;
3714
+ }
3715
+ $cssArg = $this->cssValidArg($argValue[1], $allowed_function, $arg[1]);
3716
+ if (empty($argValue[0]) && $cssArg !== false) {
3717
+ $cssArgs[] = [$argValue[0], $cssArg];
3718
+ } else {
3719
+ return false;
3720
+ }
3721
  }
3722
 
3723
+ return $this->fncall([Type::T_FUNCTION, $arg[1], [Type::T_LIST, ',', []]], $cssArgs);
3724
 
3725
  case Type::T_STRING:
3726
+ case Type::T_KEYWORD:
3727
+ if (!$inFunction or !\in_array($inFunction, ['calc', 'env', 'var'])) {
3728
+ return false;
3729
+ }
3730
+ return $this->stringifyFncallArgs($arg);
3731
 
3732
+ case Type::T_LIST:
3733
+ if (!$inFunction) {
3734
+ return false;
3735
+ }
3736
+ if (empty($arg['enclosing']) and $arg[1] === '') {
3737
+ foreach ($arg[2] as $k => $subarg) {
3738
+ $arg[2][$k] = $this->cssValidArg($subarg, $allowed_function, $inFunction);
3739
+ if ($arg[2][$k] === false) {
3740
+ return false;
3741
+ }
3742
+ }
3743
+ $arg[0] = Type::T_STRING;
3744
+ return $arg;
3745
+ }
3746
+ return false;
3747
 
3748
+ case Type::T_EXPRESSION:
3749
+ if (! \in_array($arg[1], ['+', '-', '/', '*'])) {
3750
+ return false;
3751
+ }
3752
+ $arg[2] = $this->cssValidArg($arg[2], $allowed_function, $inFunction);
3753
+ $arg[3] = $this->cssValidArg($arg[3], $allowed_function, $inFunction);
3754
+ if ($arg[2] === false || $arg[3] === false) {
3755
+ return false;
3756
+ }
3757
+ return $this->expToString($arg, true);
3758
 
3759
+ case Type::T_VARIABLE:
3760
+ case Type::T_SELF:
3761
  default:
3762
+ return false;
3763
  }
3764
  }
3765
 
 
 
 
 
 
 
 
 
 
 
 
 
3766
 
3767
  /**
3768
+ * Reformat fncall arguments to proper css function output
3769
  *
3770
+ * @param array|Number $arg
 
3771
  *
3772
+ * @return array|Number
3773
  */
3774
+ protected function stringifyFncallArgs($arg)
3775
  {
3776
+ if ($arg instanceof Number) {
3777
+ return $arg;
3778
+ }
3779
+
3780
+ switch ($arg[0]) {
3781
+ case Type::T_LIST:
3782
+ foreach ($arg[2] as $k => $v) {
3783
+ $arg[2][$k] = $this->stringifyFncallArgs($v);
3784
+ }
3785
+ break;
3786
+
3787
+ case Type::T_EXPRESSION:
3788
+ if ($arg[1] === '/') {
3789
+ $arg[2] = $this->stringifyFncallArgs($arg[2]);
3790
+ $arg[3] = $this->stringifyFncallArgs($arg[3]);
3791
+ $arg[5] = $arg[6] = false; // no space around /
3792
+ $arg = $this->expToString($arg);
3793
+ }
3794
+ break;
3795
+
3796
+ case Type::T_FUNCTION_CALL:
3797
+ $name = strtolower($arg[1]);
3798
+
3799
+ if (in_array($name, ['max', 'min', 'calc'])) {
3800
+ $args = $arg[2];
3801
+ $arg = $this->fncall([Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]], $args);
3802
+ }
3803
+ break;
3804
+ }
3805
+
3806
+ return $arg;
3807
  }
3808
 
3809
  /**
3810
+ * Find a function reference
3811
+ * @param string $name
3812
+ * @param bool $safeCopy
3813
+ * @return array
3814
+ */
3815
+ protected function getFunctionReference($name, $safeCopy = false)
3816
+ {
3817
+ // SCSS @function
3818
+ if ($func = $this->get(static::$namespaces['function'] . $name, false)) {
3819
+ if ($safeCopy) {
3820
+ $func = clone $func;
3821
+ }
3822
+
3823
+ return [Type::T_FUNCTION_REFERENCE, 'scss', $name, $func];
3824
+ }
3825
+
3826
+ // native PHP functions
3827
+
3828
+ // try to find a native lib function
3829
+ $normalizedName = $this->normalizeName($name);
3830
+
3831
+ if (isset($this->userFunctions[$normalizedName])) {
3832
+ // see if we can find a user function
3833
+ list($f, $prototype) = $this->userFunctions[$normalizedName];
3834
+
3835
+ return [Type::T_FUNCTION_REFERENCE, 'user', $name, $f, $prototype];
3836
+ }
3837
+
3838
+ $lowercasedName = strtolower($normalizedName);
3839
+
3840
+ // Special functions overriding a CSS function are case-insensitive. We normalize them as lowercase
3841
+ // to avoid the deprecation warning about the wrong case being used.
3842
+ if ($lowercasedName === 'min' || $lowercasedName === 'max') {
3843
+ $normalizedName = $lowercasedName;
3844
+ }
3845
+
3846
+ if (($f = $this->getBuiltinFunction($normalizedName)) && \is_callable($f)) {
3847
+ $libName = $f[1];
3848
+ $prototype = isset(static::$$libName) ? static::$$libName : null;
3849
+
3850
+ // All core functions have a prototype defined. Not finding the
3851
+ // prototype can mean 2 things:
3852
+ // - the function comes from a child class (deprecated just after)
3853
+ // - the function was found with a different case, which relates to calling the
3854
+ // wrong Sass function due to our camelCase usage (`fade-in()` vs `fadein()`),
3855
+ // because PHP method names are case-insensitive while property names are
3856
+ // case-sensitive.
3857
+ if ($prototype === null || strtolower($normalizedName) !== $normalizedName) {
3858
+ $r = new \ReflectionMethod($this, $libName);
3859
+ $actualLibName = $r->name;
3860
+
3861
+ if ($actualLibName !== $libName || strtolower($normalizedName) !== $normalizedName) {
3862
+ $kebabCaseName = preg_replace('~(?<=\\w)([A-Z])~', '-$1', substr($actualLibName, 3));
3863
+ assert($kebabCaseName !== null);
3864
+ $originalName = strtolower($kebabCaseName);
3865
+ $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\".";
3866
+ @trigger_error($warning, E_USER_DEPRECATED);
3867
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
3868
+ $line = $this->sourceLine;
3869
+ Warn::deprecation("$warning\n on line $line of $fname");
3870
+
3871
+ // Use the actual function definition
3872
+ $prototype = isset(static::$$actualLibName) ? static::$$actualLibName : null;
3873
+ $f[1] = $libName = $actualLibName;
3874
+ }
3875
+ }
3876
+
3877
+ if (\get_class($this) !== __CLASS__ && !isset($this->warnedChildFunctions[$libName])) {
3878
+ $r = new \ReflectionMethod($this, $libName);
3879
+ $declaringClass = $r->getDeclaringClass()->name;
3880
+
3881
+ $needsWarning = $this->warnedChildFunctions[$libName] = $declaringClass !== __CLASS__;
3882
+
3883
+ if ($needsWarning) {
3884
+ if (method_exists(__CLASS__, $libName)) {
3885
+ @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);
3886
+ } else {
3887
+ @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);
3888
+ }
3889
+ }
3890
+ }
3891
+
3892
+ return [Type::T_FUNCTION_REFERENCE, 'native', $name, $f, $prototype];
3893
+ }
3894
+
3895
+ return static::$null;
3896
+ }
3897
+
3898
+
3899
+ /**
3900
+ * Normalize name
3901
  *
3902
+ * @param string $name
 
3903
  *
3904
+ * @return string
3905
  */
3906
+ protected function normalizeName($name)
3907
  {
3908
+ return str_replace('-', '_', $name);
3909
  }
3910
 
3911
  /**
3912
+ * Normalize value
3913
  *
3914
+ * @internal
3915
+ *
3916
+ * @param array|Number $value
3917
  *
3918
+ * @return array|Number
3919
  */
3920
+ public function normalizeValue($value)
3921
  {
3922
+ $value = $this->coerceForExpression($this->reduce($value));
3923
+
3924
+ if ($value instanceof Number) {
3925
+ return $value;
3926
  }
3927
 
3928
+ switch ($value[0]) {
3929
+ case Type::T_LIST:
3930
+ $value = $this->extractInterpolation($value);
3931
+
3932
+ if ($value[0] !== Type::T_LIST) {
3933
+ return [Type::T_KEYWORD, $this->compileValue($value)];
3934
+ }
3935
+
3936
+ foreach ($value[2] as $key => $item) {
3937
+ $value[2][$key] = $this->normalizeValue($item);
3938
+ }
3939
+
3940
+ if (! empty($value['enclosing'])) {
3941
+ unset($value['enclosing']);
3942
+ }
3943
+
3944
+ return $value;
3945
+
3946
+ case Type::T_STRING:
3947
+ return [$value[0], '"', [$this->compileStringContent($value)]];
3948
+
3949
+ case Type::T_INTERPOLATE:
3950
+ return [Type::T_KEYWORD, $this->compileValue($value)];
3951
+
3952
+ default:
3953
+ return $value;
3954
+ }
3955
  }
3956
 
3957
  /**
3958
+ * Add numbers
3959
  *
3960
+ * @param Number $left
3961
+ * @param Number $right
3962
  *
3963
+ * @return Number
3964
  */
3965
+ protected function opAddNumberNumber(Number $left, Number $right)
3966
  {
3967
+ return $left->plus($right);
3968
+ }
3969
+
3970
+ /**
3971
+ * Multiply numbers
3972
+ *
3973
+ * @param Number $left
3974
+ * @param Number $right
3975
+ *
3976
+ * @return Number
3977
+ */
3978
+ protected function opMulNumberNumber(Number $left, Number $right)
3979
+ {
3980
+ return $left->times($right);
3981
+ }
3982
+
3983
+ /**
3984
+ * Subtract numbers
3985
+ *
3986
+ * @param Number $left
3987
+ * @param Number $right
3988
+ *
3989
+ * @return Number
3990
+ */
3991
+ protected function opSubNumberNumber(Number $left, Number $right)
3992
+ {
3993
+ return $left->minus($right);
3994
+ }
3995
+
3996
+ /**
3997
+ * Divide numbers
3998
+ *
3999
+ * @param Number $left
4000
+ * @param Number $right
4001
+ *
4002
+ * @return Number
4003
+ */
4004
+ protected function opDivNumberNumber(Number $left, Number $right)
4005
+ {
4006
+ return $left->dividedBy($right);
4007
+ }
4008
 
4009
+ /**
4010
+ * Mod numbers
4011
+ *
4012
+ * @param Number $left
4013
+ * @param Number $right
4014
+ *
4015
+ * @return Number
4016
+ */
4017
+ protected function opModNumberNumber(Number $left, Number $right)
4018
+ {
4019
+ return $left->modulo($right);
4020
  }
4021
 
4022
  /**
4055
  /**
4056
  * Boolean and
4057
  *
4058
+ * @param array|Number $left
4059
+ * @param array|Number $right
4060
  * @param boolean $shouldEval
4061
  *
4062
+ * @return array|Number|null
4063
  */
4064
  protected function opAnd($left, $right, $shouldEval)
4065
  {
4083
  /**
4084
  * Boolean or
4085
  *
4086
+ * @param array|Number $left
4087
+ * @param array|Number $right
4088
  * @param boolean $shouldEval
4089
  *
4090
+ * @return array|Number|null
4091
  */
4092
  protected function opOr($left, $right, $shouldEval)
4093
  {
4119
  */
4120
  protected function opColorColor($op, $left, $right)
4121
  {
4122
+ if ($op !== '==' && $op !== '!=') {
4123
+ $warning = "Color arithmetic is deprecated and will be an error in future versions.\n"
4124
+ . "Consider using Sass's color functions instead.";
4125
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
4126
+ $line = $this->sourceLine;
4127
+
4128
+ Warn::deprecation("$warning\n on line $line of $fname");
4129
+ }
4130
+
4131
  $out = [Type::T_COLOR];
4132
 
4133
  foreach ([1, 2, 3] as $i) {
4148
  break;
4149
 
4150
  case '%':
4151
+ if ($rval == 0) {
4152
+ throw $this->error("color: Can't take modulo by zero");
4153
+ }
4154
+
4155
  $out[] = $lval % $rval;
4156
  break;
4157
 
4158
  case '/':
4159
  if ($rval == 0) {
4160
+ throw $this->error("color: Can't divide by zero");
 
4161
  }
4162
 
4163
  $out[] = (int) ($lval / $rval);
4170
  return $this->opNeq($left, $right);
4171
 
4172
  default:
4173
+ throw $this->error("color: unknown op $op");
 
4174
  }
4175
  }
4176
 
4188
  *
4189
  * @param string $op
4190
  * @param array $left
4191
+ * @param Number $right
4192
  *
4193
  * @return array
4194
  */
4195
+ protected function opColorNumber($op, $left, Number $right)
4196
  {
4197
+ if ($op === '==') {
4198
+ return static::$false;
4199
+ }
4200
+
4201
+ if ($op === '!=') {
4202
+ return static::$true;
4203
+ }
4204
+
4205
+ $value = $right->getDimension();
4206
 
4207
  return $this->opColorColor(
4208
  $op,
4215
  * Compare number and color
4216
  *
4217
  * @param string $op
4218
+ * @param Number $left
4219
  * @param array $right
4220
  *
4221
  * @return array
4222
  */
4223
+ protected function opNumberColor($op, Number $left, $right)
4224
  {
4225
+ if ($op === '==') {
4226
+ return static::$false;
4227
+ }
4228
+
4229
+ if ($op === '!=') {
4230
+ return static::$true;
4231
+ }
4232
+
4233
+ $value = $left->getDimension();
4234
 
4235
  return $this->opColorColor(
4236
  $op,
4242
  /**
4243
  * Compare number1 == number2
4244
  *
4245
+ * @param array|Number $left
4246
+ * @param array|Number $right
4247
  *
4248
  * @return array
4249
  */
4263
  /**
4264
  * Compare number1 != number2
4265
  *
4266
+ * @param array|Number $left
4267
+ * @param array|Number $right
4268
  *
4269
  * @return array
4270
  */
4282
  }
4283
 
4284
  /**
4285
+ * Compare number1 == number2
4286
  *
4287
+ * @param Number $left
4288
+ * @param Number $right
4289
  *
4290
  * @return array
4291
  */
4292
+ protected function opEqNumberNumber(Number $left, Number $right)
4293
  {
4294
+ return $this->toBool($left->equals($right));
4295
  }
4296
 
4297
  /**
4298
+ * Compare number1 != number2
4299
  *
4300
+ * @param Number $left
4301
+ * @param Number $right
4302
  *
4303
  * @return array
4304
  */
4305
+ protected function opNeqNumberNumber(Number $left, Number $right)
4306
  {
4307
+ return $this->toBool(!$left->equals($right));
4308
  }
4309
 
4310
  /**
4311
+ * Compare number1 >= number2
4312
  *
4313
+ * @param Number $left
4314
+ * @param Number $right
4315
  *
4316
  * @return array
4317
  */
4318
+ protected function opGteNumberNumber(Number $left, Number $right)
4319
  {
4320
+ return $this->toBool($left->greaterThanOrEqual($right));
4321
  }
4322
 
4323
  /**
4324
+ * Compare number1 > number2
4325
  *
4326
+ * @param Number $left
4327
+ * @param Number $right
4328
  *
4329
  * @return array
4330
  */
4331
+ protected function opGtNumberNumber(Number $left, Number $right)
4332
  {
4333
+ return $this->toBool($left->greaterThan($right));
4334
  }
4335
 
4336
  /**
4337
+ * Compare number1 <= number2
4338
  *
4339
+ * @param Number $left
4340
+ * @param Number $right
4341
  *
4342
+ * @return array
4343
  */
4344
+ protected function opLteNumberNumber(Number $left, Number $right)
4345
  {
4346
+ return $this->toBool($left->lessThanOrEqual($right));
4347
+ }
4348
 
4349
+ /**
4350
+ * Compare number1 < number2
4351
+ *
4352
+ * @param Number $left
4353
+ * @param Number $right
4354
+ *
4355
+ * @return array
4356
+ */
4357
+ protected function opLtNumberNumber(Number $left, Number $right)
4358
+ {
4359
+ return $this->toBool($left->lessThan($right));
4360
  }
4361
 
4362
  /**
4364
  *
4365
  * @api
4366
  *
4367
+ * @param bool $thing
4368
  *
4369
  * @return array
4370
  */
4373
  return $thing ? static::$true : static::$false;
4374
  }
4375
 
4376
+ /**
4377
+ * Escape non printable chars in strings output as in dart-sass
4378
+ *
4379
+ * @internal
4380
+ *
4381
+ * @param string $string
4382
+ * @param bool $inKeyword
4383
+ *
4384
+ * @return string
4385
+ */
4386
+ public function escapeNonPrintableChars($string, $inKeyword = false)
4387
+ {
4388
+ static $replacement = [];
4389
+ if (empty($replacement[$inKeyword])) {
4390
+ for ($i = 0; $i < 32; $i++) {
4391
+ if ($i !== 9 || $inKeyword) {
4392
+ $replacement[$inKeyword][chr($i)] = '\\' . dechex($i) . ($inKeyword ? ' ' : chr(0));
4393
+ }
4394
+ }
4395
+ }
4396
+ $string = str_replace(array_keys($replacement[$inKeyword]), array_values($replacement[$inKeyword]), $string);
4397
+ // chr(0) is not a possible char from the input, so any chr(0) comes from our escaping replacement
4398
+ if (strpos($string, chr(0)) !== false) {
4399
+ if (substr($string, -1) === chr(0)) {
4400
+ $string = substr($string, 0, -1);
4401
+ }
4402
+ $string = str_replace(
4403
+ [chr(0) . '\\',chr(0) . ' '],
4404
+ [ '\\', ' '],
4405
+ $string
4406
+ );
4407
+ if (strpos($string, chr(0)) !== false) {
4408
+ $parts = explode(chr(0), $string);
4409
+ $string = array_shift($parts);
4410
+ while (count($parts)) {
4411
+ $next = array_shift($parts);
4412
+ if (strpos("0123456789abcdefABCDEF" . chr(9), $next[0]) !== false) {
4413
+ $string .= " ";
4414
+ }
4415
+ $string .= $next;
4416
+ }
4417
+ }
4418
+ }
4419
+
4420
+ return $string;
4421
+ }
4422
+
4423
  /**
4424
  * Compiles a primitive value into a CSS property value.
4425
  *
4433
  *
4434
  * @api
4435
  *
4436
+ * @param array|Number $value
4437
+ * @param bool $quote
4438
  *
4439
+ * @return string
4440
  */
4441
+ public function compileValue($value, $quote = true)
4442
  {
4443
  $value = $this->reduce($value);
4444
 
4445
+ if ($value instanceof Number) {
4446
+ return $value->output($this);
4447
+ }
4448
+
4449
  switch ($value[0]) {
4450
  case Type::T_KEYWORD:
4451
+ return $this->escapeNonPrintableChars($value[1], true);
4452
 
4453
  case Type::T_COLOR:
4454
  // [1] - red component (either number for a %)
4472
  }
4473
 
4474
  if (is_numeric($alpha)) {
4475
+ $a = new Number($alpha, '');
4476
  } else {
4477
  $a = $alpha;
4478
  }
4500
 
4501
  return $h;
4502
 
 
 
 
4503
  case Type::T_STRING:
4504
+ $content = $this->compileStringContent($value, $quote);
4505
+
4506
+ if ($value[1] && $quote) {
4507
+ $content = str_replace('\\', '\\\\', $content);
4508
+
4509
+ $content = $this->escapeNonPrintableChars($content);
4510
+
4511
+ // force double quote as string quote for the output in certain cases
4512
+ if (
4513
+ $value[1] === "'" &&
4514
+ (strpos($content, '"') === false or strpos($content, "'") !== false) &&
4515
+ strpbrk($content, '{}\\\'') !== false
4516
+ ) {
4517
+ $value[1] = '"';
4518
+ } elseif (
4519
+ $value[1] === '"' &&
4520
+ (strpos($content, '"') !== false and strpos($content, "'") === false)
4521
+ ) {
4522
+ $value[1] = "'";
4523
+ }
4524
+
4525
+ $content = str_replace($value[1], '\\' . $value[1], $content);
4526
+ }
4527
+
4528
+ return $value[1] . $content . $value[1];
4529
 
4530
  case Type::T_FUNCTION:
4531
+ $args = ! empty($value[2]) ? $this->compileValue($value[2], $quote) : '';
4532
 
4533
  return "$value[1]($args)";
4534
 
4535
+ case Type::T_FUNCTION_REFERENCE:
4536
+ $name = ! empty($value[2]) ? $value[2] : '';
4537
+
4538
+ return "get-function(\"$name\")";
4539
+
4540
  case Type::T_LIST:
4541
  $value = $this->extractInterpolation($value);
4542
 
4543
  if ($value[0] !== Type::T_LIST) {
4544
+ return $this->compileValue($value, $quote);
4545
  }
4546
 
4547
  list(, $delim, $items) = $value;
4548
+ $pre = $post = '';
4549
 
4550
  if (! empty($value['enclosing'])) {
4551
  switch ($value['enclosing']) {
4552
  case 'parent':
4553
+ //$pre = '(';
4554
+ //$post = ')';
4555
  break;
4556
  case 'forced_parent':
4557
+ $pre = '(';
4558
+ $post = ')';
4559
  break;
4560
  case 'bracket':
4561
  case 'forced_bracket':
4562
+ $pre = '[';
4563
+ $post = ']';
4564
  break;
4565
  }
4566
  }
4567
 
4568
  $prefix_value = '';
4569
+
4570
  if ($delim !== ' ') {
4571
  $prefix_value = ' ';
4572
  }
4573
 
4574
  $filtered = [];
4575
 
4576
+ $same_string_quote = null;
4577
  foreach ($items as $item) {
4578
+ if (\is_null($same_string_quote)) {
4579
+ $same_string_quote = false;
4580
+ if ($item[0] === Type::T_STRING) {
4581
+ $same_string_quote = $item[1];
4582
+ foreach ($items as $ii) {
4583
+ if ($ii[0] !== Type::T_STRING) {
4584
+ $same_string_quote = false;
4585
+ break;
4586
+ }
4587
+ }
4588
+ }
4589
+ }
4590
  if ($item[0] === Type::T_NULL) {
4591
  continue;
4592
  }
4593
+ if ($same_string_quote === '"' && $item[0] === Type::T_STRING && $item[1]) {
4594
+ $item[1] = $same_string_quote;
4595
+ }
4596
+
4597
+ $compiled = $this->compileValue($item, $quote);
4598
 
 
4599
  if ($prefix_value && \strlen($compiled)) {
4600
  $compiled = $prefix_value . $compiled;
4601
  }
4602
+
4603
  $filtered[] = $compiled;
4604
  }
4605
 
4611
  $filtered = [];
4612
 
4613
  for ($i = 0, $s = \count($keys); $i < $s; $i++) {
4614
+ $filtered[$this->compileValue($keys[$i], $quote)] = $this->compileValue($values[$i], $quote);
4615
  }
4616
 
4617
  array_walk($filtered, function (&$value, $key) {
4631
  $delim .= ' ';
4632
  }
4633
 
4634
+ $left = \count($left[2]) > 0
4635
+ ? $this->compileValue($left, $quote) . $delim . $whiteLeft
4636
+ : '';
4637
 
4638
  $delim = $right[1];
4639
 
4642
  }
4643
 
4644
  $right = \count($right[2]) > 0 ?
4645
+ $whiteRight . $delim . $this->compileValue($right, $quote) : '';
4646
 
4647
+ return $left . $this->compileValue($interpolate, $quote) . $right;
4648
 
4649
  case Type::T_INTERPOLATE:
4650
  // strip quotes if it's a string
4651
  $reduced = $this->reduce($value[1]);
4652
 
4653
+ if ($reduced instanceof Number) {
4654
+ return $this->compileValue($reduced, $quote);
4655
+ }
4656
+
4657
  switch ($reduced[0]) {
4658
  case Type::T_LIST:
4659
  $reduced = $this->extractInterpolation($reduced);
4675
  continue;
4676
  }
4677
 
4678
+ if ($item[0] === Type::T_STRING) {
4679
+ $filtered[] = $this->compileStringContent($item, $quote);
4680
+ } elseif ($item[0] === Type::T_KEYWORD) {
4681
+ $filtered[] = $item[1];
 
 
4682
  } else {
4683
+ $filtered[] = $this->compileValue($item, $quote);
4684
  }
4685
  }
4686
 
4688
  break;
4689
 
4690
  case Type::T_STRING:
4691
+ $reduced = [Type::T_STRING, '', [$this->compileStringContent($reduced)]];
4692
  break;
4693
 
4694
  case Type::T_NULL:
4695
  $reduced = [Type::T_KEYWORD, ''];
4696
  }
4697
 
4698
+ return $this->compileValue($reduced, $quote);
4699
 
4700
  case Type::T_NULL:
4701
  return 'null';
4704
  return $this->compileCommentValue($value);
4705
 
4706
  default:
4707
+ throw $this->error('unknown value type: ' . json_encode($value));
4708
+ }
4709
+ }
4710
+
4711
+ /**
4712
+ * @param array|Number $value
4713
+ *
4714
+ * @return string
4715
+ */
4716
+ protected function compileDebugValue($value)
4717
+ {
4718
+ $value = $this->reduce($value, true);
4719
+
4720
+ if ($value instanceof Number) {
4721
+ return $this->compileValue($value);
4722
+ }
4723
+
4724
+ switch ($value[0]) {
4725
+ case Type::T_STRING:
4726
+ return $this->compileStringContent($value);
4727
+
4728
+ default:
4729
+ return $this->compileValue($value);
4730
  }
4731
  }
4732
 
4736
  * @param array $list
4737
  *
4738
  * @return string
4739
+ *
4740
+ * @deprecated
4741
  */
4742
  protected function flattenList($list)
4743
  {
4744
+ @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
4745
+
4746
  return $this->compileValue($list);
4747
  }
4748
 
4749
  /**
4750
+ * Gets the text of a Sass string
4751
  *
4752
+ * Calling this method on anything else than a SassString is unsupported. Use {@see assertString} first
4753
+ * to ensure that the value is indeed a string.
4754
+ *
4755
+ * @param array $value
4756
  *
4757
  * @return string
4758
  */
4759
+ public function getStringText(array $value)
4760
  {
4761
+ if ($value[0] !== Type::T_STRING) {
4762
+ throw new \InvalidArgumentException('The argument is not a sass string. Did you forgot to use "assertString"?');
4763
+ }
4764
+
4765
+ return $this->compileStringContent($value);
4766
+ }
4767
+
4768
+ /**
4769
+ * Compile string content
4770
+ *
4771
+ * @param array $string
4772
+ * @param bool $quote
4773
+ *
4774
+ * @return string
4775
+ */
4776
+ protected function compileStringContent($string, $quote = true)
4777
+ {
4778
+ $parts = [];
4779
+
4780
+ foreach ($string[2] as $part) {
4781
+ if (\is_array($part) || $part instanceof Number) {
4782
+ $parts[] = $this->compileValue($part, $quote);
4783
+ } else {
4784
+ $parts[] = $part;
4785
+ }
4786
  }
4787
 
4788
  return implode($parts);
4843
  $prevSelectors = $selectors;
4844
  $selectors = [];
4845
 
4846
+ foreach ($parentSelectors as $parent) {
4847
+ foreach ($prevSelectors as $selector) {
4848
  if ($selfParentSelectors) {
4849
  foreach ($selfParentSelectors as $selfParent) {
4850
  // if no '&' in the selector, each call will give same result, only add once
4865
  $selectors = array_values($selectors);
4866
 
4867
  // case we are just starting a at-root : nothing to multiply but parentSelectors
4868
+ if (! $selectors && $selfParentSelectors) {
4869
  $selectors = $selfParentSelectors;
4870
  }
4871
 
4944
  */
4945
  protected function multiplyMedia(Environment $env = null, $childQueries = null)
4946
  {
4947
+ if (
4948
+ ! isset($env) ||
4949
  ! empty($env->block->type) && $env->block->type !== Type::T_MEDIA
4950
  ) {
4951
  return $childQueries;
4991
  /**
4992
  * Convert env linked list to stack
4993
  *
4994
+ * @param Environment $env
4995
  *
4996
+ * @return Environment[]
4997
+ *
4998
+ * @phpstan-return non-empty-array<Environment>
4999
  */
5000
  protected function compactEnv(Environment $env)
5001
  {
5009
  /**
5010
  * Convert env stack to singly linked list
5011
  *
5012
+ * @param Environment[] $envs
5013
  *
5014
+ * @return Environment
5015
+ *
5016
+ * @phpstan-param non-empty-array<Environment> $envs
5017
  */
5018
  protected function extractEnv($envs)
5019
  {
5034
  */
5035
  protected function pushEnv(Block $block = null)
5036
  {
5037
+ $env = new Environment();
5038
  $env->parent = $this->env;
5039
  $env->parentStore = $this->storeEnv;
5040
  $env->store = [];
5049
 
5050
  /**
5051
  * Pop environment
5052
+ *
5053
+ * @return void
5054
  */
5055
  protected function popEnv()
5056
  {
5061
  /**
5062
  * Propagate vars from a just poped Env (used in @each and @for)
5063
  *
5064
+ * @param array $store
5065
+ * @param null|string[] $excludedVars
5066
+ *
5067
+ * @return void
5068
  */
5069
  protected function backPropagateEnv($store, $excludedVars = null)
5070
  {
5093
  * @param boolean $shadow
5094
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
5095
  * @param mixed $valueUnreduced
5096
+ *
5097
+ * @return void
5098
  */
5099
  protected function set($name, $value, $shadow = false, Environment $env = null, $valueUnreduced = null)
5100
  {
5118
  * @param mixed $value
5119
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
5120
  * @param mixed $valueUnreduced
5121
+ *
5122
+ * @return void
5123
  */
5124
  protected function setExisting($name, $value, Environment $env, $valueUnreduced = null)
5125
  {
5178
  * @param mixed $value
5179
  * @param \ScssPhp\ScssPhp\Compiler\Environment $env
5180
  * @param mixed $valueUnreduced
5181
+ *
5182
+ * @return void
5183
  */
5184
  protected function setRaw($name, $value, Environment $env, $valueUnreduced = null)
5185
  {
5193
  /**
5194
  * Get variable
5195
  *
5196
+ * @internal
5197
  *
5198
  * @param string $name
5199
  * @param boolean $shouldThrow
5252
  }
5253
 
5254
  if ($shouldThrow) {
5255
+ throw $this->error("Undefined variable \$$name" . ($maxDepth <= 0 ? ' (infinite recursion)' : ''));
5256
  }
5257
 
5258
  // found nothing
5276
  * Inject variables
5277
  *
5278
  * @param array $args
5279
+ *
5280
+ * @return void
5281
  */
5282
  protected function injectVariables(array $args)
5283
  {
5292
  $name = substr($name, 1);
5293
  }
5294
 
5295
+ if (!\is_string($strValue) || ! $parser->parseValue($strValue, $value)) {
5296
  $value = $this->coerceValue($strValue);
5297
  }
5298
 
5300
  }
5301
  }
5302
 
5303
+ /**
5304
+ * Replaces variables.
5305
+ *
5306
+ * @param array<string, mixed> $variables
5307
+ *
5308
+ * @return void
5309
+ */
5310
+ public function replaceVariables(array $variables)
5311
+ {
5312
+ $this->registeredVars = [];
5313
+ $this->addVariables($variables);
5314
+ }
5315
+
5316
+ /**
5317
+ * Replaces variables.
5318
+ *
5319
+ * @param array<string, mixed> $variables
5320
+ *
5321
+ * @return void
5322
+ */
5323
+ public function addVariables(array $variables)
5324
+ {
5325
+ $triggerWarning = false;
5326
+
5327
+ foreach ($variables as $name => $value) {
5328
+ if (!$value instanceof Number && !\is_array($value)) {
5329
+ $triggerWarning = true;
5330
+ }
5331
+
5332
+ $this->registeredVars[$name] = $value;
5333
+ }
5334
+
5335
+ if ($triggerWarning) {
5336
+ @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);
5337
+ }
5338
+ }
5339
+
5340
  /**
5341
  * Set variables
5342
  *
5343
  * @api
5344
  *
5345
  * @param array $variables
5346
+ *
5347
+ * @return void
5348
+ *
5349
+ * @deprecated Use "addVariables" or "replaceVariables" instead.
5350
  */
5351
  public function setVariables(array $variables)
5352
  {
5353
+ @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.');
5354
+
5355
+ $this->addVariables($variables);
5356
  }
5357
 
5358
  /**
5361
  * @api
5362
  *
5363
  * @param string $name
5364
+ *
5365
+ * @return void
5366
  */
5367
  public function unsetVariable($name)
5368
  {
5384
  /**
5385
  * Adds to list of parsed files
5386
  *
5387
+ * @internal
5388
  *
5389
+ * @param string|null $path
5390
+ *
5391
+ * @return void
5392
  */
5393
  public function addParsedFile($path)
5394
  {
5395
+ if (! \is_null($path) && is_file($path)) {
5396
  $this->parsedFiles[realpath($path)] = filemtime($path);
5397
  }
5398
  }
5400
  /**
5401
  * Returns list of parsed files
5402
  *
5403
+ * @deprecated
5404
+ * @return array<string, int>
 
5405
  */
5406
  public function getParsedFiles()
5407
  {
5408
+ @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);
5409
  return $this->parsedFiles;
5410
  }
5411
 
5415
  * @api
5416
  *
5417
  * @param string|callable $path
5418
+ *
5419
+ * @return void
5420
  */
5421
  public function addImportPath($path)
5422
  {
5430
  *
5431
  * @api
5432
  *
5433
+ * @param string|array<string|callable> $path
5434
+ *
5435
+ * @return void
5436
  */
5437
  public function setImportPaths($path)
5438
  {
5439
+ $paths = (array) $path;
5440
+ $actualImportPaths = array_filter($paths, function ($path) {
5441
+ return $path !== '';
5442
+ });
5443
+
5444
+ $this->legacyCwdImportPath = \count($actualImportPaths) !== \count($paths);
5445
+
5446
+ if ($this->legacyCwdImportPath) {
5447
+ @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);
5448
+ }
5449
+
5450
+ $this->importPaths = $actualImportPaths;
5451
  }
5452
 
5453
  /**
5456
  * @api
5457
  *
5458
  * @param integer $numberPrecision
5459
+ *
5460
+ * @return void
5461
+ *
5462
+ * @deprecated The number precision is not configurable anymore. The default is enough for all browsers.
5463
  */
5464
  public function setNumberPrecision($numberPrecision)
5465
  {
5466
+ @trigger_error('The number precision is not configurable anymore. '
5467
+ . 'The default is enough for all browsers.', E_USER_DEPRECATED);
5468
+ }
5469
+
5470
+ /**
5471
+ * Sets the output style.
5472
+ *
5473
+ * @api
5474
+ *
5475
+ * @param string $style One of the OutputStyle constants
5476
+ *
5477
+ * @return void
5478
+ *
5479
+ * @phpstan-param OutputStyle::* $style
5480
+ */
5481
+ public function setOutputStyle($style)
5482
+ {
5483
+ switch ($style) {
5484
+ case OutputStyle::EXPANDED:
5485
+ $this->formatter = Expanded::class;
5486
+ break;
5487
+
5488
+ case OutputStyle::COMPRESSED:
5489
+ $this->formatter = Compressed::class;
5490
+ break;
5491
+
5492
+ default:
5493
+ throw new \InvalidArgumentException(sprintf('Invalid output style "%s".', $style));
5494
+ }
5495
  }
5496
 
5497
  /**
5500
  * @api
5501
  *
5502
  * @param string $formatterName
5503
+ *
5504
+ * @return void
5505
+ *
5506
+ * @deprecated Use {@see setOutputStyle} instead.
5507
  */
5508
  public function setFormatter($formatterName)
5509
  {
5510
+ if (!\in_array($formatterName, [Expanded::class, Compressed::class], true)) {
5511
+ @trigger_error('Formatters other than Expanded and Compressed are deprecated.', E_USER_DEPRECATED);
5512
+ }
5513
+ @trigger_error('The method "setFormatter" is deprecated. Use "setOutputStyle" instead.', E_USER_DEPRECATED);
5514
+
5515
  $this->formatter = $formatterName;
5516
  }
5517
 
5521
  * @api
5522
  *
5523
  * @param string $lineNumberStyle
5524
+ *
5525
+ * @return void
5526
+ *
5527
+ * @deprecated The line number output is not supported anymore. Use source maps instead.
5528
  */
5529
  public function setLineNumberStyle($lineNumberStyle)
5530
  {
5531
+ @trigger_error('The line number output is not supported anymore. '
5532
+ . 'Use source maps instead.', E_USER_DEPRECATED);
5533
+ }
5534
+
5535
+ /**
5536
+ * Configures the handling of non-ASCII outputs.
5537
+ *
5538
+ * If $charset is `true`, this will include a `@charset` declaration or a
5539
+ * UTF-8 [byte-order mark][] if the stylesheet contains any non-ASCII
5540
+ * characters. Otherwise, it will never include a `@charset` declaration or a
5541
+ * byte-order mark.
5542
+ *
5543
+ * [byte-order mark]: https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8
5544
+ *
5545
+ * @param bool $charset
5546
+ *
5547
+ * @return void
5548
+ */
5549
+ public function setCharset($charset)
5550
+ {
5551
+ $this->charset = $charset;
5552
  }
5553
 
5554
  /**
5557
  * @api
5558
  *
5559
  * @param integer $sourceMap
5560
+ *
5561
+ * @return void
5562
+ *
5563
+ * @phpstan-param self::SOURCE_MAP_* $sourceMap
5564
  */
5565
  public function setSourceMap($sourceMap)
5566
  {
5573
  * @api
5574
  *
5575
  * @param array $sourceMapOptions
5576
+ *
5577
+ * @phpstan-param array{sourceRoot?: string, sourceMapFilename?: string|null, sourceMapURL?: string|null, sourceMapWriteTo?: string|null, outputSourceFiles?: bool, sourceMapRootpath?: string, sourceMapBasepath?: string} $sourceMapOptions
5578
+ *
5579
+ * @return void
5580
  */
5581
  public function setSourceMapOptions($sourceMapOptions)
5582
  {
5588
  *
5589
  * @api
5590
  *
5591
+ * @param string $name
5592
+ * @param callable $callback
5593
+ * @param string[]|null $argumentDeclaration
5594
+ *
5595
+ * @return void
5596
  */
5597
+ public function registerFunction($name, $callback, $argumentDeclaration = null)
5598
  {
5599
+ if (self::isNativeFunction($name)) {
5600
+ @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);
5601
+ }
5602
+
5603
+ if ($argumentDeclaration === null) {
5604
+ @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);
5605
+ }
5606
+
5607
+ $this->userFunctions[$this->normalizeName($name)] = [$callback, $argumentDeclaration];
5608
  }
5609
 
5610
  /**
5613
  * @api
5614
  *
5615
  * @param string $name
5616
+ *
5617
+ * @return void
5618
  */
5619
  public function unregisterFunction($name)
5620
  {
5627
  * @api
5628
  *
5629
  * @param string $name
5630
+ *
5631
+ * @return void
5632
+ *
5633
+ * @deprecated Registering additional features is deprecated.
5634
  */
5635
  public function addFeature($name)
5636
  {
5637
+ @trigger_error('Registering additional features is deprecated.', E_USER_DEPRECATED);
5638
+
5639
  $this->registeredFeatures[$name] = true;
5640
  }
5641
 
5644
  *
5645
  * @param string $path
5646
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
5647
+ *
5648
+ * @return void
5649
  */
5650
  protected function importFile($path, OutputBlock $out)
5651
  {
5652
+ $this->pushCallStack('import ' . $this->getPrettyPath($path));
5653
  // see if tree is cached
5654
  $realPath = realpath($path);
5655
 
5656
+ if (substr($path, -5) === '.sass') {
5657
+ $this->sourceIndex = \count($this->sourceNames);
5658
+ $this->sourceNames[] = $path;
5659
+ $this->sourceLine = 1;
5660
+ $this->sourceColumn = 1;
5661
+
5662
+ throw $this->error('The Sass indented syntax is not implemented.');
5663
+ }
5664
+
5665
  if (isset($this->importCache[$realPath])) {
5666
  $this->handleImportLoop($realPath);
5667
 
5674
  $this->importCache[$realPath] = $tree;
5675
  }
5676
 
5677
+ $currentDirectory = $this->currentDirectory;
5678
+ $this->currentDirectory = dirname($path);
5679
 
 
5680
  $this->compileChildrenNoReturn($tree->children, $out);
5681
+ $this->currentDirectory = $currentDirectory;
5682
  $this->popCallStack();
5683
  }
5684
 
5685
  /**
5686
+ * Save the imported files with their resolving path context
5687
  *
5688
+ * @param string|null $currentDirectory
5689
+ * @param string $path
5690
+ * @param string $filePath
5691
+ *
5692
+ * @return void
5693
+ */
5694
+ private function registerImport($currentDirectory, $path, $filePath)
5695
+ {
5696
+ $this->resolvedImports[] = ['currentDir' => $currentDirectory, 'path' => $path, 'filePath' => $filePath];
5697
+ }
5698
+
5699
+ /**
5700
+ * Detects whether the import is a CSS import.
5701
+ *
5702
+ * For legacy reasons, custom importers are called for those, allowing them
5703
+ * to replace them with an actual Sass import. However this behavior is
5704
+ * deprecated. Custom importers are expected to return null when they receive
5705
+ * a CSS import.
5706
  *
5707
  * @param string $url
5708
  *
5709
+ * @return bool
5710
  */
5711
+ public static function isCssImport($url)
5712
  {
5713
+ return 1 === preg_match('~\.css$|^https?://|^//~', $url);
5714
+ }
5715
 
5716
+ /**
5717
+ * Return the file path for an import url if it exists
5718
+ *
5719
+ * @internal
5720
+ *
5721
+ * @param string $url
5722
+ * @param string|null $currentDir
5723
+ *
5724
+ * @return string|null
5725
+ */
5726
+ public function findImport($url, $currentDir = null)
5727
+ {
5728
+ // Vanilla css and external requests. These are not meant to be Sass imports.
5729
+ // Callback importers are still called for BC.
5730
+ if (self::isCssImport($url)) {
5731
+ foreach ($this->importPaths as $dir) {
5732
+ if (\is_string($dir)) {
5733
+ continue;
5734
+ }
5735
 
5736
+ if (\is_callable($dir)) {
5737
+ // check custom callback for import path
5738
+ $file = \call_user_func($dir, $url);
5739
 
5740
+ if (! \is_null($file)) {
5741
+ if (\is_array($dir)) {
5742
+ $callableDescription = (\is_object($dir[0]) ? \get_class($dir[0]) : $dir[0]).'::'.$dir[1];
5743
+ } elseif ($dir instanceof \Closure) {
5744
+ $r = new \ReflectionFunction($dir);
5745
+ if (false !== strpos($r->name, '{closure}')) {
5746
+ $callableDescription = sprintf('closure{%s:%s}', $r->getFileName(), $r->getStartLine());
5747
+ } elseif ($class = $r->getClosureScopeClass()) {
5748
+ $callableDescription = $class->name.'::'.$r->name;
5749
+ } else {
5750
+ $callableDescription = $r->name;
5751
+ }
5752
+ } elseif (\is_object($dir)) {
5753
+ $callableDescription = \get_class($dir) . '::__invoke';
5754
+ } else {
5755
+ $callableDescription = 'callable'; // Fallback if we don't have a dedicated description
5756
+ }
5757
+ @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);
5758
 
5759
+ return $file;
5760
+ }
5761
+ }
5762
  }
5763
+ return null;
5764
+ }
5765
+
5766
+ if (!\is_null($currentDir)) {
5767
+ $relativePath = $this->resolveImportPath($url, $currentDir);
5768
 
5769
+ if (!\is_null($relativePath)) {
5770
+ return $relativePath;
 
 
 
5771
  }
5772
  }
5773
 
5774
  foreach ($this->importPaths as $dir) {
5775
  if (\is_string($dir)) {
5776
+ $path = $this->resolveImportPath($url, $dir);
5777
+
5778
+ if (!\is_null($path)) {
5779
+ return $path;
 
 
 
 
 
 
 
 
5780
  }
5781
  } elseif (\is_callable($dir)) {
5782
  // check custom callback for import path
5788
  }
5789
  }
5790
 
5791
+ if ($this->legacyCwdImportPath) {
5792
+ $path = $this->resolveImportPath($url, getcwd());
5793
+
5794
+ if (!\is_null($path)) {
5795
+ @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);
5796
+
5797
+ return $path;
5798
  }
5799
  }
5800
 
5801
+ throw $this->error("`$url` file not found for @import");
5802
  }
5803
 
5804
  /**
5805
+ * @param string $url
5806
+ * @param string $baseDir
 
5807
  *
5808
+ * @return string|null
5809
  */
5810
+ private function resolveImportPath($url, $baseDir)
5811
  {
5812
+ $path = Path::join($baseDir, $url);
 
5813
 
5814
+ $hasExtension = preg_match('/.s[ac]ss$/', $url);
 
 
 
 
 
 
 
 
 
 
 
5815
 
5816
+ if ($hasExtension) {
5817
+ return $this->checkImportPathConflicts($this->tryImportPath($path));
5818
+ }
5819
+
5820
+ $result = $this->checkImportPathConflicts($this->tryImportPathWithExtensions($path));
5821
+
5822
+ if (!\is_null($result)) {
5823
+ return $result;
5824
+ }
5825
+
5826
+ return $this->tryImportPathAsDirectory($path);
5827
  }
5828
 
5829
  /**
5830
+ * @param string[] $paths
 
 
 
 
5831
  *
5832
+ * @return string|null
5833
  */
5834
+ private function checkImportPathConflicts(array $paths)
5835
  {
5836
+ if (\count($paths) === 0) {
5837
+ return null;
5838
  }
5839
 
5840
+ if (\count($paths) === 1) {
5841
+ return $paths[0];
5842
  }
5843
 
5844
+ $formattedPrettyPaths = [];
 
 
5845
 
5846
+ foreach ($paths as $path) {
5847
+ $formattedPrettyPaths[] = ' ' . $this->getPrettyPath($path);
5848
+ }
5849
 
5850
+ throw $this->error("It's not clear which file to import. Found:\n" . implode("\n", $formattedPrettyPaths));
5851
+ }
5852
 
5853
+ /**
5854
+ * @param string $path
5855
+ *
5856
+ * @return string[]
5857
+ */
5858
+ private function tryImportPathWithExtensions($path)
5859
+ {
5860
+ $result = array_merge(
5861
+ $this->tryImportPath($path.'.sass'),
5862
+ $this->tryImportPath($path.'.scss')
5863
+ );
5864
 
5865
+ if ($result) {
5866
+ return $result;
 
5867
  }
5868
 
5869
+ return $this->tryImportPath($path.'.css');
5870
  }
5871
 
5872
  /**
5873
+ * @param string $path
 
 
 
5874
  *
5875
+ * @return string[]
5876
  */
5877
+ private function tryImportPath($path)
5878
  {
5879
+ $partial = dirname($path).'/_'.basename($path);
 
5880
 
5881
+ $candidates = [];
 
 
 
 
 
 
 
5882
 
5883
+ if (is_file($partial)) {
5884
+ $candidates[] = $partial;
5885
+ }
5886
 
5887
+ if (is_file($path)) {
5888
+ $candidates[] = $path;
 
 
 
5889
  }
5890
 
5891
+ return $candidates;
5892
  }
5893
 
5894
  /**
5895
+ * @param string $path
 
 
5896
  *
5897
+ * @return string|null
5898
  */
5899
+ private function tryImportPathAsDirectory($path)
5900
  {
5901
+ if (!is_dir($path)) {
5902
+ return null;
 
 
 
 
 
 
 
 
 
5903
  }
5904
+
5905
+ return $this->checkImportPathConflicts($this->tryImportPathWithExtensions($path.'/index'));
5906
  }
5907
 
5908
  /**
5909
+ * @param string|null $path
 
 
 
 
5910
  *
5911
+ * @return string
5912
  */
5913
+ private function getPrettyPath($path)
5914
  {
5915
+ if ($path === null) {
5916
+ return '(unknown file)';
 
 
5917
  }
5918
 
5919
+ $normalizedPath = $path;
5920
+ $normalizedRootDirectory = $this->rootDirectory.'/';
5921
 
5922
+ if (\DIRECTORY_SEPARATOR === '\\') {
5923
+ $normalizedRootDirectory = str_replace('\\', '/', $normalizedRootDirectory);
5924
+ $normalizedPath = str_replace('\\', '/', $path);
5925
  }
5926
 
5927
+ if (0 === strpos($normalizedPath, $normalizedRootDirectory)) {
5928
+ return substr($path, \strlen($normalizedRootDirectory));
5929
+ }
 
5930
 
5931
+ return $path;
5932
+ }
5933
 
5934
+ /**
5935
+ * Set encoding
5936
+ *
5937
+ * @api
5938
+ *
5939
+ * @param string|null $encoding
5940
+ *
5941
+ * @return void
5942
+ *
5943
+ * @deprecated Non-compliant support for other encodings than UTF-8 is deprecated.
5944
+ */
5945
+ public function setEncoding($encoding)
5946
+ {
5947
+ if (!$encoding || strtolower($encoding) === 'utf-8') {
5948
+ @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
5949
  } else {
5950
+ @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);
5951
  }
5952
 
5953
+ $this->encoding = $encoding;
5954
+ }
5955
 
5956
+ /**
5957
+ * Ignore errors?
5958
+ *
5959
+ * @api
5960
+ *
5961
+ * @param boolean $ignoreErrors
5962
+ *
5963
+ * @return \ScssPhp\ScssPhp\Compiler
5964
+ *
5965
+ * @deprecated Ignoring Sass errors is not longer supported.
5966
+ */
5967
+ public function setIgnoreErrors($ignoreErrors)
5968
+ {
5969
+ @trigger_error('Ignoring Sass errors is not longer supported.', E_USER_DEPRECATED);
5970
 
5971
+ return $this;
5972
+ }
5973
 
5974
+ /**
5975
+ * Get source position
5976
+ *
5977
+ * @api
5978
+ *
5979
+ * @return array
5980
+ *
5981
+ * @deprecated
5982
+ */
5983
+ public function getSourcePosition()
5984
+ {
5985
+ @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
5986
+
5987
+ $sourceFile = isset($this->sourceNames[$this->sourceIndex]) ? $this->sourceNames[$this->sourceIndex] : '';
5988
+
5989
+ return [$sourceFile, $this->sourceLine, $this->sourceColumn];
5990
  }
5991
 
5992
  /**
5993
+ * Throw error (exception)
5994
  *
5995
+ * @api
5996
+ *
5997
+ * @param string $msg Message with optional sprintf()-style vararg parameters
5998
+ *
5999
+ * @throws \ScssPhp\ScssPhp\Exception\CompilerException
6000
  *
6001
+ * @deprecated use "error" and throw the exception in the caller instead.
6002
  */
6003
+ public function throwError($msg)
6004
  {
6005
+ @trigger_error(
6006
+ 'The method "throwError" is deprecated. Use "error" and throw the exception in the caller instead',
6007
+ E_USER_DEPRECATED
6008
+ );
6009
 
6010
+ throw $this->error(...func_get_args());
6011
+ }
6012
+
6013
+ /**
6014
+ * Build an error (exception)
6015
+ *
6016
+ * @internal
6017
+ *
6018
+ * @param string $msg Message with optional sprintf()-style vararg parameters
6019
+ *
6020
+ * @return CompilerException
6021
+ */
6022
+ public function error($msg, ...$args)
6023
+ {
6024
+ if ($args) {
6025
+ $msg = sprintf($msg, ...$args);
6026
+ }
6027
+
6028
+ if (! $this->ignoreCallStackMessage) {
6029
+ $msg = $this->addLocationToMessage($msg);
6030
+ }
6031
+
6032
+ return new CompilerException($msg);
6033
+ }
6034
+
6035
+ /**
6036
+ * @param string $msg
6037
+ *
6038
+ * @return string
6039
+ */
6040
+ private function addLocationToMessage($msg)
6041
+ {
6042
+ $line = $this->sourceLine;
6043
+ $column = $this->sourceColumn;
6044
+
6045
+ $loc = isset($this->sourceNames[$this->sourceIndex])
6046
+ ? $this->getPrettyPath($this->sourceNames[$this->sourceIndex]) . " on line $line, at column $column"
6047
+ : "line: $line, column: $column";
6048
+
6049
+ $msg = "$msg: $loc";
6050
+
6051
+ $callStackMsg = $this->callStackMessage();
6052
+
6053
+ if ($callStackMsg) {
6054
+ $msg .= "\nCall Stack:\n" . $callStackMsg;
6055
+ }
6056
+
6057
+ return $msg;
6058
+ }
6059
+
6060
+ /**
6061
+ * @param string $functionName
6062
+ * @param array $ExpectedArgs
6063
+ * @param int $nbActual
6064
+ * @return CompilerException
6065
+ *
6066
+ * @deprecated
6067
+ */
6068
+ public function errorArgsNumber($functionName, $ExpectedArgs, $nbActual)
6069
+ {
6070
+ @trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
6071
+
6072
+ $nbExpected = \count($ExpectedArgs);
6073
+
6074
+ if ($nbActual > $nbExpected) {
6075
+ return $this->error(
6076
+ 'Error: Only %d arguments allowed in %s(), but %d were passed.',
6077
+ $nbExpected,
6078
+ $functionName,
6079
+ $nbActual
6080
+ );
6081
  } else {
6082
+ $missing = [];
6083
+
6084
+ while (count($ExpectedArgs) && count($ExpectedArgs) > $nbActual) {
6085
+ array_unshift($missing, array_pop($ExpectedArgs));
6086
+ }
6087
+
6088
+ return $this->error(
6089
+ 'Error: %s() argument%s %s missing.',
6090
+ $functionName,
6091
+ count($missing) > 1 ? 's' : '',
6092
+ implode(', ', $missing)
6093
+ );
6094
+ }
6095
+ }
6096
+
6097
+ /**
6098
+ * Beautify call stack for output
6099
+ *
6100
+ * @param boolean $all
6101
+ * @param int|null $limit
6102
+ *
6103
+ * @return string
6104
+ */
6105
+ protected function callStackMessage($all = false, $limit = null)
6106
+ {
6107
+ $callStackMsg = [];
6108
+ $ncall = 0;
6109
+
6110
+ if ($this->callStack) {
6111
+ foreach (array_reverse($this->callStack) as $call) {
6112
+ if ($all || (isset($call['n']) && $call['n'])) {
6113
+ $msg = '#' . $ncall++ . ' ' . $call['n'] . ' ';
6114
+ $msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]])
6115
+ ? $this->getPrettyPath($this->sourceNames[$call[Parser::SOURCE_INDEX]])
6116
+ : '(unknown file)');
6117
+ $msg .= ' on line ' . $call[Parser::SOURCE_LINE];
6118
+
6119
+ $callStackMsg[] = $msg;
6120
+
6121
+ if (! \is_null($limit) && $ncall > $limit) {
6122
+ break;
6123
+ }
6124
+ }
6125
+ }
6126
  }
6127
 
6128
+ return implode("\n", $callStackMsg);
6129
+ }
6130
+
6131
+ /**
6132
+ * Handle import loop
6133
+ *
6134
+ * @param string $name
6135
+ *
6136
+ * @throws \Exception
6137
+ */
6138
+ protected function handleImportLoop($name)
6139
+ {
6140
+ for ($env = $this->env; $env; $env = $env->parent) {
6141
+ if (! $env->block) {
6142
+ continue;
6143
+ }
6144
+
6145
+ $file = $this->sourceNames[$env->block->sourceIndex];
6146
 
6147
+ if ($file === null) {
6148
+ continue;
6149
+ }
6150
 
6151
+ if (realpath($file) === $name) {
6152
+ throw $this->error('An @import loop has been found: %s imports %s', $file, basename($file));
6153
  }
6154
+ }
6155
+ }
6156
+
6157
+ /**
6158
+ * Call SCSS @function
6159
+ *
6160
+ * @param Object $func
6161
+ * @param array $argValues
6162
+ *
6163
+ * @return array|Number
6164
+ */
6165
+ protected function callScssFunction($func, $argValues)
6166
+ {
6167
+ if (! $func) {
6168
+ return static::$defaultValue;
6169
+ }
6170
+ $name = $func->name;
6171
+
6172
+ $this->pushEnv();
6173
+
6174
+ // set the args
6175
+ if (isset($func->args)) {
6176
+ $this->applyArguments($func->args, $argValues);
6177
+ }
6178
+
6179
+ // throw away lines and children
6180
+ $tmp = new OutputBlock();
6181
+ $tmp->lines = [];
6182
+ $tmp->children = [];
6183
 
6184
+ $this->env->marker = 'function';
6185
+
6186
+ if (! empty($func->parentEnv)) {
6187
+ $this->env->declarationScopeParent = $func->parentEnv;
6188
+ } else {
6189
+ throw $this->error("@function $name() without parentEnv");
6190
+ }
6191
+
6192
+ $ret = $this->compileChildren($func->children, $tmp, $this->env->marker . ' ' . $name);
6193
+
6194
+ $this->popEnv();
6195
+
6196
+ return ! isset($ret) ? static::$defaultValue : $ret;
6197
+ }
6198
+
6199
+ /**
6200
+ * Call built-in and registered (PHP) functions
6201
+ *
6202
+ * @param string $name
6203
+ * @param callable $function
6204
+ * @param array $prototype
6205
+ * @param array $args
6206
+ *
6207
+ * @return array|Number|null
6208
+ */
6209
+ protected function callNativeFunction($name, $function, $prototype, $args)
6210
+ {
6211
+ $libName = (is_array($function) ? end($function) : null);
6212
+ $sorted_kwargs = $this->sortNativeFunctionArgs($libName, $prototype, $args);
6213
+
6214
+ if (\is_null($sorted_kwargs)) {
6215
+ return null;
6216
+ }
6217
+ @list($sorted, $kwargs) = $sorted_kwargs;
6218
+
6219
+ if ($name !== 'if') {
6220
  foreach ($sorted as &$val) {
6221
+ if ($val !== null) {
6222
+ $val = $this->reduce($val, true);
6223
+ }
6224
  }
6225
  }
6226
 
6227
+ $returnValue = \call_user_func($function, $sorted, $kwargs);
6228
 
6229
  if (! isset($returnValue)) {
6230
+ return null;
6231
  }
6232
 
6233
+ if (\is_array($returnValue) || $returnValue instanceof Number) {
6234
+ return $returnValue;
6235
+ }
6236
 
6237
+ @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);
6238
+
6239
+ return $this->coerceValue($returnValue);
6240
  }
6241
 
6242
  /**
6248
  */
6249
  protected function getBuiltinFunction($name)
6250
  {
6251
+ $libName = self::normalizeNativeFunctionName($name);
6252
+ return [$this, $libName];
6253
+ }
6254
+
6255
+ /**
6256
+ * Normalize native function name
6257
+ *
6258
+ * @internal
6259
+ *
6260
+ * @param string $name
6261
+ *
6262
+ * @return string
6263
+ */
6264
+ public static function normalizeNativeFunctionName($name)
6265
+ {
6266
+ $name = str_replace("-", "_", $name);
6267
  $libName = 'lib' . preg_replace_callback(
6268
  '/_(.)/',
6269
  function ($m) {
6271
  },
6272
  ucfirst($name)
6273
  );
6274
+ return $libName;
6275
+ }
6276
 
6277
+ /**
6278
+ * Check if a function is a native built-in scss function, for css parsing
6279
+ *
6280
+ * @internal
6281
+ *
6282
+ * @param string $name
6283
+ *
6284
+ * @return bool
6285
+ */
6286
+ public static function isNativeFunction($name)
6287
+ {
6288
+ return method_exists(Compiler::class, self::normalizeNativeFunctionName($name));
6289
  }
6290
 
6291
  /**
6292
  * Sorts keyword arguments
6293
  *
6294
  * @param string $functionName
6295
+ * @param array|null $prototypes
6296
  * @param array $args
6297
  *
6298
+ * @return array|null
6299
  */
6300
  protected function sortNativeFunctionArgs($functionName, $prototypes, $args)
6301
  {
6305
  $keyArgs = [];
6306
  $posArgs = [];
6307
 
6308
+ if (\is_array($args) && \count($args) && \end($args) === static::$null) {
6309
+ array_pop($args);
6310
+ }
6311
+
6312
  // separate positional and keyword arguments
6313
  foreach ($args as $arg) {
6314
  list($key, $value) = $arg;
6315
 
6316
+ if (empty($key) or empty($key[1])) {
 
 
6317
  $posArgs[] = empty($arg[2]) ? $value : $arg;
6318
  } else {
6319
+ $keyArgs[$key[1]] = $value;
6320
  }
6321
  }
6322
 
6328
  // notation 100 127 255 / 0 is in fact a simple list of 4 values
6329
  foreach ($args as $k => $arg) {
6330
  if ($arg[1][0] === Type::T_LIST && \count($arg[1][2]) === 3) {
6331
+ $args[$k][1][2] = $this->extractSlashAlphaInColorFunction($arg[1][2]);
6332
+ }
6333
+ }
6334
+ }
6335
+
6336
+ list($positionalArgs, $namedArgs, $names, $separator, $hasSplat) = $this->evaluateArguments($args, false);
6337
+
6338
+ if (! \is_array(reset($prototypes))) {
6339
+ $prototypes = [$prototypes];
6340
+ }
6341
+
6342
+ $parsedPrototypes = array_map([$this, 'parseFunctionPrototype'], $prototypes);
6343
+ assert(!empty($parsedPrototypes));
6344
+ $matchedPrototype = $this->selectFunctionPrototype($parsedPrototypes, \count($positionalArgs), $names);
6345
+
6346
+ $this->verifyPrototype($matchedPrototype, \count($positionalArgs), $names, $hasSplat);
6347
+
6348
+ $vars = $this->applyArgumentsToDeclaration($matchedPrototype, $positionalArgs, $namedArgs, $separator);
6349
+
6350
+ $finalArgs = [];
6351
+ $keyArgs = [];
6352
+
6353
+ foreach ($matchedPrototype['arguments'] as $argument) {
6354
+ list($normalizedName, $originalName, $default) = $argument;
6355
+
6356
+ if (isset($vars[$normalizedName])) {
6357
+ $value = $vars[$normalizedName];
6358
+ } else {
6359
+ $value = $default;
6360
+ }
6361
+
6362
+ // special null value as default: translate to real null here
6363
+ if ($value === [Type::T_KEYWORD, 'null']) {
6364
+ $value = null;
6365
+ }
6366
+
6367
+ $finalArgs[] = $value;
6368
+ $keyArgs[$originalName] = $value;
6369
+ }
6370
+
6371
+ if ($matchedPrototype['rest_argument'] !== null) {
6372
+ $value = $vars[$matchedPrototype['rest_argument']];
6373
+
6374
+ $finalArgs[] = $value;
6375
+ $keyArgs[$matchedPrototype['rest_argument']] = $value;
6376
+ }
6377
+
6378
+ return [$finalArgs, $keyArgs];
6379
+ }
6380
+
6381
+ /**
6382
+ * Parses a function prototype to the internal representation of arguments.
6383
+ *
6384
+ * The input is an array of strings describing each argument, as supported
6385
+ * in {@see registerFunction}. Argument names don't include the `$`.
6386
+ * The output contains the list of positional argument, with their normalized
6387
+ * name (underscores are replaced by dashes), their original name (to be used
6388
+ * in case of error reporting) and their default value. The output also contains
6389
+ * the normalized name of the rest argument, or null if the function prototype
6390
+ * is not variadic.
6391
+ *
6392
+ * @param string[] $prototype
6393
+ *
6394
+ * @return array
6395
+ * @phpstan-return array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null}
6396
+ */
6397
+ private function parseFunctionPrototype(array $prototype)
6398
+ {
6399
+ static $parser = null;
6400
+
6401
+ $arguments = [];
6402
+ $restArgument = null;
6403
+
6404
+ foreach ($prototype as $p) {
6405
+ if (null !== $restArgument) {
6406
+ throw new \InvalidArgumentException('The argument declaration is invalid. The rest argument must be the last one.');
6407
+ }
6408
+
6409
+ $default = null;
6410
+ $p = explode(':', $p, 2);
6411
+ $name = str_replace('_', '-', $p[0]);
6412
+
6413
+ if (isset($p[1])) {
6414
+ $defaultSource = trim($p[1]);
6415
+
6416
+ if ($defaultSource === 'null') {
6417
+ // differentiate this null from the static::$null
6418
+ $default = [Type::T_KEYWORD, 'null'];
6419
+ } else {
6420
+ if (\is_null($parser)) {
6421
+ $parser = $this->parserFactory(__METHOD__);
6422
+ }
6423
+
6424
+ $parser->parseValue($defaultSource, $default);
6425
+ }
6426
+ }
6427
+
6428
+ if (substr($name, -3) === '...') {
6429
+ $restArgument = substr($name, 0, -3);
6430
+ } else {
6431
+ $arguments[] = [$name, $p[0], $default];
6432
+ }
6433
+ }
6434
+
6435
+ return [
6436
+ 'arguments' => $arguments,
6437
+ 'rest_argument' => $restArgument,
6438
+ ];
6439
+ }
6440
+
6441
+ /**
6442
+ * Returns the function prototype for the given positional and named arguments.
6443
+ *
6444
+ * If no exact match is found, finds the closest approximation. Note that this
6445
+ * doesn't guarantee that $positional and $names are valid for the returned
6446
+ * prototype.
6447
+ *
6448
+ * @param array[] $prototypes
6449
+ * @param int $positional
6450
+ * @param array<string, string> $names A set of names, as both keys and values
6451
+ *
6452
+ * @return array
6453
+ *
6454
+ * @phpstan-param non-empty-list<array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null}> $prototypes
6455
+ * @phpstan-return array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null}
6456
+ */
6457
+ private function selectFunctionPrototype(array $prototypes, $positional, array $names)
6458
+ {
6459
+ $fuzzyMatch = null;
6460
+ $minMismatchDistance = null;
6461
+
6462
+ foreach ($prototypes as $prototype) {
6463
+ // Ideally, find an exact match.
6464
+ if ($this->checkPrototypeMatches($prototype, $positional, $names)) {
6465
+ return $prototype;
6466
+ }
6467
+
6468
+ $mismatchDistance = \count($prototype['arguments']) - $positional;
6469
+
6470
+ if ($minMismatchDistance !== null) {
6471
+ if (abs($mismatchDistance) > abs($minMismatchDistance)) {
6472
+ continue;
6473
+ }
6474
+
6475
+ // If two overloads have the same mismatch distance, favor the overload
6476
+ // that has more arguments.
6477
+ if (abs($mismatchDistance) === abs($minMismatchDistance) && $mismatchDistance < 0) {
6478
+ continue;
6479
+ }
6480
+ }
6481
+
6482
+ $minMismatchDistance = $mismatchDistance;
6483
+ $fuzzyMatch = $prototype;
6484
+ }
6485
+
6486
+ return $fuzzyMatch;
6487
+ }
6488
+
6489
+ /**
6490
+ * Checks whether the argument invocation matches the callable prototype.
6491
+ *
6492
+ * The rules are similar to {@see verifyPrototype}. The boolean return value
6493
+ * avoids the overhead of building and catching exceptions when the reason of
6494
+ * not matching the prototype does not need to be known.
6495
+ *
6496
+ * @param array $prototype
6497
+ * @param int $positional
6498
+ * @param array<string, string> $names
6499
+ *
6500
+ * @return bool
6501
+ *
6502
+ * @phpstan-param array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null} $prototype
6503
+ */
6504
+ private function checkPrototypeMatches(array $prototype, $positional, array $names)
6505
+ {
6506
+ $nameUsed = 0;
6507
+
6508
+ foreach ($prototype['arguments'] as $i => $argument) {
6509
+ list ($name, $originalName, $default) = $argument;
6510
+
6511
+ if ($i < $positional) {
6512
+ if (isset($names[$name])) {
6513
+ return false;
6514
+ }
6515
+ } elseif (isset($names[$name])) {
6516
+ $nameUsed++;
6517
+ } elseif ($default === null) {
6518
+ return false;
6519
+ }
6520
+ }
6521
+
6522
+ if ($prototype['rest_argument'] !== null) {
6523
+ return true;
6524
+ }
6525
+
6526
+ if ($positional > \count($prototype['arguments'])) {
6527
+ return false;
6528
+ }
6529
+
6530
+ if ($nameUsed < \count($names)) {
6531
+ return false;
6532
+ }
6533
+
6534
+ return true;
6535
+ }
6536
+
6537
+ /**
6538
+ * Verifies that the argument invocation is valid for the callable prototype.
6539
+ *
6540
+ * @param array $prototype
6541
+ * @param int $positional
6542
+ * @param array<string, string> $names
6543
+ * @param bool $hasSplat
6544
+ *
6545
+ * @return void
6546
+ *
6547
+ * @throws SassScriptException
6548
+ *
6549
+ * @phpstan-param array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null} $prototype
6550
+ */
6551
+ private function verifyPrototype(array $prototype, $positional, array $names, $hasSplat)
6552
+ {
6553
+ $nameUsed = 0;
6554
 
6555
+ foreach ($prototype['arguments'] as $i => $argument) {
6556
+ list ($name, $originalName, $default) = $argument;
6557
+
6558
+ if ($i < $positional) {
6559
+ if (isset($names[$name])) {
6560
+ throw new SassScriptException(sprintf('Argument $%s was passed both by position and by name.', $originalName));
6561
  }
6562
+ } elseif (isset($names[$name])) {
6563
+ $nameUsed++;
6564
+ } elseif ($default === null) {
6565
+ throw new SassScriptException(sprintf('Missing argument $%s', $originalName));
6566
  }
6567
  }
6568
 
6569
+ if ($prototype['rest_argument'] !== null) {
6570
+ return;
 
 
6571
  }
6572
 
6573
+ if ($positional > \count($prototype['arguments'])) {
6574
+ $message = sprintf(
6575
+ 'Only %d %sargument%s allowed, but %d %s passed.',
6576
+ \count($prototype['arguments']),
6577
+ empty($names) ? '' : 'positional ',
6578
+ \count($prototype['arguments']) === 1 ? '' : 's',
6579
+ $positional,
6580
+ $positional === 1 ? 'was' : 'were'
6581
+ );
6582
+ if (!$hasSplat) {
6583
+ throw new SassScriptException($message);
6584
+ }
6585
 
6586
+ $message = $this->addLocationToMessage($message);
6587
+ $message .= "\nThis will be an error in future versions of Sass.";
6588
+ $this->logger->warn($message, true);
6589
+ }
6590
 
6591
+ if ($nameUsed < \count($names)) {
6592
+ $unknownNames = array_values(array_diff($names, array_column($prototype['arguments'], 0)));
6593
+ $lastName = array_pop($unknownNames);
6594
+ $message = sprintf(
6595
+ 'No argument%s named $%s%s.',
6596
+ $unknownNames ? 's' : '',
6597
+ $unknownNames ? implode(', $', $unknownNames) . ' or $' : '',
6598
+ $lastName
6599
+ );
6600
+ throw new SassScriptException($message);
6601
+ }
6602
+ }
6603
 
6604
+ /**
6605
+ * Evaluates the argument from the invocation.
6606
+ *
6607
+ * This returns several things about this invocation:
6608
+ * - the list of positional arguments
6609
+ * - the map of named arguments, indexed by normalized names
6610
+ * - the set of names used in the arguments (that's an array using the normalized names as keys for O(1) access)
6611
+ * - the separator used by the list using the splat operator, if any
6612
+ * - a boolean indicator whether any splat argument (list or map) was used, to support the incomplete error reporting.
6613
+ *
6614
+ * @param array[] $args
6615
+ * @param bool $reduce Whether arguments should be reduced to their value
6616
+ *
6617
+ * @return array
6618
+ *
6619
+ * @throws SassScriptException
6620
+ *
6621
+ * @phpstan-return array{0: list<array|Number>, 1: array<string, array|Number>, 2: array<string, string>, 3: string|null, 4: bool}
6622
+ */
6623
+ private function evaluateArguments(array $args, $reduce = true)
6624
+ {
6625
+ // this represents trailing commas
6626
+ if (\count($args) && end($args) === static::$null) {
6627
+ array_pop($args);
6628
+ }
6629
 
6630
+ $splatSeparator = null;
6631
+ $keywordArgs = [];
6632
+ $names = [];
6633
+ $positionalArgs = [];
6634
+ $hasKeywordArgument = false;
6635
+ $hasSplat = false;
6636
 
6637
+ foreach ($args as $arg) {
6638
+ if (!empty($arg[0])) {
6639
+ $hasKeywordArgument = true;
 
 
 
 
6640
 
6641
+ assert(\is_string($arg[0][1]));
6642
+ $name = str_replace('_', '-', $arg[0][1]);
6643
+
6644
+ if (isset($keywordArgs[$name])) {
6645
+ throw new SassScriptException(sprintf('Duplicate named argument $%s.', $arg[0][1]));
6646
  }
6647
 
6648
+ $keywordArgs[$name] = $this->maybeReduce($reduce, $arg[1]);
6649
+ $names[$name] = $name;
6650
+ } elseif (! empty($arg[2])) {
6651
+ // $arg[2] means a var followed by ... in the arg ($list... )
6652
+ $val = $this->reduce($arg[1], true);
6653
+ $hasSplat = true;
6654
 
6655
+ if ($val[0] === Type::T_LIST) {
6656
+ foreach ($val[2] as $item) {
6657
+ if (\is_null($splatSeparator)) {
6658
+ $splatSeparator = $val[1];
6659
+ }
6660
 
6661
+ $positionalArgs[] = $this->maybeReduce($reduce, $item);
6662
+ }
6663
 
6664
+ if (isset($val[3]) && \is_array($val[3])) {
6665
+ foreach ($val[3] as $name => $item) {
6666
+ assert(\is_string($name));
6667
 
6668
+ $normalizedName = str_replace('_', '-', $name);
 
6669
 
6670
+ if (isset($keywordArgs[$normalizedName])) {
6671
+ throw new SassScriptException(sprintf('Duplicate named argument $%s.', $name));
6672
+ }
6673
 
6674
+ $keywordArgs[$normalizedName] = $this->maybeReduce($reduce, $item);
6675
+ $names[$normalizedName] = $normalizedName;
6676
+ $hasKeywordArgument = true;
6677
+ }
6678
  }
6679
+ } elseif ($val[0] === Type::T_MAP) {
6680
+ foreach ($val[1] as $i => $name) {
6681
+ $name = $this->compileStringContent($this->coerceString($name));
6682
+ $item = $val[2][$i];
 
 
6683
 
6684
+ if (! is_numeric($name)) {
6685
+ $normalizedName = str_replace('_', '-', $name);
6686
 
6687
+ if (isset($keywordArgs[$normalizedName])) {
6688
+ throw new SassScriptException(sprintf('Duplicate named argument $%s.', $name));
6689
+ }
6690
 
6691
+ $keywordArgs[$normalizedName] = $this->maybeReduce($reduce, $item);
6692
+ $names[$normalizedName] = $normalizedName;
6693
+ $hasKeywordArgument = true;
6694
+ } else {
6695
+ if (\is_null($splatSeparator)) {
6696
+ $splatSeparator = $val[1];
6697
+ }
6698
 
6699
+ $positionalArgs[] = $this->maybeReduce($reduce, $item);
6700
+ }
 
6701
  }
6702
+ } elseif ($val[0] !== Type::T_NULL) { // values other than null are treated a single-element lists, while null is the empty list
6703
+ $positionalArgs[] = $this->maybeReduce($reduce, $val);
6704
  }
6705
+ } elseif ($hasKeywordArgument) {
6706
+ throw new SassScriptException('Positional arguments must come before keyword arguments.');
6707
+ } else {
6708
+ $positionalArgs[] = $this->maybeReduce($reduce, $arg[1]);
6709
  }
 
6710
  }
6711
 
6712
+ return [$positionalArgs, $keywordArgs, $names, $splatSeparator, $hasSplat];
6713
+ }
6714
+
6715
+ /**
6716
+ * @param bool $reduce
6717
+ * @param array|Number $value
6718
+ *
6719
+ * @return array|Number
6720
+ */
6721
+ private function maybeReduce($reduce, $value)
6722
+ {
6723
+ if ($reduce) {
6724
+ return $this->reduce($value, true);
6725
  }
6726
 
6727
+ return $value;
6728
  }
6729
 
6730
  /**
6731
  * Apply argument values per definition
6732
  *
6733
+ * @param array[] $argDef
6734
+ * @param array|null $argValues
6735
  * @param boolean $storeInEnv
6736
  * @param boolean $reduce
6737
  * only used if $storeInEnv = false
6738
  *
6739
+ * @return array<string, array|Number>
6740
+ *
6741
+ * @phpstan-param list<array{0: string, 1: array|Number|null, 2: bool}> $argDef
6742
  *
6743
  * @throws \Exception
6744
  */
6746
  {
6747
  $output = [];
6748
 
6749
+ if (\is_null($argValues)) {
6750
+ $argValues = [];
6751
  }
6752
 
6753
  if ($storeInEnv) {
6754
  $storeEnv = $this->getStoreEnv();
6755
 
6756
+ $env = new Environment();
6757
  $env->store = $storeEnv->store;
6758
  }
6759
 
6760
+ $prototype = ['arguments' => [], 'rest_argument' => null];
6761
+ $originalRestArgumentName = null;
6762
 
6763
  foreach ($argDef as $i => $arg) {
6764
+ list($name, $default, $isVariable) = $arg;
6765
+ $normalizedName = str_replace('_', '-', $name);
6766
 
6767
+ if ($isVariable) {
6768
+ $originalRestArgumentName = $name;
6769
+ $prototype['rest_argument'] = $normalizedName;
6770
+ } else {
6771
+ $prototype['arguments'][] = [$normalizedName, $name, !empty($default) ? $default : null];
6772
+ }
6773
  }
6774
 
6775
+ list($positionalArgs, $namedArgs, $names, $splatSeparator, $hasSplat) = $this->evaluateArguments($argValues, $reduce);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6776
 
6777
+ $this->verifyPrototype($prototype, \count($positionalArgs), $names, $hasSplat);
 
 
 
 
 
 
 
 
 
 
6778
 
6779
+ $vars = $this->applyArgumentsToDeclaration($prototype, $positionalArgs, $namedArgs, $splatSeparator);
 
 
 
 
 
 
 
 
6780
 
6781
+ foreach ($prototype['arguments'] as $argument) {
6782
+ list($normalizedName, $name) = $argument;
 
 
 
 
 
6783
 
6784
+ if (!isset($vars[$normalizedName])) {
6785
+ continue;
6786
+ }
 
 
 
 
 
 
6787
 
6788
+ $val = $vars[$normalizedName];
 
 
 
 
 
 
 
 
6789
 
6790
+ if ($storeInEnv) {
6791
+ $this->set($name, $this->reduce($val, true), true, $env);
 
 
 
 
 
 
 
6792
  } else {
6793
+ $output[$name] = ($reduce ? $this->reduce($val, true) : $val);
6794
  }
6795
  }
6796
 
6797
+ if ($prototype['rest_argument'] !== null) {
6798
+ assert($originalRestArgumentName !== null);
6799
+ $name = $originalRestArgumentName;
6800
+ $val = $vars[$prototype['rest_argument']];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6801
 
6802
  if ($storeInEnv) {
6803
  $this->set($name, $this->reduce($val, true), true, $env);
6810
  $storeEnv->store = $env->store;
6811
  }
6812
 
6813
+ foreach ($prototype['arguments'] as $argument) {
6814
+ list($normalizedName, $name, $default) = $argument;
6815
 
6816
+ if (isset($vars[$normalizedName])) {
6817
  continue;
6818
  }
6819
+ assert($default !== null);
6820
 
6821
  if ($storeInEnv) {
6822
  $this->set($name, $this->reduce($default, true), true);
6828
  return $output;
6829
  }
6830
 
6831
+ /**
6832
+ * Apply argument values per definition.
6833
+ *
6834
+ * This method assumes that the arguments are valid for the provided prototype.
6835
+ * The validation with {@see verifyPrototype} must have been run before calling
6836
+ * it.
6837
+ * Arguments are returned as a map from the normalized argument names to the
6838
+ * value. Additional arguments are collected in a sass argument list available
6839
+ * under the name of the rest argument in the result.
6840
+ *
6841
+ * Defaults are not applied as they are resolved in a different environment.
6842
+ *
6843
+ * @param array $prototype
6844
+ * @param array<array|Number> $positionalArgs
6845
+ * @param array<string, array|Number> $namedArgs
6846
+ * @param string|null $splatSeparator
6847
+ *
6848
+ * @return array<string, array|Number>
6849
+ *
6850
+ * @phpstan-param array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null} $prototype
6851
+ */
6852
+ private function applyArgumentsToDeclaration(array $prototype, array $positionalArgs, array $namedArgs, $splatSeparator)
6853
+ {
6854
+ $output = [];
6855
+ $minLength = min(\count($positionalArgs), \count($prototype['arguments']));
6856
+
6857
+ for ($i = 0; $i < $minLength; $i++) {
6858
+ list($name) = $prototype['arguments'][$i];
6859
+ $val = $positionalArgs[$i];
6860
+
6861
+ $output[$name] = $val;
6862
+ }
6863
+
6864
+ $restNamed = $namedArgs;
6865
+
6866
+ for ($i = \count($positionalArgs); $i < \count($prototype['arguments']); $i++) {
6867
+ $argument = $prototype['arguments'][$i];
6868
+ list($name) = $argument;
6869
+
6870
+ if (isset($namedArgs[$name])) {
6871
+ $val = $namedArgs[$name];
6872
+ unset($restNamed[$name]);
6873
+ } else {
6874
+ continue;
6875
+ }
6876
+
6877
+ $output[$name] = $val;
6878
+ }
6879
+
6880
+ if ($prototype['rest_argument'] !== null) {
6881
+ $name = $prototype['rest_argument'];
6882
+ $rest = array_values(array_slice($positionalArgs, \count($prototype['arguments'])));
6883
+
6884
+ $val = [Type::T_LIST, \is_null($splatSeparator) ? ',' : $splatSeparator , $rest, $restNamed];
6885
+
6886
+ $output[$name] = $val;
6887
+ }
6888
+
6889
+ return $output;
6890
+ }
6891
+
6892
  /**
6893
  * Coerce a php value into a scss one
6894
  *
6895
  * @param mixed $value
6896
  *
6897
+ * @return array|Number
6898
  */
6899
  protected function coerceValue($value)
6900
  {
6901
+ if (\is_array($value) || $value instanceof Number) {
6902
  return $value;
6903
  }
6904
 
6911
  }
6912
 
6913
  if (is_numeric($value)) {
6914
+ return new Number($value, '');
6915
  }
6916
 
6917
  if ($value === '') {
6931
  /**
6932
  * Coerce something to map
6933
  *
6934
+ * @param array|Number $item
6935
  *
6936
+ * @return array|Number
6937
  */
6938
  protected function coerceMap($item)
6939
  {
6941
  return $item;
6942
  }
6943
 
6944
+ if (
6945
+ $item[0] === Type::T_LIST &&
6946
+ $item[2] === []
6947
  ) {
6948
  return static::$emptyMap;
6949
  }
6950
 
6951
+ return $item;
6952
  }
6953
 
6954
  /**
6955
  * Coerce something to list
6956
  *
6957
+ * @param array|Number $item
6958
+ * @param string $delim
6959
+ * @param boolean $removeTrailingNull
6960
  *
6961
  * @return array
6962
  */
6963
  protected function coerceList($item, $delim = ',', $removeTrailingNull = false)
6964
  {
6965
+ if ($item instanceof Number) {
6966
+ return [Type::T_LIST, $delim, [$item]];
6967
+ }
6968
+
6969
+ if ($item[0] === Type::T_LIST) {
6970
  // remove trailing null from the list
6971
  if ($removeTrailingNull && end($item[2]) === static::$null) {
6972
  array_pop($item[2]);
6975
  return $item;
6976
  }
6977
 
6978
+ if ($item[0] === Type::T_MAP) {
6979
  $keys = $item[1];
6980
  $values = $item[2];
6981
  $list = [];
7006
  return [Type::T_LIST, ',', $list];
7007
  }
7008
 
7009
+ return [Type::T_LIST, $delim, [$item]];
7010
  }
7011
 
7012
  /**
7013
  * Coerce color for expression
7014
  *
7015
+ * @param array|Number $value
7016
  *
7017
+ * @return array|Number
7018
  */
7019
  protected function coerceForExpression($value)
7020
  {
7028
  /**
7029
  * Coerce value to color
7030
  *
7031
+ * @param array|Number $value
7032
+ * @param bool $inRGBFunction
7033
  *
7034
  * @return array|null
7035
  */
7036
  protected function coerceColor($value, $inRGBFunction = false)
7037
  {
7038
+ if ($value instanceof Number) {
7039
+ return null;
7040
+ }
7041
+
7042
  switch ($value[0]) {
7043
  case Type::T_COLOR:
7044
  for ($i = 1; $i <= 3; $i++) {
7124
  if ($color[3] === 255) {
7125
  $color[3] = 1; // fully opaque
7126
  } else {
7127
+ $color[3] = round($color[3] / 255, Number::PRECISION);
7128
  }
7129
  }
7130
 
7147
  }
7148
 
7149
  /**
7150
+ * @param integer|Number $value
7151
+ * @param boolean $isAlpha
7152
  *
7153
  * @return integer|mixed
7154
  */
7166
  * @param integer|float $min
7167
  * @param integer|float $max
7168
  * @param boolean $isInt
 
 
7169
  *
7170
  * @return integer|mixed
7171
  */
7172
+ protected function compileColorPartValue($value, $min, $max, $isInt = true)
7173
  {
7174
  if (! is_numeric($value)) {
7175
  if (\is_array($value)) {
7176
  $reduced = $this->reduce($value);
7177
 
7178
+ if ($reduced instanceof Number) {
7179
  $value = $reduced;
7180
  }
7181
  }
7182
 
7183
+ if ($value instanceof Number) {
7184
+ if ($value->unitless()) {
7185
+ $num = $value->getDimension();
7186
+ } elseif ($value->hasUnit('%')) {
7187
+ $num = $max * $value->getDimension() / 100;
7188
+ } else {
7189
+ throw $this->error('Expected %s to have no units or "%%".', $value);
 
 
 
 
 
 
 
7190
  }
7191
 
7192
  $value = $num;
7200
  $value = round($value);
7201
  }
7202
 
7203
+ $value = min($max, max($min, $value));
 
 
 
 
 
 
 
 
 
 
 
7204
 
7205
  return $value;
7206
  }
7211
  /**
7212
  * Coerce value to string
7213
  *
7214
+ * @param array|Number $value
7215
  *
7216
+ * @return array
7217
  */
7218
  protected function coerceString($value)
7219
  {
7224
  return [Type::T_STRING, '', [$this->compileValue($value)]];
7225
  }
7226
 
7227
+ /**
7228
+ * Assert value is a string
7229
+ *
7230
+ * This method deals with internal implementation details of the value
7231
+ * representation where unquoted strings can sometimes be stored under
7232
+ * other types.
7233
+ * The returned value is always using the T_STRING type.
7234
+ *
7235
+ * @api
7236
+ *
7237
+ * @param array|Number $value
7238
+ * @param string|null $varName
7239
+ *
7240
+ * @return array
7241
+ *
7242
+ * @throws SassScriptException
7243
+ */
7244
+ public function assertString($value, $varName = null)
7245
+ {
7246
+ // case of url(...) parsed a a function
7247
+ if ($value[0] === Type::T_FUNCTION) {
7248
+ $value = $this->coerceString($value);
7249
+ }
7250
+
7251
+ if (! \in_array($value[0], [Type::T_STRING, Type::T_KEYWORD])) {
7252
+ $value = $this->compileValue($value);
7253
+ throw SassScriptException::forArgument("$value is not a string.", $varName);
7254
+ }
7255
+
7256
+ return $this->coerceString($value);
7257
+ }
7258
+
7259
  /**
7260
  * Coerce value to a percentage
7261
  *
7262
+ * @param array|Number $value
7263
  *
7264
  * @return integer|float
7265
+ *
7266
+ * @deprecated
7267
  */
7268
  protected function coercePercent($value)
7269
  {
7270
+ @trigger_error(sprintf('"%s" is deprecated since 1.7.0.', __METHOD__), E_USER_DEPRECATED);
7271
+
7272
+ if ($value instanceof Number) {
7273
+ if ($value->hasUnit('%')) {
7274
+ return $value->getDimension() / 100;
7275
  }
7276
 
7277
+ return $value->getDimension();
7278
  }
7279
 
7280
  return 0;
7285
  *
7286
  * @api
7287
  *
7288
+ * @param array|Number $value
7289
+ * @param string|null $varName
7290
  *
7291
  * @return array
7292
  *
7293
+ * @throws SassScriptException
7294
  */
7295
+ public function assertMap($value, $varName = null)
7296
  {
7297
  $value = $this->coerceMap($value);
7298
 
7299
  if ($value[0] !== Type::T_MAP) {
7300
+ $value = $this->compileValue($value);
7301
+
7302
+ throw SassScriptException::forArgument("$value is not a map.", $varName);
7303
  }
7304
 
7305
  return $value;
7310
  *
7311
  * @api
7312
  *
7313
+ * @param array|Number $value
7314
+ *
7315
+ * @return array
7316
+ *
7317
+ * @throws \Exception
7318
+ */
7319
+ public function assertList($value)
7320
+ {
7321
+ if ($value[0] !== Type::T_LIST) {
7322
+ throw $this->error('expecting list, %s received', $value[0]);
7323
+ }
7324
+
7325
+ return $value;
7326
+ }
7327
+
7328
+ /**
7329
+ * Gets the keywords of an argument list.
7330
+ *
7331
+ * Keys in the returned array are normalized names (underscores are replaced with dashes)
7332
+ * without the leading `$`.
7333
+ * Calling this helper with anything that an argument list received for a rest argument
7334
+ * of the function argument declaration is not supported.
7335
+ *
7336
+ * @param array|Number $value
7337
+ *
7338
+ * @return array<string, array|Number>
7339
+ */
7340
+ public function getArgumentListKeywords($value)
7341
+ {
7342
+ if ($value[0] !== Type::T_LIST || !isset($value[3]) || !\is_array($value[3])) {
7343
+ throw new \InvalidArgumentException('The argument is not a sass argument list.');
7344
+ }
7345
+
7346
+ return $value[3];
7347
+ }
7348
+
7349
+ /**
7350
+ * Assert value is a color
7351
+ *
7352
+ * @api
7353
+ *
7354
+ * @param array|Number $value
7355
+ * @param string|null $varName
7356
  *
7357
  * @return array
7358
  *
7359
+ * @throws SassScriptException
7360
+ */
7361
+ public function assertColor($value, $varName = null)
7362
+ {
7363
+ if ($color = $this->coerceColor($value)) {
7364
+ return $color;
7365
+ }
7366
+
7367
+ $value = $this->compileValue($value);
7368
+
7369
+ throw SassScriptException::forArgument("$value is not a color.", $varName);
7370
+ }
7371
+
7372
+ /**
7373
+ * Assert value is a number
7374
+ *
7375
+ * @api
7376
+ *
7377
+ * @param array|Number $value
7378
+ * @param string|null $varName
7379
+ *
7380
+ * @return Number
7381
+ *
7382
+ * @throws SassScriptException
7383
  */
7384
+ public function assertNumber($value, $varName = null)
7385
  {
7386
+ if (!$value instanceof Number) {
7387
+ $value = $this->compileValue($value);
7388
+ throw SassScriptException::forArgument("$value is not a number.", $varName);
7389
  }
7390
 
7391
  return $value;
7392
  }
7393
 
7394
  /**
7395
+ * Assert value is a integer
7396
  *
7397
  * @api
7398
  *
7399
+ * @param array|Number $value
7400
+ * @param string|null $varName
7401
  *
7402
+ * @return integer
7403
  *
7404
+ * @throws SassScriptException
7405
  */
7406
+ public function assertInteger($value, $varName = null)
7407
  {
7408
+ $value = $this->assertNumber($value, $varName)->getDimension();
7409
+ if (round($value - \intval($value), Number::PRECISION) > 0) {
7410
+ throw SassScriptException::forArgument("$value is not an integer.", $varName);
7411
  }
7412
 
7413
+ return intval($value);
7414
  }
7415
 
7416
  /**
7417
+ * Extract the ... / alpha on the last argument of channel arg
7418
+ * in color functions
 
 
 
 
 
7419
  *
7420
+ * @param array $args
7421
+ * @return array
7422
  */
7423
+ private function extractSlashAlphaInColorFunction($args)
7424
  {
7425
+ $last = end($args);
7426
+ if (\count($args) === 3 && $last[0] === Type::T_EXPRESSION && $last[1] === '/') {
7427
+ array_pop($args);
7428
+ $args[] = $last[2];
7429
+ $args[] = $last[3];
7430
  }
7431
+ return $args;
 
7432
  }
7433
 
7434
+
7435
  /**
7436
  * Make sure a color's components don't go out of bounds
7437
  *
7449
  if ($c[$i] > 255) {
7450
  $c[$i] = 255;
7451
  }
7452
+
7453
+ if (!\is_int($c[$i])) {
7454
+ $c[$i] = round($c[$i]);
7455
+ }
7456
  }
7457
 
7458
  return $c;
7461
  /**
7462
  * Convert RGB to HSL
7463
  *
7464
+ * @internal
7465
  *
7466
  * @param integer $red
7467
  * @param integer $green
7495
  }
7496
  }
7497
 
7498
+ return [Type::T_HSL, fmod($h + 360, 360), $s * 100, $l / 5.1];
7499
  }
7500
 
7501
  /**
7524
  }
7525
 
7526
  if ($h * 3 < 2) {
7527
+ return $m1 + ($m2 - $m1) * (2 / 3 - $h) * 6;
7528
  }
7529
 
7530
  return $m1;
7533
  /**
7534
  * Convert HSL to RGB
7535
  *
7536
+ * @internal
7537
  *
7538
+ * @param int|float $hue H from 0 to 360
7539
+ * @param int|float $saturation S from 0 to 100
7540
+ * @param int|float $lightness L from 0 to 100
7541
  *
7542
  * @return array
7543
  */
7554
  $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
7555
  $m1 = $l * 2 - $m2;
7556
 
7557
+ $r = $this->hueToRGB($m1, $m2, $h + 1 / 3) * 255;
7558
  $g = $this->hueToRGB($m1, $m2, $h) * 255;
7559
+ $b = $this->hueToRGB($m1, $m2, $h - 1 / 3) * 255;
7560
 
7561
  $out = [Type::T_COLOR, $r, $g, $b];
7562
 
7563
  return $out;
7564
  }
7565
 
7566
+ /**
7567
+ * Convert HWB to RGB
7568
+ * https://www.w3.org/TR/css-color-4/#hwb-to-rgb
7569
+ *
7570
+ * @api
7571
+ *
7572
+ * @param integer $hue H from 0 to 360
7573
+ * @param integer $whiteness W from 0 to 100
7574
+ * @param integer $blackness B from 0 to 100
7575
+ *
7576
+ * @return array
7577
+ */
7578
+ private function HWBtoRGB($hue, $whiteness, $blackness)
7579
+ {
7580
+ $w = min(100, max(0, $whiteness)) / 100;
7581
+ $b = min(100, max(0, $blackness)) / 100;
7582
+
7583
+ $sum = $w + $b;
7584
+ if ($sum > 1.0) {
7585
+ $w = $w / $sum;
7586
+ $b = $b / $sum;
7587
+ }
7588
+ $b = min(1.0 - $w, $b);
7589
+
7590
+ $rgb = $this->toRGB($hue, 100, 50);
7591
+ for($i = 1; $i < 4; $i++) {
7592
+ $rgb[$i] *= (1.0 - $w - $b);
7593
+ $rgb[$i] = round($rgb[$i] + 255 * $w + 0.0001);
7594
+ }
7595
 
7596
+ return $rgb;
7597
+ }
7598
+
7599
+ /**
7600
+ * Convert RGB to HWB
7601
+ *
7602
+ * @api
7603
+ *
7604
+ * @param integer $red
7605
+ * @param integer $green
7606
+ * @param integer $blue
7607
+ *
7608
+ * @return array
7609
+ */
7610
+ private function RGBtoHWB($red, $green, $blue)
7611
  {
7612
+ $min = min($red, $green, $blue);
7613
+ $max = max($red, $green, $blue);
7614
 
7615
+ $d = $max - $min;
7616
+
7617
+ if ((int) $d === 0) {
7618
+ $h = 0;
7619
+ } else {
7620
+
7621
+ if ($red == $max) {
7622
+ $h = 60 * ($green - $blue) / $d;
7623
+ } elseif ($green == $max) {
7624
+ $h = 60 * ($blue - $red) / $d + 120;
7625
+ } elseif ($blue == $max) {
7626
+ $h = 60 * ($red - $green) / $d + 240;
7627
  }
7628
+ }
7629
+
7630
+ return [Type::T_HWB, fmod($h, 360), $min / 255 * 100, 100 - $max / 255 *100];
7631
+ }
7632
+
7633
+
7634
+ // Built in functions
7635
+
7636
+ protected static $libCall = ['function', 'args...'];
7637
+ protected function libCall($args)
7638
+ {
7639
+ $functionReference = $args[0];
7640
+
7641
+ if (in_array($functionReference[0], [Type::T_STRING, Type::T_KEYWORD])) {
7642
+ $name = $this->compileStringContent($this->coerceString($functionReference));
7643
+ $warning = "Passing a string to call() is deprecated and will be illegal\n"
7644
+ . "in Sass 4.0. Use call(function-reference($name)) instead.";
7645
+ Warn::deprecation($warning);
7646
+ $functionReference = $this->libGetFunction([$this->assertString($functionReference, 'function')]);
7647
+ }
7648
+
7649
+ if ($functionReference === static::$null) {
7650
+ return static::$null;
7651
+ }
7652
+
7653
+ if (! in_array($functionReference[0], [Type::T_FUNCTION_REFERENCE, Type::T_FUNCTION])) {
7654
+ throw $this->error('Function reference expected, got ' . $functionReference[0]);
7655
+ }
7656
+
7657
+ $callArgs = [
7658
+ [null, $args[1], true]
7659
+ ];
7660
+
7661
+ return $this->reduce([Type::T_FUNCTION_CALL, $functionReference, $callArgs]);
7662
+ }
7663
+
7664
+
7665
+ protected static $libGetFunction = [
7666
+ ['name'],
7667
+ ['name', 'css']
7668
+ ];
7669
+ protected function libGetFunction($args)
7670
+ {
7671
+ $name = $this->compileStringContent($this->assertString(array_shift($args), 'name'));
7672
+ $isCss = false;
7673
+
7674
+ if (count($args)) {
7675
+ $isCss = array_shift($args);
7676
+ $isCss = (($isCss === static::$true) ? true : false);
7677
+ }
7678
 
7679
+ if ($isCss) {
7680
+ return [Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]];
7681
  }
7682
 
7683
+ return $this->getFunctionReference($name, true);
7684
  }
7685
 
7686
  protected static $libIf = ['condition', 'if-true', 'if-false:'];
7700
  {
7701
  list($list, $value) = $args;
7702
 
7703
+ if (
7704
+ $list[0] === Type::T_MAP ||
7705
  $list[0] === Type::T_STRING ||
7706
  $list[0] === Type::T_KEYWORD ||
7707
  $list[0] === Type::T_INTERPOLATE
7713
  return static::$null;
7714
  }
7715
 
7716
+ // Numbers are represented with value objects, for which the PHP equality operator does not
7717
+ // match the Sass rules (and we cannot overload it). As they are the only type of values
7718
+ // represented with a value object for now, they require a special case.
7719
+ if ($value instanceof Number) {
7720
+ $key = 0;
7721
+ foreach ($list[2] as $item) {
7722
+ $key++;
7723
+ $itemValue = $this->normalizeValue($item);
7724
+
7725
+ if ($itemValue instanceof Number && $value->equals($itemValue)) {
7726
+ return new Number($key, '');
7727
+ }
7728
+ }
7729
+ return static::$null;
7730
+ }
7731
+
7732
  $values = [];
7733
 
7734
+
7735
  foreach ($list[2] as $item) {
7736
  $values[] = $this->normalizeValue($item);
7737
  }
7738
 
7739
  $key = array_search($this->normalizeValue($value), $values);
7740
 
7741
+ return false === $key ? static::$null : new Number($key + 1, '');
7742
  }
7743
 
7744
  protected static $libRgb = [
7760
  $color = [Type::T_COLOR, $args[0], $args[1], $args[2]];
7761
 
7762
  if (! $color = $this->coerceColor($color)) {
7763
+ $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
7764
  }
7765
 
7766
  return $color;
7776
  [$funcName . '(', $color[1], ', ', $color[2], ', ', $color[3], ', ', $alpha, ')']];
7777
  }
7778
  } else {
7779
+ $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ')']];
7780
  }
7781
  break;
7782
 
7805
  return $this->libRgb($args, $kwargs, 'rgba');
7806
  }
7807
 
7808
+ /**
7809
+ * Helper function for adjust_color, change_color, and scale_color
7810
+ *
7811
+ * @param array<array|Number> $args
7812
+ * @param string $operation
7813
+ * @param callable $fn
7814
+ *
7815
+ * @return array
7816
+ *
7817
+ * @phpstan-param callable(float|int, float|int|null, float|int): (float|int) $fn
7818
+ */
7819
+ protected function alterColor(array $args, $operation, $fn)
7820
  {
7821
+ $color = $this->assertColor($args[0], 'color');
7822
+
7823
+ if ($args[1][2]) {
7824
+ throw new SassScriptException('Only one positional argument is allowed. All other arguments must be passed by name.');
7825
+ }
7826
+
7827
+ $kwargs = $this->getArgumentListKeywords($args[1]);
7828
+
7829
+ $scale = $operation === 'scale';
7830
+ $change = $operation === 'change';
7831
+
7832
+ /**
7833
+ * @param string $name
7834
+ * @param float|int $max
7835
+ * @param bool $checkPercent
7836
+ * @param bool $assertPercent
7837
+ *
7838
+ * @return float|int|null
7839
+ */
7840
+ $getParam = function ($name, $max, $checkPercent = false, $assertPercent = false) use (&$kwargs, $scale, $change) {
7841
+ if (!isset($kwargs[$name])) {
7842
+ return null;
7843
+ }
7844
 
7845
+ $number = $this->assertNumber($kwargs[$name], $name);
7846
+ unset($kwargs[$name]);
 
7847
 
7848
+ if (!$scale && $checkPercent) {
7849
+ if (!$number->hasUnit('%')) {
7850
+ $warning = $this->error("{$name} Passing a number `$number` without unit % is deprecated.");
7851
+ $this->logger->warn($warning->getMessage(), true);
7852
  }
7853
+ }
7854
+
7855
+ if ($scale || $assertPercent) {
7856
+ $number->assertUnit('%', $name);
7857
+ }
7858
 
7859
+ if ($scale) {
7860
+ $max = 100;
7861
  }
7862
+
7863
+ return $number->valueInRange($change ? 0 : -$max, $max, $name);
7864
+ };
7865
+
7866
+ $alpha = $getParam('alpha', 1);
7867
+ $red = $getParam('red', 255);
7868
+ $green = $getParam('green', 255);
7869
+ $blue = $getParam('blue', 255);
7870
+
7871
+ if ($scale || !isset($kwargs['hue'])) {
7872
+ $hue = null;
7873
+ } else {
7874
+ $hueNumber = $this->assertNumber($kwargs['hue'], 'hue');
7875
+ unset($kwargs['hue']);
7876
+ $hue = $hueNumber->getDimension();
7877
+ }
7878
+ $saturation = $getParam('saturation', 100, true);
7879
+ $lightness = $getParam('lightness', 100, true);
7880
+ $whiteness = $getParam('whiteness', 100, false, true);
7881
+ $blackness = $getParam('blackness', 100, false, true);
7882
+
7883
+ if (!empty($kwargs)) {
7884
+ $unknownNames = array_keys($kwargs);
7885
+ $lastName = array_pop($unknownNames);
7886
+ $message = sprintf(
7887
+ 'No argument%s named $%s%s.',
7888
+ $unknownNames ? 's' : '',
7889
+ $unknownNames ? implode(', $', $unknownNames) . ' or $' : '',
7890
+ $lastName
7891
+ );
7892
+ throw new SassScriptException($message);
7893
+ }
7894
+
7895
+ $hasRgb = $red !== null || $green !== null || $blue !== null;
7896
+ $hasSL = $saturation !== null || $lightness !== null;
7897
+ $hasWB = $whiteness !== null || $blackness !== null;
7898
+ $found = false;
7899
+
7900
+ if ($hasRgb && ($hasSL || $hasWB || $hue !== null)) {
7901
+ throw new SassScriptException(sprintf('RGB parameters may not be passed along with %s parameters.', $hasWB ? 'HWB' : 'HSL'));
7902
+ }
7903
+
7904
+ if ($hasWB && $hasSL) {
7905
+ throw new SassScriptException('HSL parameters may not be passed along with HWB parameters.');
7906
  }
7907
 
7908
+ if ($hasRgb) {
7909
+ $color[1] = round($fn($color[1], $red, 255));
7910
+ $color[2] = round($fn($color[2], $green, 255));
7911
+ $color[3] = round($fn($color[3], $blue, 255));
7912
+ } elseif ($hasWB) {
7913
+ $hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]);
7914
+ if ($hue !== null) {
7915
+ $hwb[1] = $change ? $hue : $hwb[1] + $hue;
7916
+ }
7917
+ $hwb[2] = $fn($hwb[2], $whiteness, 100);
7918
+ $hwb[3] = $fn($hwb[3], $blackness, 100);
7919
+
7920
+ $rgb = $this->HWBtoRGB($hwb[1], $hwb[2], $hwb[3]);
7921
+
7922
+ if (isset($color[4])) {
7923
+ $rgb[4] = $color[4];
7924
+ }
7925
+
7926
+ $color = $rgb;
7927
+ } elseif ($hue !== null || $hasSL) {
7928
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
7929
 
7930
+ if ($hue !== null) {
7931
+ $hsl[1] = $change ? $hue : $hsl[1] + $hue;
 
 
 
7932
  }
7933
+ $hsl[2] = $fn($hsl[2], $saturation, 100);
7934
+ $hsl[3] = $fn($hsl[3], $lightness, 100);
7935
 
7936
  $rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
7937
 
7942
  $color = $rgb;
7943
  }
7944
 
7945
+ if ($alpha !== null) {
7946
+ $existingAlpha = isset($color[4]) ? $color[4] : 1;
7947
+ $color[4] = $fn($existingAlpha, $alpha, 1);
7948
+ }
7949
+
7950
  return $color;
7951
  }
7952
 
7953
+ protected static $libAdjustColor = ['color', 'kwargs...'];
 
 
 
7954
  protected function libAdjustColor($args)
7955
  {
7956
+ return $this->alterColor($args, 'adjust', function ($base, $alter, $max) {
7957
+ if ($alter === null) {
7958
+ return $base;
7959
+ }
7960
+
7961
+ $new = $base + $alter;
7962
+
7963
+ if ($new < 0) {
7964
+ return 0;
7965
+ }
7966
+
7967
+ if ($new > $max) {
7968
+ return $max;
7969
+ }
7970
+
7971
+ return $new;
7972
  });
7973
  }
7974
 
7975
+ protected static $libChangeColor = ['color', 'kwargs...'];
 
 
 
7976
  protected function libChangeColor($args)
7977
  {
7978
+ return $this->alterColor($args,'change', function ($base, $alter, $max) {
7979
+ if ($alter === null) {
7980
+ return $base;
7981
+ }
7982
+
7983
  return $alter;
7984
  });
7985
  }
7986
 
7987
+ protected static $libScaleColor = ['color', 'kwargs...'];
 
 
 
7988
  protected function libScaleColor($args)
7989
  {
7990
+ return $this->alterColor($args, 'scale', function ($base, $scale, $max) {
7991
+ if ($scale === null) {
7992
+ return $base;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7993
  }
7994
 
7995
  $scale = $scale / 100;
8006
  protected function libIeHexStr($args)
8007
  {
8008
  $color = $this->coerceColor($args[0]);
8009
+
8010
+ if (\is_null($color)) {
8011
+ throw $this->error('Error: argument `$color` of `ie-hex-str($color)` must be a color');
8012
+ }
8013
+
8014
  $color[4] = isset($color[4]) ? round(255 * $color[4]) : 255;
8015
 
8016
  return [Type::T_STRING, '', [sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3])]];
8021
  {
8022
  $color = $this->coerceColor($args[0]);
8023
 
8024
+ if (\is_null($color)) {
8025
+ throw $this->error('Error: argument `$color` of `red($color)` must be a color');
8026
+ }
8027
+
8028
+ return new Number((int) $color[1], '');
8029
  }
8030
 
8031
  protected static $libGreen = ['color'];
8033
  {
8034
  $color = $this->coerceColor($args[0]);
8035
 
8036
+ if (\is_null($color)) {
8037
+ throw $this->error('Error: argument `$color` of `green($color)` must be a color');
8038
+ }
8039
+
8040
+ return new Number((int) $color[2], '');
8041
  }
8042
 
8043
  protected static $libBlue = ['color'];
8045
  {
8046
  $color = $this->coerceColor($args[0]);
8047
 
8048
+ if (\is_null($color)) {
8049
+ throw $this->error('Error: argument `$color` of `blue($color)` must be a color');
8050
+ }
8051
+
8052
+ return new Number((int) $color[3], '');
8053
  }
8054
 
8055
  protected static $libAlpha = ['color'];
8056
  protected function libAlpha($args)
8057
  {
8058
  if ($color = $this->coerceColor($args[0])) {
8059
+ return new Number(isset($color[4]) ? $color[4] : 1, '');
8060
  }
8061
 
8062
  // this might be the IE function, so return value unchanged
8068
  {
8069
  $value = $args[0];
8070
 
8071
+ if ($value instanceof Number) {
8072
  return null;
8073
  }
8074
 
8076
  }
8077
 
8078
  // mix two colors
8079
+ protected static $libMix = [
8080
+ ['color1', 'color2', 'weight:50%'],
8081
+ ['color-1', 'color-2', 'weight:50%']
8082
+ ];
8083
  protected function libMix($args)
8084
  {
8085
  list($first, $second, $weight) = $args;
8086
 
8087
+ $first = $this->assertColor($first, 'color1');
8088
+ $second = $this->assertColor($second, 'color2');
8089
+ $weightScale = $this->assertNumber($weight, 'weight')->valueInRange(0, 100, 'weight') / 100;
 
 
 
 
 
8090
 
8091
  $firstAlpha = isset($first[4]) ? $first[4] : 1;
8092
  $secondAlpha = isset($second[4]) ? $second[4] : 1;
8093
 
8094
+ $normalizedWeight = $weightScale * 2 - 1;
8095
+ $alphaDistance = $firstAlpha - $secondAlpha;
8096
 
8097
+ $combinedWeight = $normalizedWeight * $alphaDistance == -1 ? $normalizedWeight : ($normalizedWeight + $alphaDistance) / (1 + $normalizedWeight * $alphaDistance);
8098
+ $weight1 = ($combinedWeight + 1) / 2.0;
8099
+ $weight2 = 1.0 - $weight1;
8100
 
8101
  $new = [Type::T_COLOR,
8102
+ $weight1 * $first[1] + $weight2 * $second[1],
8103
+ $weight1 * $first[2] + $weight2 * $second[2],
8104
+ $weight1 * $first[3] + $weight2 * $second[3],
8105
  ];
8106
 
8107
  if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
8108
+ $new[] = $firstAlpha * $weightScale + $secondAlpha * (1 - $weightScale);
8109
+ }
8110
+
8111
+ return $this->fixColor($new);
8112
+ }
8113
+
8114
+ protected static $libHsl = [
8115
+ ['channels'],
8116
+ ['hue', 'saturation'],
8117
+ ['hue', 'saturation', 'lightness'],
8118
+ ['hue', 'saturation', 'lightness', 'alpha'] ];
8119
+ protected function libHsl($args, $kwargs, $funcName = 'hsl')
8120
+ {
8121
+ $args_to_check = $args;
8122
+
8123
+ if (\count($args) == 1) {
8124
+ if ($args[0][0] !== Type::T_LIST || \count($args[0][2]) < 3 || \count($args[0][2]) > 4) {
8125
+ return [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
8126
+ }
8127
+
8128
+ $args = $args[0][2];
8129
+ $args_to_check = $kwargs['channels'][2];
8130
+ }
8131
+
8132
+ if (\count($args) === 2) {
8133
+ // if var() is used as an argument, return as a css function
8134
+ foreach ($args as $arg) {
8135
+ if ($arg[0] === Type::T_FUNCTION && in_array($arg[1], ['var'])) {
8136
+ return null;
8137
+ }
8138
+ }
8139
+
8140
+ throw new SassScriptException('Missing argument $lightness.');
8141
+ }
8142
+
8143
+ foreach ($kwargs as $k => $arg) {
8144
+ if (in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) && in_array($arg[1], ['min', 'max'])) {
8145
+ return null;
8146
+ }
8147
+ }
8148
+
8149
+ foreach ($args_to_check as $k => $arg) {
8150
+ if (in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) && in_array($arg[1], ['min', 'max'])) {
8151
+ if (count($kwargs) > 1 || ($k >= 2 && count($args) === 4)) {
8152
+ return null;
8153
+ }
8154
+
8155
+ $args[$k] = $this->stringifyFncallArgs($arg);
8156
+ }
8157
+
8158
+ if (
8159
+ $k >= 2 && count($args) === 4 &&
8160
+ in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) &&
8161
+ in_array($arg[1], ['calc','env'])
8162
+ ) {
8163
+ return null;
8164
+ }
8165
+ }
8166
+
8167
+ $hue = $this->reduce($args[0]);
8168
+ $saturation = $this->reduce($args[1]);
8169
+ $lightness = $this->reduce($args[2]);
8170
+ $alpha = null;
8171
+
8172
+ if (\count($args) === 4) {
8173
+ $alpha = $this->compileColorPartValue($args[3], 0, 100, false);
8174
+
8175
+ if (!$hue instanceof Number || !$saturation instanceof Number || ! $lightness instanceof Number || ! is_numeric($alpha)) {
8176
+ return [Type::T_STRING, '',
8177
+ [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']];
8178
+ }
8179
+ } else {
8180
+ if (!$hue instanceof Number || !$saturation instanceof Number || ! $lightness instanceof Number) {
8181
+ return [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
8182
+ }
8183
+ }
8184
+
8185
+ $hueValue = fmod($hue->getDimension(), 360);
8186
+
8187
+ while ($hueValue < 0) {
8188
+ $hueValue += 360;
8189
  }
8190
 
8191
+ $color = $this->toRGB($hueValue, max(0, min($saturation->getDimension(), 100)), max(0, min($lightness->getDimension(), 100)));
8192
+
8193
+ if (! \is_null($alpha)) {
8194
+ $color[4] = $alpha;
8195
+ }
8196
+
8197
+ return $color;
8198
+ }
8199
+
8200
+ protected static $libHsla = [
8201
+ ['channels'],
8202
+ ['hue', 'saturation'],
8203
+ ['hue', 'saturation', 'lightness'],
8204
+ ['hue', 'saturation', 'lightness', 'alpha']];
8205
+ protected function libHsla($args, $kwargs)
8206
+ {
8207
+ return $this->libHsl($args, $kwargs, 'hsla');
8208
+ }
8209
+
8210
+ protected static $libHue = ['color'];
8211
+ protected function libHue($args)
8212
+ {
8213
+ $color = $this->assertColor($args[0], 'color');
8214
+ $hsl = $this->toHSL($color[1], $color[2], $color[3]);
8215
+
8216
+ return new Number($hsl[1], 'deg');
8217
+ }
8218
+
8219
+ protected static $libSaturation = ['color'];
8220
+ protected function libSaturation($args)
8221
+ {
8222
+ $color = $this->assertColor($args[0], 'color');
8223
+ $hsl = $this->toHSL($color[1], $color[2], $color[3]);
8224
+
8225
+ return new Number($hsl[2], '%');
8226
+ }
8227
+
8228
+ protected static $libLightness = ['color'];
8229
+ protected function libLightness($args)
8230
+ {
8231
+ $color = $this->assertColor($args[0], 'color');
8232
+ $hsl = $this->toHSL($color[1], $color[2], $color[3]);
8233
+
8234
+ return new Number($hsl[3], '%');
8235
  }
8236
 
8237
+ /*
8238
+ * Todo : a integrer dans le futur module color
8239
+ protected static $libHwb = [
8240
  ['channels'],
8241
+ ['hue', 'whiteness', 'blackness'],
8242
+ ['hue', 'whiteness', 'blackness', 'alpha'] ];
8243
+ protected function libHwb($args, $kwargs, $funcName = 'hwb')
8244
  {
8245
+ $args_to_check = $args;
8246
+
8247
  if (\count($args) == 1) {
8248
+ if ($args[0][0] !== Type::T_LIST) {
8249
+ throw $this->error("Missing elements \$whiteness and \$blackness");
8250
+ }
8251
+
8252
+ if (\trim($args[0][1])) {
8253
+ throw $this->error("\$channels must be a space-separated list.");
8254
+ }
8255
+
8256
+ if (! empty($args[0]['enclosing'])) {
8257
+ throw $this->error("\$channels must be an unbracketed list.");
8258
  }
8259
 
8260
  $args = $args[0][2];
8261
+ if (\count($args) > 3) {
8262
+ throw $this->error("hwb() : Only 3 elements are allowed but ". \count($args) . "were passed");
8263
+ }
8264
+
8265
+ $args_to_check = $this->extractSlashAlphaInColorFunction($kwargs['channels'][2]);
8266
+ if (\count($args_to_check) !== \count($kwargs['channels'][2])) {
8267
+ $args = $args_to_check;
8268
+ }
8269
  }
8270
 
8271
+ if (\count($args_to_check) < 2) {
8272
+ throw $this->error("Missing elements \$whiteness and \$blackness");
8273
+ }
8274
+ if (\count($args_to_check) < 3) {
8275
+ throw $this->error("Missing element \$blackness");
8276
+ }
8277
+ if (\count($args_to_check) > 4) {
8278
+ throw $this->error("hwb() : Only 4 elements are allowed but ". \count($args) . "were passed");
8279
+ }
8280
+
8281
+ foreach ($kwargs as $k => $arg) {
8282
+ if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
8283
+ return null;
8284
+ }
8285
+ }
8286
+
8287
+ foreach ($args_to_check as $k => $arg) {
8288
+ if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
8289
+ if (count($kwargs) > 1 || ($k >= 2 && count($args) === 4)) {
8290
+ return null;
8291
+ }
8292
+
8293
+ $args[$k] = $this->stringifyFncallArgs($arg);
8294
+ }
8295
+
8296
+ if (
8297
+ $k >= 2 && count($args) === 4 &&
8298
+ in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) &&
8299
+ in_array($arg[1], ['calc','env'])
8300
+ ) {
8301
+ return null;
8302
+ }
8303
+ }
8304
 
8305
+ $hue = $this->reduce($args[0]);
8306
+ $whiteness = $this->reduce($args[1]);
8307
+ $blackness = $this->reduce($args[2]);
8308
  $alpha = null;
8309
 
8310
  if (\count($args) === 4) {
8311
+ $alpha = $this->compileColorPartValue($args[3], 0, 1, false);
8312
 
8313
+ if (! \is_numeric($alpha)) {
8314
+ $val = $this->compileValue($args[3]);
8315
+ throw $this->error("\$alpha: $val is not a number");
 
 
 
 
8316
  }
8317
  }
8318
 
8319
+ $this->assertNumber($hue, 'hue');
8320
+ $this->assertUnit($whiteness, ['%'], 'whiteness');
8321
+ $this->assertUnit($blackness, ['%'], 'blackness');
8322
+
8323
+ $this->assertRange($whiteness, 0, 100, "0% and 100%", "whiteness");
8324
+ $this->assertRange($blackness, 0, 100, "0% and 100%", "blackness");
8325
+
8326
+ $w = $whiteness->getDimension();
8327
+ $b = $blackness->getDimension();
8328
+
8329
+ $hueValue = $hue->getDimension() % 360;
8330
+
8331
+ while ($hueValue < 0) {
8332
+ $hueValue += 360;
8333
+ }
8334
+
8335
+ $color = $this->HWBtoRGB($hueValue, $w, $b);
8336
 
8337
  if (! \is_null($alpha)) {
8338
  $color[4] = $alpha;
8341
  return $color;
8342
  }
8343
 
8344
+ protected static $libWhiteness = ['color'];
8345
+ protected function libWhiteness($args, $kwargs, $funcName = 'whiteness') {
 
 
 
 
 
8346
 
 
 
 
8347
  $color = $this->assertColor($args[0]);
8348
+ $hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]);
8349
 
8350
+ return new Number($hwb[2], '%');
8351
  }
8352
 
8353
+ protected static $libBlackness = ['color'];
8354
+ protected function libBlackness($args, $kwargs, $funcName = 'blackness') {
 
 
 
 
 
 
8355
 
 
 
 
8356
  $color = $this->assertColor($args[0]);
8357
+ $hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]);
8358
 
8359
+ return new Number($hwb[3], '%');
8360
  }
8361
+ */
8362
 
8363
  protected function adjustHsl($color, $idx, $amount)
8364
  {
8365
  $hsl = $this->toHSL($color[1], $color[2], $color[3]);
8366
  $hsl[$idx] += $amount;
8367
+
8368
+ if ($idx !== 1) {
8369
+ // Clamp the saturation and lightness
8370
+ $hsl[$idx] = min(max(0, $hsl[$idx]), 100);
8371
+ }
8372
+
8373
  $out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
8374
 
8375
  if (isset($color[4])) {
8382
  protected static $libAdjustHue = ['color', 'degrees'];
8383
  protected function libAdjustHue($args)
8384
  {
8385
+ $color = $this->assertColor($args[0], 'color');
8386
+ $degrees = $this->assertNumber($args[1], 'degrees')->getDimension();
8387
 
8388
  return $this->adjustHsl($color, 1, $degrees);
8389
  }
8391
  protected static $libLighten = ['color', 'amount'];
8392
  protected function libLighten($args)
8393
  {
8394
+ $color = $this->assertColor($args[0], 'color');
8395
  $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
8396
 
8397
  return $this->adjustHsl($color, 3, $amount);
8400
  protected static $libDarken = ['color', 'amount'];
8401
  protected function libDarken($args)
8402
  {
8403
+ $color = $this->assertColor($args[0], 'color');
8404
  $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
8405
 
8406
  return $this->adjustHsl($color, 3, -$amount);
8407
  }
8408
 
8409
+ protected static $libSaturate = [['color', 'amount'], ['amount']];
8410
  protected function libSaturate($args)
8411
  {
8412
  $value = $args[0];
8413
 
8414
+ if (count($args) === 1) {
8415
+ $this->assertNumber($args[0], 'amount');
8416
+
8417
  return null;
8418
  }
8419
 
8420
+ $color = $this->assertColor($args[0], 'color');
8421
+ $amount = $this->assertNumber($args[1], 'amount');
8422
 
8423
+ return $this->adjustHsl($color, 2, $amount->valueInRange(0, 100, 'amount'));
8424
  }
8425
 
8426
  protected static $libDesaturate = ['color', 'amount'];
8427
  protected function libDesaturate($args)
8428
  {
8429
+ $color = $this->assertColor($args[0], 'color');
8430
+ $amount = $this->assertNumber($args[1], 'amount');
8431
 
8432
+ return $this->adjustHsl($color, 2, -$amount->valueInRange(0, 100, 'amount'));
8433
  }
8434
 
8435
  protected static $libGrayscale = ['color'];
8437
  {
8438
  $value = $args[0];
8439
 
8440
+ if ($value instanceof Number) {
8441
  return null;
8442
  }
8443
 
8444
+ return $this->adjustHsl($this->assertColor($value, 'color'), 2, -100);
8445
  }
8446
 
8447
  protected static $libComplement = ['color'];
8448
  protected function libComplement($args)
8449
  {
8450
+ return $this->adjustHsl($this->assertColor($args[0], 'color'), 1, 180);
8451
  }
8452
 
8453
+ protected static $libInvert = ['color', 'weight:100%'];
8454
  protected function libInvert($args)
8455
  {
8456
+ $value = $args[0];
8457
 
8458
+ $weight = $this->assertNumber($args[1], 'weight');
8459
+
8460
+ if ($value instanceof Number) {
8461
+ if ($weight->getDimension() != 100 || !$weight->hasUnit('%')) {
8462
+ throw new SassScriptException('Only one argument may be passed to the plain-CSS invert() function.');
8463
+ }
8464
 
 
8465
  return null;
8466
  }
8467
 
8468
+ $color = $this->assertColor($value, 'color');
8469
  $inverted = $color;
8470
  $inverted[1] = 255 - $inverted[1];
8471
  $inverted[2] = 255 - $inverted[2];
8472
  $inverted[3] = 255 - $inverted[3];
8473
 
8474
+ return $this->libMix([$inverted, $color, $weight]);
 
 
 
 
8475
  }
8476
 
8477
  // increases opacity by amount
8478
  protected static $libOpacify = ['color', 'amount'];
8479
  protected function libOpacify($args)
8480
  {
8481
+ $color = $this->assertColor($args[0], 'color');
8482
+ $amount = $this->assertNumber($args[1], 'amount');
8483
 
8484
+ $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount->valueInRange(0, 1, 'amount');
8485
  $color[4] = min(1, max(0, $color[4]));
8486
 
8487
  return $color;
8497
  protected static $libTransparentize = ['color', 'amount'];
8498
  protected function libTransparentize($args)
8499
  {
8500
+ $color = $this->assertColor($args[0], 'color');
8501
+ $amount = $this->assertNumber($args[1], 'amount');
8502
 
8503
+ $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount->valueInRange(0, 1, 'amount');
8504
  $color[4] = min(1, max(0, $color[4]));
8505
 
8506
  return $color;
8515
  protected static $libUnquote = ['string'];
8516
  protected function libUnquote($args)
8517
  {
8518
+ try {
8519
+ $str = $this->assertString($args[0], 'string');
8520
+ } catch (SassScriptException $e) {
8521
+ $value = $this->compileValue($args[0]);
8522
+ $fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
8523
+ $line = $this->sourceLine;
8524
+
8525
+ $message = "Passing $value, a non-string value, to unquote()
8526
+ will be an error in future versions of Sass.\n on line $line of $fname";
8527
 
8528
+ $this->logger->warn($message, true);
8529
+
8530
+ return $args[0];
8531
  }
8532
 
8533
+ $str[1] = '';
8534
+
8535
  return $str;
8536
  }
8537
 
8538
  protected static $libQuote = ['string'];
8539
  protected function libQuote($args)
8540
  {
8541
+ $value = $this->assertString($args[0], 'string');
8542
 
8543
+ $value[1] = '"';
 
 
8544
 
8545
+ return $value;
8546
  }
8547
 
8548
  protected static $libPercentage = ['number'];
8549
  protected function libPercentage($args)
8550
  {
8551
+ $num = $this->assertNumber($args[0], 'number');
8552
+ $num->assertNoUnits('number');
8553
+
8554
+ return new Number($num->getDimension() * 100, '%');
8555
  }
8556
 
8557
  protected static $libRound = ['number'];
8558
  protected function libRound($args)
8559
  {
8560
+ $num = $this->assertNumber($args[0], 'number');
8561
 
8562
+ return new Number(round($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
8563
  }
8564
 
8565
  protected static $libFloor = ['number'];
8566
  protected function libFloor($args)
8567
  {
8568
+ $num = $this->assertNumber($args[0], 'number');
8569
 
8570
+ return new Number(floor($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
8571
  }
8572
 
8573
  protected static $libCeil = ['number'];
8574
  protected function libCeil($args)
8575
  {
8576
+ $num = $this->assertNumber($args[0], 'number');
8577
 
8578
+ return new Number(ceil($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
8579
  }
8580
 
8581
  protected static $libAbs = ['number'];
8582
  protected function libAbs($args)
8583
  {
8584
+ $num = $this->assertNumber($args[0], 'number');
8585
 
8586
+ return new Number(abs($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
8587
  }
8588
 
8589
+ protected static $libMin = ['numbers...'];
8590
  protected function libMin($args)
8591
  {
8592
+ /**
8593
+ * @var Number|null
8594
+ */
8595
+ $min = null;
8596
 
8597
+ foreach ($args[0][2] as $arg) {
8598
+ $number = $this->assertNumber($arg);
8599
 
8600
+ if (\is_null($min) || $min->greaterThan($number)) {
8601
+ $min = $number;
 
 
 
 
 
 
8602
  }
8603
  }
8604
 
8605
+ if (!\is_null($min)) {
8606
+ return $min;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8607
  }
8608
 
8609
+ throw $this->error('At least one argument must be passed.');
8610
  }
8611
 
8612
+ protected static $libMax = ['numbers...'];
8613
+ protected function libMax($args)
 
 
 
 
 
 
8614
  {
8615
+ /**
8616
+ * @var Number|null
8617
+ */
8618
+ $max = null;
 
 
 
 
 
8619
 
8620
+ foreach ($args[0][2] as $arg) {
8621
+ $number = $this->assertNumber($arg);
8622
 
8623
+ if (\is_null($max) || $max->lessThan($number)) {
8624
+ $max = $number;
 
 
 
 
8625
  }
8626
+ }
8627
 
8628
+ if (!\is_null($max)) {
8629
+ return $max;
8630
  }
8631
 
8632
+ throw $this->error('At least one argument must be passed.');
8633
  }
8634
 
8635
  protected static $libLength = ['list'];
8637
  {
8638
  $list = $this->coerceList($args[0], ',', true);
8639
 
8640
+ return new Number(\count($list[2]), '');
8641
  }
8642
 
8643
+ protected static $libListSeparator = ['list'];
8644
  protected function libListSeparator($args)
8645
  {
8646
+ if (! \in_array($args[0][0], [Type::T_LIST, Type::T_MAP])) {
8647
+ return [Type::T_KEYWORD, 'space'];
8648
  }
8649
 
8650
  $list = $this->coerceList($args[0]);
8651
 
8652
+ if (\count($list[2]) <= 1 && empty($list['enclosing'])) {
8653
+ return [Type::T_KEYWORD, 'space'];
8654
  }
8655
 
8656
  if ($list[1] === ',') {
8657
+ return [Type::T_KEYWORD, 'comma'];
8658
  }
8659
 
8660
+ return [Type::T_KEYWORD, 'space'];
8661
  }
8662
 
8663
  protected static $libNth = ['list', 'n'];
8664
  protected function libNth($args)
8665
  {
8666
  $list = $this->coerceList($args[0], ',', false);
8667
+ $n = $this->assertNumber($args[1])->getDimension();
8668
 
8669
  if ($n > 0) {
8670
  $n--;
8679
  protected function libSetNth($args)
8680
  {
8681
  $list = $this->coerceList($args[0]);
8682
+ $n = $this->assertNumber($args[1])->getDimension();
8683
 
8684
  if ($n > 0) {
8685
  $n--;
8688
  }
8689
 
8690
  if (! isset($list[2][$n])) {
8691
+ throw $this->error('Invalid argument for "n"');
 
 
8692
  }
8693
 
8694
  $list[2][$n] = $args[2];
8699
  protected static $libMapGet = ['map', 'key'];
8700
  protected function libMapGet($args)
8701
  {
8702
+ $map = $this->assertMap($args[0], 'map');
8703
  $key = $args[1];
8704
 
8705
  if (! \is_null($key)) {
8718
  protected static $libMapKeys = ['map'];
8719
  protected function libMapKeys($args)
8720
  {
8721
+ $map = $this->assertMap($args[0], 'map');
8722
  $keys = $map[1];
8723
 
8724
  return [Type::T_LIST, ',', $keys];
8727
  protected static $libMapValues = ['map'];
8728
  protected function libMapValues($args)
8729
  {
8730
+ $map = $this->assertMap($args[0], 'map');
8731
  $values = $map[2];
8732
 
8733
  return [Type::T_LIST, ',', $values];
8734
  }
8735
 
8736
+ protected static $libMapRemove = [
8737
+ ['map'],
8738
+ ['map', 'key', 'keys...'],
8739
+ ];
8740
  protected function libMapRemove($args)
8741
  {
8742
+ $map = $this->assertMap($args[0], 'map');
8743
+
8744
+ if (\count($args) === 1) {
8745
+ return $map;
8746
+ }
8747
+
8748
+ $keys = [];
8749
+ $keys[] = $this->compileStringContent($this->coerceString($args[1]));
8750
+
8751
+ foreach ($args[2][2] as $key) {
8752
+ $keys[] = $this->compileStringContent($this->coerceString($key));
8753
+ }
8754
 
8755
  for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
8756
+ if (in_array($this->compileStringContent($this->coerceString($map[1][$i])), $keys)) {
8757
  array_splice($map[1], $i, 1);
8758
  array_splice($map[2], $i, 1);
8759
  }
8765
  protected static $libMapHasKey = ['map', 'key'];
8766
  protected function libMapHasKey($args)
8767
  {
8768
+ $map = $this->assertMap($args[0], 'map');
8769
+
8770
+ return $this->toBool($this->mapHasKey($map, $args[1]));
8771
+ }
8772
+
8773
+ /**
8774
+ * @param array|Number $keyValue
8775
+ *
8776
+ * @return bool
8777
+ */
8778
+ private function mapHasKey(array $map, $keyValue)
8779
+ {
8780
+ $key = $this->compileStringContent($this->coerceString($keyValue));
8781
 
8782
  for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
8783
  if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
8788
  return false;
8789
  }
8790
 
8791
+ protected static $libMapMerge = [
8792
+ ['map1', 'map2'],
8793
+ ['map-1', 'map-2']
8794
+ ];
8795
  protected function libMapMerge($args)
8796
  {
8797
+ $map1 = $this->assertMap($args[0], 'map1');
8798
+ $map2 = $this->assertMap($args[1], 'map2');
8799
 
8800
  foreach ($map2[1] as $i2 => $key2) {
8801
  $key = $this->compileStringContent($this->coerceString($key2));
8817
  protected static $libKeywords = ['args'];
8818
  protected function libKeywords($args)
8819
  {
8820
+ $value = $args[0];
8821
+
8822
+ if ($value[0] !== Type::T_LIST || !isset($value[3]) || !\is_array($value[3])) {
8823
+ $compiledValue = $this->compileValue($value);
8824
+
8825
+ throw SassScriptException::forArgument($compiledValue . ' is not an argument list.', 'args');
8826
+ }
8827
 
8828
  $keys = [];
8829
  $values = [];
8830
 
8831
+ foreach ($this->getArgumentListKeywords($value) as $name => $arg) {
8832
  $keys[] = [Type::T_KEYWORD, $name];
8833
  $values[] = $arg;
8834
  }
8843
  $this->coerceList($list, ' ');
8844
 
8845
  if (! empty($list['enclosing']) && $list['enclosing'] === 'bracket') {
8846
+ return self::$true;
8847
  }
8848
 
8849
+ return self::$false;
8850
  }
8851
 
8852
+ /**
8853
+ * @param array $list1
8854
+ * @param array|Number|null $sep
8855
+ *
8856
+ * @return string
8857
+ * @throws CompilerException
8858
+ */
8859
  protected function listSeparatorForJoin($list1, $sep)
8860
  {
8861
  if (! isset($sep)) {
8937
  return $res;
8938
  }
8939
 
8940
+ protected static $libZip = ['lists...'];
8941
  protected function libZip($args)
8942
  {
8943
+ $argLists = [];
8944
+ foreach ($args[0][2] as $arg) {
8945
+ $argLists[] = $this->coerceList($arg);
8946
  }
8947
 
8948
  $lists = [];
8949
+ $firstList = array_shift($argLists);
8950
 
8951
+ $result = [Type::T_LIST, ',', $lists];
8952
+ if (! \is_null($firstList)) {
8953
+ foreach ($firstList[2] as $key => $item) {
8954
+ $list = [Type::T_LIST, '', [$item]];
8955
 
8956
+ foreach ($argLists as $arg) {
8957
+ if (isset($arg[2][$key])) {
8958
+ $list[2][] = $arg[2][$key];
8959
+ } else {
8960
+ break 2;
8961
+ }
8962
  }
8963
+
8964
+ $lists[] = $list;
8965
  }
8966
 
8967
+ $result[2] = $lists;
8968
+ } else {
8969
+ $result['enclosing'] = 'parent';
8970
  }
8971
 
8972
+ return $result;
8973
  }
8974
 
8975
  protected static $libTypeOf = ['value'];
8977
  {
8978
  $value = $args[0];
8979
 
8980
+ return [Type::T_KEYWORD, $this->getTypeOf($value)];
8981
+ }
8982
+
8983
+ /**
8984
+ * @param array|Number $value
8985
+ *
8986
+ * @return string
8987
+ */
8988
+ private function getTypeOf($value)
8989
+ {
8990
  switch ($value[0]) {
8991
  case Type::T_KEYWORD:
8992
  if ($value === static::$true || $value === static::$false) {
9001
  case Type::T_FUNCTION:
9002
  return 'string';
9003
 
9004
+ case Type::T_FUNCTION_REFERENCE:
9005
+ return 'function';
9006
+
9007
  case Type::T_LIST:
9008
+ if (isset($value[3]) && \is_array($value[3])) {
9009
  return 'arglist';
9010
  }
9011
 
9018
  protected static $libUnit = ['number'];
9019
  protected function libUnit($args)
9020
  {
9021
+ $num = $this->assertNumber($args[0], 'number');
 
 
 
 
9022
 
9023
+ return [Type::T_STRING, '"', [$num->unitStr()]];
9024
  }
9025
 
9026
  protected static $libUnitless = ['number'];
9027
  protected function libUnitless($args)
9028
  {
9029
+ $value = $this->assertNumber($args[0], 'number');
9030
 
9031
+ return $this->toBool($value->unitless());
9032
  }
9033
 
9034
+ protected static $libComparable = [
9035
+ ['number1', 'number2'],
9036
+ ['number-1', 'number-2']
9037
+ ];
9038
  protected function libComparable($args)
9039
  {
9040
  list($number1, $number2) = $args;
9041
 
9042
+ if (
9043
+ ! $number1 instanceof Number ||
9044
+ ! $number2 instanceof Number
9045
  ) {
9046
+ throw $this->error('Invalid argument(s) for "comparable"');
 
 
9047
  }
9048
 
9049
+ return $this->toBool($number1->isComparableTo($number2));
 
 
 
9050
  }
9051
 
9052
  protected static $libStrIndex = ['string', 'substring'];
9053
  protected function libStrIndex($args)
9054
  {
9055
+ $string = $this->assertString($args[0], 'string');
9056
  $stringContent = $this->compileStringContent($string);
9057
 
9058
+ $substring = $this->assertString($args[1], 'substring');
9059
  $substringContent = $this->compileStringContent($substring);
9060
 
9061
+ if (! \strlen($substringContent)) {
9062
+ $result = 0;
9063
+ } else {
9064
+ $result = Util::mbStrpos($stringContent, $substringContent);
9065
+ }
9066
 
9067
+ return $result === false ? static::$null : new Number($result + 1, '');
9068
  }
9069
 
9070
  protected static $libStrInsert = ['string', 'insert', 'index'];
9071
  protected function libStrInsert($args)
9072
  {
9073
+ $string = $this->assertString($args[0], 'string');
9074
  $stringContent = $this->compileStringContent($string);
9075
 
9076
+ $insert = $this->assertString($args[1], 'insert');
9077
  $insertContent = $this->compileStringContent($insert);
9078
 
9079
+ $index = $this->assertInteger($args[2], 'index');
9080
+ if ($index > 0) {
9081
+ $index = $index - 1;
9082
+ }
9083
+ if ($index < 0) {
9084
+ $index = Util::mbStrlen($stringContent) + 1 + $index;
9085
+ }
9086
 
9087
+ $string[2] = [
9088
+ Util::mbSubstr($stringContent, 0, $index),
9089
+ $insertContent,
9090
+ Util::mbSubstr($stringContent, $index)
9091
+ ];
9092
 
9093
  return $string;
9094
  }
9096
  protected static $libStrLength = ['string'];
9097
  protected function libStrLength($args)
9098
  {
9099
+ $string = $this->assertString($args[0], 'string');
9100
  $stringContent = $this->compileStringContent($string);
9101
 
9102
+ return new Number(Util::mbStrlen($stringContent), '');
9103
  }
9104
 
9105
  protected static $libStrSlice = ['string', 'start-at', 'end-at:-1'];
9106
  protected function libStrSlice($args)
9107
  {
9108
+ $string = $this->assertString($args[0], 'string');
9109
+ $stringContent = $this->compileStringContent($string);
9110
+
9111
+ $start = $this->assertNumber($args[1], 'start-at');
9112
+ $start->assertNoUnits('start-at');
9113
+ $startInt = $this->assertInteger($start, 'start-at');
9114
+ $end = $this->assertNumber($args[2], 'end-at');
9115
+ $end->assertNoUnits('end-at');
9116
+ $endInt = $this->assertInteger($end, 'end-at');
9117
+
9118
+ if ($endInt === 0) {
9119
+ return [Type::T_STRING, $string[1], []];
9120
  }
9121
 
9122
+ if ($startInt > 0) {
9123
+ $startInt--;
9124
+ }
9125
 
9126
+ if ($endInt < 0) {
9127
+ $endInt = Util::mbStrlen($stringContent) + $endInt;
9128
+ } else {
9129
+ $endInt--;
9130
+ }
9131
 
9132
+ if ($endInt < $startInt) {
9133
+ return [Type::T_STRING, $string[1], []];
9134
  }
9135
 
9136
+ $length = $endInt - $startInt + 1; // The end of the slice is inclusive
 
9137
 
9138
+ $string[2] = [Util::mbSubstr($stringContent, $startInt, $length)];
 
 
9139
 
9140
  return $string;
9141
  }
9143
  protected static $libToLowerCase = ['string'];
9144
  protected function libToLowerCase($args)
9145
  {
9146
+ $string = $this->assertString($args[0], 'string');
9147
  $stringContent = $this->compileStringContent($string);
9148
 
9149
+ $string[2] = [$this->stringTransformAsciiOnly($stringContent, 'strtolower')];
9150
 
9151
  return $string;
9152
  }
9154
  protected static $libToUpperCase = ['string'];
9155
  protected function libToUpperCase($args)
9156
  {
9157
+ $string = $this->assertString($args[0], 'string');
9158
  $stringContent = $this->compileStringContent($string);
9159
 
9160
+ $string[2] = [$this->stringTransformAsciiOnly($stringContent, 'strtoupper')];
9161
 
9162
  return $string;
9163
  }
9164
 
9165
+ /**
9166
+ * Apply a filter on a string content, only on ascii chars
9167
+ * let extended chars untouched
9168
+ *
9169
+ * @param string $stringContent
9170
+ * @param callable $filter
9171
+ * @return string
9172
+ */
9173
+ protected function stringTransformAsciiOnly($stringContent, $filter)
9174
+ {
9175
+ $mblength = Util::mbStrlen($stringContent);
9176
+ if ($mblength === strlen($stringContent)) {
9177
+ return $filter($stringContent);
9178
+ }
9179
+ $filteredString = "";
9180
+ for ($i = 0; $i < $mblength; $i++) {
9181
+ $char = Util::mbSubstr($stringContent, $i, 1);
9182
+ if (strlen($char) > 1) {
9183
+ $filteredString .= $char;
9184
+ } else {
9185
+ $filteredString .= $filter($char);
9186
+ }
9187
+ }
9188
+
9189
+ return $filteredString;
9190
+ }
9191
+
9192
  protected static $libFeatureExists = ['feature'];
9193
  protected function libFeatureExists($args)
9194
  {
9195
+ $string = $this->assertString($args[0], 'feature');
9196
  $name = $this->compileStringContent($string);
9197
 
9198
  return $this->toBool(
9203
  protected static $libFunctionExists = ['name'];
9204
  protected function libFunctionExists($args)
9205
  {
9206
+ $string = $this->assertString($args[0], 'name');
9207
  $name = $this->compileStringContent($string);
9208
 
9209
  // user defined functions
9210
  if ($this->has(static::$namespaces['function'] . $name)) {
9211
+ return self::$true;
9212
  }
9213
 
9214
  $name = $this->normalizeName($name);
9215
 
9216
  if (isset($this->userFunctions[$name])) {
9217
+ return self::$true;
9218
  }
9219
 
9220
  // built-in functions
9226
  protected static $libGlobalVariableExists = ['name'];
9227
  protected function libGlobalVariableExists($args)
9228
  {
9229
+ $string = $this->assertString($args[0], 'name');
9230
  $name = $this->compileStringContent($string);
9231
 
9232
+ return $this->toBool($this->has($name, $this->rootEnv));
9233
  }
9234
 
9235
  protected static $libMixinExists = ['name'];
9236
  protected function libMixinExists($args)
9237
  {
9238
+ $string = $this->assertString($args[0], 'name');
9239
  $name = $this->compileStringContent($string);
9240
 
9241
+ return $this->toBool($this->has(static::$namespaces['mixin'] . $name));
9242
  }
9243
 
9244
  protected static $libVariableExists = ['name'];
9245
  protected function libVariableExists($args)
9246
  {
9247
+ $string = $this->assertString($args[0], 'name');
9248
  $name = $this->compileStringContent($string);
9249
 
9250
+ return $this->toBool($this->has($name));
9251
  }
9252
 
9253
+ protected static $libCounter = ['args...'];
9254
  /**
9255
  * Workaround IE7's content counter bug.
9256
  *
9260
  */
9261
  protected function libCounter($args)
9262
  {
9263
+ $list = array_map([$this, 'compileValue'], $args[0][2]);
9264
 
9265
  return [Type::T_STRING, '', ['counter(' . implode(',', $list) . ')']];
9266
  }
9267
 
9268
+ protected static $libRandom = ['limit:null'];
9269
  protected function libRandom($args)
9270
  {
9271
+ if (isset($args[0]) && $args[0] !== static::$null) {
9272
+ $n = $this->assertInteger($args[0], 'limit');
9273
 
9274
  if ($n < 1) {
9275
+ throw new SassScriptException("\$limit: Must be greater than 0, was $n.");
 
 
 
 
 
 
 
 
9276
  }
9277
 
9278
+ return new Number(mt_rand(1, $n), '');
9279
  }
9280
 
9281
+ $max = mt_getrandmax();
9282
+ return new Number(mt_rand(0, $max - 1) / $max, '');
9283
  }
9284
 
9285
+ protected static $libUniqueId = [];
9286
  protected function libUniqueId()
9287
  {
9288
  static $id;
9298
  return [Type::T_STRING, '', ['u' . str_pad(base_convert($id, 10, 36), 8, '0', STR_PAD_LEFT)]];
9299
  }
9300
 
9301
+ /**
9302
+ * @param array|Number $value
9303
+ * @param bool $force_enclosing_display
9304
+ *
9305
+ * @return array
9306
+ */
9307
  protected function inspectFormatValue($value, $force_enclosing_display = false)
9308
  {
9309
  if ($value === static::$null) {
9312
 
9313
  $stringValue = [$value];
9314
 
9315
+ if ($value instanceof Number) {
9316
+ return [Type::T_STRING, '', $stringValue];
9317
+ }
9318
+
9319
  if ($value[0] === Type::T_LIST) {
9320
  if (end($value[2]) === static::$null) {
9321
  array_pop($value[2]);
9323
  $force_enclosing_display = true;
9324
  }
9325
 
9326
+ if (
9327
+ ! empty($value['enclosing']) &&
9328
  ($force_enclosing_display ||
9329
  ($value['enclosing'] === 'bracket') ||
9330
  ! \count($value[2]))
9331
  ) {
9332
+ $value['enclosing'] = 'forced_' . $value['enclosing'];
9333
  $force_enclosing_display = true;
9334
  }
9335
 
9354
  /**
9355
  * Preprocess selector args
9356
  *
9357
+ * @param array $arg
9358
+ * @param string|null $varname
9359
+ * @param bool $allowParent
9360
  *
9361
+ * @return array
9362
  */
9363
+ protected function getSelectorArg($arg, $varname = null, $allowParent = false)
9364
  {
9365
  static $parser = null;
9366
 
9368
  $parser = $this->parserFactory(__METHOD__);
9369
  }
9370
 
9371
+ if (! $this->checkSelectorArgType($arg)) {
9372
+ $var_value = $this->compileValue($arg);
9373
+ 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);
9374
+ }
9375
+
9376
+
9377
+ if ($arg[0] === Type::T_STRING) {
9378
+ $arg[1] = '';
9379
+ }
9380
  $arg = $this->compileValue($arg);
9381
 
9382
  $parsedSelector = [];
9383
 
9384
+ if ($parser->parseSelector($arg, $parsedSelector, true)) {
9385
  $selector = $this->evalSelectors($parsedSelector);
9386
  $gluedSelector = $this->glueFunctionSelectors($selector);
9387
 
9388
+ if (! $allowParent) {
9389
+ foreach ($gluedSelector as $selector) {
9390
+ foreach ($selector as $s) {
9391
+ if (in_array(static::$selfSelector, $s)) {
9392
+ throw SassScriptException::forArgument("Parent selectors aren't allowed here.", $varname);
9393
+ }
9394
+ }
9395
+ }
9396
+ }
9397
+
9398
  return $gluedSelector;
9399
  }
9400
 
9401
+ throw SassScriptException::forArgument("expected more input, invalid selector.", $varname);
9402
+ }
9403
+
9404
+ /**
9405
+ * Check variable type for getSelectorArg() function
9406
+ * @param array $arg
9407
+ * @param int $maxDepth
9408
+ * @return bool
9409
+ */
9410
+ protected function checkSelectorArgType($arg, $maxDepth = 2)
9411
+ {
9412
+ if ($arg[0] === Type::T_LIST && $maxDepth > 0) {
9413
+ foreach ($arg[2] as $elt) {
9414
+ if (! $this->checkSelectorArgType($elt, $maxDepth - 1)) {
9415
+ return false;
9416
+ }
9417
+ }
9418
+ return true;
9419
+ }
9420
+ if (!in_array($arg[0], [Type::T_STRING, Type::T_KEYWORD])) {
9421
+ return false;
9422
+ }
9423
+ return true;
9424
  }
9425
 
9426
  /**
9428
  *
9429
  * @param array $selectors
9430
  *
9431
+ * @return array
9432
  */
9433
  protected function formatOutputSelector($selectors)
9434
  {
9435
+ $selectors = $this->collapseSelectorsAsList($selectors);
9436
 
9437
  return $selectors;
9438
  }
9442
  {
9443
  list($super, $sub) = $args;
9444
 
9445
+ $super = $this->getSelectorArg($super, 'super');
9446
+ $sub = $this->getSelectorArg($sub, 'sub');
9447
 
9448
+ return $this->toBool($this->isSuperSelector($super, $sub));
9449
  }
9450
 
9451
  /**
9459
  protected function isSuperSelector($super, $sub)
9460
  {
9461
  // one and only one selector for each arg
9462
+ if (! $super) {
9463
+ throw $this->error('Invalid super selector for isSuperSelector()');
9464
+ }
9465
+
9466
+ if (! $sub) {
9467
+ throw $this->error('Invalid sub selector for isSuperSelector()');
9468
+ }
9469
+
9470
+ if (count($sub) > 1) {
9471
+ foreach ($sub as $s) {
9472
+ if (! $this->isSuperSelector($super, [$s])) {
9473
+ return false;
9474
+ }
9475
+ }
9476
+ return true;
9477
  }
9478
 
9479
+ if (count($super) > 1) {
9480
+ foreach ($super as $s) {
9481
+ if ($this->isSuperSelector([$s], $sub)) {
9482
+ return true;
9483
+ }
9484
+ }
9485
+ return false;
9486
  }
9487
 
9488
  $super = reset($super);
9564
  $args = $args[2];
9565
 
9566
  if (\count($args) < 1) {
9567
+ throw $this->error('selector-append() needs at least 1 argument');
9568
  }
9569
 
9570
+ $selectors = [];
9571
+ foreach ($args as $arg) {
9572
+ $selectors[] = $this->getSelectorArg($arg, 'selector');
9573
+ }
9574
 
9575
  return $this->formatOutputSelector($this->selectorAppend($selectors));
9576
  }
9589
  $lastSelectors = array_pop($selectors);
9590
 
9591
  if (! $lastSelectors) {
9592
+ throw $this->error('Invalid selector list in selector-append()');
9593
  }
9594
 
9595
  while (\count($selectors)) {
9596
  $previousSelectors = array_pop($selectors);
9597
 
9598
  if (! $previousSelectors) {
9599
+ throw $this->error('Invalid selector list in selector-append()');
9600
  }
9601
 
9602
  // do the trick, happening $lastSelector to $previousSelector
9626
  return $lastSelectors;
9627
  }
9628
 
9629
+ protected static $libSelectorExtend = [
9630
+ ['selector', 'extendee', 'extender'],
9631
+ ['selectors', 'extendee', 'extender']
9632
+ ];
9633
  protected function libSelectorExtend($args)
9634
  {
9635
  list($selectors, $extendee, $extender) = $args;
9636
 
9637
+ $selectors = $this->getSelectorArg($selectors, 'selector');
9638
+ $extendee = $this->getSelectorArg($extendee, 'extendee');
9639
+ $extender = $this->getSelectorArg($extender, 'extender');
9640
 
9641
  if (! $selectors || ! $extendee || ! $extender) {
9642
+ throw $this->error('selector-extend() invalid arguments');
9643
  }
9644
 
9645
  $extended = $this->extendOrReplaceSelectors($selectors, $extendee, $extender);
9647
  return $this->formatOutputSelector($extended);
9648
  }
9649
 
9650
+ protected static $libSelectorReplace = [
9651
+ ['selector', 'original', 'replacement'],
9652
+ ['selectors', 'original', 'replacement']
9653
+ ];
9654
  protected function libSelectorReplace($args)
9655
  {
9656
  list($selectors, $original, $replacement) = $args;
9657
 
9658
+ $selectors = $this->getSelectorArg($selectors, 'selector');
9659
+ $original = $this->getSelectorArg($original, 'original');
9660
+ $replacement = $this->getSelectorArg($replacement, 'replacement');
9661
 
9662
  if (! $selectors || ! $original || ! $replacement) {
9663
+ throw $this->error('selector-replace() invalid arguments');
9664
  }
9665
 
9666
  $replaced = $this->extendOrReplaceSelectors($selectors, $original, $replacement, true);
9688
  $this->extendsMap = [];
9689
 
9690
  foreach ($extendee as $es) {
9691
+ if (\count($es) !== 1) {
9692
+ throw $this->error('Can\'t extend complex selector.');
9693
+ }
9694
+
9695
  // only use the first one
9696
  $this->pushExtends(reset($es), $extender, null);
9697
  }
9708
  $this->matchExtends($selector, $extended);
9709
 
9710
  // if didnt match, keep the original selector if we are in a replace operation
9711
+ if ($replace && \count($extended) === $n) {
9712
  $extended[] = $selector;
9713
  }
9714
  }
9727
  $args = $args[2];
9728
 
9729
  if (\count($args) < 1) {
9730
+ throw $this->error('selector-nest() needs at least 1 argument');
9731
+ }
9732
+
9733
+ $selectorsMap = [];
9734
+ foreach ($args as $arg) {
9735
+ $selectorsMap[] = $this->getSelectorArg($arg, 'selector', true);
9736
  }
9737
 
 
9738
  $envs = [];
9739
 
9740
  foreach ($selectorsMap as $selectors) {
9751
  return $this->formatOutputSelector($outputSelectors);
9752
  }
9753
 
9754
+ protected static $libSelectorParse = [
9755
+ ['selector'],
9756
+ ['selectors']
9757
+ ];
9758
  protected function libSelectorParse($args)
9759
  {
9760
  $selectors = reset($args);
9761
+ $selectors = $this->getSelectorArg($selectors, 'selector');
9762
 
9763
  return $this->formatOutputSelector($selectors);
9764
  }
9768
  {
9769
  list($selectors1, $selectors2) = $args;
9770
 
9771
+ $selectors1 = $this->getSelectorArg($selectors1, 'selectors1');
9772
+ $selectors2 = $this->getSelectorArg($selectors2, 'selectors2');
9773
 
9774
  if (! $selectors1 || ! $selectors2) {
9775
+ throw $this->error('selector-unify() invalid arguments');
9776
  }
9777
 
9778
  // only consider the first compound of each
9792
  * @param array $compound1
9793
  * @param array $compound2
9794
  *
9795
+ * @return array
9796
  */
9797
  protected function unifyCompoundSelectors($compound1, $compound2)
9798
  {
9908
  * @param array $part
9909
  * @param array $compound
9910
  *
9911
+ * @return array|false
9912
  */
9913
  protected function matchPartInCompound($part, $compound)
9914
  {
10003
  * @param string $tag1
10004
  * @param string $tag2
10005
  *
10006
+ * @return array|false
10007
  */
10008
  protected function checkCompatibleTags($tag1, $tag2)
10009
  {
10026
  /**
10027
  * Find the html tag name in a selector parts list
10028
  *
10029
+ * @param string[] $parts
10030
  *
10031
+ * @return string
10032
  */
10033
  protected function findTagName($parts)
10034
  {
10045
  protected function libSimpleSelectors($args)
10046
  {
10047
  $selector = reset($args);
10048
+ $selector = $this->getSelectorArg($selector, 'selector');
10049
 
10050
  // remove selectors list layer, keeping the first one
10051
  $selector = reset($selector);
10065
  protected static $libScssphpGlob = ['pattern'];
10066
  protected function libScssphpGlob($args)
10067
  {
10068
+ @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);
10069
+
10070
+ $this->logger->warn('The "scssphp-glob" function is deprecated an will be removed in ScssPhp 2.0.', true);
10071
+
10072
+ $string = $this->assertString($args[0], 'pattern');
10073
  $pattern = $this->compileStringContent($string);
10074
  $matches = glob($pattern);
10075
  $listParts = [];
assets/libraries/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
+ }
assets/libraries/scssphp/src/Compiler/Environment.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,16 +16,18 @@ 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
 
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
 
assets/libraries/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
  }
assets/libraries/scssphp/src/Exception/ParserException.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,7 +16,35 @@ 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
26
+ */
27
+ private $sourcePosition;
28
+
29
+ /**
30
+ * Get source position
31
+ *
32
+ * @api
33
+ */
34
+ public function getSourcePosition()
35
+ {
36
+ return $this->sourcePosition;
37
+ }
38
+
39
+ /**
40
+ * Set source position
41
+ *
42
+ * @api
43
+ *
44
+ * @param array $sourcePosition
45
+ */
46
+ public function setSourcePosition($sourcePosition)
47
+ {
48
+ $this->sourcePosition = $sourcePosition;
49
+ }
50
  }
assets/libraries/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
  }
assets/libraries/scssphp/src/Exception/SassException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace ScssPhp\ScssPhp\Exception;
4
+
5
+ interface SassException
6
+ {
7
+ }
assets/libraries/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
+ }
assets/libraries/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
  }
assets/libraries/scssphp/src/Formatter.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -18,6 +19,8 @@ use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
18
  * Base formatter
19
  *
20
  * @author Leaf Corcoran <leafot@gmail.com>
 
 
21
  */
22
  abstract class Formatter
23
  {
@@ -77,7 +80,7 @@ abstract class Formatter
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
  {
@@ -284,6 +297,8 @@ abstract class Formatter
284
  * Output content
285
  *
286
  * @param string $str
 
 
287
  */
288
  protected function write($str)
289
  {
@@ -297,7 +312,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 +324,39 @@ 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
  {
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
  {
297
  * Output content
298
  *
299
  * @param string $str
300
+ *
301
+ * @return void
302
  */
303
  protected function write($str)
304
  {
312
  * Maybe Strip semi-colon appended by property(); it's a separator, not a terminator
313
  * will be striped for real before a closing, otherwise displayed unchanged starting the next write
314
  */
315
+ if (
316
+ ! $this->keepSemicolons &&
317
  $str &&
318
  (strpos($str, ';') !== false) &&
319
  (substr($str, -1) === ';')
324
  }
325
 
326
  if ($this->sourceMapGenerator) {
 
 
 
 
 
 
 
 
 
327
  $lines = explode("\n", $str);
 
 
 
328
  $lastLine = array_pop($lines);
329
 
330
+ foreach ($lines as $line) {
331
+ // If the written line starts is empty, adding a mapping would add it for
332
+ // a non-existent column as we are at the end of the line
333
+ if ($line !== '') {
334
+ $this->sourceMapGenerator->addMapping(
335
+ $this->currentLine,
336
+ $this->currentColumn,
337
+ $this->currentBlock->sourceLine,
338
+ //columns from parser are off by one
339
+ $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
340
+ $this->currentBlock->sourceName
341
+ );
342
+ }
343
+
344
+ $this->currentLine++;
345
+ $this->currentColumn = 0;
346
+ }
347
+
348
+ if ($lastLine !== '') {
349
+ $this->sourceMapGenerator->addMapping(
350
+ $this->currentLine,
351
+ $this->currentColumn,
352
+ $this->currentBlock->sourceLine,
353
+ //columns from parser are off by one
354
+ $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
355
+ $this->currentBlock->sourceName
356
+ );
357
+ }
358
+
359
+ $this->currentColumn += \strlen($lastLine);
360
  }
361
 
362
  echo $str;
assets/libraries/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 = '';
assets/libraries/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
  {
@@ -67,6 +69,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
  {
69
  */
70
  protected function blockSelectors(OutputBlock $block)
71
  {
72
+ assert(! empty($block->selectors));
73
+
74
  $inner = $this->indentStr();
75
 
76
  $this->write(
assets/libraries/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(
assets/libraries/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";
assets/libraries/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,7 @@ 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
+ $block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line);
61
  }
62
  }
63
 
assets/libraries/scssphp/src/Formatter/Nested.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -12,13 +13,16 @@
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
  {
@@ -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,7 @@ 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 +103,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;
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
  {
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
+ $block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line);
72
  }
73
  }
74
 
103
  array_pop($depths);
104
  $this->depth--;
105
 
106
+ if (
107
+ ! $this->depth && ($block->depth <= 1 || (! $this->indentLevel && $block->type === Type::T_COMMENT)) &&
108
  (($block->selectors && ! $isMediaOrDirective) || $previousHasSelector)
109
  ) {
110
  $downLevel = $this->break;
assets/libraries/scssphp/src/Formatter/OutputBlock.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -15,6 +16,8 @@ namespace ScssPhp\ScssPhp\Formatter;
15
  * Output block
16
  *
17
  * @author Anthon Pang <anthon.pang@gmail.com>
 
 
18
  */
19
  class OutputBlock
20
  {
@@ -29,37 +32,37 @@ class OutputBlock
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
  {
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 integer|null
61
  */
62
  public $sourceLine;
63
 
64
  /**
65
+ * @var integer|null
66
  */
67
  public $sourceColumn;
68
  }
assets/libraries/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
+ }
assets/libraries/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
+ }
assets/libraries/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
+ }
assets/libraries/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
  {
@@ -29,12 +32,12 @@ abstract class Node
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
  {
32
  public $sourceIndex;
33
 
34
  /**
35
+ * @var int|null
36
  */
37
  public $sourceLine;
38
 
39
  /**
40
+ * @var int|null
41
  */
42
  public $sourceColumn;
43
  }
assets/libraries/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
@@ -169,6 +175,7 @@ class Number extends Node implements \ArrayAccess
169
  /**
170
  * {@inheritdoc}
171
  */
 
172
  public function offsetGet($offset)
173
  {
174
  switch ($offset) {
@@ -182,50 +189,32 @@ 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
  /**
@@ -235,61 +224,311 @@ class Number extends Node implements \ArrayAccess
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 integer
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 integer|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 integer|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
  * {@inheritdoc}
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
175
  /**
176
  * {@inheritdoc}
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
  * {@inheritdoc}
204
  */
205
+ #[\ReturnTypeWillChange]
206
  public function offsetSet($offset, $value)
207
  {
208
+ throw new \BadMethodCallException('Number is immutable');
 
 
 
 
 
 
 
 
 
 
209
  }
210
 
211
  /**
212
  * {@inheritdoc}
213
  */
214
+ #[\ReturnTypeWillChange]
215
  public function offsetUnset($offset)
216
  {
217
+ throw new \BadMethodCallException('Number is immutable');
 
 
 
 
 
 
 
 
 
 
218
  }
219
 
220
  /**
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
  }
assets/libraries/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
+ }
assets/libraries/scssphp/src/Parser.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -11,17 +12,16 @@
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 +30,7 @@ class Parser
30
  const SOURCE_COLUMN = -3;
31
 
32
  /**
33
- * @var array
34
  */
35
  protected static $precedence = [
36
  '=' => 0,
@@ -38,7 +38,6 @@ class Parser
38
  'and' => 2,
39
  '==' => 3,
40
  '!=' => 3,
41
- '<=>' => 3,
42
  '<=' => 4,
43
  '>=' => 4,
44
  '<' => 4,
@@ -50,41 +49,89 @@ 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;
@@ -95,9 +142,10 @@ class Parser
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 +157,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 +179,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 +212,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 +236,17 @@ 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,11 +277,11 @@ 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) {
@@ -216,7 +291,7 @@ class Parser
216
  $this->restoreEncoding();
217
 
218
  if ($this->cache) {
219
- $this->cache->setCache("parse", $cacheKey, $this->env, $parseOptions);
220
  }
221
 
222
  return $this->env;
@@ -241,6 +316,7 @@ class Parser
241
  $this->buffer = (string) $buffer;
242
 
243
  $this->saveEncoding();
 
244
 
245
  $list = $this->valueList($out);
246
 
@@ -256,10 +332,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 +345,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
 
@@ -295,6 +382,7 @@ class Parser
295
  $this->buffer = (string) $buffer;
296
 
297
  $this->saveEncoding();
 
298
 
299
  $isMediaQuery = $this->mediaQueryList($out);
300
 
@@ -348,17 +436,16 @@ 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;
@@ -369,7 +456,11 @@ 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
 
@@ -378,14 +469,13 @@ 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;
@@ -396,20 +486,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,
@@ -431,13 +520,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 +538,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 +557,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 +575,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,14 +591,13 @@ 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;
@@ -507,34 +608,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,15 +622,14 @@ 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
 
@@ -566,12 +644,20 @@ 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);
@@ -582,7 +668,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,9 +678,7 @@ 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];
@@ -606,17 +691,20 @@ 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 +716,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 +729,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 +742,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 +755,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
 
@@ -699,7 +780,10 @@ class Parser
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
  }
@@ -716,7 +800,8 @@ 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
  ) {
@@ -737,9 +822,10 @@ class Parser
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';
@@ -751,10 +837,16 @@ 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 {
@@ -763,6 +855,7 @@ class Parser
763
  }
764
 
765
  if (isset($dirValue)) {
 
766
  $directive->value = $dirValue;
767
  }
768
 
@@ -772,12 +865,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 +906,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 +933,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 +958,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 +973,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 +990,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 +1010,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,9 +1026,7 @@ 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;
@@ -958,9 +1077,7 @@ class Parser
958
  }
959
 
960
  // extra stuff
961
- if ($this->matchChar(';') ||
962
- $this->literal('<!--', 4)
963
- ) {
964
  return true;
965
  }
966
 
@@ -970,16 +1087,16 @@ 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;
@@ -1019,7 +1136,7 @@ class Parser
1019
  * @param string $type
1020
  * @param integer $pos
1021
  *
1022
- * @return \ScssPhp\ScssPhp\Block
1023
  */
1024
  protected function pushSpecialBlock($type, $pos)
1025
  {
@@ -1032,7 +1149,7 @@ class Parser
1032
  /**
1033
  * Pop scope and return last block
1034
  *
1035
- * @return \ScssPhp\ScssPhp\Block
1036
  *
1037
  * @throws \Exception
1038
  */
@@ -1048,7 +1165,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) {
@@ -1079,7 +1196,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
  }
@@ -1094,13 +1211,217 @@ class Parser
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
  */
@@ -1111,7 +1432,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) {
@@ -1148,7 +1469,7 @@ class Parser
1148
  {
1149
  $r = '/' . $regex . '/' . $this->patternModifiers;
1150
 
1151
- if (! preg_match($r, $this->buffer, $out, null, $this->count)) {
1152
  return false;
1153
  }
1154
 
@@ -1229,7 +1550,7 @@ class Parser
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 +1569,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 +1578,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;
@@ -1274,7 +1600,14 @@ class Parser
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;
@@ -1282,6 +1615,10 @@ class Parser
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;
@@ -1298,25 +1635,6 @@ class Parser
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 +1642,14 @@ 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
 
@@ -1387,7 +1707,9 @@ class Parser
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];
@@ -1443,7 +1765,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 +1785,8 @@ class Parser
1462
  $this->seek($s);
1463
  }
1464
 
1465
- if ($this->matchChar('(') &&
 
1466
  $this->supportsQuery($subQuery) &&
1467
  $this->matchChar(')')
1468
  ) {
@@ -1472,7 +1796,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 +1806,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 +1844,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 +1856,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];
@@ -1564,9 +1894,11 @@ class Parser
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];
@@ -1592,12 +1924,19 @@ class Parser
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
 
@@ -1620,7 +1959,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,6 +1975,53 @@ class Parser
1636
  return false;
1637
  }
1638
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1639
  /**
1640
  * Parse directive value list that considers $vars as keyword
1641
  *
@@ -1660,8 +2046,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
  }
@@ -1710,6 +2101,45 @@ class Parser
1710
  return $res;
1711
  }
1712
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1713
  /**
1714
  * Parse space separated value list
1715
  *
@@ -1725,10 +2155,10 @@ 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
  */
@@ -1748,6 +2178,59 @@ class Parser
1748
  }
1749
 
1750
  $trailing_delim = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1751
  }
1752
  }
1753
 
@@ -1787,7 +2270,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 +2286,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 {
@@ -1845,17 +2328,17 @@ class Parser
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 +2346,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 +2357,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
  }
@@ -1928,12 +2413,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]);
@@ -1960,7 +2448,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 +2470,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 +2488,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 +2546,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 +2577,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 +2608,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)) {
@@ -2155,7 +2662,10 @@ class Parser
2155
 
2156
  $this->inParens = true;
2157
 
2158
- if ($this->expression($exp) && $this->matchChar(')')) {
 
 
 
2159
  $out = $exp;
2160
  $this->inParens = $inParens;
2161
 
@@ -2180,7 +2690,8 @@ class Parser
2180
  {
2181
  $s = $this->count;
2182
 
2183
- if ($this->literal('progid:', 7, false) &&
 
2184
  $this->openString('(', $fn) &&
2185
  $this->matchChar('(')
2186
  ) {
@@ -2222,7 +2733,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 +2745,8 @@ class Parser
2231
  $this->seek($ss);
2232
  }
2233
 
2234
- if (($this->openString(')', $str, '(') || true) &&
 
2235
  $this->matchChar(')')
2236
  ) {
2237
  $args = [];
@@ -2266,7 +2781,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 {
@@ -2312,7 +2830,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 +2845,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;
@@ -2370,8 +2891,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;
@@ -2403,7 +2926,7 @@ class Parser
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
 
@@ -2451,7 +2974,7 @@ class Parser
2451
  *
2452
  * @return boolean
2453
  */
2454
- protected function string(&$out)
2455
  {
2456
  $s = $this->count;
2457
 
@@ -2483,21 +3006,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 +3037,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,6 +3051,55 @@ class Parser
2532
  return false;
2533
  }
2534
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2535
  /**
2536
  * Parse keyword or interpolation
2537
  *
@@ -2581,24 +3149,26 @@ class Parser
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 +3179,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 +3207,7 @@ class Parser
2637
  }
2638
 
2639
  $content[] = $tok;
2640
- $this->count+= \strlen($tok);
2641
  }
2642
 
2643
  $this->eatWhiteDefault = $oldWhite;
@@ -2647,7 +3217,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
 
@@ -2673,13 +3243,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
  }
@@ -2746,7 +3323,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]);
@@ -2786,6 +3363,11 @@ class Parser
2786
  continue;
2787
  }
2788
 
 
 
 
 
 
2789
  if ($this->variable($var)) {
2790
  $parts[] = $var;
2791
  continue;
@@ -2817,8 +3399,8 @@ class Parser
2817
  /**
2818
  * Parse comma separated selector list
2819
  *
2820
- * @param array $out
2821
- * @param boolean $subSelector
2822
  *
2823
  * @return boolean
2824
  */
@@ -2853,8 +3435,8 @@ class Parser
2853
  /**
2854
  * Parse whitespace separated selector list
2855
  *
2856
- * @param array $out
2857
- * @param boolean $subSelector
2858
  *
2859
  * @return boolean
2860
  */
@@ -2862,11 +3444,15 @@ class Parser
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 +3464,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 +3482,55 @@ class Parser
2899
  return true;
2900
  }
2901
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2902
  /**
2903
  * Parse the parts that make up a selector
2904
  *
@@ -2906,8 +3538,8 @@ 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
  */
@@ -2945,6 +3577,7 @@ class Parser
2945
  case '&':
2946
  $parts[] = Compiler::$selfSelector;
2947
  $this->count++;
 
2948
  continue 2;
2949
 
2950
  case '.':
@@ -2958,9 +3591,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 +3607,7 @@ class Parser
2969
  if ($this->placeholder($placeholder)) {
2970
  $parts[] = '%';
2971
  $parts[] = $placeholder;
 
2972
  continue;
2973
  }
2974
 
@@ -2978,6 +3617,7 @@ class Parser
2978
  if ($char === '#') {
2979
  if ($this->interpolation($inter)) {
2980
  $parts[] = $inter;
 
2981
  continue;
2982
  }
2983
 
@@ -3005,13 +3645,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 +3683,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 +3717,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 +3741,7 @@ class Parser
3095
  continue;
3096
  }
3097
 
3098
- if ($this->restrictedKeyword($name)) {
3099
  $parts[] = $name;
3100
  continue;
3101
  }
@@ -3125,7 +3771,10 @@ class Parser
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 {
@@ -3145,20 +3794,62 @@ class Parser
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
 
@@ -3170,14 +3861,15 @@ class Parser
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
 
@@ -3195,12 +3887,14 @@ class Parser
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;
@@ -3222,10 +3916,28 @@ class Parser
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 +3945,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
 
@@ -3381,11 +4094,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 +4116,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\Exception\ParserException;
16
+ use ScssPhp\ScssPhp\Logger\LoggerInterface;
17
+ use ScssPhp\ScssPhp\Logger\QuietLogger;
18
 
19
  /**
20
  * Parser
21
  *
22
  * @author Leaf Corcoran <leafot@gmail.com>
23
+ *
24
+ * @internal
25
  */
26
  class Parser
27
  {
30
  const SOURCE_COLUMN = -3;
31
 
32
  /**
33
+ * @var array<string, int>
34
  */
35
  protected static $precedence = [
36
  '=' => 0,
38
  'and' => 2,
39
  '==' => 3,
40
  '!=' => 3,
 
41
  '<=' => 4,
42
  '>=' => 4,
43
  '<' => 4,
49
  '%' => 6,
50
  ];
51
 
52
+ /**
53
+ * @var string
54
+ */
55
  protected static $commentPattern;
56
+ /**
57
+ * @var string
58
+ */
59
  protected static $operatorPattern;
60
+ /**
61
+ * @var string
62
+ */
63
  protected static $whitePattern;
64
 
65
+ /**
66
+ * @var Cache|null
67
+ */
68
  protected $cache;
69
 
70
  private $sourceName;
71
  private $sourceIndex;
72
+ /**
73
+ * @var array<int, int>
74
+ */
75
  private $sourcePositions;
76
+ /**
77
+ * @var array|null
78
+ */
79
  private $charset;
80
+ /**
81
+ * The current offset in the buffer
82
+ *
83
+ * @var int
84
+ */
85
  private $count;
86
+ /**
87
+ * @var Block|null
88
+ */
89
  private $env;
90
+ /**
91
+ * @var bool
92
+ */
93
  private $inParens;
94
+ /**
95
+ * @var bool
96
+ */
97
  private $eatWhiteDefault;
98
+ /**
99
+ * @var bool
100
+ */
101
  private $discardComments;
102
  private $allowVars;
103
+ /**
104
+ * @var string
105
+ */
106
  private $buffer;
107
  private $utf8;
108
+ /**
109
+ * @var string|null
110
+ */
111
  private $encoding;
112
  private $patternModifiers;
113
  private $commentsSeen;
114
 
115
  private $cssOnly;
116
 
117
+ /**
118
+ * @var LoggerInterface
119
+ */
120
+ private $logger;
121
+
122
  /**
123
  * Constructor
124
  *
125
  * @api
126
  *
127
+ * @param string|null $sourceName
128
+ * @param integer $sourceIndex
129
+ * @param string|null $encoding
130
+ * @param Cache|null $cache
131
+ * @param bool $cssOnly
132
+ * @param LoggerInterface|null $logger
133
  */
134
+ public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8', Cache $cache = null, $cssOnly = false, LoggerInterface $logger = null)
135
  {
136
  $this->sourceName = $sourceName ?: '(stdin)';
137
  $this->sourceIndex = $sourceIndex;
142
  $this->commentsSeen = [];
143
  $this->allowVars = true;
144
  $this->cssOnly = $cssOnly;
145
+ $this->logger = $logger ?: new QuietLogger();
146
 
147
  if (empty(static::$operatorPattern)) {
148
+ static::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=?|and|or)';
149
 
150
  $commentSingle = '\/\/';
151
  $commentMultiLeft = '\/\*';
157
  : '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisS';
158
  }
159
 
160
+ $this->cache = $cache;
 
 
161
  }
162
 
163
  /**
179
  *
180
  * @param string $msg
181
  *
182
+ * @phpstan-return never-return
183
+ *
184
+ * @throws ParserException
185
+ *
186
+ * @deprecated use "parseError" and throw the exception in the caller instead.
187
  */
188
  public function throwParseError($msg = 'parse error')
189
+ {
190
+ @trigger_error(
191
+ 'The method "throwParseError" is deprecated. Use "parseError" and throw the exception in the caller instead',
192
+ E_USER_DEPRECATED
193
+ );
194
+
195
+ throw $this->parseError($msg);
196
+ }
197
+
198
+ /**
199
+ * Creates a parser error
200
+ *
201
+ * @api
202
+ *
203
+ * @param string $msg
204
+ *
205
+ * @return ParserException
206
+ */
207
+ public function parseError($msg = 'parse error')
208
  {
209
  list($line, $column) = $this->getSourcePosition($this->count);
210
 
212
  ? "line: $line, column: $column"
213
  : "$this->sourceName on line $line, at column $column";
214
 
215
+ if ($this->peek('(.*?)(\n|$)', $m, $this->count)) {
216
  $this->restoreEncoding();
217
 
218
+ $e = new ParserException("$msg: failed at `$m[1]` $loc");
219
+ $e->setSourcePosition([$this->sourceName, $line, $column]);
220
+
221
+ return $e;
222
  }
223
 
224
  $this->restoreEncoding();
225
 
226
+ $e = new ParserException("$msg: $loc");
227
+ $e->setSourcePosition([$this->sourceName, $line, $column]);
228
+
229
+ return $e;
230
  }
231
 
232
  /**
236
  *
237
  * @param string $buffer
238
  *
239
+ * @return Block
240
  */
241
  public function parse($buffer)
242
  {
243
  if ($this->cache) {
244
+ $cacheKey = $this->sourceName . ':' . md5($buffer);
245
  $parseOptions = [
246
  'charset' => $this->charset,
247
  'utf8' => $this->utf8,
248
  ];
249
+ $v = $this->cache->getCache('parse', $cacheKey, $parseOptions);
250
 
251
  if (! \is_null($v)) {
252
  return $v;
277
  }
278
 
279
  if ($this->count !== \strlen($this->buffer)) {
280
+ throw $this->parseError();
281
  }
282
 
283
  if (! empty($this->env->parent)) {
284
+ throw $this->parseError('unclosed block');
285
  }
286
 
287
  if ($this->charset) {
291
  $this->restoreEncoding();
292
 
293
  if ($this->cache) {
294
+ $this->cache->setCache('parse', $cacheKey, $this->env, $parseOptions);
295
  }
296
 
297
  return $this->env;
316
  $this->buffer = (string) $buffer;
317
 
318
  $this->saveEncoding();
319
+ $this->extractLineNumbers($this->buffer);
320
 
321
  $list = $this->valueList($out);
322
 
332
  *
333
  * @param string $buffer
334
  * @param string|array $out
335
+ * @param bool $shouldValidate
336
  *
337
  * @return boolean
338
  */
339
+ public function parseSelector($buffer, &$out, $shouldValidate = true)
340
  {
341
  $this->count = 0;
342
  $this->env = null;
345
  $this->buffer = (string) $buffer;
346
 
347
  $this->saveEncoding();
348
+ $this->extractLineNumbers($this->buffer);
349
+
350
+ // discard space/comments at the start
351
+ $this->discardComments = true;
352
+ $this->whitespace();
353
+ $this->discardComments = false;
354
 
355
  $selector = $this->selectors($out);
356
 
357
  $this->restoreEncoding();
358
 
359
+ if ($shouldValidate && $this->count !== strlen($buffer)) {
360
+ throw $this->parseError("`" . substr($buffer, $this->count) . "` is not a valid Selector in `$buffer`");
361
+ }
362
+
363
  return $selector;
364
  }
365
 
382
  $this->buffer = (string) $buffer;
383
 
384
  $this->saveEncoding();
385
+ $this->extractLineNumbers($this->buffer);
386
 
387
  $isMediaQuery = $this->mediaQueryList($out);
388
 
436
 
437
  // the directives
438
  if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') {
439
+ if (
440
+ $this->literal('@at-root', 8) &&
441
  ($this->selectors($selector) || true) &&
442
  ($this->map($with) || true) &&
443
+ (($this->matchChar('(') &&
444
+ $this->interpolation($with) &&
445
+ $this->matchChar(')')) || true) &&
446
  $this->matchChar('{', false)
447
  ) {
448
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
449
 
450
  $atRoot = $this->pushSpecialBlock(Type::T_AT_ROOT, $s);
451
  $atRoot->selector = $selector;
456
 
457
  $this->seek($s);
458
 
459
+ if (
460
+ $this->literal('@media', 6) &&
461
+ $this->mediaQueryList($mediaQueryList) &&
462
+ $this->matchChar('{', false)
463
+ ) {
464
  $media = $this->pushSpecialBlock(Type::T_MEDIA, $s);
465
  $media->queryList = $mediaQueryList[2];
466
 
469
 
470
  $this->seek($s);
471
 
472
+ if (
473
+ $this->literal('@mixin', 6) &&
474
  $this->keyword($mixinName) &&
475
  ($this->argumentDef($args) || true) &&
476
  $this->matchChar('{', false)
477
  ) {
478
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
479
 
480
  $mixin = $this->pushSpecialBlock(Type::T_MIXIN, $s);
481
  $mixin->name = $mixinName;
486
 
487
  $this->seek($s);
488
 
489
+ if (
490
+ ($this->literal('@include', 8) &&
491
+ $this->keyword($mixinName) &&
492
+ ($this->matchChar('(') &&
493
  ($this->argValues($argValues) || true) &&
494
  $this->matchChar(')') || true) &&
495
+ ($this->end()) ||
496
+ ($this->literal('using', 5) &&
497
+ $this->argumentDef($argUsing) &&
498
+ ($this->end() || $this->matchChar('{') && $hasBlock = true)) ||
499
+ $this->matchChar('{') && $hasBlock = true)
500
  ) {
501
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
502
 
503
  $child = [
504
  Type::T_INCLUDE,
520
 
521
  $this->seek($s);
522
 
523
+ if (
524
+ $this->literal('@scssphp-import-once', 20) &&
525
  $this->valueList($importPath) &&
526
  $this->end()
527
  ) {
528
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
529
+
530
+ list($line, $column) = $this->getSourcePosition($s);
531
+ $file = $this->sourceName;
532
+ $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);
533
 
534
  $this->append([Type::T_SCSSPHP_IMPORT_ONCE, $importPath], $s);
535
 
538
 
539
  $this->seek($s);
540
 
541
+ if (
542
+ $this->literal('@import', 7) &&
543
  $this->valueList($importPath) &&
544
+ $importPath[0] !== Type::T_FUNCTION_CALL &&
545
  $this->end()
546
  ) {
547
+ if ($this->cssOnly) {
548
+ $this->assertPlainCssValid([Type::T_IMPORT, $importPath], $s);
549
+ $this->append([Type::T_COMMENT, rtrim(substr($this->buffer, $s, $this->count - $s))]);
550
+ return true;
551
+ }
552
+
553
  $this->append([Type::T_IMPORT, $importPath], $s);
554
 
555
  return true;
557
 
558
  $this->seek($s);
559
 
560
+ if (
561
+ $this->literal('@import', 7) &&
562
  $this->url($importPath) &&
563
  $this->end()
564
  ) {
565
  if ($this->cssOnly) {
566
+ $this->assertPlainCssValid([Type::T_IMPORT, $importPath], $s);
567
+ $this->append([Type::T_COMMENT, rtrim(substr($this->buffer, $s, $this->count - $s))]);
568
+ return true;
569
  }
570
 
571
  $this->append([Type::T_IMPORT, $importPath], $s);
575
 
576
  $this->seek($s);
577
 
578
+ if (
579
+ $this->literal('@extend', 7) &&
580
  $this->selectors($selectors) &&
581
  $this->end()
582
  ) {
583
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
584
 
585
  // check for '!flag'
586
  $optional = $this->stripOptionalFlag($selectors);
591
 
592
  $this->seek($s);
593
 
594
+ if (
595
+ $this->literal('@function', 9) &&
596
  $this->keyword($fnName) &&
597
  $this->argumentDef($args) &&
598
  $this->matchChar('{', false)
599
  ) {
600
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
601
 
602
  $func = $this->pushSpecialBlock(Type::T_FUNCTION, $s);
603
  $func->name = $fnName;
608
 
609
  $this->seek($s);
610
 
611
+ if (
612
+ $this->literal('@return', 7) &&
613
+ ($this->valueList($retVal) || true) &&
614
+ $this->end()
615
+ ) {
616
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617
 
618
  $this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s);
619
 
622
 
623
  $this->seek($s);
624
 
625
+ if (
626
+ $this->literal('@each', 5) &&
627
  $this->genericList($varNames, 'variable', ',', false) &&
628
  $this->literal('in', 2) &&
629
  $this->valueList($list) &&
630
  $this->matchChar('{', false)
631
  ) {
632
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
633
 
634
  $each = $this->pushSpecialBlock(Type::T_EACH, $s);
635
 
644
 
645
  $this->seek($s);
646
 
647
+ if (
648
+ $this->literal('@while', 6) &&
649
  $this->expression($cond) &&
650
  $this->matchChar('{', false)
651
  ) {
652
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
653
+
654
+ while (
655
+ $cond[0] === Type::T_LIST &&
656
+ ! empty($cond['enclosing']) &&
657
+ $cond['enclosing'] === 'parent' &&
658
+ \count($cond[2]) == 1
659
+ ) {
660
+ $cond = reset($cond[2]);
661
  }
662
 
663
  $while = $this->pushSpecialBlock(Type::T_WHILE, $s);
668
 
669
  $this->seek($s);
670
 
671
+ if (
672
+ $this->literal('@for', 4) &&
673
  $this->variable($varName) &&
674
  $this->literal('from', 4) &&
675
  $this->expression($start) &&
678
  $this->expression($end) &&
679
  $this->matchChar('{', false)
680
  ) {
681
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
682
 
683
  $for = $this->pushSpecialBlock(Type::T_FOR, $s);
684
  $for->var = $varName[1];
691
 
692
  $this->seek($s);
693
 
694
+ if (
695
+ $this->literal('@if', 3) &&
696
+ $this->functionCallArgumentsList($cond, false, '{', false)
697
+ ) {
698
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
699
 
700
  $if = $this->pushSpecialBlock(Type::T_IF, $s);
701
 
702
+ while (
703
+ $cond[0] === Type::T_LIST &&
704
+ ! empty($cond['enclosing']) &&
705
+ $cond['enclosing'] === 'parent' &&
706
+ \count($cond[2]) == 1
707
+ ) {
708
  $cond = reset($cond[2]);
709
  }
710
 
716
 
717
  $this->seek($s);
718
 
719
+ if (
720
+ $this->literal('@debug', 6) &&
721
+ $this->functionCallArgumentsList($value, false)
722
  ) {
723
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
724
 
725
  $this->append([Type::T_DEBUG, $value], $s);
726
 
729
 
730
  $this->seek($s);
731
 
732
+ if (
733
+ $this->literal('@warn', 5) &&
734
+ $this->functionCallArgumentsList($value, false)
735
  ) {
736
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
737
 
738
  $this->append([Type::T_WARN, $value], $s);
739
 
742
 
743
  $this->seek($s);
744
 
745
+ if (
746
+ $this->literal('@error', 6) &&
747
+ $this->functionCallArgumentsList($value, false)
748
  ) {
749
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
750
 
751
  $this->append([Type::T_ERROR, $value], $s);
752
 
755
 
756
  $this->seek($s);
757
 
758
+ if (
759
+ $this->literal('@content', 8) &&
760
  ($this->end() ||
761
  $this->matchChar('(') &&
762
  $this->argValues($argContent) &&
763
  $this->matchChar(')') &&
764
  $this->end())
765
  ) {
766
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
767
 
768
  $this->append([Type::T_MIXIN_CONTENT, isset($argContent) ? $argContent : null], $s);
769
 
780
  if ($this->literal('@else', 5)) {
781
  if ($this->matchChar('{', false)) {
782
  $else = $this->pushSpecialBlock(Type::T_ELSE, $s);
783
+ } elseif (
784
+ $this->literal('if', 2) &&
785
+ $this->functionCallArgumentsList($cond, false, '{', false)
786
+ ) {
787
  $else = $this->pushSpecialBlock(Type::T_ELSEIF, $s);
788
  $else->cond = $cond;
789
  }
800
  }
801
 
802
  // only retain the first @charset directive encountered
803
+ if (
804
+ $this->literal('@charset', 8) &&
805
  $this->valueList($charset) &&
806
  $this->end()
807
  ) {
822
 
823
  $this->seek($s);
824
 
825
+ if (
826
+ $this->literal('@supports', 9) &&
827
+ ($t1 = $this->supportsQuery($supportQuery)) &&
828
+ ($t2 = $this->matchChar('{', false))
829
  ) {
830
  $directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s);
831
  $directive->name = 'supports';
837
  $this->seek($s);
838
 
839
  // doesn't match built in directive, do generic one
840
+ if (
841
+ $this->matchChar('@', false) &&
842
+ $this->mixedKeyword($dirName) &&
843
  $this->directiveValue($dirValue, '{')
844
  ) {
845
+ if (count($dirName) === 1 && is_string(reset($dirName))) {
846
+ $dirName = reset($dirName);
847
+ } else {
848
+ $dirName = [Type::T_STRING, '', $dirName];
849
+ }
850
  if ($dirName === 'media') {
851
  $directive = $this->pushSpecialBlock(Type::T_MEDIA, $s);
852
  } else {
855
  }
856
 
857
  if (isset($dirValue)) {
858
+ ! $this->cssOnly || ($dirValue = $this->assertPlainCssValid($dirValue));
859
  $directive->value = $dirValue;
860
  }
861
 
865
  $this->seek($s);
866
 
867
  // maybe it's a generic blockless directive
868
+ if (
869
+ $this->matchChar('@', false) &&
870
+ $this->mixedKeyword($dirName) &&
871
+ ! $this->isKnownGenericDirective($dirName) &&
872
+ ($this->end(false) || ($this->directiveValue($dirValue, '') && $this->end(false)))
873
  ) {
874
+ if (\count($dirName) === 1 && \is_string(\reset($dirName))) {
875
+ $dirName = \reset($dirName);
876
+ } else {
877
+ $dirName = [Type::T_STRING, '', $dirName];
878
+ }
879
+ if (
880
+ ! empty($this->env->parent) &&
881
+ $this->env->type &&
882
+ ! \in_array($this->env->type, [Type::T_DIRECTIVE, Type::T_MEDIA])
883
+ ) {
884
+ $plain = \trim(\substr($this->buffer, $s, $this->count - $s));
885
+ throw $this->parseError(
886
+ "Unknown directive `{$plain}` not allowed in `" . $this->env->type . "` block"
887
+ );
888
+ }
889
+ // blockless directives with a blank line after keeps their blank lines after
890
+ // sass-spec compliance purpose
891
+ $s = $this->count;
892
+ $hasBlankLine = false;
893
+ if ($this->match('\s*?\n\s*\n', $out, false)) {
894
+ $hasBlankLine = true;
895
+ $this->seek($s);
896
+ }
897
+ $isNotRoot = ! empty($this->env->parent);
898
+ $this->append([Type::T_DIRECTIVE, [$dirName, $dirValue, $hasBlankLine, $isNotRoot]], $s);
899
+ $this->whitespace();
900
 
901
  return true;
902
  }
906
  return false;
907
  }
908
 
909
+ $inCssSelector = null;
910
+ if ($this->cssOnly) {
911
+ $inCssSelector = (! empty($this->env->parent) &&
912
+ ! in_array($this->env->type, [Type::T_DIRECTIVE, Type::T_MEDIA]));
913
+ }
914
  // custom properties : right part is static
915
+ if (($this->customProperty($name) ) && $this->matchChar(':', false)) {
 
 
916
  $start = $this->count;
917
 
918
  // but can be complex and finish with ; or }
919
  foreach ([';','}'] as $ending) {
920
+ if (
921
+ $this->openString($ending, $stringValue, '(', ')', false) &&
922
  $this->end()
923
  ) {
924
  $end = $this->count;
933
  if ($p && $p < $end) {
934
  $this->seek($start);
935
 
936
+ if (
937
+ $this->openString($ending, $stringValue, $nestingPair[0], $nestingPair[1], false) &&
938
  $this->end() &&
939
  $this->count > $end
940
  ) {
958
 
959
  // property shortcut
960
  // captures most properties before having to parse a selector
961
+ if (
962
+ $this->keyword($name, false) &&
963
  $this->literal(': ', 2) &&
964
  $this->valueList($value) &&
965
  $this->end()
973
  $this->seek($s);
974
 
975
  // variable assigns
976
+ if (
977
+ $this->variable($name) &&
978
  $this->matchChar(':') &&
979
  $this->valueList($value) &&
980
  $this->end()
981
  ) {
982
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
 
 
983
 
984
  // check for '!flag'
985
  $assignmentFlags = $this->stripAssignmentFlags($value);
990
 
991
  $this->seek($s);
992
 
 
 
 
 
 
993
  // opening css block
994
+ if (
995
+ $this->selectors($selectors) &&
996
+ $this->matchChar('{', false)
997
+ ) {
998
+ ! $this->cssOnly || ! $inCssSelector || $this->assertPlainCssValid(false);
 
999
 
1000
  $this->pushBlock($selectors, $s);
1001
 
1010
  $this->seek($s);
1011
 
1012
  // property assign, or nested assign
1013
+ if (
1014
+ $this->propertyName($name) &&
1015
+ $this->matchChar(':')
1016
+ ) {
1017
  $foundSomething = false;
1018
 
1019
  if ($this->valueList($value)) {
1020
  if (empty($this->env->parent)) {
1021
+ throw $this->parseError('expected "{"');
1022
  }
1023
 
1024
  $this->append([Type::T_ASSIGN, $name, $value], $s);
1026
  }
1027
 
1028
  if ($this->matchChar('{', false)) {
1029
+ ! $this->cssOnly || $this->assertPlainCssValid(false);
 
 
1030
 
1031
  $propBlock = $this->pushSpecialBlock(Type::T_NESTED_PROPERTY, $s);
1032
  $propBlock->prefix = $name;
1077
  }
1078
 
1079
  // extra stuff
1080
+ if ($this->matchChar(';')) {
 
 
1081
  return true;
1082
  }
1083
 
1087
  /**
1088
  * Push block onto parse tree
1089
  *
1090
+ * @param array|null $selectors
1091
  * @param integer $pos
1092
  *
1093
+ * @return Block
1094
  */
1095
  protected function pushBlock($selectors, $pos = 0)
1096
  {
1097
  list($line, $column) = $this->getSourcePosition($pos);
1098
 
1099
+ $b = new Block();
1100
  $b->sourceName = $this->sourceName;
1101
  $b->sourceLine = $line;
1102
  $b->sourceColumn = $column;
1136
  * @param string $type
1137
  * @param integer $pos
1138
  *
1139
+ * @return Block
1140
  */
1141
  protected function pushSpecialBlock($type, $pos)
1142
  {
1149
  /**
1150
  * Pop scope and return last block
1151
  *
1152
+ * @return Block
1153
  *
1154
  * @throws \Exception
1155
  */
1165
  $block = $this->env;
1166
 
1167
  if (empty($block->parent)) {
1168
+ throw $this->parseError('unexpected }');
1169
  }
1170
 
1171
  if ($block->type == Type::T_AT_ROOT) {
1196
  }
1197
 
1198
  $r = '/' . $regex . '/' . $this->patternModifiers;
1199
+ $result = preg_match($r, $this->buffer, $out, 0, $from);
1200
 
1201
  return $result;
1202
  }
1211
  $this->count = $where;
1212
  }
1213
 
1214
+ /**
1215
+ * Assert a parsed part is plain CSS Valid
1216
+ *
1217
+ * @param array|false $parsed
1218
+ * @param int $startPos
1219
+ * @throws ParserException
1220
+ */
1221
+ protected function assertPlainCssValid($parsed, $startPos = null)
1222
+ {
1223
+ $type = '';
1224
+ if ($parsed) {
1225
+ $type = $parsed[0];
1226
+ $parsed = $this->isPlainCssValidElement($parsed);
1227
+ }
1228
+ if (! $parsed) {
1229
+ if (! \is_null($startPos)) {
1230
+ $plain = rtrim(substr($this->buffer, $startPos, $this->count - $startPos));
1231
+ $message = "Error : `{$plain}` isn't allowed in plain CSS";
1232
+ } else {
1233
+ $message = 'Error: SCSS syntax not allowed in CSS file';
1234
+ }
1235
+ if ($type) {
1236
+ $message .= " ($type)";
1237
+ }
1238
+ throw $this->parseError($message);
1239
+ }
1240
+
1241
+ return $parsed;
1242
+ }
1243
+
1244
+ /**
1245
+ * Check a parsed element is plain CSS Valid
1246
+ * @param array $parsed
1247
+ * @return bool|array
1248
+ */
1249
+ protected function isPlainCssValidElement($parsed, $allowExpression = false)
1250
+ {
1251
+ // keep string as is
1252
+ if (is_string($parsed)) {
1253
+ return $parsed;
1254
+ }
1255
+
1256
+ if (
1257
+ \in_array($parsed[0], [Type::T_FUNCTION, Type::T_FUNCTION_CALL]) &&
1258
+ !\in_array($parsed[1], [
1259
+ 'alpha',
1260
+ 'attr',
1261
+ 'calc',
1262
+ 'cubic-bezier',
1263
+ 'env',
1264
+ 'grayscale',
1265
+ 'hsl',
1266
+ 'hsla',
1267
+ 'hwb',
1268
+ 'invert',
1269
+ 'linear-gradient',
1270
+ 'min',
1271
+ 'max',
1272
+ 'radial-gradient',
1273
+ 'repeating-linear-gradient',
1274
+ 'repeating-radial-gradient',
1275
+ 'rgb',
1276
+ 'rgba',
1277
+ 'rotate',
1278
+ 'saturate',
1279
+ 'var',
1280
+ ]) &&
1281
+ Compiler::isNativeFunction($parsed[1])
1282
+ ) {
1283
+ return false;
1284
+ }
1285
+
1286
+ switch ($parsed[0]) {
1287
+ case Type::T_BLOCK:
1288
+ case Type::T_KEYWORD:
1289
+ case Type::T_NULL:
1290
+ case Type::T_NUMBER:
1291
+ case Type::T_MEDIA:
1292
+ return $parsed;
1293
+
1294
+ case Type::T_COMMENT:
1295
+ if (isset($parsed[2])) {
1296
+ return false;
1297
+ }
1298
+ return $parsed;
1299
+
1300
+ case Type::T_DIRECTIVE:
1301
+ if (\is_array($parsed[1])) {
1302
+ $parsed[1][1] = $this->isPlainCssValidElement($parsed[1][1]);
1303
+ if (! $parsed[1][1]) {
1304
+ return false;
1305
+ }
1306
+ }
1307
+
1308
+ return $parsed;
1309
+
1310
+ case Type::T_IMPORT:
1311
+ if ($parsed[1][0] === Type::T_LIST) {
1312
+ return false;
1313
+ }
1314
+ $parsed[1] = $this->isPlainCssValidElement($parsed[1]);
1315
+ if ($parsed[1] === false) {
1316
+ return false;
1317
+ }
1318
+ return $parsed;
1319
+
1320
+ case Type::T_STRING:
1321
+ foreach ($parsed[2] as $k => $substr) {
1322
+ if (\is_array($substr)) {
1323
+ $parsed[2][$k] = $this->isPlainCssValidElement($substr);
1324
+ if (! $parsed[2][$k]) {
1325
+ return false;
1326
+ }
1327
+ }
1328
+ }
1329
+ return $parsed;
1330
+
1331
+ case Type::T_LIST:
1332
+ if (!empty($parsed['enclosing'])) {
1333
+ return false;
1334
+ }
1335
+ foreach ($parsed[2] as $k => $listElement) {
1336
+ $parsed[2][$k] = $this->isPlainCssValidElement($listElement);
1337
+ if (! $parsed[2][$k]) {
1338
+ return false;
1339
+ }
1340
+ }
1341
+ return $parsed;
1342
+
1343
+ case Type::T_ASSIGN:
1344
+ foreach ([1, 2, 3] as $k) {
1345
+ if (! empty($parsed[$k])) {
1346
+ $parsed[$k] = $this->isPlainCssValidElement($parsed[$k]);
1347
+ if (! $parsed[$k]) {
1348
+ return false;
1349
+ }
1350
+ }
1351
+ }
1352
+ return $parsed;
1353
+
1354
+ case Type::T_EXPRESSION:
1355
+ list( ,$op, $lhs, $rhs, $inParens, $whiteBefore, $whiteAfter) = $parsed;
1356
+ if (! $allowExpression && ! \in_array($op, ['and', 'or', '/'])) {
1357
+ return false;
1358
+ }
1359
+ $lhs = $this->isPlainCssValidElement($lhs, true);
1360
+ if (! $lhs) {
1361
+ return false;
1362
+ }
1363
+ $rhs = $this->isPlainCssValidElement($rhs, true);
1364
+ if (! $rhs) {
1365
+ return false;
1366
+ }
1367
+
1368
+ return [
1369
+ Type::T_STRING,
1370
+ '', [
1371
+ $this->inParens ? '(' : '',
1372
+ $lhs,
1373
+ ($whiteBefore ? ' ' : '') . $op . ($whiteAfter ? ' ' : ''),
1374
+ $rhs,
1375
+ $this->inParens ? ')' : ''
1376
+ ]
1377
+ ];
1378
+
1379
+ case Type::T_CUSTOM_PROPERTY:
1380
+ case Type::T_UNARY:
1381
+ $parsed[2] = $this->isPlainCssValidElement($parsed[2]);
1382
+ if (! $parsed[2]) {
1383
+ return false;
1384
+ }
1385
+ return $parsed;
1386
+
1387
+ case Type::T_FUNCTION:
1388
+ $argsList = $parsed[2];
1389
+ foreach ($argsList[2] as $argElement) {
1390
+ if (! $this->isPlainCssValidElement($argElement)) {
1391
+ return false;
1392
+ }
1393
+ }
1394
+ return $parsed;
1395
+
1396
+ case Type::T_FUNCTION_CALL:
1397
+ $parsed[0] = Type::T_FUNCTION;
1398
+ $argsList = [Type::T_LIST, ',', []];
1399
+ foreach ($parsed[2] as $arg) {
1400
+ if ($arg[0] || ! empty($arg[2])) {
1401
+ // no named arguments possible in a css function call
1402
+ // nor ... argument
1403
+ return false;
1404
+ }
1405
+ $arg = $this->isPlainCssValidElement($arg[1], $parsed[1] === 'calc');
1406
+ if (! $arg) {
1407
+ return false;
1408
+ }
1409
+ $argsList[2][] = $arg;
1410
+ }
1411
+ $parsed[2] = $argsList;
1412
+ return $parsed;
1413
+ }
1414
+
1415
+ return false;
1416
+ }
1417
+
1418
  /**
1419
  * Match string looking for either ending delim, escape, or string interpolation
1420
  *
1421
  * {@internal This is a workaround for preg_match's 250K string match limit. }}
1422
  *
1423
  * @param array $m Matches (passed by reference)
1424
+ * @param string $delim Delimiter
1425
  *
1426
  * @return boolean True if match; false otherwise
1427
  */
1432
  $end = \strlen($this->buffer);
1433
 
1434
  // look for either ending delim, escape, or string interpolation
1435
+ foreach (['#{', '\\', "\r", $delim] as $lookahead) {
1436
  $pos = strpos($this->buffer, $lookahead, $this->count);
1437
 
1438
  if ($pos !== false && $pos < $end) {
1469
  {
1470
  $r = '/' . $regex . '/' . $this->patternModifiers;
1471
 
1472
+ if (! preg_match($r, $this->buffer, $out, 0, $this->count)) {
1473
  return false;
1474
  }
1475
 
1550
  {
1551
  $gotWhite = false;
1552
 
1553
+ while (preg_match(static::$whitePattern, $this->buffer, $m, 0, $this->count)) {
1554
  if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
1555
  // comment that are kept in the output CSS
1556
  $comment = [];
1569
  if ($this->interpolation($out)) {
1570
  // keep right spaces in the following string part
1571
  if ($out[3]) {
1572
+ while ($this->buffer[$this->count - 1] !== '}') {
1573
  $this->count--;
1574
  }
1575
 
1578
 
1579
  $comment[] = [Type::T_COMMENT, substr($this->buffer, $p, $this->count - $p), $out];
1580
  } else {
1581
+ list($line, $column) = $this->getSourcePosition($this->count);
1582
+ $file = $this->sourceName;
1583
+ if (!$this->discardComments) {
1584
+ $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);
1585
+ }
1586
  $comment[] = substr($this->buffer, $this->count, 2);
1587
 
1588
  $this->count += 2;
1600
  } else {
1601
  $comment[] = $c;
1602
  $staticComment = substr($this->buffer, $startCommentCount, $endCommentCount - $startCommentCount);
1603
+ $commentStatement = [Type::T_COMMENT, $staticComment, [Type::T_STRING, '', $comment]];
1604
+
1605
+ list($line, $column) = $this->getSourcePosition($startCommentCount);
1606
+ $commentStatement[self::SOURCE_LINE] = $line;
1607
+ $commentStatement[self::SOURCE_COLUMN] = $column;
1608
+ $commentStatement[self::SOURCE_INDEX] = $this->sourceIndex;
1609
+
1610
+ $this->appendComment($commentStatement);
1611
  }
1612
 
1613
  $this->commentsSeen[$startCommentCount] = true;
1615
  } else {
1616
  // comment that are ignored and not kept in the output css
1617
  $this->count += \strlen($m[0]);
1618
+ // silent comments are not allowed in plain CSS files
1619
+ ! $this->cssOnly
1620
+ || ! \strlen(trim($m[0]))
1621
+ || $this->assertPlainCssValid(false, $this->count - \strlen($m[0]));
1622
  }
1623
 
1624
  $gotWhite = true;
1635
  protected function appendComment($comment)
1636
  {
1637
  if (! $this->discardComments) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1638
  $this->env->comments[] = $comment;
1639
  }
1640
  }
1642
  /**
1643
  * Append statement to current block
1644
  *
1645
+ * @param array|null $statement
1646
  * @param integer $pos
1647
  */
1648
  protected function append($statement, $pos = null)
1649
  {
1650
  if (! \is_null($statement)) {
1651
+ ! $this->cssOnly || ($statement = $this->assertPlainCssValid($statement, $pos));
1652
+
1653
  if (! \is_null($pos)) {
1654
  list($line, $column) = $this->getSourcePosition($pos);
1655
 
1707
  $expressions = null;
1708
  $parts = [];
1709
 
1710
+ if (
1711
+ ($this->literal('only', 4) && ($only = true) ||
1712
+ $this->literal('not', 3) && ($not = true) || true) &&
1713
  $this->mixedKeyword($mediaType)
1714
  ) {
1715
  $prop = [Type::T_MEDIA_TYPE];
1765
 
1766
  $not = false;
1767
 
1768
+ if (
1769
+ ($this->literal('not', 3) && ($not = true) || true) &&
1770
  $this->matchChar('(') &&
1771
  ($this->expression($property)) &&
1772
  $this->literal(': ', 2) &&
1785
  $this->seek($s);
1786
  }
1787
 
1788
+ if (
1789
+ $this->matchChar('(') &&
1790
  $this->supportsQuery($subQuery) &&
1791
  $this->matchChar(')')
1792
  ) {
1796
  $this->seek($s);
1797
  }
1798
 
1799
+ if (
1800
+ $this->literal('not', 3) &&
1801
  $this->supportsQuery($subQuery)
1802
  ) {
1803
  $parts[] = [Type::T_STRING, '', [[Type::T_KEYWORD, 'not '], $subQuery]];
1806
  $this->seek($s);
1807
  }
1808
 
1809
+ if (
1810
+ $this->literal('selector(', 9) &&
1811
  $this->selector($selector) &&
1812
  $this->matchChar(')')
1813
  ) {
1844
  $this->seek($s);
1845
  }
1846
 
1847
+ if (
1848
+ $this->literal('and', 3) &&
1849
+ $this->genericList($expressions, 'supportsQuery', ' and', false)
1850
+ ) {
1851
  array_unshift($expressions[2], [Type::T_STRING, '', $parts]);
1852
 
1853
  $parts = [$expressions];
1856
  $this->seek($s);
1857
  }
1858
 
1859
+ if (
1860
+ $this->literal('or', 2) &&
1861
+ $this->genericList($expressions, 'supportsQuery', ' or', false)
1862
+ ) {
1863
  array_unshift($expressions[2], [Type::T_STRING, '', $parts]);
1864
 
1865
  $parts = [$expressions];
1894
  $s = $this->count;
1895
  $value = null;
1896
 
1897
+ if (
1898
+ $this->matchChar('(') &&
1899
  $this->expression($feature) &&
1900
+ ($this->matchChar(':') &&
1901
+ $this->expression($value) || true) &&
1902
  $this->matchChar(')')
1903
  ) {
1904
  $out = [Type::T_MEDIA_EXPRESSION, $feature];
1924
  */
1925
  protected function argValues(&$out)
1926
  {
1927
+ $discardComments = $this->discardComments;
1928
+ $this->discardComments = true;
1929
+
1930
  if ($this->genericList($list, 'argValue', ',', false)) {
1931
  $out = $list[2];
1932
 
1933
+ $this->discardComments = $discardComments;
1934
+
1935
  return true;
1936
  }
1937
 
1938
+ $this->discardComments = $discardComments;
1939
+
1940
  return false;
1941
  }
1942
 
1959
  $keyword = null;
1960
  }
1961
 
1962
+ if ($this->genericList($value, 'expression', '', true)) {
1963
  $out = [$keyword, $value, false];
1964
  $s = $this->count;
1965
 
1975
  return false;
1976
  }
1977
 
1978
+ /**
1979
+ * Check if a generic directive is known to be able to allow almost any syntax or not
1980
+ * @param mixed $directiveName
1981
+ * @return bool
1982
+ */
1983
+ protected function isKnownGenericDirective($directiveName)
1984
+ {
1985
+ if (\is_array($directiveName) && \is_string(reset($directiveName))) {
1986
+ $directiveName = reset($directiveName);
1987
+ }
1988
+ if (! \is_string($directiveName)) {
1989
+ return false;
1990
+ }
1991
+ if (
1992
+ \in_array($directiveName, [
1993
+ 'at-root',
1994
+ 'media',
1995
+ 'mixin',
1996
+ 'include',
1997
+ 'scssphp-import-once',
1998
+ 'import',
1999
+ 'extend',
2000
+ 'function',
2001
+ 'break',
2002
+ 'continue',
2003
+ 'return',
2004
+ 'each',
2005
+ 'while',
2006
+ 'for',
2007
+ 'if',
2008
+ 'debug',
2009
+ 'warn',
2010
+ 'error',
2011
+ 'content',
2012
+ 'else',
2013
+ 'charset',
2014
+ 'supports',
2015
+ // Todo
2016
+ 'use',
2017
+ 'forward',
2018
+ ])
2019
+ ) {
2020
+ return true;
2021
+ }
2022
+ return false;
2023
+ }
2024
+
2025
  /**
2026
  * Parse directive value list that considers $vars as keyword
2027
  *
2046
 
2047
  $this->seek($s);
2048
 
2049
+ if (\is_string($endChar) && $this->openString($endChar ? $endChar : ';', $out, null, null, true, ";}{")) {
2050
+ if ($endChar && $this->matchChar($endChar, false)) {
2051
+ return true;
2052
+ }
2053
+ $ss = $this->count;
2054
+ if (!$endChar && $this->end()) {
2055
+ $this->seek($ss);
2056
  return true;
2057
  }
2058
  }
2101
  return $res;
2102
  }
2103
 
2104
+ /**
2105
+ * Parse a function call, where externals () are part of the call
2106
+ * and not of the value list
2107
+ *
2108
+ * @param $out
2109
+ * @param bool $mandatoryEnclos
2110
+ * @param null|string $charAfter
2111
+ * @param null|bool $eatWhiteSp
2112
+ * @return bool
2113
+ */
2114
+ protected function functionCallArgumentsList(&$out, $mandatoryEnclos = true, $charAfter = null, $eatWhiteSp = null)
2115
+ {
2116
+ $s = $this->count;
2117
+
2118
+ if (
2119
+ $this->matchChar('(') &&
2120
+ $this->valueList($out) &&
2121
+ $this->matchChar(')') &&
2122
+ ($charAfter ? $this->matchChar($charAfter, $eatWhiteSp) : $this->end())
2123
+ ) {
2124
+ return true;
2125
+ }
2126
+
2127
+ if (! $mandatoryEnclos) {
2128
+ $this->seek($s);
2129
+
2130
+ if (
2131
+ $this->valueList($out) &&
2132
+ ($charAfter ? $this->matchChar($charAfter, $eatWhiteSp) : $this->end())
2133
+ ) {
2134
+ return true;
2135
+ }
2136
+ }
2137
+
2138
+ $this->seek($s);
2139
+
2140
+ return false;
2141
+ }
2142
+
2143
  /**
2144
  * Parse space separated value list
2145
  *
2155
  /**
2156
  * Parse generic list
2157
  *
2158
+ * @param array $out
2159
+ * @param string $parseItem The name of the method used to parse items
2160
+ * @param string $delim
2161
+ * @param boolean $flatten
2162
  *
2163
  * @return boolean
2164
  */
2178
  }
2179
 
2180
  $trailing_delim = true;
2181
+ } else {
2182
+ // if no delim watch that a keyword didn't eat the single/double quote
2183
+ // from the following starting string
2184
+ if ($value[0] === Type::T_KEYWORD) {
2185
+ $word = $value[1];
2186
+
2187
+ $last_char = substr($word, -1);
2188
+
2189
+ if (
2190
+ strlen($word) > 1 &&
2191
+ in_array($last_char, [ "'", '"']) &&
2192
+ substr($word, -2, 1) !== '\\'
2193
+ ) {
2194
+ // if there is a non escaped opening quote in the keyword, this seems unlikely a mistake
2195
+ $word = str_replace('\\' . $last_char, '\\\\', $word);
2196
+ if (strpos($word, $last_char) < strlen($word) - 1) {
2197
+ continue;
2198
+ }
2199
+
2200
+ $currentCount = $this->count;
2201
+
2202
+ // let's try to rewind to previous char and try a parse
2203
+ $this->count--;
2204
+ // in case the keyword also eat spaces
2205
+ while (substr($this->buffer, $this->count, 1) !== $last_char) {
2206
+ $this->count--;
2207
+ }
2208
+
2209
+ $nextValue = null;
2210
+ if ($this->$parseItem($nextValue)) {
2211
+ if ($nextValue[0] === Type::T_KEYWORD && $nextValue[1] === $last_char) {
2212
+ // bad try, forget it
2213
+ $this->seek($currentCount);
2214
+ continue;
2215
+ }
2216
+ if ($nextValue[0] !== Type::T_STRING) {
2217
+ // bad try, forget it
2218
+ $this->seek($currentCount);
2219
+ continue;
2220
+ }
2221
+
2222
+ // OK it was a good idea
2223
+ $value[1] = substr($value[1], 0, -1);
2224
+ array_pop($items);
2225
+ $items[] = $value;
2226
+ $items[] = $nextValue;
2227
+ } else {
2228
+ // bad try, forget it
2229
+ $this->seek($currentCount);
2230
+ continue;
2231
+ }
2232
+ }
2233
+ }
2234
  }
2235
  }
2236
 
2270
  $allowedTypes = ($listOnly ? [Type::T_LIST] : [Type::T_LIST, Type::T_MAP]);
2271
 
2272
  if ($this->matchChar('(')) {
2273
+ if ($this->enclosedExpression($lhs, $s, ')', $allowedTypes)) {
2274
  if ($lookForExp) {
2275
  $out = $this->expHelper($lhs, 0);
2276
  } else {
2286
  }
2287
 
2288
  if (\in_array(Type::T_LIST, $allowedTypes) && $this->matchChar('[')) {
2289
+ if ($this->enclosedExpression($lhs, $s, ']', [Type::T_LIST])) {
2290
  if ($lookForExp) {
2291
  $out = $this->expHelper($lhs, 0);
2292
  } else {
2328
  *
2329
  * @return boolean
2330
  */
2331
+ protected function enclosedExpression(&$out, $s, $closingParen = ')', $allowedTypes = [Type::T_LIST, Type::T_MAP])
2332
  {
2333
  if ($this->matchChar($closingParen) && \in_array(Type::T_LIST, $allowedTypes)) {
2334
  $out = [Type::T_LIST, '', []];
2335
 
2336
  switch ($closingParen) {
2337
+ case ')':
2338
  $out['enclosing'] = 'parent'; // parenthesis list
2339
  break;
2340
 
2341
+ case ']':
2342
  $out['enclosing'] = 'bracket'; // bracketed list
2343
  break;
2344
  }
2346
  return true;
2347
  }
2348
 
2349
+ if (
2350
+ $this->valueList($out) &&
2351
+ $this->matchChar($closingParen) && ! ($closingParen === ')' &&
2352
+ \in_array($out[0], [Type::T_EXPRESSION, Type::T_UNARY])) &&
2353
  \in_array(Type::T_LIST, $allowedTypes)
2354
  ) {
2355
  if ($out[0] !== Type::T_LIST || ! empty($out['enclosing'])) {
2357
  }
2358
 
2359
  switch ($closingParen) {
2360
+ case ')':
2361
  $out['enclosing'] = 'parent'; // parenthesis list
2362
  break;
2363
 
2364
+ case ']':
2365
  $out['enclosing'] = 'bracket'; // bracketed list
2366
  break;
2367
  }
2413
  break;
2414
  }
2415
 
2416
+ if ($op === '-' && ! $whiteAfter && $rhs[0] === Type::T_KEYWORD) {
2417
+ break;
 
2418
  }
2419
 
2420
+ // consume higher-precedence operators on the right-hand side
2421
+ $rhs = $this->expHelper($rhs, static::$precedence[$op] + 1);
2422
+
2423
  $lhs = [Type::T_EXPRESSION, $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter];
2424
+
2425
  $ss = $this->count;
2426
  $whiteBefore = isset($this->buffer[$this->count - 1]) &&
2427
  ctype_space($this->buffer[$this->count - 1]);
2448
  $s = $this->count;
2449
  $char = $this->buffer[$this->count];
2450
 
2451
+ if (
2452
+ $this->literal('url(', 4) &&
2453
+ $this->match('data:([a-z]+)\/([a-z0-9.+-]+);base64,', $m, false)
2454
+ ) {
2455
  $len = strspn(
2456
  $this->buffer,
2457
  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwyxz0123456789+/=',
2470
 
2471
  $this->seek($s);
2472
 
2473
+ if (
2474
+ $this->literal('url(', 4, false) &&
2475
+ $this->match('\s*(\/\/[^\s\)]+)\s*', $m)
2476
+ ) {
2477
  $content = 'url(' . $m[1];
2478
 
2479
  if ($this->matchChar(')')) {
2488
 
2489
  // not
2490
  if ($char === 'n' && $this->literal('not', 3, false)) {
2491
+ if (
2492
+ $this->whitespace() &&
2493
+ $this->value($inner)
2494
+ ) {
2495
  $out = [Type::T_UNARY, 'not', $inner, $this->inParens];
2496
 
2497
  return true;
2546
  return true;
2547
  }
2548
 
2549
+ if (
2550
+ $this->keyword($inner) &&
2551
+ ! $this->func($inner, $out)
2552
+ ) {
2553
  $out = [Type::T_UNARY, '-', $inner, $this->inParens];
2554
 
2555
  return true;
2577
  $this->count++;
2578
 
2579
  if ($this->keyword($keyword)) {
2580
+ $out = [Type::T_KEYWORD, '#' . $keyword];
2581
 
2582
  return true;
2583
  }
2608
  }
2609
 
2610
  // unicode range with wildcards
2611
+ if (
2612
+ $this->literal('U+', 2) &&
2613
+ $this->match('\?+|([0-9A-F]+(\?+|(-[0-9A-F]+))?)', $m, false)
2614
+ ) {
2615
+ $unicode = explode('-', $m[0]);
2616
+ if (strlen(reset($unicode)) <= 6 && strlen(end($unicode)) <= 6) {
2617
+ $out = [Type::T_KEYWORD, 'U+' . $m[0]];
2618
 
2619
+ return true;
2620
+ }
2621
+ $this->count -= strlen($m[0]) + 2;
2622
  }
2623
 
2624
  if ($this->keyword($keyword, false)) {
2662
 
2663
  $this->inParens = true;
2664
 
2665
+ if (
2666
+ $this->expression($exp) &&
2667
+ $this->matchChar(')')
2668
+ ) {
2669
  $out = $exp;
2670
  $this->inParens = $inParens;
2671
 
2690
  {
2691
  $s = $this->count;
2692
 
2693
+ if (
2694
+ $this->literal('progid:', 7, false) &&
2695
  $this->openString('(', $fn) &&
2696
  $this->matchChar('(')
2697
  ) {
2733
  if ($name !== 'expression' && ! preg_match('/^(-[a-z]+-)?calc$/', $name)) {
2734
  $ss = $this->count;
2735
 
2736
+ if (
2737
+ $this->argValues($args) &&
2738
+ $this->matchChar(')')
2739
+ ) {
2740
  $func = [Type::T_FUNCTION_CALL, $name, $args];
2741
 
2742
  return true;
2745
  $this->seek($ss);
2746
  }
2747
 
2748
+ if (
2749
+ ($this->openString(')', $str, '(') || true) &&
2750
  $this->matchChar(')')
2751
  ) {
2752
  $args = [];
2781
  $args = [];
2782
 
2783
  while ($this->keyword($var)) {
2784
+ if (
2785
+ $this->matchChar('=') &&
2786
+ $this->expression($exp)
2787
+ ) {
2788
  $args[] = [Type::T_STRING, '', [$var . '=']];
2789
  $arg = $exp;
2790
  } else {
2830
 
2831
  $ss = $this->count;
2832
 
2833
+ if (
2834
+ $this->matchChar(':') &&
2835
+ $this->genericList($defaultVal, 'expression', '', true)
2836
+ ) {
2837
  $arg[1] = $defaultVal;
2838
  } else {
2839
  $this->seek($ss);
2845
  $sss = $this->count;
2846
 
2847
  if (! $this->matchChar(')')) {
2848
+ throw $this->parseError('... has to be after the final argument');
2849
  }
2850
 
2851
  $arg[2] = true;
2891
  $keys = [];
2892
  $values = [];
2893
 
2894
+ while (
2895
+ $this->genericList($key, 'expression', '', true) &&
2896
+ $this->matchChar(':') &&
2897
+ $this->genericList($value, 'expression', '', true)
2898
  ) {
2899
  $keys[] = $key;
2900
  $values[] = $value;
2926
  {
2927
  $s = $this->count;
2928
 
2929
+ if ($this->match('(#([0-9a-f]+)\b)', $m)) {
2930
  if (\in_array(\strlen($m[2]), [3,4,6,8])) {
2931
  $out = [Type::T_KEYWORD, $m[0]];
2932
 
2974
  *
2975
  * @return boolean
2976
  */
2977
+ protected function string(&$out, $keepDelimWithInterpolation = false)
2978
  {
2979
  $s = $this->count;
2980
 
3006
  $this->count += \strlen($m[2]);
3007
  $content[] = '#{'; // ignore it
3008
  }
3009
+ } elseif ($m[2] === "\r") {
3010
+ $content[] = chr(10);
3011
+ // TODO : warning
3012
+ # DEPRECATION WARNING on line x, column y of zzz:
3013
+ # Unescaped multiline strings are deprecated and will be removed in a future version of Sass.
3014
+ # To include a newline in a string, use "\a" or "\a " as in CSS.
3015
+ if ($this->matchChar("\n", false)) {
3016
+ $content[] = ' ';
3017
+ }
3018
  } elseif ($m[2] === '\\') {
3019
+ if (
3020
+ $this->literal("\r\n", 2, false) ||
 
 
 
 
 
3021
  $this->matchChar("\r", false) ||
3022
  $this->matchChar("\n", false) ||
3023
  $this->matchChar("\f", false)
3024
  ) {
3025
  // this is a continuation escaping, to be ignored
3026
+ } elseif ($this->matchEscapeCharacter($c)) {
3027
+ $content[] = $c;
3028
  } else {
3029
+ throw $this->parseError('Unterminated escape sequence');
3030
  }
3031
  } else {
3032
  $this->count -= \strlen($delim);
3037
  $this->eatWhiteDefault = $oldWhite;
3038
 
3039
  if ($this->literal($delim, \strlen($delim))) {
3040
+ if ($hasInterpolation && ! $keepDelimWithInterpolation) {
3041
  $delim = '"';
 
 
 
 
 
 
 
 
 
 
3042
  }
3043
 
3044
  $out = [Type::T_STRING, $delim, $content];
3051
  return false;
3052
  }
3053
 
3054
+ /**
3055
+ * @param string $out
3056
+ * @param bool $inKeywords
3057
+ * @return bool
3058
+ */
3059
+ protected function matchEscapeCharacter(&$out, $inKeywords = false)
3060
+ {
3061
+ $s = $this->count;
3062
+ if ($this->match('[a-f0-9]', $m, false)) {
3063
+ $hex = $m[0];
3064
+
3065
+ for ($i = 5; $i--;) {
3066
+ if ($this->match('[a-f0-9]', $m, false)) {
3067
+ $hex .= $m[0];
3068
+ } else {
3069
+ break;
3070
+ }
3071
+ }
3072
+
3073
+ // CSS allows Unicode escape sequences to be followed by a delimiter space
3074
+ // (necessary in some cases for shorter sequences to disambiguate their end)
3075
+ $this->matchChar(' ', false);
3076
+
3077
+ $value = hexdec($hex);
3078
+
3079
+ if (!$inKeywords && ($value == 0 || ($value >= 0xD800 && $value <= 0xDFFF) || $value >= 0x10FFFF)) {
3080
+ $out = "\xEF\xBF\xBD"; // "\u{FFFD}" but with a syntax supported on PHP 5
3081
+ } elseif ($value < 0x20) {
3082
+ $out = Util::mbChr($value);
3083
+ } else {
3084
+ $out = Util::mbChr($value);
3085
+ }
3086
+
3087
+ return true;
3088
+ }
3089
+
3090
+ if ($this->match('.', $m, false)) {
3091
+ if ($inKeywords && in_array($m[0], ["'",'"','@','&',' ','\\',':','/','%'])) {
3092
+ $this->seek($s);
3093
+ return false;
3094
+ }
3095
+ $out = $m[0];
3096
+
3097
+ return true;
3098
+ }
3099
+
3100
+ return false;
3101
+ }
3102
+
3103
  /**
3104
  * Parse keyword or interpolation
3105
  *
3149
  *
3150
  * @param string $end
3151
  * @param array $out
3152
+ * @param string $nestOpen
3153
+ * @param string $nestClose
3154
+ * @param boolean $rtrim
3155
+ * @param string $disallow
3156
  *
3157
  * @return boolean
3158
  */
3159
+ protected function openString($end, &$out, $nestOpen = null, $nestClose = null, $rtrim = true, $disallow = null)
3160
  {
3161
  $oldWhite = $this->eatWhiteDefault;
3162
  $this->eatWhiteDefault = false;
3163
 
3164
+ if ($nestOpen && ! $nestClose) {
3165
+ $nestClose = $end;
3166
  }
3167
 
3168
+ $patt = ($disallow ? '[^' . $this->pregQuote($disallow) . ']' : '.');
3169
+ $patt = '(' . $patt . '*?)([\'"]|#\{|'
3170
  . $this->pregQuote($end) . '|'
3171
+ . (($nestClose && $nestClose !== $end) ? $this->pregQuote($nestClose) . '|' : '')
3172
  . static::$commentPattern . ')';
3173
 
3174
  $nestingLevel = 0;
3179
  if (isset($m[1]) && $m[1] !== '') {
3180
  $content[] = $m[1];
3181
 
3182
+ if ($nestOpen) {
3183
+ $nestingLevel += substr_count($m[1], $nestOpen);
3184
  }
3185
  }
3186
 
3187
  $tok = $m[2];
3188
 
3189
+ $this->count -= \strlen($tok);
3190
 
3191
  if ($tok === $end && ! $nestingLevel) {
3192
  break;
3193
  }
3194
 
3195
+ if ($tok === $nestClose) {
3196
  $nestingLevel--;
3197
  }
3198
 
3199
+ if (($tok === "'" || $tok === '"') && $this->string($str, true)) {
3200
  $content[] = $str;
3201
  continue;
3202
  }
3207
  }
3208
 
3209
  $content[] = $tok;
3210
+ $this->count += \strlen($tok);
3211
  }
3212
 
3213
  $this->eatWhiteDefault = $oldWhite;
3217
  }
3218
 
3219
  // trim the end
3220
+ if ($rtrim && \is_string(end($content))) {
3221
  $content[\count($content) - 1] = rtrim(end($content));
3222
  }
3223
 
3243
 
3244
  $s = $this->count;
3245
 
3246
+ if (
3247
+ $this->literal('#{', 2) &&
3248
+ $this->valueList($value) &&
3249
+ $this->matchChar('}', false)
3250
+ ) {
3251
  if ($value === [Type::T_SELF]) {
3252
  $out = $value;
3253
  } else {
3254
  if ($lookWhite) {
3255
  $left = ($s > 0 && preg_match('/\s/', $this->buffer[$s - 1])) ? ' ' : '';
3256
+ $right = (
3257
+ ! empty($this->buffer[$this->count]) &&
3258
+ preg_match('/\s/', $this->buffer[$this->count])
3259
+ ) ? ' ' : '';
3260
  } else {
3261
  $left = $right = false;
3262
  }
3323
  }
3324
 
3325
  // match comment hack
3326
+ if (preg_match(static::$whitePattern, $this->buffer, $m, 0, $this->count)) {
3327
  if (! empty($m[0])) {
3328
  $parts[] = $m[0];
3329
  $this->count += \strlen($m[0]);
3363
  continue;
3364
  }
3365
 
3366
+ if ($this->matchChar('&', false)) {
3367
+ $parts[] = [Type::T_SELF];
3368
+ continue;
3369
+ }
3370
+
3371
  if ($this->variable($var)) {
3372
  $parts[] = $var;
3373
  continue;
3399
  /**
3400
  * Parse comma separated selector list
3401
  *
3402
+ * @param array $out
3403
+ * @param string|boolean $subSelector
3404
  *
3405
  * @return boolean
3406
  */
3435
  /**
3436
  * Parse whitespace separated selector list
3437
  *
3438
+ * @param array $out
3439
+ * @param string|boolean $subSelector
3440
  *
3441
  * @return boolean
3442
  */
3444
  {
3445
  $selector = [];
3446
 
3447
+ $discardComments = $this->discardComments;
3448
+ $this->discardComments = true;
3449
+
3450
  for (;;) {
3451
  $s = $this->count;
3452
 
3453
  if ($this->match('[>+~]+', $m, true)) {
3454
+ if (
3455
+ $subSelector && \is_string($subSelector) && strpos($subSelector, 'nth-') === 0 &&
3456
  $m[0] === '+' && $this->match("(\d+|n\b)", $counter)
3457
  ) {
3458
  $this->seek($s);
3464
 
3465
  if ($this->selectorSingle($part, $subSelector)) {
3466
  $selector[] = $part;
3467
+ $this->whitespace();
 
 
 
 
 
3468
  continue;
3469
  }
3470
 
3471
  break;
3472
  }
3473
 
3474
+ $this->discardComments = $discardComments;
3475
+
3476
  if (! $selector) {
3477
  return false;
3478
  }
3482
  return true;
3483
  }
3484
 
3485
+ /**
3486
+ * parsing escaped chars in selectors:
3487
+ * - escaped single chars are kept escaped in the selector but in a normalized form
3488
+ * (if not in 0-9a-f range as this would be ambigous)
3489
+ * - other escaped sequences (multibyte chars or 0-9a-f) are kept in their initial escaped form,
3490
+ * normalized to lowercase
3491
+ *
3492
+ * TODO: this is a fallback solution. Ideally escaped chars in selectors should be encoded as the genuine chars,
3493
+ * and escaping added when printing in the Compiler, where/if it's mandatory
3494
+ * - but this require a better formal selector representation instead of the array we have now
3495
+ *
3496
+ * @param string $out
3497
+ * @param bool $keepEscapedNumber
3498
+ * @return bool
3499
+ */
3500
+ protected function matchEscapeCharacterInSelector(&$out, $keepEscapedNumber = false)
3501
+ {
3502
+ $s_escape = $this->count;
3503
+ if ($this->match('\\\\', $m)) {
3504
+ $out = '\\' . $m[0];
3505
+ return true;
3506
+ }
3507
+
3508
+ if ($this->matchEscapeCharacter($escapedout, true)) {
3509
+ if (strlen($escapedout) === 1) {
3510
+ if (!preg_match(",\w,", $escapedout)) {
3511
+ $out = '\\' . $escapedout;
3512
+ return true;
3513
+ } elseif (! $keepEscapedNumber || ! \is_numeric($escapedout)) {
3514
+ $out = $escapedout;
3515
+ return true;
3516
+ }
3517
+ }
3518
+ $escape_sequence = rtrim(substr($this->buffer, $s_escape, $this->count - $s_escape));
3519
+ if (strlen($escape_sequence) < 6) {
3520
+ $escape_sequence .= ' ';
3521
+ }
3522
+ $out = '\\' . strtolower($escape_sequence);
3523
+ return true;
3524
+ }
3525
+ if ($this->match('\\S', $m)) {
3526
+ $out = '\\' . $m[0];
3527
+ return true;
3528
+ }
3529
+
3530
+
3531
+ return false;
3532
+ }
3533
+
3534
  /**
3535
  * Parse the parts that make up a selector
3536
  *
3538
  * div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
3539
  * }}
3540
  *
3541
+ * @param array $out
3542
+ * @param string|boolean $subSelector
3543
  *
3544
  * @return boolean
3545
  */
3577
  case '&':
3578
  $parts[] = Compiler::$selfSelector;
3579
  $this->count++;
3580
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
3581
  continue 2;
3582
 
3583
  case '.':
3591
  continue 2;
3592
  }
3593
 
3594
+ // handling of escaping in selectors : get the escaped char
3595
+ if ($char === '\\') {
3596
+ $this->count++;
3597
+ if ($this->matchEscapeCharacterInSelector($escaped, true)) {
3598
+ $parts[] = $escaped;
3599
+ continue;
3600
+ }
3601
+ $this->count--;
3602
  }
3603
 
3604
  if ($char === '%') {
3607
  if ($this->placeholder($placeholder)) {
3608
  $parts[] = '%';
3609
  $parts[] = $placeholder;
3610
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
3611
  continue;
3612
  }
3613
 
3617
  if ($char === '#') {
3618
  if ($this->interpolation($inter)) {
3619
  $parts[] = $inter;
3620
+ ! $this->cssOnly || $this->assertPlainCssValid(false, $s);
3621
  continue;
3622
  }
3623
 
3645
 
3646
  $ss = $this->count;
3647
 
3648
+ if (
3649
+ $nameParts === ['not'] ||
3650
+ $nameParts === ['is'] ||
3651
+ $nameParts === ['has'] ||
3652
+ $nameParts === ['where'] ||
3653
  $nameParts === ['slotted'] ||
3654
+ $nameParts === ['nth-child'] ||
3655
+ $nameParts === ['nth-last-child'] ||
3656
+ $nameParts === ['nth-of-type'] ||
3657
+ $nameParts === ['nth-last-of-type']
3658
  ) {
3659
+ if (
3660
+ $this->matchChar('(', true) &&
3661
  ($this->selectors($subs, reset($nameParts)) || true) &&
3662
  $this->matchChar(')')
3663
  ) {
3683
  } else {
3684
  $this->seek($ss);
3685
  }
3686
+ } elseif (
3687
+ $this->matchChar('(', true) &&
3688
+ ($this->openString(')', $str, '(') || true) &&
3689
+ $this->matchChar(')')
3690
+ ) {
3691
+ $parts[] = '(';
 
 
 
 
3692
 
3693
+ if (! empty($str)) {
3694
+ $parts[] = $str;
 
3695
  }
3696
+
3697
+ $parts[] = ')';
3698
+ } else {
3699
+ $this->seek($ss);
3700
  }
3701
 
3702
  continue;
3717
  $this->seek($s);
3718
 
3719
  // attribute selector
3720
+ if (
3721
+ $char === '[' &&
3722
  $this->matchChar('[') &&
3723
  ($this->openString(']', $str, '[') || true) &&
3724
  $this->matchChar(']')
3741
  continue;
3742
  }
3743
 
3744
+ if ($this->restrictedKeyword($name, false, true)) {
3745
  $parts[] = $name;
3746
  continue;
3747
  }
3771
  {
3772
  $s = $this->count;
3773
 
3774
+ if (
3775
+ $this->matchChar('$', false) &&
3776
+ $this->keyword($name)
3777
+ ) {
3778
  if ($this->allowVars) {
3779
  $out = [Type::T_VARIABLE, $name];
3780
  } else {
3794
  *
3795
  * @param string $word
3796
  * @param boolean $eatWhitespace
3797
+ * @param boolean $inSelector
3798
  *
3799
  * @return boolean
3800
  */
3801
+ protected function keyword(&$word, $eatWhitespace = null, $inSelector = false)
3802
  {
3803
+ $s = $this->count;
3804
+ $match = $this->match(
3805
  $this->utf8
3806
+ ? '(([\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]) ?|[\\\\].)*)'
3807
+ : '(([\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]) ?|[\\\\].)*)',
3808
  $m,
3809
+ false
3810
+ );
3811
+
3812
+ if ($match) {
3813
  $word = $m[1];
3814
 
3815
+ // handling of escaping in keyword : get the escaped char
3816
+ if (strpos($word, '\\') !== false) {
3817
+ $send = $this->count;
3818
+ $escapedWord = [];
3819
+ $this->seek($s);
3820
+ $previousEscape = false;
3821
+ while ($this->count < $send) {
3822
+ $char = $this->buffer[$this->count];
3823
+ $this->count++;
3824
+ if (
3825
+ $this->count < $send
3826
+ && $char === '\\'
3827
+ && !$previousEscape
3828
+ && (
3829
+ $inSelector ?
3830
+ $this->matchEscapeCharacterInSelector($out)
3831
+ :
3832
+ $this->matchEscapeCharacter($out, true)
3833
+ )
3834
+ ) {
3835
+ $escapedWord[] = $out;
3836
+ } else {
3837
+ if ($previousEscape) {
3838
+ $previousEscape = false;
3839
+ } elseif ($char === '\\') {
3840
+ $previousEscape = true;
3841
+ }
3842
+ $escapedWord[] = $char;
3843
+ }
3844
+ }
3845
+
3846
+ $word = implode('', $escapedWord);
3847
+ }
3848
+
3849
+ if (is_null($eatWhitespace) ? $this->eatWhiteDefault : $eatWhitespace) {
3850
+ $this->whitespace();
3851
+ }
3852
+
3853
  return true;
3854
  }
3855
 
3861
  *
3862
  * @param string $word
3863
  * @param boolean $eatWhitespace
3864
+ * @param boolean $inSelector
3865
  *
3866
  * @return boolean
3867
  */
3868
+ protected function restrictedKeyword(&$word, $eatWhitespace = null, $inSelector = false)
3869
  {
3870
  $s = $this->count;
3871
 
3872
+ if ($this->keyword($word, $eatWhitespace, $inSelector) && (\ord($word[0]) > 57 || \ord($word[0]) < 48)) {
3873
  return true;
3874
  }
3875
 
3887
  */
3888
  protected function placeholder(&$placeholder)
3889
  {
3890
+ $match = $this->match(
3891
  $this->utf8
3892
  ? '([\pL\w\-_]+)'
3893
  : '([\w\-_]+)',
3894
  $m
3895
+ );
3896
+
3897
+ if ($match) {
3898
  $placeholder = $m[1];
3899
 
3900
  return true;
3916
  */
3917
  protected function url(&$out)
3918
  {
3919
+ if ($this->literal('url(', 4)) {
3920
+ $s = $this->count;
3921
 
3922
+ if (
3923
+ ($this->string($out) || $this->spaceList($out)) &&
3924
+ $this->matchChar(')')
3925
+ ) {
3926
+ $out = [Type::T_STRING, '', ['url(', $out, ')']];
3927
+
3928
+ return true;
3929
+ }
3930
+
3931
+ $this->seek($s);
3932
+
3933
+ if (
3934
+ $this->openString(')', $out) &&
3935
+ $this->matchChar(')')
3936
+ ) {
3937
+ $out = [Type::T_STRING, '', ['url(', $out, ')']];
3938
+
3939
+ return true;
3940
+ }
3941
  }
3942
 
3943
  return false;
3945
 
3946
  /**
3947
  * Consume an end of statement delimiter
3948
+ * @param bool $eatWhitespace
3949
  *
3950
  * @return boolean
3951
  */
3952
+ protected function end($eatWhitespace = null)
3953
  {
3954
+ if ($this->matchChar(';', $eatWhitespace)) {
3955
  return true;
3956
  }
3957
 
4094
  }
4095
 
4096
  /**
4097
+ * Save internal encoding of mbstring
4098
+ *
4099
+ * When mbstring.func_overload is used to replace the standard PHP string functions,
4100
+ * this method configures the internal encoding to a single-byte one so that the
4101
+ * behavior matches the normal behavior of PHP string functions while using the parser.
4102
+ * The existing internal encoding is saved and will be restored when calling {@see restoreEncoding}.
4103
+ *
4104
+ * If mbstring.func_overload is not used (or does not override string functions), this method is a no-op.
4105
+ *
4106
+ * @return void
4107
  */
4108
  private function saveEncoding()
4109
  {
4110
+ if (\PHP_VERSION_ID < 80000 && \extension_loaded('mbstring') && (2 & (int) ini_get('mbstring.func_overload')) > 0) {
4111
  $this->encoding = mb_internal_encoding();
4112
 
4113
  mb_internal_encoding('iso-8859-1');
4116
 
4117
  /**
4118
  * Restore internal encoding
4119
+ *
4120
+ * @return void
4121
  */
4122
  private function restoreEncoding()
4123
  {
assets/libraries/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,
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,
assets/libraries/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
  {
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
  {
assets/libraries/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,16 +87,24 @@ 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);
@@ -106,6 +119,8 @@ class SourceMapGenerator
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
  {
@@ -128,6 +143,7 @@ class SourceMapGenerator
128
  * @return string
129
  *
130
  * @throws \ScssPhp\ScssPhp\Exception\CompilerException If the file could not be saved
 
131
  */
132
  public function saveMap($content)
133
  {
@@ -153,14 +169,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;
@@ -211,7 +229,7 @@ class SourceMapGenerator
211
  /**
212
  * Returns the sources contents
213
  *
214
- * @return array|null
215
  */
216
  protected function getSourcesContent()
217
  {
@@ -231,14 +249,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 +278,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 +292,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']) {
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_merge($this->defaultOptions, $options);
119
  * @param integer $originalLine The line number in original file
120
  * @param integer $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
  {
143
  * @return string
144
  *
145
  * @throws \ScssPhp\ScssPhp\Exception\CompilerException If the file could not be saved
146
+ * @deprecated
147
  */
148
  public function saveMap($content)
149
  {
169
  /**
170
  * Generates the JSON source map
171
  *
172
+ * @param string $prefix A prefix added in the output file, which needs to shift mappings
173
+ *
174
  * @return string
175
  *
176
  * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
177
  */
178
+ public function generateJson($prefix = '')
179
  {
180
  $sourceMap = [];
181
+ $mappings = $this->generateMappings($prefix);
182
 
183
  // File version (always the first entry in the object) and must be a positive integer.
184
  $sourceMap['version'] = self::VERSION;
229
  /**
230
  * Returns the sources contents
231
  *
232
+ * @return string[]|null
233
  */
234
  protected function getSourcesContent()
235
  {
249
  /**
250
  * Generates the mappings string
251
  *
252
+ * @param string $prefix A prefix added in the output file, which needs to shift mappings
253
+ *
254
  * @return string
255
  */
256
+ public function generateMappings($prefix = '')
257
  {
258
  if (! \count($this->mappings)) {
259
  return '';
260
  }
261
 
262
+ $prefixLines = substr_count($prefix, "\n");
263
+ $lastPrefixNewLine = strrpos($prefix, "\n");
264
+ $lastPrefixLineStart = false === $lastPrefixNewLine ? 0 : $lastPrefixNewLine + 1;
265
+ $prefixColumn = strlen($prefix) - $lastPrefixLineStart;
266
+
267
  $this->sourceKeys = array_flip(array_keys($this->sources));
268
 
269
  // group mappings by generated line number.
278
  $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
279
 
280
  foreach ($groupedMap as $lineNumber => $lineMap) {
281
+ if ($lineNumber > 1) {
282
+ // The prefix only impacts the column for the first line of the original output
283
+ $prefixColumn = 0;
284
+ }
285
+ $lineNumber += $prefixLines;
286
+
287
  while (++$lastGeneratedLine < $lineNumber) {
288
  $groupedMapEncoded[] = ';';
289
  }
292
  $lastGeneratedColumn = 0;
293
 
294
  foreach ($lineMap as $m) {
295
+ $generatedColumn = $m['generated_column'] + $prefixColumn;
296
+
297
+ $mapEncoded = $this->encoder->encode($generatedColumn - $lastGeneratedColumn);
298
+ $lastGeneratedColumn = $generatedColumn;
299
 
300
  // find the index
301
  if ($m['source_file']) {
assets/libraries/scssphp/src/Type.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
@@ -21,11 +22,14 @@ class Type
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';
@@ -38,8 +42,10 @@ class Type
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';
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
22
  const T_ASSIGN = 'assign';
23
  const T_AT_ROOT = 'at-root';
24
  const T_BLOCK = 'block';
25
+ /** @deprecated */
26
  const T_BREAK = 'break';
27
  const T_CHARSET = 'charset';
28
  const T_COLOR = 'color';
29
  const T_COMMENT = 'comment';
30
+ /** @deprecated */
31
  const T_CONTINUE = 'continue';
32
+ /** @deprecated */
33
  const T_CONTROL = 'control';
34
  const T_CUSTOM_PROPERTY = 'custom';
35
  const T_DEBUG = 'debug';
42
  const T_EXTEND = 'extend';
43
  const T_FOR = 'for';
44
  const T_FUNCTION = 'function';
45
+ const T_FUNCTION_REFERENCE = 'function-reference';
46
  const T_FUNCTION_CALL = 'fncall';
47
  const T_HSL = 'hsl';
48
+ const T_HWB = 'hwb';
49
  const T_IF = 'if';
50
  const T_IMPORT = 'import';
51
  const T_INCLUDE = 'include';
assets/libraries/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 integer $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
  }
assets/libraries/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
+ }
assets/libraries/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
+ }
assets/libraries/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.1';
22
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
19
  */
20
  class Version
21
  {
22
+ const VERSION = '1.8.1';
23
  }
assets/libraries/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
+ }
classes/button.php CHANGED
@@ -665,12 +665,14 @@ class maxButton
665
 
666
  }
667
  /* Remove the button from database
668
- * @todo implement, this function could be safer.
669
  */
670
- public function delete($id)
671
  {
672
  global $wpdb;
673
- $wpdb->query($wpdb->prepare("DELETE FROM " . maxUtils::get_table_name() . " WHERE id = %d", $id));
 
 
674
  }
675
 
676
  /* Save changes to the button
665
 
666
  }
667
  /* Remove the button from database
668
+ *
669
  */
670
+ public function delete(int $id)
671
  {
672
  global $wpdb;
673
+ $res = $wpdb->query($wpdb->prepare("DELETE FROM " . maxUtils::get_table_name() . " WHERE id = %d", $id));
674
+
675
+ return $res;
676
  }
677
 
678
  /* Save changes to the button
classes/controllers/listController.php CHANGED
@@ -30,7 +30,6 @@ class listController extends MaxController
30
 
31
  public function loadView()
32
  {
33
-
34
  if (! isset($this->view->listView)) // Can be set by handlePost
35
  $this->view->listView = (isset($_GET["view"])) ? sanitize_text_field($_GET["view"]) : "all";
36
 
30
 
31
  public function loadView()
32
  {
 
33
  if (! isset($this->view->listView)) // Can be set by handlePost
34
  $this->view->listView = (isset($_GET["view"])) ? sanitize_text_field($_GET["view"]) : "all";
35
 
classes/installation.php CHANGED
@@ -62,8 +62,7 @@ class maxInstall
62
  static::upgradeUTF();
63
  static::updateFA();
64
  static::migrateResponsive();
65
- // @todo Migrate to new format.
66
- //static::migrateResponsive();
67
 
68
  $button = MB()->getClass('button');
69
  $button->reset_cache(); //refresh cache
62
  static::upgradeUTF();
63
  static::updateFA();
64
  static::migrateResponsive();
65
+
 
66
 
67
  $button = MB()->getClass('button');
68
  $button->reset_cache(); //refresh cache
classes/max-utils.php CHANGED
@@ -130,21 +130,6 @@ class maxUtils
130
  return $value;
131
  }
132
 
133
- /* @todo Marked for removal
134
- static function generate_font_sizes($start, $end, $step = 1)
135
- {
136
- $sizes = array();
137
-
138
- for ($i = $start; $i <= $end; $i += $step)
139
- {
140
- $sizes[$i] = $i;
141
-
142
- }
143
- return $sizes;
144
-
145
- }
146
- */
147
-
148
  // This will be needed for converting from old formats to the new screens.
149
  static function legacy_get_media_query($get_option = 1)
150
  {
130
  return $value;
131
  }
132
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  // This will be needed for converting from old formats to the new screens.
134
  static function legacy_get_media_query($get_option = 1)
135
  {
classes/maxCSSParser.php CHANGED
@@ -1,6 +1,9 @@
1
  <?php
2
  namespace MaxButtons;
 
 
3
  defined('ABSPATH') or die('No direct access permitted');
 
4
  /* Class to Parse CSS. Load as array with diffent pseudo-types,
5
  ability to add nested and new root blocks
6
  parses via scss
@@ -161,14 +164,14 @@ class maxCSSParser
161
 
162
  public function compile($css)
163
  {
164
- $scss = new \ScssPhp\ScssPhp\Compiler();
165
  $scss->setImportPaths(MB()->get_plugin_path() . "assets/scss");
166
 
167
  $minify = get_option("maxbuttons_minify", 1);
168
 
169
  if ($minify == 1)
170
  {
171
- $scss->setFormatter('\ScssPhp\ScssPhp\Formatter\Crunched');
172
  }
173
 
174
  $compile = ' @import "_mixins.scss";' . $css;
@@ -176,7 +179,7 @@ class maxCSSParser
176
  //maxUtils::addTime("CSSParser: Compile start ");
177
  try
178
  {
179
- $css = $scss->compile($compile);
180
  } catch (\Exception $e) {
181
  $this->has_compile_error = true;
182
  $this->compile_error = array('error' => $e,
1
  <?php
2
  namespace MaxButtons;
3
+ use ScssPhp\ScssPhp\Compiler;
4
+
5
  defined('ABSPATH') or die('No direct access permitted');
6
+
7
  /* Class to Parse CSS. Load as array with diffent pseudo-types,
8
  ability to add nested and new root blocks
9
  parses via scss
164
 
165
  public function compile($css)
166
  {
167
+ $scss = new Compiler();
168
  $scss->setImportPaths(MB()->get_plugin_path() . "assets/scss");
169
 
170
  $minify = get_option("maxbuttons_minify", 1);
171
 
172
  if ($minify == 1)
173
  {
174
+ $scss->setOutputStyle(\ScssPhp\ScssPhp\OutputStyle::COMPRESSED);
175
  }
176
 
177
  $compile = ' @import "_mixins.scss";' . $css;
179
  //maxUtils::addTime("CSSParser: Compile start ");
180
  try
181
  {
182
+ $css = $scss->compileString($compile)->getCss();
183
  } catch (\Exception $e) {
184
  $this->has_compile_error = true;
185
  $this->compile_error = array('error' => $e,
includes/maxbuttons-button.php CHANGED
@@ -175,7 +175,6 @@ $admin->get_header(array("title" => $page_title, "title_action" => $action) );
175
  $screen_type = ($screen->is_responsive()) ? 'responsive' : 'default';
176
  $screen_type = ($screen->is_new()) ? 'new' : $screen_type;
177
 
178
- // @todo add sensible sizes from screen here. If possible
179
  $min_width = $max_width = 0;
180
  if ($screen->is_responsive())
181
  {
175
  $screen_type = ($screen->is_responsive()) ? 'responsive' : 'default';
176
  $screen_type = ($screen->is_new()) ? 'new' : $screen_type;
177
 
 
178
  $min_width = $max_width = 0;
179
  if ($screen->is_responsive())
180
  {
includes/maxbuttons-list-trash.php DELETED
@@ -1,186 +0,0 @@
1
- <?php
2
- namespace MaxButtons;
3
- defined('ABSPATH') or die('No direct access permitted');
4
-
5
- $result = '';
6
-
7
- exit("Not used");
8
-
9
- $button = MB()->getClass('button');
10
-
11
- if ($_POST) {
12
- if (isset($_POST['button-id']) && isset($_POST['bulk-action-select'])) {
13
- if ($_POST['bulk-action-select'] == 'restore') {
14
- $count = 0;
15
-
16
- foreach ($_POST['button-id'] as $id) {
17
- $button->set($id);
18
- $button->setStatus('publish');
19
-
20
- //maxbuttons_button_restore($id);
21
- $count++;
22
- }
23
-
24
- if ($count == 1) {
25
- $result = __('Restored 1 button.', 'maxbuttons');
26
- }
27
-
28
- if ($count > 1) {
29
- $result = __('Restored ', 'maxbuttons') . $count . __(' buttons.', 'maxbuttons');
30
- }
31
- }
32
-
33
- if ($_POST['bulk-action-select'] == 'delete') {
34
- $count = 0;
35
-
36
- foreach ($_POST['button-id'] as $id) {
37
- $button->delete($id);
38
- $count++;
39
- }
40
-
41
- if ($count == 1) {
42
- $result = __('Deleted 1 button.', 'maxbuttons');
43
- }
44
-
45
- if ($count > 1) {
46
- $result = __('Deleted ', 'maxbuttons') . $count . __(' buttons.', 'maxbuttons');
47
- }
48
- }
49
- }
50
- }
51
-
52
- if (isset($_GET['message']) && $_GET['message'] == '1restore') {
53
- $result = __('Restored 1 button.', 'maxbuttons');
54
- }
55
-
56
- if (isset($_GET['message']) && $_GET['message'] == '1delete') {
57
- $result = __('Deleted 1 button.', 'maxbuttons');
58
- }
59
-
60
-
61
-
62
- $args = array();
63
- if (isset($_GET["orderby"]))
64
- $args["orderby"] = $_GET["orderby"];
65
- if (isset($_GET["order"]))
66
- $args["order"] = $_GET["order"];
67
-
68
- $mbadmin = MB()->getClass('admin');
69
-
70
- $args["status"] = "trash";
71
- $args["limit"] = -1;
72
-
73
- //$published_buttons = $mbadmin->getButtons($args);
74
- $published_buttons_count = $mbadmin->getButtonCount($args);
75
-
76
- $trashed_buttons = $mbadmin->getButtons($args);
77
- $trashed_buttons_count = $mbadmin->getButtonCount($args);
78
-
79
- $pagination = $mbadmin->getButtonPages($args);
80
-
81
-
82
-
83
- ?>
84
-
85
- <script type="text/javascript">
86
- jQuery(document).ready(function() {
87
- jQuery("#bulk-action-all").click(function() {
88
- jQuery("#maxbuttons input[name='button-id[]']").each(function() {
89
- if (jQuery("#bulk-action-all").is(":checked")) {
90
- jQuery(this).attr("checked", "checked");
91
- }
92
- else {
93
- jQuery(this).removeAttr("checked");
94
- }
95
- });
96
- });
97
-
98
- <?php if ($result != '') { ?>
99
- jQuery("#maxbuttons .message").show();
100
- <?php } ?>
101
- });
102
- </script>
103
-
104
- <div id="maxbuttons">
105
- <div class="wrap">
106
- <div class="icon32">
107
- <a href="https://maxbuttons.com" target="_blank"><img src="<?php echo MAXBUTTONS_PLUGIN_URL ?>/images/mb-32.png" alt="MaxButtons" /></a>
108
- </div>
109
-
110
- <h2 class="title"><?php _e('MaxButtons: Button List', 'maxbuttons') ?></h2>
111
-
112
- <div class="logo">
113
- <?php do_action("mb-display-logo"); ?>
114
- </div>
115
-
116
- <div class="clear"></div>
117
- <?php do_action('mb-display-tabs'); ?>
118
-
119
- <div class="form-actions">
120
- <a class="button-primary" href="<?php echo admin_url() ?>admin.php?page=maxbuttons-controller&action=button"><?php _e('Add New', 'maxbuttons') ?></a>
121
- </div>
122
-
123
- <?php if ($result != '') { ?>
124
- <div class="message"><?php echo $result ?></div>
125
- <?php } ?>
126
-
127
- <p class="status">
128
- <a href="<?php echo admin_url() ?>admin.php?page=maxbuttons-controller&action=list"><?php _e('All', 'maxbuttons') ?></a> <span class="count">(<?php echo $published_buttons_count ?>)</span>
129
- <span class="separator">|</span>
130
- <strong><?php _e('Trash', 'maxbuttons') ?></strong> <span class="count">(<?php echo $trashed_buttons_count ?>)</span>
131
- </p>
132
-
133
- <form method="post">
134
- <select name="bulk-action-select" id="bulk-action-select">
135
- <option value=""><?php _e('Bulk Actions', 'maxbuttons') ?></option>
136
- <option value="restore"><?php _e('Restore', 'maxbuttons') ?></option>
137
- <option value="delete"><?php _e('Delete Permanently', 'maxbuttons') ?></option>
138
- </select>
139
- <input type="submit" class="button" value="<?php _e('Apply', 'maxbuttons') ?>" />
140
-
141
- <div class="button-list">
142
- <table cellpadding="0" cellspacing="0" width="100%">
143
- <tr>
144
- <th><input type="checkbox" name="bulk-action-all" id="bulk-action-all" /></th>
145
- <th><?php _e('Button', 'maxbuttons') ?></th>
146
- <th><?php _e('Name and Description', 'maxbuttons') ?></th>
147
- <th><?php _e('Shortcode', 'maxbuttons') ?></th>
148
- <th><?php _e('Actions', 'maxbuttons') ?></th>
149
- </tr>
150
- <?php foreach ($trashed_buttons as $b) {
151
-
152
- $id = $b["id"];
153
- $button->set($id,'','trash');
154
- ?>
155
- <tr>
156
- <td valign="center">
157
- <input type="checkbox" name="button-id[]" id="button-id-<?php echo $id ?>" value="<?php echo $id ?>" />
158
- </td>
159
- <td>
160
- <div class="shortcode-container">
161
- <?php $button->display( array("preview" => true, "preview_part" => "full") ); ?>
162
- </div>
163
- </td>
164
- <td>
165
- <strong><?php echo $button->getName() ?></strong>
166
- <br />
167
- <p><?php echo $button->getDescription() ?></p>
168
- </td>
169
- <td>
170
- [maxbutton id="<?php echo $id ?>"]
171
- </td>
172
- <td>
173
- <a href="<?php admin_url() ?>admin.php?page=maxbuttons-controller&action=restore&id=<?php echo $id ?>"><?php _e('Restore', 'maxbuttons') ?></a>
174
- <span class="separator">|</span>
175
- <a href="<?php admin_url() ?>admin.php?page=maxbuttons-controller&action=delete&id=<?php echo $id ?>"><?php _e('Delete Permanently', 'maxbuttons') ?></a>
176
- </td>
177
- </tr>
178
- <?php } ?>
179
- </table>
180
- </div>
181
- </form>
182
- </div>
183
- <div class="ad-wrap">
184
- <?php do_action("mb-display-ads"); ?>
185
- </div>
186
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/maxbuttons-list.php CHANGED
@@ -2,25 +2,8 @@
2
  namespace MaxButtons;
3
  defined('ABSPATH') or die('No direct access permitted');
4
 
5
-
6
  ?>
7
 
8
- <!-- @todo Remove -->
9
- <script type="text/javascript">
10
- jQuery(document).ready(function() {
11
- jQuery("#bulk-action-all").click(function() {
12
- jQuery("#maxbuttons input[name='button-id[]']").each(function() {
13
- if (jQuery("#bulk-action-all").is(":checked")) {
14
- jQuery(this).attr("checked", "checked");
15
- }
16
- else {
17
- jQuery(this).removeAttr("checked");
18
- }
19
- });
20
- });
21
-
22
- });
23
- </script>
24
 
25
  <?php
26
  $page_title = __("Overview","maxbuttons");
2
  namespace MaxButtons;
3
  defined('ABSPATH') or die('No direct access permitted');
4
 
 
5
  ?>
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  <?php
9
  $page_title = __("Overview","maxbuttons");
includes/social-share.php CHANGED
@@ -20,6 +20,7 @@ $admin = MB()->getClass('admin');
20
  $admin->get_header(array("tabs_active" => true, "title" => $page_title, 'action' => 'gosocial'));
21
 
22
  $img_url = MB()->get_plugin_url() . "images/gosocial";
 
23
  ?>
24
 
25
 
20
  $admin->get_header(array("tabs_active" => true, "title" => $page_title, 'action' => 'gosocial'));
21
 
22
  $img_url = MB()->get_plugin_url() . "images/gosocial";
23
+
24
  ?>
25
 
26
 
js/live-preview.js CHANGED
@@ -87,7 +87,7 @@ maxLivePreview.prototype.loadScreens = function ()
87
  var screen_id = $screen.attr('id').replace('screen_', '');
88
  var map = $(this).text();
89
  if (typeof map != 'undefined')
90
- self.fields[screen_id] = $.parseJSON(map);
91
 
92
  if ($screen.hasClass('current-screen'))
93
  {
@@ -596,7 +596,7 @@ maxLivePreview.prototype.setPreset = function(e)
596
  {
597
  var options = $('#' + this.currentScreen + '_preset-hidden').val();
598
  var setPreset = $('#' + this.currentScreen + '_preset option:selected').val();
599
- options = $.parseJSON(options);
600
 
601
 
602
  var $minfield = $('#' + this.currentScreen + '_min_width');
87
  var screen_id = $screen.attr('id').replace('screen_', '');
88
  var map = $(this).text();
89
  if (typeof map != 'undefined')
90
+ self.fields[screen_id] = JSON.parse(map);
91
 
92
  if ($screen.hasClass('current-screen'))
93
  {
596
  {
597
  var options = $('#' + this.currentScreen + '_preset-hidden').val();
598
  var setPreset = $('#' + this.currentScreen + '_preset option:selected').val();
599
+ options = JSON.parse(options);
600
 
601
 
602
  var $minfield = $('#' + this.currentScreen + '_min_width');
js/maxbuttons-admin.js CHANGED
@@ -39,6 +39,8 @@ maxAdmin.prototype.init = function () {
39
  $('#maxbuttons').on('change, input', 'input[type="range"]', $.proxy(this.updateRange, this ));
40
  this.updateRange(null);
41
 
 
 
42
  /*
43
  ****
44
  ### After this only init for button main edit screen
@@ -89,14 +91,14 @@ maxAdmin.prototype.init = function () {
89
  }
90
  });
91
 
92
- $(".button-save").click($.proxy(function(e) {
93
  this.saveIndicator(null, false); // prevent alert when saving.
94
 
95
  if ($(e.target).attr('id') == 'new_add_screen')
96
  {
97
  $('#add_new_screen').val('yes');
98
  }
99
- $("#new-button-form").submit();
100
  return false;
101
  }, this) );
102
 
@@ -608,12 +610,11 @@ maxAdmin.prototype.saveDone = function (res)
608
  {
609
  $('[data-form]').prop('disabled', false);
610
 
611
- var json = $.parseJSON(res);
612
 
613
  var result = json.result;
614
  var title = json.title;
615
 
616
-
617
  var collection_id = json.data.id;
618
 
619
  if (typeof json.data.new_nonce !== 'undefined')
@@ -709,3 +710,18 @@ maxAdmin.prototype.toggleSidebar = function(e)
709
  }
710
 
711
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  $('#maxbuttons').on('change, input', 'input[type="range"]', $.proxy(this.updateRange, this ));
40
  this.updateRange(null);
41
 
42
+ $('#bulk-action-all').on('click', $.proxy(this.checkAllInputs, this));
43
+
44
  /*
45
  ****
46
  ### After this only init for button main edit screen
91
  }
92
  });
93
 
94
+ $(".button-save").on('click', $.proxy(function(e) {
95
  this.saveIndicator(null, false); // prevent alert when saving.
96
 
97
  if ($(e.target).attr('id') == 'new_add_screen')
98
  {
99
  $('#add_new_screen').val('yes');
100
  }
101
+ $("#new-button-form").trigger('submit');
102
  return false;
103
  }, this) );
104
 
610
  {
611
  $('[data-form]').prop('disabled', false);
612
 
613
+ var json = JSON.parse(res);
614
 
615
  var result = json.result;
616
  var title = json.title;
617
 
 
618
  var collection_id = json.data.id;
619
 
620
  if (typeof json.data.new_nonce !== 'undefined')
710
  }
711
 
712
  }
713
+
714
+ maxAdmin.prototype.checkAllInputs = function (e)
715
+ {
716
+ e.preventDefault;
717
+ var mainbox = document.getElementById('bulk-action-all');
718
+
719
+ var selectMode = mainbox.checked;
720
+ var boxes = document.querySelectorAll('input[name="button-id[]"]');
721
+ for (var i = 0; i < boxes.length; i++)
722
+ {
723
+ boxes[i].checked = selectMode;
724
+ }
725
+
726
+
727
+ }
js/min/live-preview.js CHANGED
@@ -1 +1 @@
1
- function maxLivePreview(){}var $=jQuery;maxLivePreview.prototype={fields:{},screens:{},currentScreen:"",currentFields:{},reloadInProgress:!1,reloaded:{}},maxLivePreview.prototype.init=function(){this.loadScreens(),this.bindFields()},maxLivePreview.prototype.bindFields=function(){$("#maxbuttons input,#maxbuttons textarea").not(".mb-color-field").on("keyup change",$.proxy(this.update_preview_event,this)),$("#maxbuttons select").on("change",$.proxy(this.update_preview_event,this)),$("#maxbuttons .mb-color-field").wpColorPicker({width:300,alpha:!0,palettes:this.colorPalettes,change:$.proxy(_.throttle(function(e,t){this.update_color_event(e,t)},300),this)}),$('[data-action="set-preset"]').on("click",$.proxy(this.setPreset,this)),$(document).on("changed_screen",$.proxy(this.changed_screen,this))},maxLivePreview.prototype.changed_screen=function(e,t){this.setCurrentScreen("default"),this.reloadFields(),"default"!=t&&(this.setCurrentScreen(t),this.reloadFields())},maxLivePreview.prototype.reloadFields=function(){$(document).trigger("livepreview-reload-start"),this.reloadInProgress=!0;for(var e in this.currentFields){var t=this.fields[this.currentScreen][e];this.update_preview($("#"+e),t)}this.reloadInProgress=!1,this.reloaded={},$(document).trigger("livepreview-reload-end")},maxLivePreview.prototype.loadScreens=function(){var e=this;$(".mbscreen-editor .fieldmap").each(function(){var t=$(this).parents(".mbscreen-editor"),i=t.attr("id").replace("screen_",""),r=$(this).text();void 0!==r&&(e.fields[i]=$.parseJSON(r)),t.hasClass("current-screen")&&e.setCurrentScreen(i)})},maxLivePreview.prototype.getFieldByID=function(e,t){if(void 0===t&&(t=!1),"default"==this.currentScreen)i=e;else var i=this.currentScreen+"_"+e;if(t)r=$('input[name="'+i+'"]');else var r=$("#"+i);return r},maxLivePreview.prototype.setCurrentScreen=function(e){this.currentScreen=e,this.currentFields=this.fields[e],$(document).trigger("livepreview-screen-set",[e,this.currentFields])},maxLivePreview.prototype.update_preview_event=function(e){e.preventDefault();var t=$(e.target),i=($(t).data("field"),$(t).attr("id")),r=this.currentFields[i];$(document).trigger("livePreviewUpdate",!0),null!==r&&this.update_preview($("#"+i),r)},maxLivePreview.prototype.update_preview=function(e,t){var i=null;if(void 0!==t){if(void 0!==t.css){if(value=e.val(),e.is(":checkbox")&&!e.is(":checked")&&(value=""),e.is(":radio")&&!e.is(":checked"))return;void 0!==t.pseudo&&(i=t.pseudo),this.putCSS(t,value,i)}if(void 0!==t.attr&&$(".output .result").find("a").attr(t.attr,e.val()),void 0!==t.func){var r=t.func;r.indexOf(".")<0?r="self."+r+"(target)":r+="(target)";try{new Function("target","self",r)(e,this)}catch(e){console.error(e)}}}},maxLivePreview.prototype.putCSS=function(e,t,r){if(r=r||"both",void 0===e)return!1;var a=".maxbutton";if("hover"==r?a="a.hover ":"normal"==r&&(a="a.normal "),void 0!==e.unitfield){var o=this.getFieldByID(e.unitfield,!0).filter(":checked").val();0==t?t="auto":"percent"==o||"%"==o?t+="%":"pixel"!=o&&"px"!=o||(t+="px")}else void 0!==e.css_unit&&-1==t.indexOf(e.css_unit)&&-1==t.indexOf(e.css_unit)&&(t+=e.css_unit);if(void 0!==e.csspart){var s=e.csspart.split(",");for(i=0;i<s.length;i++){var d=a+" ."+s[i];$(".output .result").find(d).css(e.css,t)}}else $(".output .result").find(a).css(e.css,t)},maxLivePreview.prototype.update_color_event=function(e,t){console.log("update color field");var i=$(e.target),r=t.color.to_s("hex");this.update_color(i,t,r),$(document).trigger("livePreviewUpdate",!0)},maxLivePreview.prototype.update_color=function(t,i,r){var a=t.attr("id");if(-1===r.indexOf("#")&&r.indexOf("rgba")<0&&(r="#"+r),$("#"+a).val(r),""==$(t).val()?($(t).parents(".mbcolor").find(".wp-color-result").children(".the_color").css("background-image","url("+maxadmin_settings.icon_transparent+")"),void 0!==event.type&&"change"==event.type&&this.update_color(e,null,"rgba(0,0,0,0)")):$(t).parents(".mbcolor").find(".wp-color-result").children(".the_color").css("background-image","none"),-1!==a.indexOf("box_shadow"))this.updateBoxShadow(t);else if(-1!==a.indexOf("text_shadow"))this.updateTextShadow(t);else if(-1!==a.indexOf("gradient"))-1==a.indexOf("hover")?this.updateGradient():this.updateGradient(!0);else if("button_preview"==a)r.indexOf("rgba"),$(".output .result").css("backgroundColor",r);else{var o=this.currentFields[a],s="normal";void 0!==o&&void 0!==o.pseudo&&(s=o.pseudo),this.putCSS(o,r,s)}},maxLivePreview.prototype.updateBoxShadow=function(e){if(!this.reloadInProgress||void 0===this.reloaded.boxshadow){var t=$(e).attr("id"),i=this.getFieldByID("box_shadow_offset_left").val(),r=this.getFieldByID("box_shadow_offset_top").val(),a=this.getFieldByID("box_shadow_width").val(),o=this.getFieldByID("box_shadow_spread").val(),s=this.getFieldByID("box_shadow_color").val(),d=this.getFieldByID("box_shadow_color_hover").val();""==s&&(s="rgba(0,0,0,0)"),""==d&&(d="rgba(0,0,0,0)");var t=$(e).attr("id"),n=this.currentFields[t];void 0!==n&&(n.css="boxShadow",value=i+"px "+r+"px "+a+"px "+o+"px "+s,value_hover=i+"px "+r+"px "+a+"px "+o+"px "+d,this.putCSS(n,value,"normal"),this.putCSS(n,value_hover,"hover"),this.reloaded.boxshadow=!0)}},maxLivePreview.prototype.updateTextShadow=function(e){if(!this.reloadInProgress||void 0===this.reloaded.textshadow){var t=this.getFieldByID("text_shadow_offset_left").val(),i=this.getFieldByID("text_shadow_offset_top").val(),r=this.getFieldByID("text_shadow_width").val(),a=this.getFieldByID("text_shadow_color").val(),o=this.getFieldByID("text_shadow_color_hover").val(),s=$(e).attr("id"),d=this.currentFields[s];if(void 0!==d){d.css="textShadow",""==a&&(a="rgba(0,0,0,0)"),""==o&&(o="rgba(0,0,0,0)");var n=t+"px "+i+"px "+r+"px "+a;this.putCSS(d,n,"normal"),n=t+"px "+i+"px "+r+"px "+o,this.putCSS(d,n,"hover"),this.reloaded.textshadow=!0}}},maxLivePreview.prototype.updateAnchorText=function(e){0===$(".output .result").find("a .mb-text").length&&($(".output .result").find("a").append('<span class="mb-text"></span>'),$(".output .result").find("a .mb-text").css({display:"block","line-height":"1em","box-sizing":"border-box"}),this.reloadFields()),$(".output .result").find("a .mb-text").text(e.val())},maxLivePreview.prototype.updateGradientOpacity=function(e){this.updateGradient(!0),this.updateGradient(!1)},maxLivePreview.prototype.updateDimension=function(e){if(!this.reloadInProgress||void 0===this.reloaded.dimension){if(void 0===(t=e.data("field")))var t=e.attr("id");if(void 0!==t){var i={};if(t.indexOf("width")>=0){var e=this.getFieldByID("button_width"),r=this.getFieldByID("button_size_unit_width",!0);i.css="width";var a=".preview_border_width span",o=".input."+e.attr("name")+" .unit"}else if(t.indexOf("height")>=0){var e=this.getFieldByID("button_height"),r=this.getFieldByID("button_size_unit_height",!0);i.css="height";var a=".preview_border_height span",o=".input."+e.attr("name")+" .unit"}var s=e.val(),d=r.filter(":checked").val();0==s&&(d="",s="auto",this.putCSS(i,"auto")),"percent"==d&&(d="%"),"pixel"==d&&(d="px"),i.css_unit=d,$(a).text(s+d),$(a).css("width",s+d),this.putCSS(i,s),$(o).text(d),this.reloaded.dimension=!0}}},maxLivePreview.prototype.updateRadius=function(e){if(!this.reloadInProgress||void 0===this.reloaded.radius){var t=e.val(),r=["radius_bottom_left","radius_bottom_right","radius_top_left","radius_top_right"],a=this.getFieldByID("radius_toggle");if("lock"==$(a).data("lock"))for(i=0;i<r.length;i++){var o=this.getFieldByID(r[i]);o.val(t);var s=o.attr("id"),d=this.currentFields[s];this.putCSS(d,t+"px")}else{var t=$(e).val(),s=$(e).attr("id"),d=this.currentFields[s];this.putCSS(d,t)}this.reloaded.radius=!0}},maxLivePreview.prototype.getGradient=function(e){var t="";(e=e||!1)&&(t="_hover");var i=parseInt(this.getFieldByID("gradient_stop").val());isNaN(i)&&(i=45);var r=this.getFieldByID("use_gradient").prop("checked"),a=this.getFieldByID("gradient_start_color"+t).val(),o=this.getFieldByID("gradient_end_color"+t).val();""==a&&(a="rgba(0,0,0,0)"),""==o&&(o="rgba(0,0,0,0)");var s=this.hexToRgb(a),d=this.hexToRgb(o),n=parseInt(this.getFieldByID("gradient_start_opacity"+t).val()),l=parseInt(this.getFieldByID("gradient_end_opacity"+t).val());if(r||(d=s,l=n),isNaN(n)&&(n=100),isNaN(l)&&(l=100),s.indexOf("rgba")<0)h="rgba("+s+","+n/100+") ";else var h=s;if(d.indexOf("rgba")<0)u="rgba("+d+","+l/100+") ";else var u=d;return"linear-gradient("+h+i+"%,"+u+")"},maxLivePreview.prototype.updateGradient=function(e){if(!this.reloadInProgress||void 0===this.reloaded.gradient){var t=this.getGradient(e);if(e)i=$(".output .result").find("a.hover");else var i=$(".output .result").find("a.normal");i.css("background",t),$(document).trigger("livepreview/gradient/updated",[t,e]),this.reloaded.gradient=!0}},maxLivePreview.prototype.updateContainerUnit=function(e){var t=this.getFieldByID("container_width_unit",!0).filter(":checked").val();t="pixel"==t?"px":"%";var i=this.getFieldByID("container_width").attr("name");$(".option."+i+" .unit").text(t)},maxLivePreview.prototype.setPreset=function(e){var t=$("#"+this.currentScreen+"_preset-hidden").val(),i=$("#"+this.currentScreen+"_preset option:selected").val();t=$.parseJSON(t);var r=$("#"+this.currentScreen+"_min_width"),a=$("#"+this.currentScreen+"_max_width");if(t[i]&&"none"!==i){var o=t[i],s=o.minwidth,d=o.maxwidth;s<=0&&(s=0),d<=0&&(d=0),r.val(s),a.val(d)}},maxLivePreview.prototype.hexToRgb=function(e){if(e.indexOf("rgba")>=0)return e;e=e.replace("#","");var t=parseInt(e,16);return(t>>16&255)+","+(t>>8&255)+","+(255&t)};
1
+ function maxLivePreview(){}var $=jQuery;maxLivePreview.prototype={fields:{},screens:{},currentScreen:"",currentFields:{},reloadInProgress:!1,reloaded:{}},maxLivePreview.prototype.init=function(){this.loadScreens(),this.bindFields()},maxLivePreview.prototype.bindFields=function(){$("#maxbuttons input,#maxbuttons textarea").not(".mb-color-field").on("keyup change",$.proxy(this.update_preview_event,this)),$("#maxbuttons select").on("change",$.proxy(this.update_preview_event,this)),$("#maxbuttons .mb-color-field").wpColorPicker({width:300,alpha:!0,palettes:this.colorPalettes,change:$.proxy(_.throttle(function(e,t){this.update_color_event(e,t)},300),this)}),$('[data-action="set-preset"]').on("click",$.proxy(this.setPreset,this)),$(document).on("changed_screen",$.proxy(this.changed_screen,this))},maxLivePreview.prototype.changed_screen=function(e,t){this.setCurrentScreen("default"),this.reloadFields(),"default"!=t&&(this.setCurrentScreen(t),this.reloadFields())},maxLivePreview.prototype.reloadFields=function(){$(document).trigger("livepreview-reload-start"),this.reloadInProgress=!0;for(var e in this.currentFields){var t=this.fields[this.currentScreen][e];this.update_preview($("#"+e),t)}this.reloadInProgress=!1,this.reloaded={},$(document).trigger("livepreview-reload-end")},maxLivePreview.prototype.loadScreens=function(){var e=this;$(".mbscreen-editor .fieldmap").each(function(){var t=$(this).parents(".mbscreen-editor"),i=t.attr("id").replace("screen_",""),r=$(this).text();void 0!==r&&(e.fields[i]=JSON.parse(r)),t.hasClass("current-screen")&&e.setCurrentScreen(i)})},maxLivePreview.prototype.getFieldByID=function(e,t){if(void 0===t&&(t=!1),"default"==this.currentScreen)i=e;else var i=this.currentScreen+"_"+e;if(t)r=$('input[name="'+i+'"]');else var r=$("#"+i);return r},maxLivePreview.prototype.setCurrentScreen=function(e){this.currentScreen=e,this.currentFields=this.fields[e],$(document).trigger("livepreview-screen-set",[e,this.currentFields])},maxLivePreview.prototype.update_preview_event=function(e){e.preventDefault();var t=$(e.target),i=($(t).data("field"),$(t).attr("id")),r=this.currentFields[i];$(document).trigger("livePreviewUpdate",!0),null!==r&&this.update_preview($("#"+i),r)},maxLivePreview.prototype.update_preview=function(e,t){var i=null;if(void 0!==t){if(void 0!==t.css){if(value=e.val(),e.is(":checkbox")&&!e.is(":checked")&&(value=""),e.is(":radio")&&!e.is(":checked"))return;void 0!==t.pseudo&&(i=t.pseudo),this.putCSS(t,value,i)}if(void 0!==t.attr&&$(".output .result").find("a").attr(t.attr,e.val()),void 0!==t.func){var r=t.func;r.indexOf(".")<0?r="self."+r+"(target)":r+="(target)";try{new Function("target","self",r)(e,this)}catch(e){console.error(e)}}}},maxLivePreview.prototype.putCSS=function(e,t,r){if(r=r||"both",void 0===e)return!1;var a=".maxbutton";if("hover"==r?a="a.hover ":"normal"==r&&(a="a.normal "),void 0!==e.unitfield){var o=this.getFieldByID(e.unitfield,!0).filter(":checked").val();0==t?t="auto":"percent"==o||"%"==o?t+="%":"pixel"!=o&&"px"!=o||(t+="px")}else void 0!==e.css_unit&&-1==t.indexOf(e.css_unit)&&-1==t.indexOf(e.css_unit)&&(t+=e.css_unit);if(void 0!==e.csspart){var s=e.csspart.split(",");for(i=0;i<s.length;i++){var d=a+" ."+s[i];$(".output .result").find(d).css(e.css,t)}}else $(".output .result").find(a).css(e.css,t)},maxLivePreview.prototype.update_color_event=function(e,t){console.log("update color field");var i=$(e.target),r=t.color.to_s("hex");this.update_color(i,t,r),$(document).trigger("livePreviewUpdate",!0)},maxLivePreview.prototype.update_color=function(t,i,r){var a=t.attr("id");if(-1===r.indexOf("#")&&r.indexOf("rgba")<0&&(r="#"+r),$("#"+a).val(r),""==$(t).val()?($(t).parents(".mbcolor").find(".wp-color-result").children(".the_color").css("background-image","url("+maxadmin_settings.icon_transparent+")"),void 0!==event.type&&"change"==event.type&&this.update_color(e,null,"rgba(0,0,0,0)")):$(t).parents(".mbcolor").find(".wp-color-result").children(".the_color").css("background-image","none"),-1!==a.indexOf("box_shadow"))this.updateBoxShadow(t);else if(-1!==a.indexOf("text_shadow"))this.updateTextShadow(t);else if(-1!==a.indexOf("gradient"))-1==a.indexOf("hover")?this.updateGradient():this.updateGradient(!0);else if("button_preview"==a)r.indexOf("rgba"),$(".output .result").css("backgroundColor",r);else{var o=this.currentFields[a],s="normal";void 0!==o&&void 0!==o.pseudo&&(s=o.pseudo),this.putCSS(o,r,s)}},maxLivePreview.prototype.updateBoxShadow=function(e){if(!this.reloadInProgress||void 0===this.reloaded.boxshadow){var t=$(e).attr("id"),i=this.getFieldByID("box_shadow_offset_left").val(),r=this.getFieldByID("box_shadow_offset_top").val(),a=this.getFieldByID("box_shadow_width").val(),o=this.getFieldByID("box_shadow_spread").val(),s=this.getFieldByID("box_shadow_color").val(),d=this.getFieldByID("box_shadow_color_hover").val();""==s&&(s="rgba(0,0,0,0)"),""==d&&(d="rgba(0,0,0,0)");var t=$(e).attr("id"),n=this.currentFields[t];void 0!==n&&(n.css="boxShadow",value=i+"px "+r+"px "+a+"px "+o+"px "+s,value_hover=i+"px "+r+"px "+a+"px "+o+"px "+d,this.putCSS(n,value,"normal"),this.putCSS(n,value_hover,"hover"),this.reloaded.boxshadow=!0)}},maxLivePreview.prototype.updateTextShadow=function(e){if(!this.reloadInProgress||void 0===this.reloaded.textshadow){var t=this.getFieldByID("text_shadow_offset_left").val(),i=this.getFieldByID("text_shadow_offset_top").val(),r=this.getFieldByID("text_shadow_width").val(),a=this.getFieldByID("text_shadow_color").val(),o=this.getFieldByID("text_shadow_color_hover").val(),s=$(e).attr("id"),d=this.currentFields[s];if(void 0!==d){d.css="textShadow",""==a&&(a="rgba(0,0,0,0)"),""==o&&(o="rgba(0,0,0,0)");var n=t+"px "+i+"px "+r+"px "+a;this.putCSS(d,n,"normal"),n=t+"px "+i+"px "+r+"px "+o,this.putCSS(d,n,"hover"),this.reloaded.textshadow=!0}}},maxLivePreview.prototype.updateAnchorText=function(e){0===$(".output .result").find("a .mb-text").length&&($(".output .result").find("a").append('<span class="mb-text"></span>'),$(".output .result").find("a .mb-text").css({display:"block","line-height":"1em","box-sizing":"border-box"}),this.reloadFields()),$(".output .result").find("a .mb-text").text(e.val())},maxLivePreview.prototype.updateGradientOpacity=function(e){this.updateGradient(!0),this.updateGradient(!1)},maxLivePreview.prototype.updateDimension=function(e){if(!this.reloadInProgress||void 0===this.reloaded.dimension){if(void 0===(t=e.data("field")))var t=e.attr("id");if(void 0!==t){var i={};if(t.indexOf("width")>=0){var e=this.getFieldByID("button_width"),r=this.getFieldByID("button_size_unit_width",!0);i.css="width";var a=".preview_border_width span",o=".input."+e.attr("name")+" .unit"}else if(t.indexOf("height")>=0){var e=this.getFieldByID("button_height"),r=this.getFieldByID("button_size_unit_height",!0);i.css="height";var a=".preview_border_height span",o=".input."+e.attr("name")+" .unit"}var s=e.val(),d=r.filter(":checked").val();0==s&&(d="",s="auto",this.putCSS(i,"auto")),"percent"==d&&(d="%"),"pixel"==d&&(d="px"),i.css_unit=d,$(a).text(s+d),$(a).css("width",s+d),this.putCSS(i,s),$(o).text(d),this.reloaded.dimension=!0}}},maxLivePreview.prototype.updateRadius=function(e){if(!this.reloadInProgress||void 0===this.reloaded.radius){var t=e.val(),r=["radius_bottom_left","radius_bottom_right","radius_top_left","radius_top_right"],a=this.getFieldByID("radius_toggle");if("lock"==$(a).data("lock"))for(i=0;i<r.length;i++){var o=this.getFieldByID(r[i]);o.val(t);var s=o.attr("id"),d=this.currentFields[s];this.putCSS(d,t+"px")}else{var t=$(e).val(),s=$(e).attr("id"),d=this.currentFields[s];this.putCSS(d,t)}this.reloaded.radius=!0}},maxLivePreview.prototype.getGradient=function(e){var t="";(e=e||!1)&&(t="_hover");var i=parseInt(this.getFieldByID("gradient_stop").val());isNaN(i)&&(i=45);var r=this.getFieldByID("use_gradient").prop("checked"),a=this.getFieldByID("gradient_start_color"+t).val(),o=this.getFieldByID("gradient_end_color"+t).val();""==a&&(a="rgba(0,0,0,0)"),""==o&&(o="rgba(0,0,0,0)");var s=this.hexToRgb(a),d=this.hexToRgb(o),n=parseInt(this.getFieldByID("gradient_start_opacity"+t).val()),l=parseInt(this.getFieldByID("gradient_end_opacity"+t).val());if(r||(d=s,l=n),isNaN(n)&&(n=100),isNaN(l)&&(l=100),s.indexOf("rgba")<0)h="rgba("+s+","+n/100+") ";else var h=s;if(d.indexOf("rgba")<0)u="rgba("+d+","+l/100+") ";else var u=d;return"linear-gradient("+h+i+"%,"+u+")"},maxLivePreview.prototype.updateGradient=function(e){if(!this.reloadInProgress||void 0===this.reloaded.gradient){var t=this.getGradient(e);if(e)i=$(".output .result").find("a.hover");else var i=$(".output .result").find("a.normal");i.css("background",t),$(document).trigger("livepreview/gradient/updated",[t,e]),this.reloaded.gradient=!0}},maxLivePreview.prototype.updateContainerUnit=function(e){var t=this.getFieldByID("container_width_unit",!0).filter(":checked").val();t="pixel"==t?"px":"%";var i=this.getFieldByID("container_width").attr("name");$(".option."+i+" .unit").text(t)},maxLivePreview.prototype.setPreset=function(e){var t=$("#"+this.currentScreen+"_preset-hidden").val(),i=$("#"+this.currentScreen+"_preset option:selected").val();t=JSON.parse(t);var r=$("#"+this.currentScreen+"_min_width"),a=$("#"+this.currentScreen+"_max_width");if(t[i]&&"none"!==i){var o=t[i],s=o.minwidth,d=o.maxwidth;s<=0&&(s=0),d<=0&&(d=0),r.val(s),a.val(d)}},maxLivePreview.prototype.hexToRgb=function(e){if(e.indexOf("rgba")>=0)return e;e=e.replace("#","");var t=parseInt(e,16);return(t>>16&255)+","+(t>>8&255)+","+(255&t)};
js/min/maxbuttons-admin.js CHANGED
@@ -1 +1 @@
1
- function maxAdmin(){}var $=jQuery;maxAdmin.prototype={colorUpdateTime:!0,colorPalettes:!0,fields:null,button_id:null,form_updated:!1,tabs:null},maxAdmin.prototype.init=function(){this.button_id=$('input[name="button_id"]').val(),$(document).on("click",".maxbutton-preview",function(e){e.preventDefault(),e.stopPropagation()}),$("#maxbuttons .input-paging").on("change",$.proxy(this.do_paging,this)),$(".manual-toggle").on("click",$.proxy(this.toggleManual,this)),$(".manual-entry").draggable({cancel:"p, li"}),$(document).on("submit","form.mb_ajax_save",$.proxy(this.formAjaxSave,this)),$(document).on("click","[data-buttonaction]",$.proxy(this.button_action,this)),$(document).on("reInitConditionals",$.proxy(this.initConditionials,this)),this.initConditionials(),$("#maxbuttons").on("change, input",'input[type="range"]',$.proxy(this.updateRange,this)),this.updateRange(null),0!=$("#new-button-form").length&&(this.button_id>0&&$("#maxbuttons .mb-message").show(),$(document).on("livePreviewUpdate",$.proxy(this.saveIndicator,this)),$(".screen-option").on("click",$.proxy(this.change_screen_event,this)),$(".remove-screen").on("click",$.proxy(this.remove_screen,this)),$("#maxbuttons .output").draggable({cancel:".nodrag"}),$(".iris-picker-inner .iris-square-value").removeAttr("href"),$(document).on("click",".iris-picker-inner .iris-square-value",function(e){return e.preventDefault(),e.stopPropagation(),!1}),$(".input.mbcolor .arrows").on("click",$.proxy(this.copyColor,this)),$('[id$="radius_toggle"]').on("click",$.proxy(this.toggleRadiusLock,this)),$(".output .preview-toggle").on("click",$.proxy(this.toggle_preview,this)),$("#maxbuttons input.mb-color-field").on("focus",$.proxy(this.select_field,this)),$(window).on("beforeunload",$.proxy(function(){if(this.form_updated)return maxajax.leave_page},this)),$(document).on("keyup","input",function(e){if(e.keyCode&&13==e.keyCode)return $(":input")[$(":input").index(document.activeElement)+1].focus(),!1}),$(".button-save").click($.proxy(function(e){return this.saveIndicator(null,!1),"new_add_screen"==$(e.target).attr("id")&&$("#add_new_screen").val("yes"),$("#new-button-form").submit(),!1},this)),$(".shortcode-expand").on("click",this.toggleShortcode),$("#url_button").on("click",$.proxy(this.openURLDialog,this)),$(".block_sidebar .open_control").on("click",this.toggleSidebar),$(window).on("maxbuttons-js-init-done",$.proxy(this.loadLivePreview,this)),1==$('input[name="button_is_new"]').val()&&this.saveIndicator(null,!0))},maxAdmin.prototype.loadLivePreview=function(){if("function"==typeof window.maxFoundry.livePreview?(this.livePreview=new window.maxFoundry.livePreview,this.livePreview.init(),$(document).trigger("livepreview-loaded")):alert("Live Preview not loaded, button preview not functional"),void 0!==window.location.hash&&window.location.hash.length>0){var e=window.location.hash.replace("#","");this.change_screen(e)}},maxAdmin.prototype.change_screen_event=function(e){e.preventDefault();var t=$(e.target);void 0===t.data("screenid")&&(t=t.parents(".screen-option"));var o=t.data("screenid");void 0!==o&&this.change_screen(o)},maxAdmin.prototype.change_screen=function(e){var t=$(".screen-option.option-active").data("screenid"),o=$('.screen-option[data-screenid="'+e+'"]');"new"!=e&&$("#current_screen").val(e),t!==e&&(window.location.hash=e,$(".screen-option").removeClass("option-active"),o.addClass("option-active"),$(document).trigger("changed_screen",e),$(".mbscreen-editor").removeClass("current-screen"),$("#screen_"+e).addClass("current-screen"))},maxAdmin.prototype.remove_screen=function(e){if(confirm(maxadmin_settings.remove_confirm)){var t=$(e.target),o=t.parents(".mbscreen-editor").data("screenid");t.parents(".mbscreen-editor").remove(),$('.screen-option[data-screenid="'+o+'"]').remove(),$('input[name="screens[]"][value="'+o+'"]').remove(),$(".removed-note").show(),this.change_screen("default"),this.saveIndicator(null,!0)}},maxAdmin.prototype.toggle_preview=function(e){$(".output .inner").is(":hidden")?($(".output .inner").show(),$(".output").css("height","auto"),$(".preview .preview-toggle").removeClass("dashicons-arrow-down").addClass("dashicons-arrow-up")):($(".output .inner").hide(),$(".output").css("height","auto"),$(".preview .preview-toggle").removeClass("dashicons-arrow-up").addClass("dashicons-arrow-down"))},maxAdmin.prototype.select_field=function(e){$(e.target).select()},maxAdmin.prototype.button_action=function(e){e.preventDefault();var t=$(e.target).data("buttonaction"),o=$(e.target).data("confirm");if(this.form_updated=!1,void 0!==o){if(!window.confirm(o))return}var a=$(e.target).data("buttonid"),n=$('input[name="'+t+'_nonce"]').val(),i=$('input[name="paged"]').val(),r=maxajax.ajax_url,s={action:"mb_button_action",button_action:t,button_id:a,nonce:n};void 0!==i&&(s.paged=i),$.post({url:r,data:s,success:function(e){response=JSON.parse(e),void 0!==response.redirection&&(window.location=response.redirection)},error:function(){console.error("error in button action"+t)}})},maxAdmin.prototype.checkCopyModal=function(e){this.form_updated?e.currentModal.find(".mb-message").show():$(e.currentModal).find(".mb-message").hide()},maxAdmin.prototype.copyColor=function(e){e.preventDefault(),e.stopPropagation();var t=$(e.target),o=$(e.target).parents("[data-bind]"),a="#"+o.data("id"),n="#"+o.data("bind");if(t.hasClass("arrow-right"))i="right";else var i="left";if(o.hasClass("right"))r="left";else var r="right";"left"==r?copy="right"==i:"right"==r&&(copy="right"!=i),copy?($(n).val($(a).val()),$(n).trigger("change"),$(n).wpColorPicker("color",$(a).val())):($(a).val($(n).val()),$(a).trigger("change"),$(a).wpColorPicker("color",$(n).val()))},maxAdmin.prototype.toggleRadiusLock=function(e){var t=$(e.target),o=$(t).data("lock");"lock"==o?($(t).removeClass("dashicons-lock").addClass("dashicons-unlock"),$(t).data("lock","unlock")):"unlock"==o&&($(t).removeClass("dashicons-unlock").addClass("dashicons-lock"),$(t).data("lock","lock"))},maxAdmin.prototype.initResponsive=function(){},maxAdmin.prototype.do_paging=function(e){var t=parseInt($(e.target).val());if(t<=parseInt($(e.target).attr("max"))){var o=$(e.target).data("url");window.location=o+"&paged="+t}},maxAdmin.prototype.toggleShortcode=function(e){$(".shortcode-expand").hasClass("closed")?($(" .mb-message.shortcode .expanded").css("display","inline-block"),$(".shortcode-expand span").removeClass("dashicons-arrow-down").addClass("dashicons-arrow-up"),$(".shortcode-expand").removeClass("closed").addClass("open")):($(" .mb-message.shortcode .expanded").css("display","none"),$(".shortcode-expand span").addClass("dashicons-arrow-down").removeClass("dashicons-arrow-up"),$(".shortcode-expand").addClass("closed").removeClass("open"))},maxAdmin.prototype.toggleManual=function(e){e.preventDefault();var t=$(e.target),o=t.data("target"),a=$('.manual-entry[data-manual="'+o+'"]');if(a.is(":visible"))return a.hide(),!0;var n=t.parents(".option-container");a.css("top","0px"),a.css("right","-25%"),a.prependTo(n),a.show()},maxAdmin.prototype.resetConditionals=function(){$("[data-show], [data-has]").each(function(){var e=$(this).data("show");if(void 0===e&&(e=$(this).data("has")),void 0!==e){var t=e.target;$(document).off("change",'[name="'+t+'"]')}else console.error($(this)+"has a improperly set conditional")})},maxAdmin.prototype.initConditionials=function(){var e=this;this.resetConditionals(),$("[data-show]").each(function(){var t=$(this).data("show"),o=t.target,a=t.values;$(document).on("change",'[name="'+o+'"]',{child:this,values:a},$.proxy(e.updateConditional,e)),$('[name="'+o+'"]').length>1?$('[name="'+o+'"]:checked').trigger("change",["conditional"]):$('[name="'+o+'"]').trigger("change",["conditional"])});var t=[];$("[data-has]").each(function(){var o=$(this).data("has"),a=o.target,n=o.values;$('[name="'+a+'"]').on("change",{target:a,child:this,values:n},$.proxy(e.updateHasConditional,e));var i='[name="'+a+'"]';$.inArray(i,t),t.push(i)}),t.length>0&&$(t.toString()).first().trigger("change",["conditional"])},maxAdmin.prototype.updateConditional=function(e){var t=e.data,o=t.values,a=t.child,n=$(e.currentTarget),i=$(n).val();if("checkbox"===n.attr("type")){var r=$(n).prop("checked");i="checked"==o&&r?"checked":"unchecked"!=o||r?0:"unchecked"}o.indexOf(i)>=0?($(a).addClass("condshow").animate({opacity:1},300),$(a).find("input, select").trigger("change")):($(a).animate({opacity:0},300,function(){$(a).removeClass("condshow")}),$(a).find("input, select").trigger("change"))},maxAdmin.prototype.updateHasConditional=function(e){var t=e.data,o=t.values,a=t.child,n=t.target,i=[];$(o).each(function(e){i.push("[value="+this+"]")}),$('[name="'+n+'"]').filter(i.toString()).length>0?$(a).addClass("condshow").animate({opacity:1},300):$(a).animate({opacity:0},300,function(){$(a).removeClass("condshow")})},maxAdmin.prototype.updateRange=function(e){if(void 0===e||null===e)t=$('input[type="range"]');else var t=[e.target];$(t).each(function(){var e=$(this).val();$(this).parents(".input").find(".range_value output").val(e+"%")})},maxAdmin.prototype.saveIndicator=function(e,t){t?(this.form_updated=!0,$(".button-save").removeClass("disabled").addClass("button-primary")):(this.form_updated=!1,$(".button-save").addClass("disabled").removeClass("button-primary"))},maxAdmin.prototype.formAjaxSave=function(e){e.preventDefault();var t=mb_ajax.ajaxurl,o=$(e.target).serialize();$.ajax({type:"POST",url:t,data:o}).done($.proxy(this.saveDone,this))},maxAdmin.prototype.saveDone=function(e){$("[data-form]").prop("disabled",!1);var t=$.parseJSON(e),o=t.result,a=t.title,n=t.data.id;if(void 0!==t.data.new_nonce){t.data.new_nonce;$('input[name="nonce"]').val(t.data.new_nonce)}if(o){$('input[name="collection_id"]').val(n);var i=window.location.href;-1===i.indexOf("collection_id")&&window.history.replaceState({},"",i+"&collection_id="+n),$(document).trigger("mbFormSaved");var r=$('input[name="sorted"]').val();$('input[name="previous_selection"]').val(r),t.data.reload&&document.location.reload(!0)}o||($modal=window.maxFoundry.maxModal,$modal.newModal("collection_error"),$modal.setTitle(a),$modal.setContent(t.body),$modal.setControls('<button class="modal_close button-primary">'+t.close_text+"</button>"),$modal.show())},maxAdmin.prototype.openURLDialog=function(e){return window.wpActiveEditor="url",-1==window.ajaxurl.indexOf("ajax_maxbuttons")&&(window.ajaxurl=window.ajaxurl+"?ajax_maxbuttons=editor"),wpLink.open(),$("#link-options").hide(),$(".query-results").css("top","70px"),$("#wp-link-submit").off("click keyup change"),$("#wp-link-submit").on("click",$.proxy(this.updateLink,this)),!1},maxAdmin.prototype.updateLink=function(e){e.preventDefault();var t=$("#url").val(),o=$("#wp-link-url").val(),a=maxadmin_settings.homeurl;return o=o.replace(a,""),$("#url").val(o),t!=o&&$(document).trigger("livePreviewUpdate",!0),wpLink.close(),!1},maxAdmin.prototype.toggleSidebar=function(e){var t=e.target,o=$(t).parents(".block_sidebar");o.hasClass("active")?o.removeClass("active"):o.addClass("active")};
1
+ function maxAdmin(){}var $=jQuery;maxAdmin.prototype={colorUpdateTime:!0,colorPalettes:!0,fields:null,button_id:null,form_updated:!1,tabs:null},maxAdmin.prototype.init=function(){this.button_id=$('input[name="button_id"]').val(),$(document).on("click",".maxbutton-preview",function(e){e.preventDefault(),e.stopPropagation()}),$("#maxbuttons .input-paging").on("change",$.proxy(this.do_paging,this)),$(".manual-toggle").on("click",$.proxy(this.toggleManual,this)),$(".manual-entry").draggable({cancel:"p, li"}),$(document).on("submit","form.mb_ajax_save",$.proxy(this.formAjaxSave,this)),$(document).on("click","[data-buttonaction]",$.proxy(this.button_action,this)),$(document).on("reInitConditionals",$.proxy(this.initConditionials,this)),this.initConditionials(),$("#maxbuttons").on("change, input",'input[type="range"]',$.proxy(this.updateRange,this)),this.updateRange(null),$("#bulk-action-all").on("click",$.proxy(this.checkAllInputs,this)),0!=$("#new-button-form").length&&(this.button_id>0&&$("#maxbuttons .mb-message").show(),$(document).on("livePreviewUpdate",$.proxy(this.saveIndicator,this)),$(".screen-option").on("click",$.proxy(this.change_screen_event,this)),$(".remove-screen").on("click",$.proxy(this.remove_screen,this)),$("#maxbuttons .output").draggable({cancel:".nodrag"}),$(".iris-picker-inner .iris-square-value").removeAttr("href"),$(document).on("click",".iris-picker-inner .iris-square-value",function(e){return e.preventDefault(),e.stopPropagation(),!1}),$(".input.mbcolor .arrows").on("click",$.proxy(this.copyColor,this)),$('[id$="radius_toggle"]').on("click",$.proxy(this.toggleRadiusLock,this)),$(".output .preview-toggle").on("click",$.proxy(this.toggle_preview,this)),$("#maxbuttons input.mb-color-field").on("focus",$.proxy(this.select_field,this)),$(window).on("beforeunload",$.proxy(function(){if(this.form_updated)return maxajax.leave_page},this)),$(document).on("keyup","input",function(e){if(e.keyCode&&13==e.keyCode)return $(":input")[$(":input").index(document.activeElement)+1].focus(),!1}),$(".button-save").on("click",$.proxy(function(e){return this.saveIndicator(null,!1),"new_add_screen"==$(e.target).attr("id")&&$("#add_new_screen").val("yes"),$("#new-button-form").trigger("submit"),!1},this)),$(".shortcode-expand").on("click",this.toggleShortcode),$("#url_button").on("click",$.proxy(this.openURLDialog,this)),$(".block_sidebar .open_control").on("click",this.toggleSidebar),$(window).on("maxbuttons-js-init-done",$.proxy(this.loadLivePreview,this)),1==$('input[name="button_is_new"]').val()&&this.saveIndicator(null,!0))},maxAdmin.prototype.loadLivePreview=function(){if("function"==typeof window.maxFoundry.livePreview?(this.livePreview=new window.maxFoundry.livePreview,this.livePreview.init(),$(document).trigger("livepreview-loaded")):alert("Live Preview not loaded, button preview not functional"),void 0!==window.location.hash&&window.location.hash.length>0){var e=window.location.hash.replace("#","");this.change_screen(e)}},maxAdmin.prototype.change_screen_event=function(e){e.preventDefault();var t=$(e.target);void 0===t.data("screenid")&&(t=t.parents(".screen-option"));var o=t.data("screenid");void 0!==o&&this.change_screen(o)},maxAdmin.prototype.change_screen=function(e){var t=$(".screen-option.option-active").data("screenid"),o=$('.screen-option[data-screenid="'+e+'"]');"new"!=e&&$("#current_screen").val(e),t!==e&&(window.location.hash=e,$(".screen-option").removeClass("option-active"),o.addClass("option-active"),$(document).trigger("changed_screen",e),$(".mbscreen-editor").removeClass("current-screen"),$("#screen_"+e).addClass("current-screen"))},maxAdmin.prototype.remove_screen=function(e){if(confirm(maxadmin_settings.remove_confirm)){var t=$(e.target),o=t.parents(".mbscreen-editor").data("screenid");t.parents(".mbscreen-editor").remove(),$('.screen-option[data-screenid="'+o+'"]').remove(),$('input[name="screens[]"][value="'+o+'"]').remove(),$(".removed-note").show(),this.change_screen("default"),this.saveIndicator(null,!0)}},maxAdmin.prototype.toggle_preview=function(e){$(".output .inner").is(":hidden")?($(".output .inner").show(),$(".output").css("height","auto"),$(".preview .preview-toggle").removeClass("dashicons-arrow-down").addClass("dashicons-arrow-up")):($(".output .inner").hide(),$(".output").css("height","auto"),$(".preview .preview-toggle").removeClass("dashicons-arrow-up").addClass("dashicons-arrow-down"))},maxAdmin.prototype.select_field=function(e){$(e.target).select()},maxAdmin.prototype.button_action=function(e){e.preventDefault();var t=$(e.target).data("buttonaction"),o=$(e.target).data("confirm");if(this.form_updated=!1,void 0!==o){if(!window.confirm(o))return}var n=$(e.target).data("buttonid"),a=$('input[name="'+t+'_nonce"]').val(),i=$('input[name="paged"]').val(),r=maxajax.ajax_url,s={action:"mb_button_action",button_action:t,button_id:n,nonce:a};void 0!==i&&(s.paged=i),$.post({url:r,data:s,success:function(e){response=JSON.parse(e),void 0!==response.redirection&&(window.location=response.redirection)},error:function(){console.error("error in button action"+t)}})},maxAdmin.prototype.checkCopyModal=function(e){this.form_updated?e.currentModal.find(".mb-message").show():$(e.currentModal).find(".mb-message").hide()},maxAdmin.prototype.copyColor=function(e){e.preventDefault(),e.stopPropagation();var t=$(e.target),o=$(e.target).parents("[data-bind]"),n="#"+o.data("id"),a="#"+o.data("bind");if(t.hasClass("arrow-right"))i="right";else var i="left";if(o.hasClass("right"))r="left";else var r="right";"left"==r?copy="right"==i:"right"==r&&(copy="right"!=i),copy?($(a).val($(n).val()),$(a).trigger("change"),$(a).wpColorPicker("color",$(n).val())):($(n).val($(a).val()),$(n).trigger("change"),$(n).wpColorPicker("color",$(a).val()))},maxAdmin.prototype.toggleRadiusLock=function(e){var t=$(e.target),o=$(t).data("lock");"lock"==o?($(t).removeClass("dashicons-lock").addClass("dashicons-unlock"),$(t).data("lock","unlock")):"unlock"==o&&($(t).removeClass("dashicons-unlock").addClass("dashicons-lock"),$(t).data("lock","lock"))},maxAdmin.prototype.initResponsive=function(){},maxAdmin.prototype.do_paging=function(e){var t=parseInt($(e.target).val());if(t<=parseInt($(e.target).attr("max"))){var o=$(e.target).data("url");window.location=o+"&paged="+t}},maxAdmin.prototype.toggleShortcode=function(e){$(".shortcode-expand").hasClass("closed")?($(" .mb-message.shortcode .expanded").css("display","inline-block"),$(".shortcode-expand span").removeClass("dashicons-arrow-down").addClass("dashicons-arrow-up"),$(".shortcode-expand").removeClass("closed").addClass("open")):($(" .mb-message.shortcode .expanded").css("display","none"),$(".shortcode-expand span").addClass("dashicons-arrow-down").removeClass("dashicons-arrow-up"),$(".shortcode-expand").addClass("closed").removeClass("open"))},maxAdmin.prototype.toggleManual=function(e){e.preventDefault();var t=$(e.target),o=t.data("target"),n=$('.manual-entry[data-manual="'+o+'"]');if(n.is(":visible"))return n.hide(),!0;var a=t.parents(".option-container");n.css("top","0px"),n.css("right","-25%"),n.prependTo(a),n.show()},maxAdmin.prototype.resetConditionals=function(){$("[data-show], [data-has]").each(function(){var e=$(this).data("show");if(void 0===e&&(e=$(this).data("has")),void 0!==e){var t=e.target;$(document).off("change",'[name="'+t+'"]')}else console.error($(this)+"has a improperly set conditional")})},maxAdmin.prototype.initConditionials=function(){var e=this;this.resetConditionals(),$("[data-show]").each(function(){var t=$(this).data("show"),o=t.target,n=t.values;$(document).on("change",'[name="'+o+'"]',{child:this,values:n},$.proxy(e.updateConditional,e)),$('[name="'+o+'"]').length>1?$('[name="'+o+'"]:checked').trigger("change",["conditional"]):$('[name="'+o+'"]').trigger("change",["conditional"])});var t=[];$("[data-has]").each(function(){var o=$(this).data("has"),n=o.target,a=o.values;$('[name="'+n+'"]').on("change",{target:n,child:this,values:a},$.proxy(e.updateHasConditional,e));var i='[name="'+n+'"]';$.inArray(i,t),t.push(i)}),t.length>0&&$(t.toString()).first().trigger("change",["conditional"])},maxAdmin.prototype.updateConditional=function(e){var t=e.data,o=t.values,n=t.child,a=$(e.currentTarget),i=$(a).val();if("checkbox"===a.attr("type")){var r=$(a).prop("checked");i="checked"==o&&r?"checked":"unchecked"!=o||r?0:"unchecked"}o.indexOf(i)>=0?($(n).addClass("condshow").animate({opacity:1},300),$(n).find("input, select").trigger("change")):($(n).animate({opacity:0},300,function(){$(n).removeClass("condshow")}),$(n).find("input, select").trigger("change"))},maxAdmin.prototype.updateHasConditional=function(e){var t=e.data,o=t.values,n=t.child,a=t.target,i=[];$(o).each(function(e){i.push("[value="+this+"]")}),$('[name="'+a+'"]').filter(i.toString()).length>0?$(n).addClass("condshow").animate({opacity:1},300):$(n).animate({opacity:0},300,function(){$(n).removeClass("condshow")})},maxAdmin.prototype.updateRange=function(e){if(void 0===e||null===e)t=$('input[type="range"]');else var t=[e.target];$(t).each(function(){var e=$(this).val();$(this).parents(".input").find(".range_value output").val(e+"%")})},maxAdmin.prototype.saveIndicator=function(e,t){t?(this.form_updated=!0,$(".button-save").removeClass("disabled").addClass("button-primary")):(this.form_updated=!1,$(".button-save").addClass("disabled").removeClass("button-primary"))},maxAdmin.prototype.formAjaxSave=function(e){e.preventDefault();var t=mb_ajax.ajaxurl,o=$(e.target).serialize();$.ajax({type:"POST",url:t,data:o}).done($.proxy(this.saveDone,this))},maxAdmin.prototype.saveDone=function(e){$("[data-form]").prop("disabled",!1);var t=JSON.parse(e),o=t.result,n=t.title,a=t.data.id;if(void 0!==t.data.new_nonce){t.data.new_nonce;$('input[name="nonce"]').val(t.data.new_nonce)}if(o){$('input[name="collection_id"]').val(a);var i=window.location.href;-1===i.indexOf("collection_id")&&window.history.replaceState({},"",i+"&collection_id="+a),$(document).trigger("mbFormSaved");var r=$('input[name="sorted"]').val();$('input[name="previous_selection"]').val(r),t.data.reload&&document.location.reload(!0)}o||($modal=window.maxFoundry.maxModal,$modal.newModal("collection_error"),$modal.setTitle(n),$modal.setContent(t.body),$modal.setControls('<button class="modal_close button-primary">'+t.close_text+"</button>"),$modal.show())},maxAdmin.prototype.openURLDialog=function(e){return window.wpActiveEditor="url",-1==window.ajaxurl.indexOf("ajax_maxbuttons")&&(window.ajaxurl=window.ajaxurl+"?ajax_maxbuttons=editor"),wpLink.open(),$("#link-options").hide(),$(".query-results").css("top","70px"),$("#wp-link-submit").off("click keyup change"),$("#wp-link-submit").on("click",$.proxy(this.updateLink,this)),!1},maxAdmin.prototype.updateLink=function(e){e.preventDefault();var t=$("#url").val(),o=$("#wp-link-url").val(),n=maxadmin_settings.homeurl;return o=o.replace(n,""),$("#url").val(o),t!=o&&$(document).trigger("livePreviewUpdate",!0),wpLink.close(),!1},maxAdmin.prototype.toggleSidebar=function(e){var t=e.target,o=$(t).parents(".block_sidebar");o.hasClass("active")?o.removeClass("active"):o.addClass("active")},maxAdmin.prototype.checkAllInputs=function(e){e.preventDefault;for(var t=document.getElementById("bulk-action-all").checked,o=document.querySelectorAll('input[name="button-id[]"]'),n=0;n<o.length;n++)o[n].checked=t};
maxbuttons.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: MaxButtons
4
  Plugin URI: http://maxbuttons.com
5
  Description: The best WordPress button generator. This is the free version; the Pro version <a href="http://maxbuttons.com/?ref=mbfree">can be found here</a>.
6
- Version: 8.7
7
  Author: Max Foundry
8
  Author URI: http://maxfoundry.com
9
  Text Domain: maxbuttons
@@ -16,9 +16,9 @@ namespace MaxButtons;
16
  if (! defined('MAXBUTTONS_ROOT_FILE'))
17
  define("MAXBUTTONS_ROOT_FILE", __FILE__);
18
  if (! defined('MAXBUTTONS_VERSION_NUM'))
19
- define('MAXBUTTONS_VERSION_NUM', '8.7');
20
 
21
- define('MAXBUTTONS_RELEASE',"15 July 2021");
22
 
23
  if (! function_exists('MaxButtons\maxbutton_double_load'))
24
  {
3
  Plugin Name: MaxButtons
4
  Plugin URI: http://maxbuttons.com
5
  Description: The best WordPress button generator. This is the free version; the Pro version <a href="http://maxbuttons.com/?ref=mbfree">can be found here</a>.
6
+ Version: 8.8
7
  Author: Max Foundry
8
  Author URI: http://maxfoundry.com
9
  Text Domain: maxbuttons
16
  if (! defined('MAXBUTTONS_ROOT_FILE'))
17
  define("MAXBUTTONS_ROOT_FILE", __FILE__);
18
  if (! defined('MAXBUTTONS_VERSION_NUM'))
19
+ define('MAXBUTTONS_VERSION_NUM', '8.8');
20
 
21
+ define('MAXBUTTONS_RELEASE',"30 September 2021");
22
 
23
  if (! function_exists('MaxButtons\maxbutton_double_load'))
24
  {
readme.txt CHANGED
@@ -4,7 +4,7 @@ Tags: wordpress button plugin, share button, wordpress buttons, css3 button gene
4
  Requires at least: 4.8
5
  Tested up to: 5.8
6
  Requires PHP: 7.0
7
- Stable tag: 8.7
8
  WordPress button plugin so powerful and easy to use anyone can create beautiful buttons, share buttons and social icons.
9
 
10
  == Description ==
@@ -268,6 +268,11 @@ Secondly, please use latin only characters for button name ( Basic settings) and
268
 
269
  == Changelog ==
270
 
 
 
 
 
 
271
  = 8.7 =
272
 
273
  * Removed Shortcake integration
4
  Requires at least: 4.8
5
  Tested up to: 5.8
6
  Requires PHP: 7.0
7
+ Stable tag: 8.8
8
  WordPress button plugin so powerful and easy to use anyone can create beautiful buttons, share buttons and social icons.
9
 
10
  == Description ==
268
 
269
  == Changelog ==
270
 
271
+ = 8.8 =
272
+
273
+ * Updated SCSSPHP library to 1.8.1
274
+ * Updated several deprecated JQuery calls
275
+
276
  = 8.7 =
277
 
278
  * Removed Shortcake integration