WP-SCSS - Version 2.2.0

Version Description

  • Updates to allow compile() from outside the plugin niaccurshi
    • Update src to use ScssPHP github repo at 1.2.1
Download this release

Release Info

Developer Sky Bolt
Plugin Icon wp plugin WP-SCSS
Version 2.2.0
Comparing to
See all releases

Code changes from version 2.1.6 to 2.2.0

class/class-wp-scss.php CHANGED
@@ -9,9 +9,9 @@ class Wp_Scss {
9
  * Compiling preferences properites
10
  *
11
  * @var string
12
- * @access public
13
  */
14
- public $scss_dir, $css_dir, $compile_method, $scssc, $compile_errors, $sourcemaps;
15
 
16
  /**
17
  * Set values for Wp_Scss::properties
@@ -25,27 +25,62 @@ class Wp_Scss {
25
  * @var array compile_errors - catches errors from compile
26
  */
27
  public function __construct ($scss_dir, $css_dir, $compile_method, $sourcemaps) {
28
- global $scssc;
29
  $this->scss_dir = $scss_dir;
30
  $this->css_dir = $css_dir;
31
  $this->compile_method = $compile_method;
32
  $this->compile_errors = array();
33
- $scssc = new Compiler();
 
 
34
 
35
- $scssc->setFormatter( $compile_method );
36
- $scssc->setImportPaths( $this->scss_dir );
37
 
38
  $this->sourcemaps = $sourcemaps;
39
  }
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  /**
42
  * METHOD COMPILE
43
  * Loops through scss directory and compilers files that end
44
  * with .scss and do not have '_' in front.
45
  *
46
- * @function compiler - passes input content through scssphp,
47
- * puts compiled css into cache file
48
- *
49
  * @var array input_files - array of .scss files with no '_' in front
50
  * @var array sdir_arr - an array of all the files in the scss directory
51
  *
@@ -54,91 +89,93 @@ class Wp_Scss {
54
  * @access public
55
  */
56
  public function compile() {
57
- global $scssc, $cache;
58
- $cache = WPSCSS_PLUGIN_DIR . '/cache/';
 
 
 
 
 
 
 
59
 
60
- //Compiler - Takes scss $in and writes compiled css to $out file
61
- // catches errors and puts them the object's compiled_errors property
62
- if (!function_exists( 'compiler' )) {
63
- function compiler($in, $out, $instance) {
64
- global $scssc, $cache;
65
 
66
- if (!file_exists($cache)) {
67
- mkdir($cache, 0644);
68
- }
69
- if (is_writable($cache)) {
70
- try {
71
- $map = basename($out) . '.map';
72
- $scssc->setSourceMap(constant('ScssPhp\ScssPhp\Compiler::' . $instance->sourcemaps));
73
- $scssc->setSourceMapOptions(array(
74
- 'sourceMapWriteTo' => $instance->css_dir . $map, // absolute path to a file to write the map to
75
- 'sourceMapURL' => $map, // url of the map
76
- 'sourceMapBasepath' => rtrim(ABSPATH, '/'), // base path for filename normalization
77
- 'sourceRoot' => home_url('/'), // This value is prepended to the individual entries in the 'source' field.
78
- ));
79
-
80
- $css = $scssc->compile(file_get_contents($in), $in);
81
-
82
- file_put_contents($cache.basename($out), $css);
83
- } catch (Exception $e) {
84
- $errors = array (
85
- 'file' => basename($in),
86
- 'message' => $e->getMessage(),
87
- );
88
- array_push($instance->compile_errors, $errors);
89
  }
90
- } else {
91
- $errors = array (
92
- 'file' => $cache,
93
- 'message' => "File Permission Error, permission denied. Please make the cache directory writable."
94
- );
95
- array_push($instance->compile_errors, $errors);
96
  }
 
 
 
 
 
 
97
  }
 
 
98
 
99
- $input_files = array();
100
- // Loop through directory and get .scss file that do not start with '_'
101
- foreach(new DirectoryIterator($this->scss_dir) as $file) {
102
- if (substr($file, 0, 1) != "_" && pathinfo($file->getFilename(), PATHINFO_EXTENSION) == 'scss') {
103
- array_push($input_files, $file->getFilename());
104
- }
105
- }
 
 
 
 
 
 
 
 
 
106
 
107
- // For each input file, find matching css file and compile
108
- foreach ($input_files as $scss_file) {
109
- $input = $this->scss_dir.$scss_file;
110
- $outputName = preg_replace("/\.[^$]*/",".css", $scss_file);
111
- $output = $this->css_dir.$outputName;
 
 
 
 
 
 
 
 
112
 
113
- compiler($input, $output, $this);
114
- }
115
 
116
- if (count($this->compile_errors) < 1) {
117
- if ( is_writable($this->css_dir) ) {
118
- foreach (new DirectoryIterator($cache) as $cache_file) {
119
- if ( pathinfo($cache_file->getFilename(), PATHINFO_EXTENSION) == 'css') {
120
- file_put_contents($this->css_dir.$cache_file, file_get_contents($cache.$cache_file));
121
- unlink($cache.$cache_file->getFilename()); // Delete file on successful write
122
- }
123
- }
124
- } else {
125
- $errors = array(
126
- 'file' => 'CSS Directory',
127
- 'message' => "File Permissions Error, permission denied. Please make your CSS directory writable."
128
- );
129
- array_push($this->compile_errors, $errors);
130
- }
131
  }
132
- }else{
133
  $errors = array (
134
- 'file' => 'SCSS compiler',
135
- 'message' => "Compiling Error, function 'compiler' already exists."
136
  );
137
- array_push($this->compile_errors, $errors);
138
  }
139
  }
140
 
141
-
142
  /**
143
  * METHOD NEEDS_COMPILING
144
  * Gets the most recently modified file in the scss directory
@@ -159,7 +196,7 @@ class Wp_Scss {
159
  */
160
  public function needs_compiling() {
161
  global $wpscss_settings;
162
- if (defined('WP_SCSS_ALWAYS_RECOMPILE') && WP_SCSS_ALWAYS_RECOMPILE || $wpscss_settings['always_recompile'] === "1") {
163
  return true;
164
  }
165
 
@@ -245,7 +282,7 @@ class Wp_Scss {
245
  }
246
 
247
  public function set_variables(array $variables) {
248
- global $scssc;
249
- $scssc->setVariables($variables);
250
  }
251
  } // End Wp_Scss Class
9
  * Compiling preferences properites
10
  *
11
  * @var string
12
+ * @access private
13
  */
14
+ private $scss_dir, $css_dir, $compile_method, $scssc, $compile_errors, $sourcemaps, $cache;
15
 
16
  /**
17
  * Set values for Wp_Scss::properties
25
  * @var array compile_errors - catches errors from compile
26
  */
27
  public function __construct ($scss_dir, $css_dir, $compile_method, $sourcemaps) {
28
+
29
  $this->scss_dir = $scss_dir;
30
  $this->css_dir = $css_dir;
31
  $this->compile_method = $compile_method;
32
  $this->compile_errors = array();
33
+ $this->scssc = new Compiler();
34
+
35
+ $this->cache = WPSCSS_PLUGIN_DIR . '/cache/';
36
 
37
+ $this->scssc->setFormatter( $compile_method );
38
+ $this->scssc->setImportPaths( $this->scss_dir );
39
 
40
  $this->sourcemaps = $sourcemaps;
41
  }
42
 
43
+ /**
44
+ * METHOD GET SCSS DIRECTORY
45
+ * Returns the stored SCSS directory for this compile instance
46
+ *
47
+ * @return string - Value of the SCSS directory
48
+ *
49
+ * @access public
50
+ */
51
+ public function get_scss_dir() {
52
+ return $this->scss_dir;
53
+ }
54
+
55
+ /**
56
+ * METHOD GET CSS DIRECTORY
57
+ * Returns the stored CSS directory for this compile instance
58
+ *
59
+ * @return string - Value of the CSS directory
60
+ *
61
+ * @access public
62
+ */
63
+ public function get_css_dir() {
64
+ return $this->css_dir;
65
+ }
66
+
67
+ /**
68
+ * METHOD GET CSS DIRECTORY
69
+ * Returns the stored CSS directory for this compile instance
70
+ *
71
+ * @return array - List of errors from the compile process, if any
72
+ *
73
+ * @access public
74
+ */
75
+ public function get_compile_errors() {
76
+ return $this->compile_errors;
77
+ }
78
+
79
  /**
80
  * METHOD COMPILE
81
  * Loops through scss directory and compilers files that end
82
  * with .scss and do not have '_' in front.
83
  *
 
 
 
84
  * @var array input_files - array of .scss files with no '_' in front
85
  * @var array sdir_arr - an array of all the files in the scss directory
86
  *
89
  * @access public
90
  */
91
  public function compile() {
92
+
93
+ $input_files = array();
94
+
95
+ // Loop through directory and get .scss file that do not start with '_'
96
+ foreach(new DirectoryIterator($this->scss_dir) as $file) {
97
+ if (substr($file, 0, 1) != "_" && pathinfo($file->getFilename(), PATHINFO_EXTENSION) == 'scss') {
98
+ array_push($input_files, $file->getFilename());
99
+ }
100
+ }
101
 
102
+ // For each input file, find matching css file and compile
103
+ foreach ($input_files as $scss_file) {
104
+ $input = $this->scss_dir . $scss_file;
105
+ $outputName = preg_replace("/\.[^$]*/", ".css", $scss_file);
106
+ $output = $this->css_dir . $outputName;
107
 
108
+ $this->compiler($input, $output, $this);
109
+ }
110
+
111
+ if (count($this->compile_errors) < 1) {
112
+ if ( is_writable($this->css_dir) ) {
113
+ foreach (new DirectoryIterator($this->cache) as $this->cache_file) {
114
+ if ( pathinfo($this->cache_file->getFilename(), PATHINFO_EXTENSION) == 'css') {
115
+ file_put_contents($this->css_dir . $this->cache_file, file_get_contents($this->cache . $this->cache_file));
116
+ unlink($this->cache . $this->cache_file->getFilename()); // Delete file on successful write
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  }
 
 
 
 
 
 
118
  }
119
+ } else {
120
+ $errors = array(
121
+ 'file' => 'CSS Directory',
122
+ 'message' => "File Permissions Error, permission denied. Please make your CSS directory writable."
123
+ );
124
+ array_push($this->compile_errors, $errors);
125
  }
126
+ }
127
+ }
128
 
129
+ /**
130
+ * METHOD COMPILER
131
+ * Takes scss $in and writes compiled css to $out file
132
+ * catches errors and puts them the object's compiled_errors property
133
+ *
134
+ * @function compiler - passes input content through scssphp,
135
+ * puts compiled css into cache file
136
+ *
137
+ * @var array input_files - array of .scss files with no '_' in front
138
+ * @var array sdir_arr - an array of all the files in the scss directory
139
+ *
140
+ * @return nothing - Puts successfully compiled css into appropriate location
141
+ * Puts error in 'compile_errors' property
142
+ * @access public
143
+ */
144
+ private function compiler($in, $out, $instance) {
145
 
146
+ if (!file_exists($this->cache)) {
147
+ mkdir($this->cache, 0644);
148
+ }
149
+ if (is_writable($this->cache)) {
150
+ try {
151
+ $map = basename($out) . '.map';
152
+ $this->scssc->setSourceMap(constant('ScssPhp\ScssPhp\Compiler::' . $instance->sourcemaps));
153
+ $this->scssc->setSourceMapOptions(array(
154
+ 'sourceMapWriteTo' => $instance->css_dir . $map, // absolute path to a file to write the map to
155
+ 'sourceMapURL' => $map, // url of the map
156
+ 'sourceMapBasepath' => rtrim(ABSPATH, '/'), // base path for filename normalization
157
+ 'sourceRoot' => home_url('/'), // This value is prepended to the individual entries in the 'source' field.
158
+ ));
159
 
160
+ $css = $this->scssc->compile(file_get_contents($in), $in);
 
161
 
162
+ file_put_contents($this->cache . basename($out), $css);
163
+ } catch (Exception $e) {
164
+ $errors = array (
165
+ 'file' => basename($in),
166
+ 'message' => $e->getMessage(),
167
+ );
168
+ array_push($instance->compile_errors, $errors);
 
 
 
 
 
 
 
 
169
  }
170
+ } else {
171
  $errors = array (
172
+ 'file' => $this->cache,
173
+ 'message' => "File Permission Error, permission denied. Please make the cache directory writable."
174
  );
175
+ array_push($instance->compile_errors, $errors);
176
  }
177
  }
178
 
 
179
  /**
180
  * METHOD NEEDS_COMPILING
181
  * Gets the most recently modified file in the scss directory
196
  */
197
  public function needs_compiling() {
198
  global $wpscss_settings;
199
+ if (defined('WP_SCSS_ALWAYS_RECOMPILE') && WP_SCSS_ALWAYS_RECOMPILE || isset($wpscss_settings['always_recompile']) ? $wpscss_settings['always_recompile'] === "1" : false) {
200
  return true;
201
  }
202
 
282
  }
283
 
284
  public function set_variables(array $variables) {
285
+
286
+ $this->scssc->setVariables($variables);
287
  }
288
  } // End Wp_Scss Class
composer.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ConnectThink/WP-SCSS",
3
+ "description": "Compiles .scss files on your wordpress install using lefo's scssphp. Includes settings page for configuring directories, error reporting, compiling options, and auto enqueuing.",
4
+ "keywords": ["wordpress", "plugin"],
5
+ "type": "wordpress-plugin",
6
+ "repositories": [
7
+ {
8
+ "type": "vcs",
9
+ "url": "git@github.com:ConnectThink/WP-SCSS.git"
10
+ }
11
+ ]
12
+ }
13
+
readme.md CHANGED
@@ -104,6 +104,9 @@ This plugin will only work with .scss format.
104
 
105
  ## Changelog
106
 
 
 
 
107
  - 2.1.6
108
  - When enqueueing CSS files Defer to WordPress for URLs instead of trying to guess them. Change by [mmcev106](https://github.com/ConnectThink/WP-SCSS/pull/185)
109
  - Allow setting Base Directory to Parent theme folder. [Shadoath](https://github.com/ConnectThink/WP-SCSS/issues/178)
104
 
105
  ## Changelog
106
 
107
+ - 2.2.0
108
+ - Updates to allow compile() from outside the plugin [niaccurshi](https://github.com/ConnectThink/WP-SCSS/pull/190)
109
+ - Update src to use [ScssPHP github repo at 1.2.1](https://github.com/scssphp/scssphp/releases/tag/1.2.1)
110
  - 2.1.6
111
  - When enqueueing CSS files Defer to WordPress for URLs instead of trying to guess them. Change by [mmcev106](https://github.com/ConnectThink/WP-SCSS/pull/185)
112
  - Allow setting Base Directory to Parent theme folder. [Shadoath](https://github.com/ConnectThink/WP-SCSS/issues/178)
readme.txt CHANGED
@@ -5,7 +5,7 @@ Plugin URI: https://github.com/ConnectThink/WP-SCSS
5
  Requires at least: 3.0.1
6
  Tested up to: 5.7.1
7
  Requires PHP: 5.6
8
- Stable tag: 2.1.6
9
  License: GPLv3 or later
10
  License URI: http://www.gnu.org/copyleft/gpl.html
11
 
@@ -76,6 +76,10 @@ If you are having issues with the plugin, create an issue on [github](https://gi
76
 
77
  == Changelog ==
78
 
 
 
 
 
79
  = 2.1.6 =
80
  - When enqueueing CSS files Defer to WordPress for URLs instead of trying to guess them. Change by [mmcev106](https://github.com/ConnectThink/WP-SCSS/pull/185)
81
  - Allow setting Base Directory to Parent theme folder. [Shadoath](https://github.com/ConnectThink/WP-SCSS/issues/178)
5
  Requires at least: 3.0.1
6
  Tested up to: 5.7.1
7
  Requires PHP: 5.6
8
+ Stable tag: 2.2.0
9
  License: GPLv3 or later
10
  License URI: http://www.gnu.org/copyleft/gpl.html
11
 
76
 
77
  == Changelog ==
78
 
79
+ = 2.2.0 =
80
+ - Updates to allow compile() from outside the plugin [niaccurshi](https://github.com/ConnectThink/WP-SCSS/pull/190)
81
+ - Update src to use [ScssPHP github repo at 1.2.1](https://github.com/scssphp/scssphp/releases/tag/1.2.1)
82
+
83
  = 2.1.6 =
84
  - When enqueueing CSS files Defer to WordPress for URLs instead of trying to guess them. Change by [mmcev106](https://github.com/ConnectThink/WP-SCSS/pull/185)
85
  - Allow setting Base Directory to Parent theme folder. [Shadoath](https://github.com/ConnectThink/WP-SCSS/issues/178)
scssphp/bin/pscss CHANGED
@@ -1,9 +1,10 @@
1
  #!/usr/bin/env php
2
  <?php
 
3
  /**
4
  * SCSSPHP
5
  *
6
- * @copyright 2012-2019 Leaf Corcoran
7
  *
8
  * @license http://opensource.org/licenses/MIT MIT
9
  *
@@ -24,13 +25,11 @@ use ScssPhp\ScssPhp\Version;
24
 
25
  $style = null;
26
  $loadPaths = null;
27
- $precision = null;
28
  $dumpTree = false;
29
  $inputFile = null;
30
  $changeDir = false;
31
  $debugInfo = false;
32
  $lineNumbers = false;
33
- $ignoreErrors = false;
34
  $encoding = false;
35
  $sourceMap = false;
36
 
@@ -62,7 +61,7 @@ function parseArgument(&$i, $options) {
62
  }
63
 
64
  for ($i = 1; $i < $argc; $i++) {
65
- if ($argv[$i] === '-h' || $argv[$i] === '--help') {
66
  $exe = $argv[0];
67
 
68
  $HELP = <<<EOT
@@ -70,17 +69,17 @@ Usage: $exe [options] [input-file]
70
 
71
  Options include:
72
 
73
- -h, --help Show this message
74
- --continue-on-error Continue compilation (as best as possible) when error encountered
75
- --debug-info Annotate selectors with CSS referring to the source file and line number
76
- -f=format Set the output format (compact, compressed, crunched, expanded, or nested)
77
- -i=path Set import path
78
- --iso8859-1 Use iso8859-1 encoding instead of utf-8 (default utf-8)
79
- --line-numbers Annotate selectors with comments referring to the source file and line number
80
- -p=precision Set decimal number precision (default 10)
81
  --sourcemap Create source map file
82
- -T Dump formatted parse tree
83
- -v, --version Print the version
84
 
85
  EOT;
86
  exit($HELP);
@@ -90,12 +89,13 @@ EOT;
90
  exit(Version::VERSION . "\n");
91
  }
92
 
 
93
  if ($argv[$i] === '--continue-on-error') {
94
- $ignoreErrors = true;
95
  continue;
96
  }
97
 
98
- if ($argv[$i] === '--debug-info') {
99
  $debugInfo = true;
100
  continue;
101
  }
@@ -115,29 +115,30 @@ EOT;
115
  continue;
116
  }
117
 
118
- if ($argv[$i] === '-T') {
119
  $dumpTree = true;
120
  continue;
121
  }
122
 
123
- $value = parseArgument($i, array('-f', '--style'));
124
 
125
  if (isset($value)) {
126
  $style = $value;
127
  continue;
128
  }
129
 
130
- $value = parseArgument($i, array('-i', '--load_paths'));
131
 
132
  if (isset($value)) {
133
  $loadPaths = $value;
134
  continue;
135
  }
136
 
 
137
  $value = parseArgument($i, array('-p', '--precision'));
138
 
139
  if (isset($value)) {
140
- $precision = $value;
141
  continue;
142
  }
143
 
@@ -184,18 +185,10 @@ if ($lineNumbers) {
184
  $scss->setLineNumberStyle(Compiler::LINE_COMMENTS);
185
  }
186
 
187
- if ($ignoreErrors) {
188
- $scss->setIgnoreErrors($ignoreErrors);
189
- }
190
-
191
  if ($loadPaths) {
192
  $scss->setImportPaths(explode(PATH_SEPARATOR, $loadPaths));
193
  }
194
 
195
- if ($precision) {
196
- $scss->setNumberPrecision($precision);
197
- }
198
-
199
  if ($style) {
200
  $scss->setFormatter('ScssPhp\\ScssPhp\\Formatter\\' . ucfirst($style));
201
  }
1
  #!/usr/bin/env php
2
  <?php
3
+
4
  /**
5
  * SCSSPHP
6
  *
7
+ * @copyright 2012-2020 Leaf Corcoran
8
  *
9
  * @license http://opensource.org/licenses/MIT MIT
10
  *
25
 
26
  $style = null;
27
  $loadPaths = null;
 
28
  $dumpTree = false;
29
  $inputFile = null;
30
  $changeDir = false;
31
  $debugInfo = false;
32
  $lineNumbers = false;
 
33
  $encoding = false;
34
  $sourceMap = false;
35
 
61
  }
62
 
63
  for ($i = 1; $i < $argc; $i++) {
64
+ if ($argv[$i] === '-?' || $argv[$i] === '-h' || $argv[$i] === '--help') {
65
  $exe = $argv[0];
66
 
67
  $HELP = <<<EOT
69
 
70
  Options include:
71
 
72
+ --help Show this message [-h, -?]
73
+ --continue-on-error [deprecated] Ignored
74
+ --debug-info Annotate selectors with CSS referring to the source file and line number [-g]
75
+ --dump-tree Dump formatted parse tree [-T]
76
+ --iso8859-1 Use iso8859-1 encoding instead of default utf-8
77
+ --line-numbers Annotate selectors with comments referring to the source file and line number [--line-comments]
78
+ --load-path=PATH Set import path [-I]
79
+ --precision=N [deprecated] Ignored. (default 10) [-p]
80
  --sourcemap Create source map file
81
+ --style=FORMAT Set the output format (compact, compressed, crunched, expanded, or nested) [-s, -t]
82
+ --version Print the version [-v]
83
 
84
  EOT;
85
  exit($HELP);
89
  exit(Version::VERSION . "\n");
90
  }
91
 
92
+ // Keep parsing --continue-on-error to avoid BC breaks for scripts using it
93
  if ($argv[$i] === '--continue-on-error') {
94
+ // TODO report it as a warning ?
95
  continue;
96
  }
97
 
98
+ if ($argv[$i] === '-g' || $argv[$i] === '--debug-info') {
99
  $debugInfo = true;
100
  continue;
101
  }
115
  continue;
116
  }
117
 
118
+ if ($argv[$i] === '-T' || $argv[$i] === '--dump-tree') {
119
  $dumpTree = true;
120
  continue;
121
  }
122
 
123
+ $value = parseArgument($i, array('-t', '-s', '--style'));
124
 
125
  if (isset($value)) {
126
  $style = $value;
127
  continue;
128
  }
129
 
130
+ $value = parseArgument($i, array('-I', '--load-path'));
131
 
132
  if (isset($value)) {
133
  $loadPaths = $value;
134
  continue;
135
  }
136
 
137
+ // Keep parsing --precision to avoid BC breaks for scripts using it
138
  $value = parseArgument($i, array('-p', '--precision'));
139
 
140
  if (isset($value)) {
141
+ // TODO report it as a warning ?
142
  continue;
143
  }
144
 
185
  $scss->setLineNumberStyle(Compiler::LINE_COMMENTS);
186
  }
187
 
 
 
 
 
188
  if ($loadPaths) {
189
  $scss->setImportPaths(explode(PATH_SEPARATOR, $loadPaths));
190
  }
191
 
 
 
 
 
192
  if ($style) {
193
  $scss->setFormatter('ScssPhp\\ScssPhp\\Formatter\\' . ucfirst($style));
194
  }
scssphp/scss.inc.php CHANGED
@@ -1,34 +1,36 @@
1
  <?php
 
2
  if (version_compare(PHP_VERSION, '5.6') < 0) {
3
- throw new \Exception('scssphp requires PHP 5.6 or above');
4
  }
5
 
6
  if (! class_exists('ScssPhp\ScssPhp\Version', false)) {
7
- include_once __DIR__ . '/src/Base/Range.php';
8
- include_once __DIR__ . '/src/Block.php';
9
- include_once __DIR__ . '/src/Cache.php';
10
- include_once __DIR__ . '/src/Colors.php';
11
- include_once __DIR__ . '/src/Compiler.php';
12
- include_once __DIR__ . '/src/Compiler/Environment.php';
13
- include_once __DIR__ . '/src/Exception/CompilerException.php';
14
- include_once __DIR__ . '/src/Exception/ParserException.php';
15
- include_once __DIR__ . '/src/Exception/RangeException.php';
16
- include_once __DIR__ . '/src/Exception/ServerException.php';
17
- include_once __DIR__ . '/src/Formatter.php';
18
- include_once __DIR__ . '/src/Formatter/Compact.php';
19
- include_once __DIR__ . '/src/Formatter/Compressed.php';
20
- include_once __DIR__ . '/src/Formatter/Crunched.php';
21
- include_once __DIR__ . '/src/Formatter/Debug.php';
22
- include_once __DIR__ . '/src/Formatter/Expanded.php';
23
- include_once __DIR__ . '/src/Formatter/Nested.php';
24
- include_once __DIR__ . '/src/Formatter/OutputBlock.php';
25
- include_once __DIR__ . '/src/Node.php';
26
- include_once __DIR__ . '/src/Node/Number.php';
27
- include_once __DIR__ . '/src/Parser.php';
28
- include_once __DIR__ . '/src/SourceMap/Base64.php';
29
- include_once __DIR__ . '/src/SourceMap/Base64VLQ.php';
30
- include_once __DIR__ . '/src/SourceMap/SourceMapGenerator.php';
31
- include_once __DIR__ . '/src/Type.php';
32
- include_once __DIR__ . '/src/Util.php';
33
- include_once __DIR__ . '/src/Version.php';
 
34
  }
1
  <?php
2
+
3
  if (version_compare(PHP_VERSION, '5.6') < 0) {
4
+ throw new \Exception('scssphp requires PHP 5.6 or above');
5
  }
6
 
7
  if (! class_exists('ScssPhp\ScssPhp\Version', false)) {
8
+ include_once __DIR__ . '/src/Base/Range.php';
9
+ include_once __DIR__ . '/src/Block.php';
10
+ include_once __DIR__ . '/src/Cache.php';
11
+ include_once __DIR__ . '/src/Colors.php';
12
+ include_once __DIR__ . '/src/Compiler.php';
13
+ include_once __DIR__ . '/src/Compiler/Environment.php';
14
+ include_once __DIR__ . '/src/Exception/SassException.php';
15
+ include_once __DIR__ . '/src/Exception/CompilerException.php';
16
+ include_once __DIR__ . '/src/Exception/ParserException.php';
17
+ include_once __DIR__ . '/src/Exception/RangeException.php';
18
+ include_once __DIR__ . '/src/Exception/ServerException.php';
19
+ include_once __DIR__ . '/src/Formatter.php';
20
+ include_once __DIR__ . '/src/Formatter/Compact.php';
21
+ include_once __DIR__ . '/src/Formatter/Compressed.php';
22
+ include_once __DIR__ . '/src/Formatter/Crunched.php';
23
+ include_once __DIR__ . '/src/Formatter/Debug.php';
24
+ include_once __DIR__ . '/src/Formatter/Expanded.php';
25
+ include_once __DIR__ . '/src/Formatter/Nested.php';
26
+ include_once __DIR__ . '/src/Formatter/OutputBlock.php';
27
+ include_once __DIR__ . '/src/Node.php';
28
+ include_once __DIR__ . '/src/Node/Number.php';
29
+ include_once __DIR__ . '/src/Parser.php';
30
+ include_once __DIR__ . '/src/SourceMap/Base64.php';
31
+ include_once __DIR__ . '/src/SourceMap/Base64VLQ.php';
32
+ include_once __DIR__ . '/src/SourceMap/SourceMapGenerator.php';
33
+ include_once __DIR__ . '/src/Type.php';
34
+ include_once __DIR__ . '/src/Util.php';
35
+ include_once __DIR__ . '/src/Version.php';
36
  }
scssphp/src/Base/Range.php CHANGED
@@ -1,8 +1,9 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
5
- * @copyright 2015-2019 Leaf Corcoran
6
  *
7
  * @license http://opensource.org/licenses/MIT MIT
8
  *
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
6
+ * @copyright 2015-2020 Leaf Corcoran
7
  *
8
  * @license http://opensource.org/licenses/MIT MIT
9
  *
scssphp/src/Block.php CHANGED
@@ -1,8 +1,9 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
5
- * @copyright 2012-2019 Leaf Corcoran
6
  *
7
  * @license http://opensource.org/licenses/MIT MIT
8
  *
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
  *
8
  * @license http://opensource.org/licenses/MIT MIT
9
  *
scssphp/src/Cache.php CHANGED
@@ -1,8 +1,9 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
5
- * @copyright 2012-2019 Leaf Corcoran
6
  *
7
  * @license http://opensource.org/licenses/MIT MIT
8
  *
@@ -22,13 +23,12 @@ use Exception;
22
  * taking in account options that affects the result
23
  *
24
  * The cache manager is agnostic about data format and only the operation is expected to be described by string
25
- *
26
  */
27
 
28
  /**
29
  * SCSS cache
30
  *
31
- * @author Cedric Morin
32
  */
33
  class Cache
34
  {
@@ -57,12 +57,12 @@ class Cache
57
  public function __construct($options)
58
  {
59
  // check $cacheDir
60
- if (isset($options['cache_dir'])) {
61
- self::$cacheDir = $options['cache_dir'];
62
  }
63
 
64
  if (empty(self::$cacheDir)) {
65
- throw new Exception('cache_dir not set');
66
  }
67
 
68
  if (isset($options['prefix'])) {
@@ -74,7 +74,7 @@ class Cache
74
  }
75
 
76
  if (isset($options['forceRefresh'])) {
77
- self::$forceRefresh = $options['force_refresh'];
78
  }
79
 
80
  self::checkCacheDir();
@@ -97,18 +97,20 @@ class Cache
97
  {
98
  $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options);
99
 
100
- if ((! self::$forceRefresh || (self::$forceRefresh === 'once' &&
 
101
  isset(self::$refreshed[$fileCache]))) && file_exists($fileCache)
102
  ) {
103
  $cacheTime = filemtime($fileCache);
104
 
105
- if ((is_null($lastModified) || $cacheTime > $lastModified) &&
 
106
  $cacheTime + self::$gcLifetime > time()
107
  ) {
108
  $c = file_get_contents($fileCache);
109
  $c = unserialize($c);
110
 
111
- if (is_array($c) && isset($c['value'])) {
112
  return $c['value'];
113
  }
114
  }
@@ -132,6 +134,7 @@ class Cache
132
 
133
  $c = ['value' => $value];
134
  $c = serialize($c);
 
135
  file_put_contents($fileCache, $c);
136
 
137
  if (self::$forceRefresh === 'once') {
@@ -176,13 +179,11 @@ class Cache
176
  self::$cacheDir = str_replace('\\', '/', self::$cacheDir);
177
  self::$cacheDir = rtrim(self::$cacheDir, '/') . '/';
178
 
179
- if (! file_exists(self::$cacheDir)) {
180
- if (! mkdir(self::$cacheDir)) {
181
- throw new Exception('Cache directory couldn\'t be created: ' . self::$cacheDir);
182
- }
183
- } elseif (! is_dir(self::$cacheDir)) {
184
  throw new Exception('Cache directory doesn\'t exist: ' . self::$cacheDir);
185
- } elseif (! is_writable(self::$cacheDir)) {
 
 
186
  throw new Exception('Cache directory isn\'t writable: ' . self::$cacheDir);
187
  }
188
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
  *
8
  * @license http://opensource.org/licenses/MIT MIT
9
  *
23
  * taking in account options that affects the result
24
  *
25
  * The cache manager is agnostic about data format and only the operation is expected to be described by string
 
26
  */
27
 
28
  /**
29
  * SCSS cache
30
  *
31
+ * @author Cedric Morin <cedric@yterium.com>
32
  */
33
  class Cache
34
  {
57
  public function __construct($options)
58
  {
59
  // check $cacheDir
60
+ if (isset($options['cacheDir'])) {
61
+ self::$cacheDir = $options['cacheDir'];
62
  }
63
 
64
  if (empty(self::$cacheDir)) {
65
+ throw new Exception('cacheDir not set');
66
  }
67
 
68
  if (isset($options['prefix'])) {
74
  }
75
 
76
  if (isset($options['forceRefresh'])) {
77
+ self::$forceRefresh = $options['forceRefresh'];
78
  }
79
 
80
  self::checkCacheDir();
97
  {
98
  $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options);
99
 
100
+ if (
101
+ ((self::$forceRefresh === false) || (self::$forceRefresh === 'once' &&
102
  isset(self::$refreshed[$fileCache]))) && file_exists($fileCache)
103
  ) {
104
  $cacheTime = filemtime($fileCache);
105
 
106
+ if (
107
+ (\is_null($lastModified) || $cacheTime > $lastModified) &&
108
  $cacheTime + self::$gcLifetime > time()
109
  ) {
110
  $c = file_get_contents($fileCache);
111
  $c = unserialize($c);
112
 
113
+ if (\is_array($c) && isset($c['value'])) {
114
  return $c['value'];
115
  }
116
  }
134
 
135
  $c = ['value' => $value];
136
  $c = serialize($c);
137
+
138
  file_put_contents($fileCache, $c);
139
 
140
  if (self::$forceRefresh === 'once') {
179
  self::$cacheDir = str_replace('\\', '/', self::$cacheDir);
180
  self::$cacheDir = rtrim(self::$cacheDir, '/') . '/';
181
 
182
+ if (! is_dir(self::$cacheDir)) {
 
 
 
 
183
  throw new Exception('Cache directory doesn\'t exist: ' . self::$cacheDir);
184
+ }
185
+
186
+ if (! is_writable(self::$cacheDir)) {
187
  throw new Exception('Cache directory isn\'t writable: ' . self::$cacheDir);
188
  }
189
  }
scssphp/src/Colors.php CHANGED
@@ -1,8 +1,9 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
5
- * @copyright 2012-2019 Leaf Corcoran
6
  *
7
  * @license http://opensource.org/licenses/MIT MIT
8
  *
@@ -25,9 +26,10 @@ class Colors
25
  *
26
  * @var array
27
  */
28
- public 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',
@@ -46,13 +48,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',
@@ -74,15 +75,16 @@ class Colors
74
  'firebrick' => '178,34,34',
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 +100,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 +113,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 +146,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 +167,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,5 +174,72 @@ class Colors
175
  'whitesmoke' => '245,245,245',
176
  'yellow' => '255,255,0',
177
  'yellowgreen' => '154,205,50',
 
 
178
  ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  }
1
  <?php
2
+
3
  /**
4
  * SCSSPHP
5
  *
6
+ * @copyright 2012-2020 Leaf Corcoran
7
  *
8
  * @license http://opensource.org/licenses/MIT MIT
9
  *
26
  *
27
  * @var array
28
  */
29
+ protected static $cssColors = [
30
  'aliceblue' => '240,248,255',
31
  'antiquewhite' => '250,235,215',
32
+ 'cyan' => '0,255,255',
33
  'aqua' => '0,255,255',
34
  'aquamarine' => '127,255,212',
35
  'azure' => '240,255,255',
48
  'cornflowerblue' => '100,149,237',
49
  'cornsilk' => '255,248,220',
50
  'crimson' => '220,20,60',
 
51
  'darkblue' => '0,0,139',
52
  'darkcyan' => '0,139,139',
53
  'darkgoldenrod' => '184,134,11',
54
  'darkgray' => '169,169,169',
 
55
  'darkgrey' => '169,169,169',
56
+ 'darkgreen' => '0,100,0',
57
  'darkkhaki' => '189,183,107',
58
  'darkmagenta' => '139,0,139',
59
  'darkolivegreen' => '85,107,47',
75
  'firebrick' => '178,34,34',
76
  'floralwhite' => '255,250,240',
77
  'forestgreen' => '34,139,34',
78
+ 'magenta' => '255,0,255',
79
  'fuchsia' => '255,0,255',
80
  'gainsboro' => '220,220,220',
81
  'ghostwhite' => '248,248,255',
82
  'gold' => '255,215,0',
83
  'goldenrod' => '218,165,32',
84
  'gray' => '128,128,128',
85
+ 'grey' => '128,128,128',
86
  'green' => '0,128,0',
87
  'greenyellow' => '173,255,47',
 
88
  'honeydew' => '240,255,240',
89
  'hotpink' => '255,105,180',
90
  'indianred' => '205,92,92',
100
  'lightcyan' => '224,255,255',
101
  'lightgoldenrodyellow' => '250,250,210',
102
  'lightgray' => '211,211,211',
 
103
  'lightgrey' => '211,211,211',
104
+ 'lightgreen' => '144,238,144',
105
  'lightpink' => '255,182,193',
106
  'lightsalmon' => '255,160,122',
107
  'lightseagreen' => '32,178,170',
113
  'lime' => '0,255,0',
114
  'limegreen' => '50,205,50',
115
  'linen' => '250,240,230',
 
116
  'maroon' => '128,0,0',
117
  'mediumaquamarine' => '102,205,170',
118
  'mediumblue' => '0,0,205',
146
  'plum' => '221,160,221',
147
  'powderblue' => '176,224,230',
148
  'purple' => '128,0,128',
 
149
  'red' => '255,0,0',
150
  'rosybrown' => '188,143,143',
151
  'royalblue' => '65,105,225',
167
  'teal' => '0,128,128',
168
  'thistle' => '216,191,216',
169
  'tomato' => '255,99,71',
 
170
  'turquoise' => '64,224,208',
171
  'violet' => '238,130,238',
172
  'wheat' => '245,222,179',
174
  'whitesmoke' => '245,245,245',
175
  'yellow' => '255,255,0',
176
  'yellowgreen' => '154,205,50',
177
+ 'rebeccapurple' => '102,51,153',
178
+ 'transparent' => '0,0,0,0',
179
  ];
180
+
181
+ /**
182
+ * Convert named color in a [r,g,b[,a]] array
183
+ *
184
+ * @param string $colorName
185
+ *
186
+ * @return array|null
187
+ */
188
+ public static function colorNameToRGBa($colorName)
189
+ {
190
+ if (\is_string($colorName) && isset(static::$cssColors[$colorName])) {
191
+ $rgba = explode(',', static::$cssColors[$colorName]);
192
+
193
+ // only case with opacity is transparent, with opacity=0, so we can intval on opacity also
194
+ $rgba = array_map('intval', $rgba);
195
+
196
+ return $rgba;
197
+ }
198
+
199
+ return null;
200
+ }
201
+
202
+ /**
203
+ * Reverse conversion : from RGBA to a color name if possible
204
+ *
205
+ * @param integer $r
206
+ * @param integer $g
207
+ * @param integer $b
208
+ * @param integer $a
209
+ *
210
+ * @return string|null
211
+ */
212
+ public static function RGBaToColorName($r, $g, $b, $a = 1)
213
+ {
214
+ static $reverseColorTable = null;
215
+
216
+ if (! is_numeric($r) || ! is_numeric($g) || ! is_numeric($b) || ! is_numeric($a)) {
217
+ return null;
218
+ }
219
+
220
+ if ($a < 1) {
221
+ return null;
222
+ }
223
+
224
+ if (\is_null($reverseColorTable)) {
225
+ $reverseColorTable = [];
226
+
227
+ foreach (static::$cssColors as $name => $rgb_str) {
228
+ $rgb_str = explode(',', $rgb_str);
229
+
230
+ if (
231
+ \count($rgb_str) == 3 &&
232
+ ! isset($reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])])
233
+ ) {
234
+ $reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])] = $name;
235
+ }
236
+ }
237
+ }
238
+
239
+ if (isset($reverseColorTable[\intval($r)][\intval($g)][\intval($b)])) {
240
+ return $reverseColorTable[\intval($r)][\intval($g)][\intval($b)];
241
+ }
242
+
243
+ return null;
244
+ }
245
  }
scssphp/src/Compiler.php CHANGED
@@ -1,8 +1,9 @@
1
  <?php
 
2
  /**
3
  * SCSSPHP
4
  *
5
- * @copyright 2012-2019 Leaf Corcoran
6
  *
7
  * @license http://opensource.org/licenses/MIT MIT
8
  *
@@ -73,7 +74,7 @@ class Compiler
73
  /**
74
  * @var array
75
  */
76
- static protected $operatorNames = [
77
  '+' => 'add',
78
  '-' => 'sub',
79
  '*' => 'mul',
@@ -93,23 +94,25 @@ class Compiler
93
  /**
94
  * @var array
95
  */
96
- static protected $namespaces = [
97
  'special' => '%',
98
  'mixin' => '@',
99
  'function' => '^',
100
  ];
101
 
102
- static public $true = [Type::T_KEYWORD, 'true'];
103
- static public $false = [Type::T_KEYWORD, 'false'];
104
- static public $null = [Type::T_NULL];
105
- static public $nullString = [Type::T_STRING, '', []];
106
- static public $defaultValue = [Type::T_KEYWORD, ''];
107
- static public $selfSelector = [Type::T_SELF];
108
- static public $emptyList = [Type::T_LIST, '', []];
109
- static public $emptyMap = [Type::T_MAP, [], []];
110
- static public $emptyString = [Type::T_STRING, '"', []];
111
- static public $with = [Type::T_KEYWORD, 'with'];
112
- static public $without = [Type::T_KEYWORD, 'without'];
 
 
113
 
114
  protected $importPaths = [''];
115
  protected $importCache = [];
@@ -159,11 +162,14 @@ class Compiler
159
  protected $stderr;
160
  protected $shouldEvaluate;
161
  protected $ignoreErrors;
 
162
 
163
  protected $callStack = [];
164
 
165
  /**
166
  * Constructor
 
 
167
  */
168
  public function __construct($cacheOptions = null)
169
  {
@@ -173,8 +179,15 @@ class Compiler
173
  if ($cacheOptions) {
174
  $this->cache = new Cache($cacheOptions);
175
  }
 
 
176
  }
177
 
 
 
 
 
 
178
  public function getCompileOptions()
179
  {
180
  $options = [
@@ -190,6 +203,16 @@ class Compiler
190
  return $options;
191
  }
192
 
 
 
 
 
 
 
 
 
 
 
193
  /**
194
  * Compile scss
195
  *
@@ -203,14 +226,14 @@ class Compiler
203
  public function compile($code, $path = null)
204
  {
205
  if ($this->cache) {
206
- $cacheKey = ($path ? $path : "(stdin)") . ":" . md5($code);
207
  $compileOptions = $this->getCompileOptions();
208
- $cache = $this->cache->getCache("compile", $cacheKey, $compileOptions);
209
 
210
- if (is_array($cache) && isset($cache['dependencies']) && isset($cache['out'])) {
211
  // check if any dependency file changed before accepting the cache
212
  foreach ($cache['dependencies'] as $file => $mtime) {
213
- if (! file_exists($file) || filemtime($file) !== $mtime) {
214
  unset($cache);
215
  break;
216
  }
@@ -234,7 +257,7 @@ class Compiler
234
  $this->storeEnv = null;
235
  $this->charsetSeen = null;
236
  $this->shouldEvaluate = null;
237
- $this->stderr = fopen('php://stderr', 'w');
238
 
239
  $this->parser = $this->parserFactory($path);
240
  $tree = $this->parser->parse($code);
@@ -251,7 +274,7 @@ class Compiler
251
  $sourceMapGenerator = null;
252
 
253
  if ($this->sourceMap) {
254
- if (is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) {
255
  $sourceMapGenerator = $this->sourceMap;
256
  $this->sourceMap = self::SOURCE_MAP_FILE;
257
  } elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) {
@@ -284,7 +307,7 @@ class Compiler
284
  'out' => &$out,
285
  ];
286
 
287
- $this->cache->setCache("compile", $cacheKey, $v, $compileOptions);
288
  }
289
 
290
  return $out;
@@ -299,7 +322,18 @@ class Compiler
299
  */
300
  protected function parserFactory($path)
301
  {
302
- $parser = new Parser($path, count($this->sourceNames), $this->encoding, $this->cache);
 
 
 
 
 
 
 
 
 
 
 
303
 
304
  $this->sourceNames[] = $path;
305
  $this->addParsedFile($path);
@@ -318,7 +352,7 @@ class Compiler
318
  protected function isSelfExtend($target, $origin)
319
  {
320
  foreach ($origin as $sel) {
321
- if (in_array($target, $sel)) {
322
  return true;
323
  }
324
  }
@@ -329,17 +363,13 @@ class Compiler
329
  /**
330
  * Push extends
331
  *
332
- * @param array $target
333
- * @param array $origin
334
- * @param \stdClass $block
335
  */
336
  protected function pushExtends($target, $origin, $block)
337
  {
338
- if ($this->isSelfExtend($target, $origin)) {
339
- return;
340
- }
341
-
342
- $i = count($this->extends);
343
  $this->extends[] = [$target, $origin, $block];
344
 
345
  foreach ($target as $part) {
@@ -361,13 +391,13 @@ class Compiler
361
  */
362
  protected function makeOutputBlock($type, $selectors = null)
363
  {
364
- $out = new OutputBlock;
365
- $out->type = $type;
366
- $out->lines = [];
367
- $out->children = [];
368
- $out->parent = $this->scope;
369
- $out->selectors = $selectors;
370
- $out->depth = $this->env->depth;
371
 
372
  if ($this->env->block instanceof Block) {
373
  $out->sourceName = $this->env->block->sourceName;
@@ -417,7 +447,7 @@ class Compiler
417
  $origin = $this->collapseSelectors($origin);
418
 
419
  $this->sourceLine = $block[Parser::SOURCE_LINE];
420
- $this->throwError("\"$origin\" failed to @extend \"$target\". The selector \"$target\" was not found.");
421
  }
422
  }
423
 
@@ -435,7 +465,7 @@ class Compiler
435
  foreach ($block->selectors as $s) {
436
  $selectors[] = $s;
437
 
438
- if (! is_array($s)) {
439
  continue;
440
  }
441
 
@@ -468,7 +498,7 @@ class Compiler
468
  $block->selectors[] = $this->compileSelector($selector);
469
  }
470
 
471
- if ($placeholderSelector && 0 === count($block->selectors) && null !== $parentKey) {
472
  unset($block->parent->children[$parentKey]);
473
 
474
  return;
@@ -492,16 +522,20 @@ class Compiler
492
  $new = [];
493
 
494
  foreach ($parts as $part) {
495
- if (is_array($part)) {
496
  $part = $this->glueFunctionSelectors($part);
497
  $new[] = $part;
498
  } else {
499
  // a selector part finishing with a ) is the last part of a :not( or :nth-child(
500
  // and need to be joined to this
501
- if (count($new) && is_string($new[count($new) - 1]) &&
502
- strlen($part) && substr($part, -1) === ')' && strpos($part, '(') === false
 
503
  ) {
504
- $new[count($new) - 1] .= $part;
 
 
 
505
  } else {
506
  $new[] = $part;
507
  }
@@ -522,13 +556,14 @@ class Compiler
522
  protected function matchExtends($selector, &$out, $from = 0, $initial = true)
523
  {
524
  static $partsPile = [];
525
-
526
  $selector = $this->glueFunctionSelectors($selector);
527
 
528
- if (count($selector) == 1 && in_array(reset($selector), $partsPile)) {
529
  return;
530
  }
531
 
 
 
532
  foreach ($selector as $i => $part) {
533
  if ($i < $from) {
534
  continue;
@@ -536,39 +571,43 @@ class Compiler
536
 
537
  // check that we are not building an infinite loop of extensions
538
  // if the new part is just including a previous part don't try to extend anymore
539
- if (count($part) > 1) {
540
  foreach ($partsPile as $previousPart) {
541
- if (! count(array_diff($previousPart, $part))) {
542
  continue 2;
543
  }
544
  }
545
  }
546
 
547
- if ($this->matchExtendsSingle($part, $origin)) {
548
- $partsPile[] = $part;
549
- $after = array_slice($selector, $i + 1);
550
- $before = array_slice($selector, 0, $i);
551
 
 
 
 
552
  list($before, $nonBreakableBefore) = $this->extractRelationshipFromFragment($before);
553
 
554
  foreach ($origin as $new) {
555
  $k = 0;
556
 
557
  // remove shared parts
558
- if (count($new) > 1) {
559
  while ($k < $i && isset($new[$k]) && $selector[$k] === $new[$k]) {
560
  $k++;
561
  }
562
  }
563
 
 
 
 
 
564
  $replacement = [];
565
- $tempReplacement = $k > 0 ? array_slice($new, $k) : $new;
566
 
567
- for ($l = count($tempReplacement) - 1; $l >= 0; $l--) {
568
  $slice = [];
569
 
570
  foreach ($tempReplacement[$l] as $chunk) {
571
- if (! in_array($chunk, $slice)) {
572
  $slice[] = $chunk;
573
  }
574
  }
@@ -580,7 +619,7 @@ class Compiler
580
  }
581
  }
582
 
583
- $afterBefore = $l != 0 ? array_slice($tempReplacement, 0, $l) : [];
584
 
585
  // Merge shared direct relationships.
586
  $mergedBefore = $this->mergeDirectRelationships($afterBefore, $nonBreakableBefore);
@@ -596,64 +635,137 @@ class Compiler
596
  continue;
597
  }
598
 
599
- $out[] = $result;
600
 
601
  // recursively check for more matches
602
- $startRecurseFrom = count($before) + min(count($nonBreakableBefore), count($mergedBefore));
603
- $this->matchExtends($result, $out, $startRecurseFrom, false);
 
 
 
 
 
604
 
605
  // selector sequence merging
606
- if (! empty($before) && count($new) > 1) {
607
- $preSharedParts = $k > 0 ? array_slice($before, 0, $k) : [];
608
- $postSharedParts = $k > 0 ? array_slice($before, $k) : $before;
609
 
610
- list($betweenSharedParts, $nonBreakable2) = $this->extractRelationshipFromFragment($afterBefore);
611
 
612
  $result2 = array_merge(
613
  $preSharedParts,
614
  $betweenSharedParts,
615
  $postSharedParts,
616
- $nonBreakable2,
617
  $nonBreakableBefore,
618
  $replacement,
619
  $after
620
  );
621
 
622
- $out[] = $result2;
623
  }
624
  }
 
 
 
 
 
 
 
 
 
625
 
626
- array_pop($partsPile);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
  }
628
  }
 
629
  }
630
 
631
  /**
632
  * Match extends single
633
  *
634
- * @param array $rawSingle
635
- * @param array $outOrigin
 
636
  *
637
  * @return boolean
638
  */
639
- protected function matchExtendsSingle($rawSingle, &$outOrigin)
640
  {
641
  $counts = [];
642
  $single = [];
643
 
644
  // simple usual cases, no need to do the whole trick
645
- if (in_array($rawSingle, [['>'],['+'],['~']])) {
646
  return false;
647
  }
648
 
649
  foreach ($rawSingle as $part) {
650
  // matches Number
651
- if (! is_string($part)) {
652
  return false;
653
  }
654
 
655
- if (! preg_match('/^[\[.:#%]/', $part) && count($single)) {
656
- $single[count($single) - 1] .= $part;
657
  } else {
658
  $single[] = $part;
659
  }
@@ -661,21 +773,52 @@ class Compiler
661
 
662
  $extendingDecoratedTag = false;
663
 
664
- if (count($single) > 1) {
665
  $matches = null;
666
  $extendingDecoratedTag = preg_match('/^[a-z0-9]+$/i', $single[0], $matches) ? $matches[0] : false;
667
  }
668
 
669
- foreach ($single as $part) {
 
 
 
670
  if (isset($this->extendsMap[$part])) {
671
  foreach ($this->extendsMap[$part] as $idx) {
672
  $counts[$idx] = isset($counts[$idx]) ? $counts[$idx] + 1 : 1;
673
  }
674
  }
675
- }
676
 
677
- $outOrigin = [];
678
- $found = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
679
 
680
  foreach ($counts as $idx => $count) {
681
  list($target, $origin, /* $block */) = $this->extends[$idx];
@@ -683,7 +826,7 @@ class Compiler
683
  $origin = $this->glueFunctionSelectors($origin);
684
 
685
  // check count
686
- if ($count !== count($target)) {
687
  continue;
688
  }
689
 
@@ -693,14 +836,15 @@ class Compiler
693
 
694
  foreach ($origin as $j => $new) {
695
  // prevent infinite loop when target extends itself
696
- if ($this->isSelfExtend($single, $origin)) {
697
  return false;
698
  }
699
 
700
  $replacement = end($new);
701
 
702
  // Extending a decorated tag with another tag is not possible.
703
- if ($extendingDecoratedTag && $replacement[0] != $extendingDecoratedTag &&
 
704
  preg_match('/^[a-z0-9]+$/i', $replacement[0])
705
  ) {
706
  unset($origin[$j]);
@@ -709,8 +853,8 @@ class Compiler
709
 
710
  $combined = $this->combineSelectorSingle($replacement, $rem);
711
 
712
- if (count(array_diff($combined, $origin[$j][count($origin[$j]) - 1]))) {
713
- $origin[$j][count($origin[$j]) - 1] = $combined;
714
  }
715
  }
716
 
@@ -738,12 +882,13 @@ class Compiler
738
  {
739
  $parents = [];
740
  $children = [];
741
- $j = $i = count($fragment);
 
742
 
743
  for (;;) {
744
- $children = $j != $i ? array_slice($fragment, $j, $i - $j) : [];
745
- $parents = array_slice($fragment, 0, $j);
746
- $slice = end($parents);
747
 
748
  if (empty($slice) || ! $this->isImmediateRelationshipCombinator($slice[0])) {
749
  break;
@@ -765,30 +910,45 @@ class Compiler
765
  */
766
  protected function combineSelectorSingle($base, $other)
767
  {
768
- $tag = [];
769
- $out = [];
770
- $wasTag = true;
 
 
 
 
 
 
 
 
771
 
772
- foreach ([$base, $other] as $single) {
773
  foreach ($single as $part) {
774
- if (preg_match('/^[\[.:#]/', $part)) {
775
  $out[] = $part;
776
  $wasTag = false;
777
- } elseif (preg_match('/^[^_-]/', $part)) {
 
 
 
778
  $tag[] = $part;
779
  $wasTag = true;
780
  } elseif ($wasTag) {
781
- $tag[count($tag) - 1] .= $part;
782
  } else {
783
- $out[count($out) - 1] .= $part;
784
  }
 
785
  }
786
  }
787
 
788
- if (count($tag)) {
789
  array_unshift($out, $tag[0]);
790
  }
791
 
 
 
 
 
792
  return $out;
793
  }
794
 
@@ -820,7 +980,8 @@ class Compiler
820
  foreach ($media->children as $child) {
821
  $type = $child[0];
822
 
823
- if ($type !== Type::T_BLOCK &&
 
824
  $type !== Type::T_MEDIA &&
825
  $type !== Type::T_DIRECTIVE &&
826
  $type !== Type::T_IMPORT
@@ -831,17 +992,18 @@ class Compiler
831
  }
832
 
833
  if ($needsWrap) {
834
- $wrapped = new Block;
835
- $wrapped->sourceName = $media->sourceName;
836
- $wrapped->sourceIndex = $media->sourceIndex;
837
- $wrapped->sourceLine = $media->sourceLine;
838
  $wrapped->sourceColumn = $media->sourceColumn;
839
- $wrapped->selectors = [];
840
- $wrapped->comments = [];
841
- $wrapped->parent = $media;
842
- $wrapped->children = $media->children;
843
 
844
  $media->children = [[Type::T_BLOCK, $wrapped]];
 
845
  if (isset($this->lineNumberStyle)) {
846
  $annotation = $this->makeOutputBlock(Type::T_COMMENT);
847
  $annotation->depth = 0;
@@ -898,23 +1060,61 @@ class Compiler
898
  /**
899
  * Compile directive
900
  *
901
- * @param \ScssPhp\ScssPhp\Block $block
 
902
  */
903
- protected function compileDirective(Block $block)
904
  {
905
- $s = '@' . $block->name;
 
 
906
 
907
- if (! empty($block->value)) {
908
- $s .= ' ' . $this->compileValue($block->value);
909
- }
 
 
 
 
 
910
 
911
- if ($block->name === 'keyframes' || substr($block->name, -10) === '-keyframes') {
912
- $this->compileKeyframeBlock($block, [$s]);
 
 
 
913
  } else {
914
- $this->compileNestedBlock($block, [$s]);
 
 
 
 
 
 
 
 
 
 
 
915
  }
916
  }
917
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
918
  /**
919
  * Compile at-root
920
  *
@@ -928,7 +1128,7 @@ class Compiler
928
 
929
  // wrap inline selector
930
  if ($block->selector) {
931
- $wrapped = new Block;
932
  $wrapped->sourceName = $block->sourceName;
933
  $wrapped->sourceIndex = $block->sourceIndex;
934
  $wrapped->sourceLine = $block->sourceLine;
@@ -945,7 +1145,9 @@ class Compiler
945
 
946
  $selfParent = $block->selfParent;
947
 
948
- if (! $block->selfParent->selectors && isset($block->parent) && $block->parent &&
 
 
949
  isset($block->parent->selectors) && $block->parent->selectors
950
  ) {
951
  $selfParent = $block->parent;
@@ -978,6 +1180,7 @@ class Compiler
978
  protected function filterScopeWithWithout($scope, $with, $without)
979
  {
980
  $filteredScopes = [];
 
981
 
982
  if ($scope->type === TYPE::T_ROOT) {
983
  return $scope;
@@ -985,6 +1188,7 @@ class Compiler
985
 
986
  // start from the root
987
  while ($scope->parent && $scope->parent->type !== TYPE::T_ROOT) {
 
988
  $scope = $scope->parent;
989
  }
990
 
@@ -996,8 +1200,8 @@ class Compiler
996
  if ($this->isWith($scope, $with, $without)) {
997
  $s = clone $scope;
998
  $s->children = [];
999
- $s->lines = [];
1000
- $s->parent = null;
1001
 
1002
  if ($s->type !== Type::T_MEDIA && $s->type !== Type::T_DIRECTIVE) {
1003
  $s->selectors = [];
@@ -1006,14 +1210,16 @@ class Compiler
1006
  $filteredScopes[] = $s;
1007
  }
1008
 
1009
- if ($scope->children) {
 
 
1010
  $scope = end($scope->children);
1011
  } else {
1012
  $scope = null;
1013
  }
1014
  }
1015
 
1016
- if (! count($filteredScopes)) {
1017
  return $this->rootBlock;
1018
  }
1019
 
@@ -1024,11 +1230,12 @@ class Compiler
1024
 
1025
  $p = &$newScope;
1026
 
1027
- while (count($filteredScopes)) {
1028
  $s = array_shift($filteredScopes);
1029
  $s->parent = $p;
1030
- $p->children[] = &$s;
1031
- $p = $s;
 
1032
  }
1033
 
1034
  return $newScope;
@@ -1045,7 +1252,7 @@ class Compiler
1045
  */
1046
  protected function completeScope($scope, $previousScope)
1047
  {
1048
- if (! $scope->type && (! $scope->selectors || ! count($scope->selectors)) && count($scope->lines)) {
1049
  $scope->selectors = $this->findScopeSelectors($previousScope, $scope->depth);
1050
  }
1051
 
@@ -1097,6 +1304,17 @@ class Compiler
1097
  $without = ['rule' => true];
1098
 
1099
  if ($withCondition) {
 
 
 
 
 
 
 
 
 
 
 
1100
  if ($this->libMapHasKey([$withCondition, static::$with])) {
1101
  $without = []; // cancel the default
1102
  $list = $this->coerceList($this->libMapGet([$withCondition, static::$with]));
@@ -1126,7 +1344,7 @@ class Compiler
1126
  /**
1127
  * Filter env stack
1128
  *
1129
- * @param array $envs
1130
  * @param array $with
1131
  * @param array $without
1132
  *
@@ -1139,8 +1357,9 @@ class Compiler
1139
  foreach ($envs as $e) {
1140
  if ($e->block && ! $this->isWith($e->block, $with, $without)) {
1141
  $ec = clone $e;
1142
- $ec->block = null;
1143
  $ec->selectors = [];
 
1144
  $filtered[] = $ec;
1145
  } else {
1146
  $filtered[] = $e;
@@ -1168,17 +1387,27 @@ class Compiler
1168
 
1169
  if ($block->type === Type::T_DIRECTIVE) {
1170
  if (isset($block->name)) {
1171
- return $this->testWithWithout($block->name, $with, $without);
1172
- }
1173
- elseif (isset($block->selectors) && preg_match(',@(\w+),ims', json_encode($block->selectors), $m)) {
1174
  return $this->testWithWithout($m[1], $with, $without);
1175
- }
1176
- else {
1177
  return $this->testWithWithout('???', $with, $without);
1178
  }
1179
  }
1180
- }
1181
- elseif (isset($block->selectors)) {
 
 
 
 
 
 
 
 
 
 
 
 
1182
  return $this->testWithWithout('rule', $with, $without);
1183
  }
1184
 
@@ -1191,13 +1420,14 @@ class Compiler
1191
  * @param string $what
1192
  * @param array $with
1193
  * @param array $without
1194
- * @return bool
 
1195
  * true if the block should be kept, false to reject
1196
  */
1197
- protected function testWithWithout($what, $with, $without) {
1198
-
1199
  // if without, reject only if in the list (or 'all' is in the list)
1200
- if (count($without)) {
1201
  return (isset($without[$what]) || isset($without['all'])) ? false : true;
1202
  }
1203
 
@@ -1237,8 +1467,8 @@ class Compiler
1237
  /**
1238
  * Compile nested properties lines
1239
  *
1240
- * @param \ScssPhp\ScssPhp\Block $block
1241
- * @param OutputBlock $out
1242
  */
1243
  protected function compileNestedPropertiesBlock(Block $block, OutputBlock $out)
1244
  {
@@ -1263,6 +1493,7 @@ class Compiler
1263
  array_unshift($child[1]->prefix[2], $prefix);
1264
  break;
1265
  }
 
1266
  $this->compileChild($child, $nested);
1267
  }
1268
  }
@@ -1282,7 +1513,7 @@ class Compiler
1282
 
1283
  // wrap assign children in a block
1284
  // except for @font-face
1285
- if ($block->type !== Type::T_DIRECTIVE || $block->name !== "font-face") {
1286
  // need wrapping?
1287
  $needWrapping = false;
1288
 
@@ -1294,16 +1525,16 @@ class Compiler
1294
  }
1295
 
1296
  if ($needWrapping) {
1297
- $wrapped = new Block;
1298
- $wrapped->sourceName = $block->sourceName;
1299
- $wrapped->sourceIndex = $block->sourceIndex;
1300
- $wrapped->sourceLine = $block->sourceLine;
1301
  $wrapped->sourceColumn = $block->sourceColumn;
1302
- $wrapped->selectors = [];
1303
- $wrapped->comments = [];
1304
- $wrapped->parent = $block;
1305
- $wrapped->children = $block->children;
1306
- $wrapped->selfParent = $block->selfParent;
1307
 
1308
  $block->children = [[Type::T_BLOCK, $wrapped]];
1309
  }
@@ -1341,7 +1572,7 @@ class Compiler
1341
 
1342
  $out = $this->makeOutputBlock(null);
1343
 
1344
- if (isset($this->lineNumberStyle) && count($env->selectors) && count($block->children)) {
1345
  $annotation = $this->makeOutputBlock(Type::T_COMMENT);
1346
  $annotation->depth = 0;
1347
 
@@ -1367,7 +1598,7 @@ class Compiler
1367
 
1368
  $this->scope->children[] = $out;
1369
 
1370
- if (count($block->children)) {
1371
  $out->selectors = $this->multiplySelectors($env, $block->selfParent);
1372
 
1373
  // propagate selfParent to the children where they still can be useful
@@ -1380,17 +1611,52 @@ class Compiler
1380
 
1381
  $this->compileChildrenNoReturn($block->children, $out, $block->selfParent);
1382
 
1383
- // and revert for the following childs of the same block
1384
  if ($selfParentSelectors) {
1385
  $block->selfParent->selectors = $selfParentSelectors;
1386
  }
1387
  }
1388
 
1389
- $this->formatter->stripSemicolon($out->lines);
1390
-
1391
  $this->popEnv();
1392
  }
1393
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1394
  /**
1395
  * Compile root level comment
1396
  *
@@ -1399,7 +1665,7 @@ class Compiler
1399
  protected function compileComment($block)
1400
  {
1401
  $out = $this->makeOutputBlock(Type::T_COMMENT);
1402
- $out->lines[] = is_string($block[1]) ? $block[1] : $this->compileValue($block[1]);
1403
 
1404
  $this->scope->children[] = $out;
1405
  }
@@ -1419,9 +1685,9 @@ class Compiler
1419
 
1420
  // after evaluating interpolates, we might need a second pass
1421
  if ($this->shouldEvaluate) {
1422
- $selectors = $this->revertSelfSelector($selectors);
1423
- $buffer = $this->collapseSelectors($selectors);
1424
- $parser = $this->parserFactory(__METHOD__);
1425
 
1426
  if ($parser->parseSelector($buffer, $newSelectors)) {
1427
  $selectors = array_map([$this, 'evalSelector'], $newSelectors);
@@ -1453,14 +1719,15 @@ class Compiler
1453
  protected function evalSelectorPart($part)
1454
  {
1455
  foreach ($part as &$p) {
1456
- if (is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) {
1457
  $p = $this->compileValue($p);
1458
 
1459
  // force re-evaluation
1460
  if (strpos($p, '&') !== false || strpos($p, ',') !== false) {
1461
  $this->shouldEvaluate = true;
1462
  }
1463
- } elseif (is_string($p) && strlen($p) >= 2 &&
 
1464
  ($first = $p[0]) && ($first === '"' || $first === "'") &&
1465
  substr($p, -1) === $first
1466
  ) {
@@ -1500,14 +1767,15 @@ class Compiler
1500
  );
1501
 
1502
  if ($selectorFormat && $this->isImmediateRelationshipCombinator($compound)) {
1503
- if (count($output)) {
1504
- $output[count($output) - 1] .= ' ' . $compound;
1505
  } else {
1506
  $output[] = $compound;
1507
  }
 
1508
  $glueNext = true;
1509
  } elseif ($glueNext) {
1510
- $output[count($output) - 1] .= ' ' . $compound;
1511
  $glueNext = false;
1512
  } else {
1513
  $output[] = $compound;
@@ -1518,6 +1786,7 @@ class Compiler
1518
  foreach ($output as &$o) {
1519
  $o = [Type::T_STRING, '', [$o]];
1520
  }
 
1521
  $output = [Type::T_LIST, ' ', $output];
1522
  } else {
1523
  $output = implode(' ', $output);
@@ -1542,14 +1811,18 @@ class Compiler
1542
  *
1543
  * @return array
1544
  */
1545
- protected function revertSelfSelector($selectors)
1546
  {
1547
  foreach ($selectors as &$part) {
1548
- if (is_array($part)) {
1549
  if ($part === [Type::T_SELF]) {
1550
- $part = '&';
 
 
 
 
1551
  } else {
1552
- $part = $this->revertSelfSelector($part);
1553
  }
1554
  }
1555
  }
@@ -1569,18 +1842,19 @@ class Compiler
1569
  $joined = [];
1570
 
1571
  foreach ($single as $part) {
1572
- if (empty($joined) ||
1573
- ! is_string($part) ||
 
1574
  preg_match('/[\[.:#%]/', $part)
1575
  ) {
1576
  $joined[] = $part;
1577
  continue;
1578
  }
1579
 
1580
- if (is_array(end($joined))) {
1581
  $joined[] = $part;
1582
  } else {
1583
- $joined[count($joined) - 1] .= $part;
1584
  }
1585
  }
1586
 
@@ -1596,7 +1870,7 @@ class Compiler
1596
  */
1597
  protected function compileSelector($selector)
1598
  {
1599
- if (! is_array($selector)) {
1600
  return $selector; // media and the like
1601
  }
1602
 
@@ -1619,7 +1893,7 @@ class Compiler
1619
  protected function compileSelectorPart($piece)
1620
  {
1621
  foreach ($piece as &$p) {
1622
- if (! is_array($p)) {
1623
  continue;
1624
  }
1625
 
@@ -1646,13 +1920,13 @@ class Compiler
1646
  */
1647
  protected function hasSelectorPlaceholder($selector)
1648
  {
1649
- if (! is_array($selector)) {
1650
  return false;
1651
  }
1652
 
1653
  foreach ($selector as $parts) {
1654
  foreach ($parts as $part) {
1655
- if (strlen($part) && '%' === $part[0]) {
1656
  return true;
1657
  }
1658
  }
@@ -1671,11 +1945,12 @@ class Compiler
1671
  ];
1672
 
1673
  // infinite calling loop
1674
- if (count($this->callStack) > 25000) {
1675
  // not displayed but you can var_dump it to deep debug
1676
  $msg = $this->callStackMessage(true, 100);
1677
- $msg = "Infinite calling loop";
1678
- $this->throwError($msg);
 
1679
  }
1680
  }
1681
 
@@ -1701,6 +1976,8 @@ class Compiler
1701
  $ret = $this->compileChild($stm, $out);
1702
 
1703
  if (isset($ret)) {
 
 
1704
  return $ret;
1705
  }
1706
  }
@@ -1725,11 +2002,11 @@ class Compiler
1725
  $this->pushCallStack($traceName);
1726
 
1727
  foreach ($stms as $stm) {
1728
- if ($selfParent && isset($stm[1]) && is_object($stm[1]) && $stm[1] instanceof Block) {
1729
  $stm[1]->selfParent = $selfParent;
1730
  $ret = $this->compileChild($stm, $out);
1731
  $stm[1]->selfParent = null;
1732
- } elseif ($selfParent && $stm[0] === TYPE::T_INCLUDE) {
1733
  $stm['selfParent'] = $selfParent;
1734
  $ret = $this->compileChild($stm, $out);
1735
  unset($stm['selfParent']);
@@ -1738,9 +2015,7 @@ class Compiler
1738
  }
1739
 
1740
  if (isset($ret)) {
1741
- $this->throwError('@return may only be used within a function');
1742
-
1743
- return;
1744
  }
1745
  }
1746
 
@@ -1758,16 +2033,20 @@ class Compiler
1758
  protected function evaluateMediaQuery($queryList)
1759
  {
1760
  static $parser = null;
 
1761
  $outQueryList = [];
 
1762
  foreach ($queryList as $kql => $query) {
1763
  $shouldReparse = false;
 
1764
  foreach ($query as $kq => $q) {
1765
- for ($i = 1; $i < count($q); $i++) {
1766
  $value = $this->compileValue($q[$i]);
1767
 
1768
  // the parser had no mean to know if media type or expression if it was an interpolation
1769
  // so you need to reparse if the T_MEDIA_TYPE looks like anything else a media type
1770
- if ($q[0] == Type::T_MEDIA_TYPE &&
 
1771
  (strpos($value, '(') !== false ||
1772
  strpos($value, ')') !== false ||
1773
  strpos($value, ':') !== false ||
@@ -1779,24 +2058,31 @@ class Compiler
1779
  $queryList[$kql][$kq][$i] = [Type::T_KEYWORD, $value];
1780
  }
1781
  }
 
1782
  if ($shouldReparse) {
1783
- if (is_null($parser)) {
1784
  $parser = $this->parserFactory(__METHOD__);
1785
  }
 
1786
  $queryString = $this->compileMediaQuery([$queryList[$kql]]);
1787
  $queryString = reset($queryString);
 
1788
  if (strpos($queryString, '@media ') === 0) {
1789
  $queryString = substr($queryString, 7);
1790
  $queries = [];
 
1791
  if ($parser->parseMediaQueryList($queryString, $queries)) {
1792
  $queries = $this->evaluateMediaQuery($queries[2]);
1793
- while (count($queries)) {
 
1794
  $outQueryList[] = array_shift($queries);
1795
  }
 
1796
  continue;
1797
  }
1798
  }
1799
  }
 
1800
  $outQueryList[] = $queryList[$kql];
1801
  }
1802
 
@@ -1812,10 +2098,10 @@ class Compiler
1812
  */
1813
  protected function compileMediaQuery($queryList)
1814
  {
1815
- $start = '@media ';
1816
  $default = trim($start);
1817
- $out = [];
1818
- $current = "";
1819
 
1820
  foreach ($queryList as $query) {
1821
  $type = null;
@@ -1833,16 +2119,17 @@ class Compiler
1833
  foreach ($query as $q) {
1834
  switch ($q[0]) {
1835
  case Type::T_MEDIA_TYPE:
1836
- $newType = array_map([$this, 'compileValue'], array_slice($q, 1));
 
1837
  // combining not and anything else than media type is too risky and should be avoided
1838
  if (! $mediaTypeOnly) {
1839
- if (in_array(Type::T_NOT, $newType) || ($type && in_array(Type::T_NOT, $type) )) {
1840
  if ($type) {
1841
  array_unshift($parts, implode(' ', array_filter($type)));
1842
  }
1843
 
1844
  if (! empty($parts)) {
1845
- if (strlen($current)) {
1846
  $current .= $this->formatter->tagSeparator;
1847
  }
1848
 
@@ -1853,9 +2140,9 @@ class Compiler
1853
  $out[] = $start . $current;
1854
  }
1855
 
1856
- $current = "";
1857
- $type = null;
1858
- $parts = [];
1859
  }
1860
  }
1861
 
@@ -1905,7 +2192,7 @@ class Compiler
1905
  }
1906
 
1907
  if (! empty($parts)) {
1908
- if (strlen($current)) {
1909
  $current .= $this->formatter->tagSeparator;
1910
  }
1911
 
@@ -1987,23 +2274,19 @@ class Compiler
1987
  return $type1;
1988
  }
1989
 
1990
- $m1 = '';
1991
- $t1 = '';
1992
-
1993
- if (count($type1) > 1) {
1994
- $m1= strtolower($type1[0]);
1995
- $t1= strtolower($type1[1]);
1996
  } else {
 
1997
  $t1 = strtolower($type1[0]);
1998
  }
1999
 
2000
- $m2 = '';
2001
- $t2 = '';
2002
-
2003
- if (count($type2) > 1) {
2004
  $m2 = strtolower($type2[0]);
2005
  $t2 = strtolower($type2[1]);
2006
  } else {
 
2007
  $t2 = strtolower($type2[0]);
2008
  }
2009
 
@@ -2032,7 +2315,7 @@ class Compiler
2032
  }
2033
 
2034
  // t1 == t2, neither m1 nor m2 are "not"
2035
- return [empty($m1)? $m2 : $m1, $t1];
2036
  }
2037
 
2038
  /**
@@ -2049,8 +2332,8 @@ class Compiler
2049
  if ($rawPath[0] === Type::T_STRING) {
2050
  $path = $this->compileStringContent($rawPath);
2051
 
2052
- if ($path = $this->findImport($path)) {
2053
- if (! $once || ! in_array($path, $this->importedFiles)) {
2054
  $this->importFile($path, $out);
2055
  $this->importedFiles[] = $path;
2056
  }
@@ -2058,31 +2341,84 @@ class Compiler
2058
  return true;
2059
  }
2060
 
 
 
2061
  return false;
2062
  }
2063
 
2064
  if ($rawPath[0] === Type::T_LIST) {
2065
  // handle a list of strings
2066
- if (count($rawPath[2]) === 0) {
2067
  return false;
2068
  }
2069
 
2070
  foreach ($rawPath[2] as $path) {
2071
  if ($path[0] !== Type::T_STRING) {
 
 
2072
  return false;
2073
  }
2074
  }
2075
 
2076
  foreach ($rawPath[2] as $path) {
2077
- $this->compileImport($path, $out);
2078
  }
2079
 
2080
  return true;
2081
  }
2082
 
 
 
2083
  return false;
2084
  }
2085
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2086
 
2087
  /**
2088
  * Append a root directive like @import or @charset as near as the possible from the source code
@@ -2102,8 +2438,8 @@ class Compiler
2102
 
2103
  $i = 0;
2104
 
2105
- while ($i < count($root->children)) {
2106
- if (! isset($root->children[$i]->type) || ! in_array($root->children[$i]->type, $allowed)) {
2107
  break;
2108
  }
2109
 
@@ -2113,55 +2449,51 @@ class Compiler
2113
  // remove incompatible children from the bottom of the list
2114
  $saveChildren = [];
2115
 
2116
- while ($i < count($root->children)) {
2117
  $saveChildren[] = array_pop($root->children);
2118
  }
2119
 
2120
  // insert the directive as a comment
2121
  $child = $this->makeOutputBlock(Type::T_COMMENT);
2122
- $child->lines[] = $line;
2123
- $child->sourceName = $this->sourceNames[$this->sourceIndex];
2124
- $child->sourceLine = $this->sourceLine;
2125
  $child->sourceColumn = $this->sourceColumn;
2126
 
2127
  $root->children[] = $child;
2128
 
2129
  // repush children
2130
- while (count($saveChildren)) {
2131
  $root->children[] = array_pop($saveChildren);
2132
  }
2133
  }
2134
 
2135
  /**
2136
- * Append lines to the courrent output block:
2137
  * directly to the block or through a child if necessary
2138
  *
2139
  * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2140
  * @param string $type
2141
- * @param string $line
2142
  */
2143
  protected function appendOutputLine(OutputBlock $out, $type, $line)
2144
  {
2145
  $outWrite = &$out;
2146
 
2147
- if ($type === Type::T_COMMENT) {
2148
- $parent = $out->parent;
2149
-
2150
- if (end($parent->children) !== $out) {
2151
- $outWrite = &$parent->children[count($parent->children)-1];
2152
- }
2153
- }
2154
-
2155
  // check if it's a flat output or not
2156
- if (count($out->children)) {
2157
- $lastChild = &$out->children[count($out->children) -1];
2158
 
2159
- if ($lastChild->depth === $out->depth && is_null($lastChild->selectors) && ! count($lastChild->children)) {
 
 
 
 
2160
  $outWrite = $lastChild;
2161
  } else {
2162
  $nextLines = $this->makeOutputBlock($type);
2163
  $nextLines->parent = $out;
2164
- $nextLines->depth = $out->depth;
2165
 
2166
  $out->children[] = $nextLines;
2167
  $outWrite = &$nextLines;
@@ -2182,16 +2514,17 @@ class Compiler
2182
  protected function compileChild($child, OutputBlock $out)
2183
  {
2184
  if (isset($child[Parser::SOURCE_LINE])) {
2185
- $this->sourceIndex = isset($child[Parser::SOURCE_INDEX]) ? $child[Parser::SOURCE_INDEX] : null;
2186
- $this->sourceLine = isset($child[Parser::SOURCE_LINE]) ? $child[Parser::SOURCE_LINE] : -1;
2187
  $this->sourceColumn = isset($child[Parser::SOURCE_COLUMN]) ? $child[Parser::SOURCE_COLUMN] : -1;
2188
- } elseif (is_array($child) && isset($child[1]->sourceLine)) {
2189
- $this->sourceIndex = $child[1]->sourceIndex;
2190
- $this->sourceLine = $child[1]->sourceLine;
2191
  $this->sourceColumn = $child[1]->sourceColumn;
2192
  } elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) {
2193
- $this->sourceLine = $out->sourceLine;
2194
- $this->sourceIndex = array_search($out->sourceName, $this->sourceNames);
 
2195
 
2196
  if ($this->sourceIndex === false) {
2197
  $this->sourceIndex = null;
@@ -2202,21 +2535,17 @@ class Compiler
2202
  case Type::T_SCSSPHP_IMPORT_ONCE:
2203
  $rawPath = $this->reduce($child[1]);
2204
 
2205
- if (! $this->compileImport($rawPath, $out, true)) {
2206
- $this->appendRootDirective('@import ' . $this->compileValue($rawPath) . ';', $out);
2207
- }
2208
  break;
2209
 
2210
  case Type::T_IMPORT:
2211
  $rawPath = $this->reduce($child[1]);
2212
 
2213
- if (! $this->compileImport($rawPath, $out)) {
2214
- $this->appendRootDirective('@import ' . $this->compileValue($rawPath) . ';', $out);
2215
- }
2216
  break;
2217
 
2218
  case Type::T_DIRECTIVE:
2219
- $this->compileDirective($child[1]);
2220
  break;
2221
 
2222
  case Type::T_AT_ROOT:
@@ -2238,13 +2567,37 @@ class Compiler
2238
  }
2239
  break;
2240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2241
  case Type::T_ASSIGN:
2242
  list(, $name, $value) = $child;
2243
 
2244
  if ($name[0] === Type::T_VARIABLE) {
2245
- $flags = isset($child[3]) ? $child[3] : [];
2246
- $isDefault = in_array('!default', $flags);
2247
- $isGlobal = in_array('!global', $flags);
2248
 
2249
  if ($isGlobal) {
2250
  $this->set($name[1], $this->reduce($value), false, $this->rootEnv, $value);
@@ -2252,7 +2605,7 @@ class Compiler
2252
  }
2253
 
2254
  $shouldSet = $isDefault &&
2255
- (($result = $this->get($name[1], false)) === null ||
2256
  $result === static::$null);
2257
 
2258
  if (! $isDefault || $shouldSet) {
@@ -2263,28 +2616,78 @@ class Compiler
2263
 
2264